trace2: t/helper/test-trace2, t0210.sh, t0211.sh, t0212.sh

Create unit tests for Trace2. Signed-off-by: Jeff Hostetler <jeffhost@microsoft.com> Signed-off-by: Junio C Hamano <gitster@pobox.com>

Jeff Hostetler committed Feb 22, 2019 at 14:25 UTC a15860dca3f194bb87e2d6028264791a4cec031d
10 files changed +1175
Makefile
+1
@@ -773,6 +773,7 @@ TEST_BUILTINS_OBJS += test-string-list.o
773 TEST_BUILTINS_OBJS += test-submodule-config.o
774 TEST_BUILTINS_OBJS += test-submodule-nested-repo-config.o
775 TEST_BUILTINS_OBJS += test-subprocess.o
776 +TEST_BUILTINS_OBJS += test-trace2.o
777 TEST_BUILTINS_OBJS += test-urlmatch-normalization.o
778 TEST_BUILTINS_OBJS += test-xml-encode.o
779 TEST_BUILTINS_OBJS += test-wildmatch.o
t/helper/test-tool.c
+1
@@ -52,6 +52,7 @@ static struct test_cmd cmds[] = {
52 { "submodule-config", cmd__submodule_config },
53 { "submodule-nested-repo-config", cmd__submodule_nested_repo_config },
54 { "subprocess", cmd__subprocess },
55 + { "trace2", cmd__trace2 },
56 { "urlmatch-normalization", cmd__urlmatch_normalization },
57 { "xml-encode", cmd__xml_encode },
58 { "wildmatch", cmd__wildmatch },
t/helper/test-tool.h
+1
@@ -48,6 +48,7 @@ int cmd__string_list(int argc, const char **argv);
48 int cmd__submodule_config(int argc, const char **argv);
49 int cmd__submodule_nested_repo_config(int argc, const char **argv);
50 int cmd__subprocess(int argc, const char **argv);
51 +int cmd__trace2(int argc, const char **argv);
52 int cmd__urlmatch_normalization(int argc, const char **argv);
53 int cmd__xml_encode(int argc, const char **argv);
54 int cmd__wildmatch(int argc, const char **argv);
t/helper/test-trace2.c new
+273
@@ -0,0 +1,273 @@
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 +}
t/t0210-trace2-normal.sh new
+135
@@ -0,0 +1,135 @@
1 +#!/bin/sh
2 +
3 +test_description='test trace2 facility (normal target)'
4 +. ./test-lib.sh
5 +
6 +# Add t/helper directory to PATH so that we can use a relative
7 +# path to run nested instances of test-tool.exe (see 004child).
8 +# This helps with HEREDOC comparisons later.
9 +TTDIR="$GIT_BUILD_DIR/t/helper/" && export TTDIR
10 +PATH="$TTDIR:$PATH" && export PATH
11 +
12 +# Warning: use of 'test_cmp' may run test-tool.exe and/or git.exe
13 +# Warning: to do the actual diff/comparison, so the HEREDOCs here
14 +# Warning: only cover our actual calls to test-tool and/or git.
15 +# Warning: So you may see extra lines in artifact files when
16 +# Warning: interactively debugging.
17 +
18 +# Turn off any inherited trace2 settings for this test.
19 +unset GIT_TR2 GIT_TR2_PERF GIT_TR2_EVENT
20 +unset GIT_TR2_BRIEF
21 +unset GIT_TR2_CONFIG_PARAMS
22 +
23 +V=$(git version | sed -e 's/^git version //') && export V
24 +
25 +# There are multiple trace2 targets: normal, perf, and event.
26 +# Trace2 events will/can be written to each active target (subject
27 +# to whatever filtering that target decides to do).
28 +# This script tests the normal target in isolation.
29 +#
30 +# Defer setting GIT_TR2 until the actual command line we want to test
31 +# because hidden git and test-tool commands run by the test harness
32 +# can contaminate our output.
33 +
34 +# Enable "brief" feature which turns off "<clock> <file>:<line> " prefix.
35 +GIT_TR2_BRIEF=1 && export GIT_TR2_BRIEF
36 +
37 +# Basic tests of the trace2 normal stream. Since this stream is used
38 +# primarily with printf-style debugging/tracing, we do limited testing
39 +# here.
40 +#
41 +# We do confirm the following API features:
42 +# [] the 'version <v>' event
43 +# [] the 'start <argv>' event
44 +# [] the 'cmd_name <name>' event
45 +# [] the 'exit <time> code:<code>' event
46 +# [] the 'atexit <time> code:<code>' event
47 +#
48 +# Fields of the form _FIELD_ are tokens that have been replaced (such
49 +# as the elapsed time).
50 +
51 +# Verb 001return
52 +#
53 +# Implicit return from cmd_<verb> function propagates <code>.
54 +
55 +test_expect_success 'normal stream, return code 0' '
56 + test_when_finished "rm trace.normal actual expect" &&
57 + GIT_TR2="$(pwd)/trace.normal" test-tool trace2 001return 0 &&
58 + perl "$TEST_DIRECTORY/t0210/scrub_normal.perl" <trace.normal >actual &&
59 + cat >expect <<-EOF &&
60 + version $V
61 + start _EXE_ trace2 001return 0
62 + cmd_name trace2 (trace2)
63 + exit elapsed:_TIME_ code:0
64 + atexit elapsed:_TIME_ code:0
65 + EOF
66 + test_cmp expect actual
67 +'
68 +
69 +test_expect_success 'normal stream, return code 1' '
70 + test_when_finished "rm trace.normal actual expect" &&
71 + test_must_fail env GIT_TR2="$(pwd)/trace.normal" test-tool trace2 001return 1 &&
72 + perl "$TEST_DIRECTORY/t0210/scrub_normal.perl" <trace.normal >actual &&
73 + cat >expect <<-EOF &&
74 + version $V
75 + start _EXE_ trace2 001return 1
76 + cmd_name trace2 (trace2)
77 + exit elapsed:_TIME_ code:1
78 + atexit elapsed:_TIME_ code:1
79 + EOF
80 + test_cmp expect actual
81 +'
82 +
83 +# Verb 002exit
84 +#
85 +# Explicit exit(code) from within cmd_<verb> propagates <code>.
86 +
87 +test_expect_success 'normal stream, exit code 0' '
88 + test_when_finished "rm trace.normal actual expect" &&
89 + GIT_TR2="$(pwd)/trace.normal" test-tool trace2 002exit 0 &&
90 + perl "$TEST_DIRECTORY/t0210/scrub_normal.perl" <trace.normal >actual &&
91 + cat >expect <<-EOF &&
92 + version $V
93 + start _EXE_ trace2 002exit 0
94 + cmd_name trace2 (trace2)
95 + exit elapsed:_TIME_ code:0
96 + atexit elapsed:_TIME_ code:0
97 + EOF
98 + test_cmp expect actual
99 +'
100 +
101 +test_expect_success 'normal stream, exit code 1' '
102 + test_when_finished "rm trace.normal actual expect" &&
103 + test_must_fail env GIT_TR2="$(pwd)/trace.normal" test-tool trace2 002exit 1 &&
104 + perl "$TEST_DIRECTORY/t0210/scrub_normal.perl" <trace.normal >actual &&
105 + cat >expect <<-EOF &&
106 + version $V
107 + start _EXE_ trace2 002exit 1
108 + cmd_name trace2 (trace2)
109 + exit elapsed:_TIME_ code:1
110 + atexit elapsed:_TIME_ code:1
111 + EOF
112 + test_cmp expect actual
113 +'
114 +
115 +# Verb 003error
116 +#
117 +# To the above, add multiple 'error <msg>' events
118 +
119 +test_expect_success 'normal stream, error event' '
120 + test_when_finished "rm trace.normal actual expect" &&
121 + GIT_TR2="$(pwd)/trace.normal" test-tool trace2 003error "hello world" "this is a test" &&
122 + perl "$TEST_DIRECTORY/t0210/scrub_normal.perl" <trace.normal >actual &&
123 + cat >expect <<-EOF &&
124 + version $V
125 + start _EXE_ trace2 003error '\''hello world'\'' '\''this is a test'\''
126 + cmd_name trace2 (trace2)
127 + error hello world
128 + error this is a test
129 + exit elapsed:_TIME_ code:0
130 + atexit elapsed:_TIME_ code:0
131 + EOF
132 + test_cmp expect actual
133 +'
134 +
135 +test_done
t/t0210/scrub_normal.perl new
+48
@@ -0,0 +1,48 @@
1 +#!/usr/bin/perl
2 +#
3 +# Scrub the variable fields from the normal trace2 output to
4 +# make testing easier.
5 +
6 +use strict;
7 +use warnings;
8 +
9 +my $float = '[0-9]*\.[0-9]+([eE][-+]?[0-9]+)?';
10 +
11 +# This code assumes that the trace2 data was written with bare
12 +# turned on (which omits the "<clock> <file>:<line>" prefix.
13 +
14 +while (<>) {
15 + # Various messages include an elapsed time in the middle
16 + # of the message. Replace the time with a placeholder to
17 + # simplify our HEREDOC in the test script.
18 + s/elapsed:$float/elapsed:_TIME_/g;
19 +
20 + my $line = $_;
21 +
22 + # we expect:
23 + # start <argv0> [<argv1> [<argv2> [...]]]
24 + #
25 + # where argv0 might be a relative or absolute path, with
26 + # or without quotes, and platform dependent. Replace argv0
27 + # with a token for HEREDOC matching in the test script.
28 +
29 + if ($line =~ m/^start/) {
30 + $line =~ /^start\s+(.*)/;
31 + my $argv = $1;
32 + $argv =~ m/(\'[^\']*\'|[^ ]+)\s+(.*)/;
33 + my $argv_0 = $1;
34 + my $argv_rest = $2;
35 +
36 + print "start _EXE_ $argv_rest\n";
37 + }
38 + elsif ($line =~ m/^cmd_path/) {
39 + # Likewise, the 'cmd_path' message breaks out argv[0].
40 + #
41 + # This line is only emitted when RUNTIME_PREFIX is defined,
42 + # so just omit it for testing purposes.
43 + # print "cmd_path _EXE_\n";
44 + }
45 + else {
46 + print "$line";
47 + }
48 +}
t/t0211-trace2-perf.sh new
+153
@@ -0,0 +1,153 @@
1 +#!/bin/sh
2 +
3 +test_description='test trace2 facility (perf target)'
4 +. ./test-lib.sh
5 +
6 +# Add t/helper directory to PATH so that we can use a relative
7 +# path to run nested instances of test-tool.exe (see 004child).
8 +# This helps with HEREDOC comparisons later.
9 +TTDIR="$GIT_BUILD_DIR/t/helper/" && export TTDIR
10 +PATH="$TTDIR:$PATH" && export PATH
11 +
12 +# Warning: use of 'test_cmp' may run test-tool.exe and/or git.exe
13 +# Warning: to do the actual diff/comparison, so the HEREDOCs here
14 +# Warning: only cover our actual calls to test-tool and/or git.
15 +# Warning: So you may see extra lines in artifact files when
16 +# Warning: interactively debugging.
17 +
18 +# Turn off any inherited trace2 settings for this test.
19 +unset GIT_TR2 GIT_TR2_PERF GIT_TR2_EVENT
20 +unset GIT_TR2_PERF_BRIEF
21 +unset GIT_TR2_CONFIG_PARAMS
22 +
23 +V=$(git version | sed -e 's/^git version //') && export V
24 +
25 +# There are multiple trace2 targets: normal, perf, and event.
26 +# Trace2 events will/can be written to each active target (subject
27 +# to whatever filtering that target decides to do).
28 +# Test each target independently.
29 +#
30 +# Defer setting GIT_TR2_PERF until the actual command we want to
31 +# test because hidden git and test-tool commands in the test
32 +# harness can contaminate our output.
33 +
34 +# Enable "brief" feature which turns off the prefix:
35 +# "<clock> <file>:<line> | <nr_parents> | "
36 +GIT_TR2_PERF_BRIEF=1 && export GIT_TR2_PERF_BRIEF
37 +
38 +# Repeat some of the t0210 tests using the perf target stream instead of
39 +# the normal stream.
40 +#
41 +# Tokens here of the form _FIELD_ have been replaced in the observed output.
42 +
43 +# Verb 001return
44 +#
45 +# Implicit return from cmd_<verb> function propagates <code>.
46 +
47 +test_expect_success 'perf stream, return code 0' '
48 + test_when_finished "rm trace.perf actual expect" &&
49 + GIT_TR2_PERF="$(pwd)/trace.perf" test-tool trace2 001return 0 &&
50 + perl "$TEST_DIRECTORY/t0211/scrub_perf.perl" <trace.perf >actual &&
51 + cat >expect <<-EOF &&
52 + d0|main|version|||||$V
53 + d0|main|start|||||_EXE_ trace2 001return 0
54 + d0|main|cmd_name|||||trace2 (trace2)
55 + d0|main|exit||_T_ABS_|||code:0
56 + d0|main|atexit||_T_ABS_|||code:0
57 + EOF
58 + test_cmp expect actual
59 +'
60 +
61 +test_expect_success 'perf stream, return code 1' '
62 + test_when_finished "rm trace.perf actual expect" &&
63 + test_must_fail env GIT_TR2_PERF="$(pwd)/trace.perf" test-tool trace2 001return 1 &&
64 + perl "$TEST_DIRECTORY/t0211/scrub_perf.perl" <trace.perf >actual &&
65 + cat >expect <<-EOF &&
66 + d0|main|version|||||$V
67 + d0|main|start|||||_EXE_ trace2 001return 1
68 + d0|main|cmd_name|||||trace2 (trace2)
69 + d0|main|exit||_T_ABS_|||code:1
70 + d0|main|atexit||_T_ABS_|||code:1
71 + EOF
72 + test_cmp expect actual
73 +'
74 +
75 +# Verb 003error
76 +#
77 +# To the above, add multiple 'error <msg>' events
78 +
79 +test_expect_success 'perf stream, error event' '
80 + test_when_finished "rm trace.perf actual expect" &&
81 + GIT_TR2_PERF="$(pwd)/trace.perf" test-tool trace2 003error "hello world" "this is a test" &&
82 + perl "$TEST_DIRECTORY/t0211/scrub_perf.perl" <trace.perf >actual &&
83 + cat >expect <<-EOF &&
84 + d0|main|version|||||$V
85 + d0|main|start|||||_EXE_ trace2 003error '\''hello world'\'' '\''this is a test'\''
86 + d0|main|cmd_name|||||trace2 (trace2)
87 + d0|main|error|||||hello world
88 + d0|main|error|||||this is a test
89 + d0|main|exit||_T_ABS_|||code:0
90 + d0|main|atexit||_T_ABS_|||code:0
91 + EOF
92 + test_cmp expect actual
93 +'
94 +
95 +# Verb 004child
96 +#
97 +# Test nested spawning of child processes.
98 +#
99 +# Conceptually, this looks like:
100 +# P1: TT trace2 004child
101 +# P2: |--- TT trace2 004child
102 +# P3: |--- TT trace2 001return 0
103 +#
104 +# Which should generate events:
105 +# P1: version
106 +# P1: start
107 +# P1: cmd_name
108 +# P1: child_start
109 +# P2: version
110 +# P2: start
111 +# P2: cmd_name
112 +# P2: child_start
113 +# P3: version
114 +# P3: start
115 +# P3: cmd_name
116 +# P3: exit
117 +# P3: atexit
118 +# P2: child_exit
119 +# P2: exit
120 +# P2: atexit
121 +# P1: child_exit
122 +# P1: exit
123 +# P1: atexit
124 +
125 +test_expect_success 'perf stream, child processes' '
126 + test_when_finished "rm trace.perf actual expect" &&
127 + GIT_TR2_PERF="$(pwd)/trace.perf" test-tool trace2 004child test-tool trace2 004child test-tool trace2 001return 0 &&
128 + perl "$TEST_DIRECTORY/t0211/scrub_perf.perl" <trace.perf >actual &&
129 + cat >expect <<-EOF &&
130 + d0|main|version|||||$V
131 + d0|main|start|||||_EXE_ trace2 004child test-tool trace2 004child test-tool trace2 001return 0
132 + d0|main|cmd_name|||||trace2 (trace2)
133 + d0|main|child_start||_T_ABS_|||[ch0] class:? argv: test-tool trace2 004child test-tool trace2 001return 0
134 + d1|main|version|||||$V
135 + d1|main|start|||||_EXE_ trace2 004child test-tool trace2 001return 0
136 + d1|main|cmd_name|||||trace2 (trace2/trace2)
137 + d1|main|child_start||_T_ABS_|||[ch0] class:? argv: test-tool trace2 001return 0
138 + d2|main|version|||||$V
139 + d2|main|start|||||_EXE_ trace2 001return 0
140 + d2|main|cmd_name|||||trace2 (trace2/trace2/trace2)
141 + d2|main|exit||_T_ABS_|||code:0
142 + d2|main|atexit||_T_ABS_|||code:0
143 + d1|main|child_exit||_T_ABS_|_T_REL_||[ch0] pid:_PID_ code:0
144 + d1|main|exit||_T_ABS_|||code:0
145 + d1|main|atexit||_T_ABS_|||code:0
146 + d0|main|child_exit||_T_ABS_|_T_REL_||[ch0] pid:_PID_ code:0
147 + d0|main|exit||_T_ABS_|||code:0
148 + d0|main|atexit||_T_ABS_|||code:0
149 + EOF
150 + test_cmp expect actual
151 +'
152 +
153 +test_done
t/t0211/scrub_perf.perl new
+76
@@ -0,0 +1,76 @@
1 +#!/usr/bin/perl
2 +#
3 +# Scrub the variable fields from the perf trace2 output to
4 +# make testing easier.
5 +
6 +use strict;
7 +use warnings;
8 +
9 +my $qpath = '\'[^\']*\'|[^ ]*';
10 +
11 +my $col_depth=0;
12 +my $col_thread=1;
13 +my $col_event=2;
14 +my $col_repo=3;
15 +my $col_t_abs=4;
16 +my $col_t_rel=5;
17 +my $col_category=6;
18 +my $col_rest=7;
19 +
20 +# This code assumes that the trace2 data was written with bare
21 +# turned on (which omits the "<clock> <file>:<line> | <parents>"
22 +# prefix.
23 +
24 +while (<>) {
25 + my @tokens = split /\|/;
26 +
27 + foreach my $col (@tokens) { $col =~ s/^\s+|\s+$//g; }
28 +
29 + if ($tokens[$col_event] =~ m/^start/) {
30 + # The 'start' message lists the contents of argv in $col_rest.
31 + # On some platforms (Windows), argv[0] is *sometimes* a canonical
32 + # absolute path to the EXE rather than the value passed in the
33 + # shell script. Replace it with a placeholder to simplify our
34 + # HEREDOC in the test script.
35 + my $argv0;
36 + my $argvRest;
37 + $tokens[$col_rest] =~ s/^($qpath)\W*(.*)/_EXE_ $2/;
38 + }
39 + elsif ($tokens[$col_event] =~ m/cmd_path/) {
40 + # Likewise, the 'cmd_path' message breaks out argv[0].
41 + #
42 + # This line is only emitted when RUNTIME_PREFIX is defined,
43 + # so just omit it for testing purposes.
44 + # $tokens[$col_rest] = "_EXE_";
45 + goto SKIP_LINE;
46 + }
47 + elsif ($tokens[$col_event] =~ m/child_exit/) {
48 + $tokens[$col_rest] =~ s/ pid:\d* / pid:_PID_ /;
49 + }
50 + elsif ($tokens[$col_event] =~ m/data/) {
51 + if ($tokens[$col_category] =~ m/process/) {
52 + # 'data' and 'data_json' events containing 'process'
53 + # category data are assumed to be platform-specific
54 + # and highly variable. Just omit them.
55 + goto SKIP_LINE;
56 + }
57 + }
58 +
59 + # t_abs and t_rel are either blank or a float. Replace the float
60 + # with a constant for matching the HEREDOC in the test script.
61 + if ($tokens[$col_t_abs] =~ m/\d/) {
62 + $tokens[$col_t_abs] = "_T_ABS_";
63 + }
64 + if ($tokens[$col_t_rel] =~ m/\d/) {
65 + $tokens[$col_t_rel] = "_T_REL_";
66 + }
67 +
68 + my $out;
69 +
70 + $out = join('|', @tokens);
71 + print "$out\n";
72 +
73 + SKIP_LINE:
74 +}
75 +
76 +
t/t0212-trace2-event.sh new
+236
@@ -0,0 +1,236 @@
1 +#!/bin/sh
2 +
3 +test_description='test trace2 facility'
4 +. ./test-lib.sh
5 +
6 +perl -MJSON::PP -e 0 >/dev/null 2>&1 && test_set_prereq JSON_PP
7 +
8 +# Add t/helper directory to PATH so that we can use a relative
9 +# path to run nested instances of test-tool.exe (see 004child).
10 +# This helps with HEREDOC comparisons later.
11 +TTDIR="$GIT_BUILD_DIR/t/helper/" && export TTDIR
12 +PATH="$TTDIR:$PATH" && export PATH
13 +
14 +# Warning: use of 'test_cmp' may run test-tool.exe and/or git.exe
15 +# Warning: to do the actual diff/comparison, so the HEREDOCs here
16 +# Warning: only cover our actual calls to test-tool and/or git.
17 +# Warning: So you may see extra lines in artifact files when
18 +# Warning: interactively debugging.
19 +
20 +# Turn off any inherited trace2 settings for this test.
21 +unset GIT_TR2 GIT_TR2_PERF GIT_TR2_EVENT
22 +unset GIT_TR2_BARE
23 +unset GIT_TR2_CONFIG_PARAMS
24 +
25 +V=$(git version | sed -e 's/^git version //') && export V
26 +
27 +# There are multiple trace2 targets: normal, perf, and event.
28 +# Trace2 events will/can be written to each active target (subject
29 +# to whatever filtering that target decides to do).
30 +# Test each target independently.
31 +#
32 +# Defer setting GIT_TR2_PERF until the actual command we want to
33 +# test because hidden git and test-tool commands in the test
34 +# harness can contaminate our output.
35 +
36 +# We don't bother repeating the 001return and 002exit tests, since they
37 +# have coverage in the normal and perf targets.
38 +
39 +# Verb 003error
40 +#
41 +# To the above, add multiple 'error <msg>' events
42 +
43 +test_expect_success JSON_PP 'event stream, error event' '
44 + test_when_finished "rm trace.event actual expect" &&
45 + GIT_TR2_EVENT="$(pwd)/trace.event" test-tool trace2 003error "hello world" "this is a test" &&
46 + perl "$TEST_DIRECTORY/t0212/parse_events.perl" <trace.event >actual &&
47 + sed -e "s/^|//" >expect <<-EOF &&
48 + |VAR1 = {
49 + | "_SID0_":{
50 + | "argv":[
51 + | "_EXE_",
52 + | "trace2",
53 + | "003error",
54 + | "hello world",
55 + | "this is a test"
56 + | ],
57 + | "errors":[
58 + | "%s",
59 + | "%s"
60 + | ],
61 + | "exit_code":0,
62 + | "hierarchy":"trace2",
63 + | "name":"trace2",
64 + | "version":"$V"
65 + | }
66 + |};
67 + EOF
68 + test_cmp expect actual
69 +'
70 +
71 +# Verb 004child
72 +#
73 +# Test nested spawning of child processes.
74 +#
75 +# Conceptually, this looks like:
76 +# P1: TT trace2 004child
77 +# P2: |--- TT trace2 004child
78 +# P3: |--- TT trace2 001return 0
79 +
80 +test_expect_success JSON_PP 'event stream, return code 0' '
81 + test_when_finished "rm trace.event actual expect" &&
82 + GIT_TR2_EVENT="$(pwd)/trace.event" test-tool trace2 004child test-tool trace2 004child test-tool trace2 001return 0 &&
83 + perl "$TEST_DIRECTORY/t0212/parse_events.perl" <trace.event >actual &&
84 + sed -e "s/^|//" >expect <<-EOF &&
85 + |VAR1 = {
86 + | "_SID0_":{
87 + | "argv":[
88 + | "_EXE_",
89 + | "trace2",
90 + | "004child",
91 + | "test-tool",
92 + | "trace2",
93 + | "004child",
94 + | "test-tool",
95 + | "trace2",
96 + | "001return",
97 + | "0"
98 + | ],
99 + | "child":{
100 + | "0":{
101 + | "child_argv":[
102 + | "_EXE_",
103 + | "trace2",
104 + | "004child",
105 + | "test-tool",
106 + | "trace2",
107 + | "001return",
108 + | "0"
109 + | ],
110 + | "child_class":"?",
111 + | "child_code":0,
112 + | "use_shell":0
113 + | }
114 + | },
115 + | "exit_code":0,
116 + | "hierarchy":"trace2",
117 + | "name":"trace2",
118 + | "version":"$V"
119 + | },
120 + | "_SID0_/_SID1_":{
121 + | "argv":[
122 + | "_EXE_",
123 + | "trace2",
124 + | "004child",
125 + | "test-tool",
126 + | "trace2",
127 + | "001return",
128 + | "0"
129 + | ],
130 + | "child":{
131 + | "0":{
132 + | "child_argv":[
133 + | "_EXE_",
134 + | "trace2",
135 + | "001return",
136 + | "0"
137 + | ],
138 + | "child_class":"?",
139 + | "child_code":0,
140 + | "use_shell":0
141 + | }
142 + | },
143 + | "exit_code":0,
144 + | "hierarchy":"trace2/trace2",
145 + | "name":"trace2",
146 + | "version":"$V"
147 + | },
148 + | "_SID0_/_SID1_/_SID2_":{
149 + | "argv":[
150 + | "_EXE_",
151 + | "trace2",
152 + | "001return",
153 + | "0"
154 + | ],
155 + | "exit_code":0,
156 + | "hierarchy":"trace2/trace2/trace2",
157 + | "name":"trace2",
158 + | "version":"$V"
159 + | }
160 + |};
161 + EOF
162 + test_cmp expect actual
163 +'
164 +
165 +# Test listing of all "interesting" config settings.
166 +
167 +test_expect_success JSON_PP 'event stream, list config' '
168 + test_when_finished "rm trace.event actual expect" &&
169 + git config --local t0212.abc 1 &&
170 + git config --local t0212.def "hello world" &&
171 + GIT_TR2_EVENT="$(pwd)/trace.event" GIT_TR2_CONFIG_PARAMS="t0212.*" test-tool trace2 001return 0 &&
172 + perl "$TEST_DIRECTORY/t0212/parse_events.perl" <trace.event >actual &&
173 + sed -e "s/^|//" >expect <<-EOF &&
174 + |VAR1 = {
175 + | "_SID0_":{
176 + | "argv":[
177 + | "_EXE_",
178 + | "trace2",
179 + | "001return",
180 + | "0"
181 + | ],
182 + | "exit_code":0,
183 + | "hierarchy":"trace2",
184 + | "name":"trace2",
185 + | "params":[
186 + | {
187 + | "param":"t0212.abc",
188 + | "value":"1"
189 + | },
190 + | {
191 + | "param":"t0212.def",
192 + | "value":"hello world"
193 + | }
194 + | ],
195 + | "version":"$V"
196 + | }
197 + |};
198 + EOF
199 + test_cmp expect actual
200 +'
201 +
202 +test_expect_success JSON_PP 'basic trace2_data' '
203 + test_when_finished "rm trace.event actual expect" &&
204 + GIT_TR2_EVENT="$(pwd)/trace.event" test-tool trace2 006data test_category k1 v1 test_category k2 v2 &&
205 + perl "$TEST_DIRECTORY/t0212/parse_events.perl" <trace.event >actual &&
206 + sed -e "s/^|//" >expect <<-EOF &&
207 + |VAR1 = {
208 + | "_SID0_":{
209 + | "argv":[
210 + | "_EXE_",
211 + | "trace2",
212 + | "006data",
213 + | "test_category",
214 + | "k1",
215 + | "v1",
216 + | "test_category",
217 + | "k2",
218 + | "v2"
219 + | ],
220 + | "data":{
221 + | "test_category":{
222 + | "k1":"v1",
223 + | "k2":"v2"
224 + | }
225 + | },
226 + | "exit_code":0,
227 + | "hierarchy":"trace2",
228 + | "name":"trace2",
229 + | "version":"$V"
230 + | }
231 + |};
232 + EOF
233 + test_cmp expect actual
234 +'
235 +
236 +test_done
t/t0212/parse_events.perl new
+251
@@ -0,0 +1,251 @@
1 +#!/usr/bin/perl
2 +#
3 +# Parse event stream and convert individual events into a summary
4 +# record for the process.
5 +#
6 +# Git.exe generates one or more "event" records for each API method,
7 +# such as "start <argv>" and "exit <code>", during the life of the git
8 +# process. Additionally, the input may contain interleaved events
9 +# from multiple concurrent git processes and/or multiple threads from
10 +# within a git process.
11 +#
12 +# Accumulate events for each process (based on its unique SID) in a
13 +# dictionary and emit process summary records.
14 +#
15 +# Convert some of the variable fields (such as elapsed time) into
16 +# placeholders (or omit them) to make HEREDOC comparisons easier in
17 +# the test scripts.
18 +#
19 +# We may also omit fields not (currently) useful for testing purposes.
20 +
21 +use strict;
22 +use warnings;
23 +use JSON::PP;
24 +use Data::Dumper;
25 +use Getopt::Long;
26 +
27 +# The version of the trace2 event target format that we understand.
28 +# This is reported in the 'version' event in the 'evt' field.
29 +# It comes from the GIT_TR2_EVENT_VERSION macro in trace2/tr2_tgt_event.c
30 +my $evt_version = '1';
31 +
32 +my $show_children = 1;
33 +my $show_exec = 1;
34 +my $show_threads = 1;
35 +
36 +# A hack to generate test HEREDOC data for pasting into the test script.
37 +# Usage:
38 +# cd "t/trash directory.t0212-trace2-event"
39 +# $TT trace ... >trace.event
40 +# VV=$(../../git.exe version | sed -e 's/^git version //')
41 +# perl ../t0212/parse_events.perl --HEREDOC --VERSION=$VV <trace.event >heredoc
42 +# Then paste heredoc into your new test.
43 +
44 +my $gen_heredoc = 0;
45 +my $gen_version = '';
46 +
47 +GetOptions("children!" => \$show_children,
48 + "exec!" => \$show_exec,
49 + "threads!" => \$show_threads,
50 + "HEREDOC!" => \$gen_heredoc,
51 + "VERSION=s" => \$gen_version )
52 + or die("Error in command line arguments\n");
53 +
54 +
55 +# SIDs contains timestamps and PIDs of the process and its parents.
56 +# This makes it difficult to match up in a HEREDOC in the test script.
57 +# Build a map from actual SIDs to predictable constant values and yet
58 +# keep the parent/child relationships. For example:
59 +# {..., "sid":"1539706952458276-8652", ...}
60 +# {..., "sid":"1539706952458276-8652/1539706952649493-15452", ...}
61 +# becomes:
62 +# {..., "sid":"_SID1_", ...}
63 +# {..., "sid":"_SID1_/_SID2_", ...}
64 +my $sid_map;
65 +my $sid_count = 0;
66 +
67 +my $processes;
68 +
69 +while (<>) {
70 + my $line = decode_json( $_ );
71 +
72 + my $sid = "";
73 + my $sid_sep = "";
74 +
75 + my $raw_sid = $line->{'sid'};
76 + my @raw_sid_parts = split /\//, $raw_sid;
77 + foreach my $raw_sid_k (@raw_sid_parts) {
78 + if (!exists $sid_map->{$raw_sid_k}) {
79 + $sid_map->{$raw_sid_k} = '_SID' . $sid_count . '_';
80 + $sid_count++;
81 + }
82 + $sid = $sid . $sid_sep . $sid_map->{$raw_sid_k};
83 + $sid_sep = '/';
84 + }
85 +
86 + my $event = $line->{'event'};
87 +
88 + if ($event eq 'version') {
89 + $processes->{$sid}->{'version'} = $line->{'exe'};
90 + if ($gen_heredoc == 1 && $gen_version eq $line->{'exe'}) {
91 + # If we are generating data FOR the test script, replace
92 + # the reported git.exe version with a reference to an
93 + # environment variable. When our output is pasted into
94 + # the test script, it will then be expanded in future
95 + # test runs to the THEN current version of git.exe.
96 + # We assume that the test script uses env var $V.
97 + $processes->{$sid}->{'version'} = "\$V";
98 + }
99 + }
100 +
101 + elsif ($event eq 'start') {
102 + $processes->{$sid}->{'argv'} = $line->{'argv'};
103 + $processes->{$sid}->{'argv'}[0] = "_EXE_";
104 + }
105 +
106 + elsif ($event eq 'exit') {
107 + $processes->{$sid}->{'exit_code'} = $line->{'code'};
108 + }
109 +
110 + elsif ($event eq 'atexit') {
111 + $processes->{$sid}->{'exit_code'} = $line->{'code'};
112 + }
113 +
114 + elsif ($event eq 'error') {
115 + # For HEREDOC purposes, use the error message format string if
116 + # available, rather than the formatted message (which probably
117 + # has an absolute pathname).
118 + if (exists $line->{'fmt'}) {
119 + push( @{$processes->{$sid}->{'errors'}}, $line->{'fmt'} );
120 + }
121 + elsif (exists $line->{'msg'}) {
122 + push( @{$processes->{$sid}->{'errors'}}, $line->{'msg'} );
123 + }
124 + }
125 +
126 + elsif ($event eq 'cmd_path') {
127 + ## $processes->{$sid}->{'path'} = $line->{'path'};
128 + #
129 + # Like in the 'start' event, we need to replace the value of
130 + # argv[0] with a token for HEREDOC purposes. However, the
131 + # event is only emitted when RUNTIME_PREFIX is defined, so
132 + # just omit it for testing purposes.
133 + # $processes->{$sid}->{'path'} = "_EXE_";
134 + }
135 +
136 + elsif ($event eq 'cmd_name') {
137 + $processes->{$sid}->{'name'} = $line->{'name'};
138 + $processes->{$sid}->{'hierarchy'} = $line->{'hierarchy'};
139 + }
140 +
141 + elsif ($event eq 'alias') {
142 + $processes->{$sid}->{'alias'}->{'key'} = $line->{'alias'};
143 + $processes->{$sid}->{'alias'}->{'argv'} = $line->{'argv'};
144 + }
145 +
146 + elsif ($event eq 'def_param') {
147 + my $kv;
148 + $kv->{'param'} = $line->{'param'};
149 + $kv->{'value'} = $line->{'value'};
150 + push( @{$processes->{$sid}->{'params'}}, $kv );
151 + }
152 +
153 + elsif ($event eq 'child_start') {
154 + if ($show_children == 1) {
155 + $processes->{$sid}->{'child'}->{$line->{'child_id'}}->{'child_class'} = $line->{'child_class'};
156 + $processes->{$sid}->{'child'}->{$line->{'child_id'}}->{'child_argv'} = $line->{'argv'};
157 + $processes->{$sid}->{'child'}->{$line->{'child_id'}}->{'child_argv'}[0] = "_EXE_";
158 + $processes->{$sid}->{'child'}->{$line->{'child_id'}}->{'use_shell'} = $line->{'use_shell'} ? 1 : 0;
159 + }
160 + }
161 +
162 + elsif ($event eq 'child_exit') {
163 + if ($show_children == 1) {
164 + $processes->{$sid}->{'child'}->{$line->{'child_id'}}->{'child_code'} = $line->{'code'};
165 + }
166 + }
167 +
168 + # TODO decide what information we want to test from thread events.
169 +
170 + elsif ($event eq 'thread_start') {
171 + if ($show_threads == 1) {
172 + }
173 + }
174 +
175 + elsif ($event eq 'thread_exit') {
176 + if ($show_threads == 1) {
177 + }
178 + }
179 +
180 + # TODO decide what information we want to test from exec events.
181 +
182 + elsif ($event eq 'exec') {
183 + if ($show_exec == 1) {
184 + }
185 + }
186 +
187 + elsif ($event eq 'exec_result') {
188 + if ($show_exec == 1) {
189 + }
190 + }
191 +
192 + elsif ($event eq 'def_param') {
193 + # Accumulate parameter key/value pairs by key rather than in an array
194 + # so that we get overwrite (last one wins) effects.
195 + $processes->{$sid}->{'params'}->{$line->{'param'}} = $line->{'value'};
196 + }
197 +
198 + elsif ($event eq 'def_repo') {
199 + # $processes->{$sid}->{'repos'}->{$line->{'repo'}} = $line->{'worktree'};
200 + $processes->{$sid}->{'repos'}->{$line->{'repo'}} = "_WORKTREE_";
201 + }
202 +
203 + # A series of potentially nested and threaded region and data events
204 + # is fundamentally incompatibile with the type of summary record we
205 + # are building in this script. Since they are intended for
206 + # perf-trace-like analysis rather than a result summary, we ignore
207 + # most of them here.
208 +
209 + # elsif ($event eq 'region_enter') {
210 + # }
211 + # elsif ($event eq 'region_leave') {
212 + # }
213 +
214 + elsif ($event eq 'data') {
215 + my $cat = $line->{'category'};
216 + if ($cat eq 'test_category') {
217 +
218 + my $key = $line->{'key'};
219 + my $value = $line->{'value'};
220 + $processes->{$sid}->{'data'}->{$cat}->{$key} = $value;
221 + }
222 + }
223 +
224 + # This trace2 target does not emit 'printf' events.
225 + #
226 + # elsif ($event eq 'printf') {
227 + # }
228 +}
229 +
230 +# Dump the resulting hash into something that we can compare against
231 +# in the test script. These options make Dumper output look a little
232 +# bit like JSON. Also convert variable references of the form "$VAR*"
233 +# so that the matching HEREDOC doesn't need to escape it.
234 +
235 +$Data::Dumper::Sortkeys = 1;
236 +$Data::Dumper::Indent = 1;
237 +$Data::Dumper::Purity = 1;
238 +$Data::Dumper::Pair = ':';
239 +
240 +my $out = Dumper($processes);
241 +$out =~ s/'/"/g;
242 +$out =~ s/\$VAR/VAR/g;
243 +
244 +# Finally, if we're running this script to generate (manually confirmed)
245 +# data to add to the test script, guard the indentation.
246 +
247 +if ($gen_heredoc == 1) {
248 + $out =~ s/^/\t\|/gms;
249 +}
250 +
251 +print $out;