1 #include "test-tool.h"
2 #include "cache.h"
3 #include "strvec.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 
get_i(int * p_value,const char * data)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  */
ut_001return(int argc,const char ** argv)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  */
ut_002exit(int argc,const char ** argv)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  */
ut_003error(int argc,const char ** argv)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  */
ut_004child(int argc,const char ** argv)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  */
ut_005exec(int argc,const char ** argv)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 
ut_006data(int argc,const char ** argv)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 
ut_007bug(int argc,const char ** argv)201 static int ut_007bug(int argc, const char **argv)
202 {
203 	/*
204 	 * Exercise BUG() to ensure that the message is printed to trace2.
205 	 */
206 	BUG("the bug message");
207 }
208 
209 /*
210  * Usage:
211  *     test-tool trace2 <ut_name_1> <ut_usage_1>
212  *     test-tool trace2 <ut_name_2> <ut_usage_2>
213  *     ...
214  */
215 #define USAGE_PREFIX "test-tool trace2"
216 
217 /* clang-format off */
218 static struct unit_test ut_table[] = {
219 	{ ut_001return,   "001return", "<exit_code>" },
220 	{ ut_002exit,     "002exit",   "<exit_code>" },
221 	{ ut_003error,    "003error",  "<error_message>+" },
222 	{ ut_004child,    "004child",  "[<child_command_line>]" },
223 	{ ut_005exec,     "005exec",   "<git_command_args>" },
224 	{ ut_006data,     "006data",   "[<category> <key> <value>]+" },
225 	{ ut_007bug,      "007bug",    "" },
226 };
227 /* clang-format on */
228 
229 /* clang-format off */
230 #define for_each_ut(k, ut_k)			\
231 	for (k = 0, ut_k = &ut_table[k];	\
232 	     k < ARRAY_SIZE(ut_table);		\
233 	     k++, ut_k = &ut_table[k])
234 /* clang-format on */
235 
print_usage(void)236 static int print_usage(void)
237 {
238 	int k;
239 	struct unit_test *ut_k;
240 
241 	fprintf(stderr, "usage:\n");
242 	for_each_ut (k, ut_k)
243 		fprintf(stderr, "\t%s %s %s\n", USAGE_PREFIX, ut_k->ut_name,
244 			ut_k->ut_usage);
245 
246 	return 129;
247 }
248 
249 /*
250  * Issue various trace2 events for testing.
251  *
252  * We assume that these trace2 routines has already been called:
253  *    [] trace2_initialize()      [common-main.c:main()]
254  *    [] trace2_cmd_start()       [common-main.c:main()]
255  *    [] trace2_cmd_name()        [test-tool.c:cmd_main()]
256  *    [] tracd2_cmd_list_config() [test-tool.c:cmd_main()]
257  * So that:
258  *    [] the various trace2 streams are open.
259  *    [] the process SID has been created.
260  *    [] the "version" event has been generated.
261  *    [] the "start" event has been generated.
262  *    [] the "cmd_name" event has been generated.
263  *    [] this writes various "def_param" events for interesting config values.
264  *
265  * We further assume that if we return (rather than exit()), trace2_cmd_exit()
266  * will be called by test-tool.c:cmd_main().
267  */
cmd__trace2(int argc,const char ** argv)268 int cmd__trace2(int argc, const char **argv)
269 {
270 	int k;
271 	struct unit_test *ut_k;
272 
273 	argc--; /* skip over "trace2" arg */
274 	argv++;
275 
276 	if (argc)
277 		for_each_ut (k, ut_k)
278 			if (!strcmp(argv[0], ut_k->ut_name))
279 				return ut_k->ut_fn(argc - 1, argv + 1);
280 
281 	return print_usage();
282 }
283