Subject: [PATCH v2 6/6] t0213: add trace2 cmd_ancestry tests

From: "Matthew John Cheetham via GitGitGadget" <[email-masked]>
Date: Fri, 13 Feb 2026 19:55:00 +0000

From: Matthew John Cheetham <[email-masked]>

Add a new test script t0213-trace2-ancestry.sh that verifies
cmd_ancestry events across all three trace2 output formats (normal,
perf, and event).

The tests use the "400ancestry" test helper to spawn child processes
with controlled trace2 environments. Git alias resolution (which
spawns a child git process) creates a predictable multi-level process
tree. Filter functions extract cmd_ancestry events from each format,
truncating the ancestor list at the outermost "test-tool" so that only
the controlled portion of the tree is verified, regardless of the test
runner environment.

A runtime prerequisite (TRACE2_ANCESTRY) is used to detect whether the
platform has a real procinfo implementation; platforms with only the
stub are skipped.

We must pay attention to an extra ancestor on Windows (MINGW) when
running without the bin-wrappers (such as we do in CI). In this
situation we see an extra "sh.exe" ancestor after "test-tool.exe".

Also update the comment in t0210-trace2-normal.sh to reflect that
ancestry testing now has its own dedicated test script.

Signed-off-by: Matthew John Cheetham <[email-masked]>
---
 t/meson.build              |   1 +
 t/t0210-trace2-normal.sh   |   5 +-
 t/t0213-trace2-ancestry.sh | 180 +++++++++++++++++++++++++++++++++++++
 3 files changed, 184 insertions(+), 2 deletions(-)
 create mode 100755 t/t0213-trace2-ancestry.sh

diff --git a/t/meson.build b/t/meson.build
index a5531df415..551c3036c0 100644
--- a/t/meson.build
+++ b/t/meson.build
@@ -131,6 +131,7 @@ integration_tests = [
   't0210-trace2-normal.sh',
   't0211-trace2-perf.sh',
   't0212-trace2-event.sh',
+  't0213-trace2-ancestry.sh',
   't0300-credentials.sh',
   't0301-credential-cache.sh',
   't0302-credential-store.sh',
diff --git a/t/t0210-trace2-normal.sh b/t/t0210-trace2-normal.sh
index 96c68f65df..7e1e7af862 100755
--- a/t/t0210-trace2-normal.sh
+++ b/t/t0210-trace2-normal.sh
@@ -74,8 +74,9 @@ scrub_normal () {
 	#      This line is only emitted when RUNTIME_PREFIX is defined,
 	#      so just omit it for testing purposes.
 	#
-	#   4. 'cmd_ancestry' is not implemented everywhere, so for portability's
-	#      sake, skip it when parsing normal.
+	#   4. 'cmd_ancestry' output depends on how the test is run and
+	#      is not relevant to the features we are testing here.
+	#      Ancestry tests are covered in t0213-trace2-ancestry.sh instead.
 	sed \
 		-e 's/elapsed:[0-9]*\.[0-9][0-9]*\([eE][-+]\{0,1\}[0-9][0-9]*\)\{0,1\}/elapsed:_TIME_/g' \
 		-e "s/^start '[^']*' \(.*\)/start _EXE_ \1/" \
diff --git a/t/t0213-trace2-ancestry.sh b/t/t0213-trace2-ancestry.sh
new file mode 100755
index 0000000000..a2b9536da8
--- /dev/null
+++ b/t/t0213-trace2-ancestry.sh
@@ -0,0 +1,180 @@
+#!/bin/sh
+
+test_description='test trace2 cmd_ancestry event'
+
+. ./test-lib.sh
+
+# Turn off any inherited trace2 settings for this test.
+sane_unset GIT_TRACE2 GIT_TRACE2_PERF GIT_TRACE2_EVENT
+sane_unset GIT_TRACE2_BRIEF
+sane_unset GIT_TRACE2_CONFIG_PARAMS
+
+# Add t/helper directory to PATH so that we can use a relative
+# path to run nested instances of test-tool.exe (see 004child).
+# This helps with HEREDOC comparisons later.
+TTDIR="$GIT_BUILD_DIR/t/helper/" && export TTDIR
+PATH="$TTDIR:$PATH" && export PATH
+
+# The 400ancestry helper spawns a child process so that the child
+# sees "test-tool" in its process ancestry.  We capture only the
+# child's trace2 output to a file.
+#
+# The tests use git commands that spawn child git processes (e.g.,
+# alias resolution) to create a controlled multi-level process tree.
+# Because cmd_ancestry walks the real process tree, processes will
+# also report ancestors above "test-tool" that depend on the test
+# runner environment (e.g., bash, make, tmux).  The filter functions
+# below truncate the ancestry at "test-tool", discarding anything
+# above it, so only the controlled portion is verified.
+#
+# On platforms without a real procinfo implementation (the stub),
+# no cmd_ancestry event is emitted.  We detect this at runtime and
+# skip the format-specific tests accordingly.
+
+# Determine if cmd_ancestry is supported on this platform.
+test_expect_success 'detect cmd_ancestry support' '
+	test_when_finished "rm -f trace.detect" &&
+	GIT_TRACE2_BRIEF=1 GIT_TRACE2="$(pwd)/trace.detect" \
+		test-tool trace2 001return 0 &&
+	if grep -q "^cmd_ancestry" trace.detect
+	then
+		test_set_prereq TRACE2_ANCESTRY
+	fi
+'
+
+# Filter functions for each trace2 target format.
+#
+# Each extracts cmd_ancestry events, strips format-specific syntax,
+# and truncates the ancestor list at the outermost "test-tool"
+# (or "test-tool.exe" on Windows), discarding any higher-level
+# (uncontrolled) ancestors.
+#
+# Output is a space-separated list of ancestor names, one line per
+# cmd_ancestry event, with the immediate parent listed first:
+#
+#   test-tool                          (or: test-tool.exe)
+#   git test-tool                      (or: git.exe test-tool.exe)
+#   git test-tool test-tool            (or: git.exe test-tool.exe test-tool.exe)
+
+if test_have_prereq MINGW
+then
+	TT=test-tool$X
+else
+	TT=test-tool
+fi
+
+filter_ancestry_normal () {
+	sed -n '/^cmd_ancestry/{
+		s/^cmd_ancestry //
+		s/ <- / /g
+		s/\(.*'"$TT"'\) .*/\1/
+		p
+	}'
+}
+
+filter_ancestry_perf () {
+	sed -n '/cmd_ancestry/{
+		s/.*ancestry:\[//
+		s/\]//
+		s/\(.*'"$TT"'\) .*/\1/
+		p
+	}'
+}
+
+filter_ancestry_event () {
+	sed -n '/"cmd_ancestry"/{
+		s/.*"ancestry":\[//
+		s/\].*//
+		s/"//g
+		s/,/ /g
+		s/\(.*'"$TT"'\) .*/\1/
+		p
+	}'
+}
+
+# On Windows (MINGW) when running with the bin-wrappers, we also see "sh.exe" in
+# the ancestry. We must therefore account for this expected ancestry element in
+# the expected output of the tests.
+if test_have_prereq MINGW && test -z "$no_bin_wrappers"; then
+	SH_TT="sh$X $TT"
+else
+	SH_TT="$TT"
+fi
+
+# Git alias resolution spawns the target command as a child process.
+# Using "git -c alias.xyz=version xyz" creates a two-level chain:
+#
+#   test-tool (400ancestry)
+#     -> git (resolves alias xyz -> version)
+#          -> git (version)
+#
+# Both git processes are instrumented and emit cmd_ancestry.  After
+# filtering out ancestors above test-tool, we get:
+#
+#   test-tool                 (from git alias resolver)
+#   git test-tool             (from git version)
+
+test_expect_success TRACE2_ANCESTRY 'normal: git alias chain, 2 levels' '
+	test_when_finished "rm -f trace.normal actual expect" &&
+	test-tool trace2 400ancestry normal "$(pwd)/trace.normal" \
+		git -c alias.xyz=version xyz &&
+	filter_ancestry_normal <trace.normal >actual &&
+	cat >expect <<-EOF &&
+	$SH_TT
+	git$X $SH_TT
+	EOF
+	test_cmp expect actual
+'
+
+test_expect_success TRACE2_ANCESTRY 'perf: git alias chain, 2 levels' '
+	test_when_finished "rm -f trace.perf actual expect" &&
+	test-tool trace2 400ancestry perf "$(pwd)/trace.perf" \
+		git -c alias.xyz=version xyz &&
+	filter_ancestry_perf <trace.perf >actual &&
+	cat >expect <<-EOF &&
+	$SH_TT
+	git$X $SH_TT
+	EOF
+	test_cmp expect actual
+'
+
+test_expect_success TRACE2_ANCESTRY 'event: git alias chain, 2 levels' '
+	test_when_finished "rm -f trace.event actual expect" &&
+	test-tool trace2 400ancestry event "$(pwd)/trace.event" \
+		git -c alias.xyz=version xyz &&
+	filter_ancestry_event <trace.event >actual &&
+	cat >expect <<-EOF &&
+	$SH_TT
+	git$X $SH_TT
+	EOF
+	test_cmp expect actual
+'
+
+# Use 004child to add a test-tool layer, creating a three-level chain:
+#
+#   test-tool (400ancestry)
+#     -> test-tool (004child)
+#          -> git (resolves alias xyz -> version)
+#               -> git (version)
+#
+# Three instrumented processes emit cmd_ancestry.  After filtering:
+#
+#   test-tool                  (from test-tool 004child)
+#   test-tool test-tool        (from git alias resolver)
+#   git test-tool test-tool    (from git version)
+
+test_expect_success TRACE2_ANCESTRY 'normal: deeper chain, 3 levels' '
+	test_when_finished "rm -f trace.normal actual expect" &&
+	test-tool trace2 400ancestry normal "$(pwd)/trace.normal" \
+		test-tool trace2 004child \
+			git -c alias.xyz=version xyz &&
+	filter_ancestry_normal <trace.normal >actual &&
+	cat >expect <<-EOF &&
+	$TT
+	$SH_TT $TT
+	git$X $SH_TT $TT
+	EOF
+	test_cmp expect actual
+'
+
+test_done
-- 
gitgitgadget