Merge branch 'jh/trace2-sid-fix'
[git] / t / helper / test-trace2.c
1 #include "test-tool.h"
2 #include "cache.h"
3 #include "argv-array.h"
4 #include "run-command.h"
5 #include "exec-cmd.h"
6 #include "config.h"
7
8 typedef int(fn_unit_test)(int argc, const char **argv);
9
10 struct unit_test {
11         fn_unit_test *ut_fn;
12         const char *ut_name;
13         const char *ut_usage;
14 };
15
16 #define MyOk 0
17 #define MyError 1
18
19 static int get_i(int *p_value, const char *data)
20 {
21         char *endptr;
22
23         if (!data || !*data)
24                 return MyError;
25
26         *p_value = strtol(data, &endptr, 10);
27         if (*endptr || errno == ERANGE)
28                 return MyError;
29
30         return MyOk;
31 }
32
33 /*
34  * Cause process to exit with the requested value via "return".
35  *
36  * Rely on test-tool.c:cmd_main() to call trace2_cmd_exit()
37  * with our result.
38  *
39  * Test harness can confirm:
40  * [] the process-exit value.
41  * [] the "code" field in the "exit" trace2 event.
42  * [] the "code" field in the "atexit" trace2 event.
43  * [] the "name" field in the "cmd_name" trace2 event.
44  * [] "def_param" events for all of the "interesting" pre-defined
45  * config settings.
46  */
47 static int ut_001return(int argc, const char **argv)
48 {
49         int rc;
50
51         if (get_i(&rc, argv[0]))
52                 die("expect <exit_code>");
53
54         return rc;
55 }
56
57 /*
58  * Cause the process to exit with the requested value via "exit()".
59  *
60  * Test harness can confirm:
61  * [] the "code" field in the "exit" trace2 event.
62  * [] the "code" field in the "atexit" trace2 event.
63  * [] the "name" field in the "cmd_name" trace2 event.
64  * [] "def_param" events for all of the "interesting" pre-defined
65  * config settings.
66  */
67 static int ut_002exit(int argc, const char **argv)
68 {
69         int rc;
70
71         if (get_i(&rc, argv[0]))
72                 die("expect <exit_code>");
73
74         exit(rc);
75 }
76
77 /*
78  * Send an "error" event with each value in argv.  Normally, git only issues
79  * a single "error" event immediately before issuing an "exit" event (such
80  * as in die() or BUG()), but multiple "error" events are allowed.
81  *
82  * Test harness can confirm:
83  * [] a trace2 "error" event for each value in argv.
84  * [] the "name" field in the "cmd_name" trace2 event.
85  * [] (optional) the file:line in the "exit" event refers to this function.
86  */
87 static int ut_003error(int argc, const char **argv)
88 {
89         int k;
90
91         if (!argv[0] || !*argv[0])
92                 die("expect <error_message>");
93
94         for (k = 0; k < argc; k++)
95                 error("%s", argv[k]);
96
97         return 0;
98 }
99
100 /*
101  * Run a child process and wait for it to finish and exit with its return code.
102  * test-tool trace2 004child [<child-command-line>]
103  *
104  * For example:
105  * test-tool trace2 004child git version
106  * test-tool trace2 004child test-tool trace2 001return 0
107  * test-tool trace2 004child test-tool trace2 004child test-tool trace2 004child
108  * test-tool trace2 004child git -c alias.xyz=version xyz
109  *
110  * Test harness can confirm:
111  * [] the "name" field in the "cmd_name" trace2 event.
112  * [] that the outer process has a single component SID (or depth "d0" in
113  *    the PERF stream).
114  * [] that "child_start" and "child_exit" events are generated for the child.
115  * [] if the child process is an instrumented executable:
116  *    [] that "version", "start", ..., "exit", and "atexit" events are
117  *       generated by the child process.
118  *    [] that the child process events have a multiple component SID (or
119  *       depth "dN+1" in the PERF stream).
120  * [] that the child exit code is propagated to the parent process "exit"
121  *    and "atexit" events..
122  * [] (optional) that the "t_abs" field in the child process "atexit" event
123  *    is less than the "t_rel" field in the "child_exit" event of the parent
124  *    process.
125  * [] if the child process is like the alias example above,
126  *    [] (optional) the child process attempts to run "git-xyx" as a dashed
127  *       command.
128  *    [] the child process emits an "alias" event with "xyz" => "version"
129  *    [] the child process runs "git version" as a child process.
130  *    [] the child process has a 3 component SID (or depth "d2" in the PERF
131  *       stream).
132  */
133 static int ut_004child(int argc, const char **argv)
134 {
135         int result;
136
137         /*
138          * Allow empty <child_command_line> so we can do arbitrarily deep
139          * command nesting and let the last one be null.
140          */
141         if (!argc)
142                 return 0;
143
144         result = run_command_v_opt(argv, 0);
145         exit(result);
146 }
147
148 /*
149  * Exec a git command.  This may either create a child process (Windows)
150  * or replace the existing process.
151  * test-tool trace2 005exec <git_command_args>
152  *
153  * For example:
154  * test-tool trace2 005exec version
155  *
156  * Test harness can confirm (on Windows):
157  * [] the "name" field in the "cmd_name" trace2 event.
158  * [] that the outer process has a single component SID (or depth "d0" in
159  *    the PERF stream).
160  * [] that "exec" and "exec_result" events are generated for the child
161  *    process (since the Windows compatibility layer fakes an exec() with
162  *    a CreateProcess(), WaitForSingleObject(), and exit()).
163  * [] that the child process has multiple component SID (or depth "dN+1"
164  *    in the PERF stream).
165  *
166  * Test harness can confirm (on platforms with a real exec() function):
167  * [] TODO talk about process replacement and how it affects SID.
168  */
169 static int ut_005exec(int argc, const char **argv)
170 {
171         int result;
172
173         if (!argc)
174                 return 0;
175
176         result = execv_git_cmd(argv);
177         return result;
178 }
179
180 static int ut_006data(int argc, const char **argv)
181 {
182         const char *usage_error =
183                 "expect <cat0> <k0> <v0> [<cat1> <k1> <v1> [...]]";
184
185         if (argc % 3 != 0)
186                 die("%s", usage_error);
187
188         while (argc) {
189                 if (!argv[0] || !*argv[0] || !argv[1] || !*argv[1] ||
190                     !argv[2] || !*argv[2])
191                         die("%s", usage_error);
192
193                 trace2_data_string(argv[0], the_repository, argv[1], argv[2]);
194                 argv += 3;
195                 argc -= 3;
196         }
197
198         return 0;
199 }
200
201 /*
202  * Usage:
203  *     test-tool trace2 <ut_name_1> <ut_usage_1>
204  *     test-tool trace2 <ut_name_2> <ut_usage_2>
205  *     ...
206  */
207 #define USAGE_PREFIX "test-tool trace2"
208
209 /* clang-format off */
210 static struct unit_test ut_table[] = {
211         { ut_001return,   "001return", "<exit_code>" },
212         { ut_002exit,     "002exit",   "<exit_code>" },
213         { ut_003error,    "003error",  "<error_message>+" },
214         { ut_004child,    "004child",  "[<child_command_line>]" },
215         { ut_005exec,     "005exec",   "<git_command_args>" },
216         { ut_006data,     "006data",   "[<category> <key> <value>]+" },
217 };
218 /* clang-format on */
219
220 /* clang-format off */
221 #define for_each_ut(k, ut_k)                    \
222         for (k = 0, ut_k = &ut_table[k];        \
223              k < ARRAY_SIZE(ut_table);          \
224              k++, ut_k = &ut_table[k])
225 /* clang-format on */
226
227 static int print_usage(void)
228 {
229         int k;
230         struct unit_test *ut_k;
231
232         fprintf(stderr, "usage:\n");
233         for_each_ut (k, ut_k)
234                 fprintf(stderr, "\t%s %s %s\n", USAGE_PREFIX, ut_k->ut_name,
235                         ut_k->ut_usage);
236
237         return 129;
238 }
239
240 /*
241  * Issue various trace2 events for testing.
242  *
243  * We assume that these trace2 routines has already been called:
244  *    [] trace2_initialize()      [common-main.c:main()]
245  *    [] trace2_cmd_start()       [common-main.c:main()]
246  *    [] trace2_cmd_name()        [test-tool.c:cmd_main()]
247  *    [] tracd2_cmd_list_config() [test-tool.c:cmd_main()]
248  * So that:
249  *    [] the various trace2 streams are open.
250  *    [] the process SID has been created.
251  *    [] the "version" event has been generated.
252  *    [] the "start" event has been generated.
253  *    [] the "cmd_name" event has been generated.
254  *    [] this writes various "def_param" events for interesting config values.
255  *
256  * We further assume that if we return (rather than exit()), trace2_cmd_exit()
257  * will be called by test-tool.c:cmd_main().
258  */
259 int cmd__trace2(int argc, const char **argv)
260 {
261         int k;
262         struct unit_test *ut_k;
263
264         argc--; /* skip over "trace2" arg */
265         argv++;
266
267         if (argc)
268                 for_each_ut (k, ut_k)
269                         if (!strcmp(argv[0], ut_k->ut_name))
270                                 return ut_k->ut_fn(argc - 1, argv + 1);
271
272         return print_usage();
273 }