test-lib: add a SANITIZE=leak logging mode

Add the ability to run the test suite under a new "GIT_TEST_SANITIZE_LEAK_LOG=true" mode, when true we'll log the leaks we find an a new "test-results/<test-name>.leak" directory. That new path is consistent with the existing "test-results/<test-name>.<type>" results, except that those are all files, not directories. We also set "log_exe_name=1" to include the name of the executable in the filename. This gives us files like "trace.git.<pid>" instead of the default of "trace.<pid>". I.e. we'll be able to distinguish "git" leaks from "test-tool", "git-daemon" etc. We then set "dedup_token_length" to non-zero ("0" is the default) to succinctly log a token we can de-duplicate these stacktraces on. The string is simply a one-line stack-trace with only function names up to N frames, which we limit at "9999" as a shorthand for "infinite" (there appears to be no way to say "no limit"). With these combined we can now easily get e.g. the top 10 leaks in the test suite grouped by full stacktrace: grep -o -P -h '(?<=DEDUP_TOKEN: ).*' test-results/*.leak/trace.git.* | sort | uniq -c | sort -nr | head -n 10 Or add "grep -E -o '[^-]+'" to that to group by functions instead of stack traces: grep -o -P -h '(?<=DEDUP_TOKEN: ).*' test-results/*.leak/trace.git.* | grep -E -o '[^-]+' | sort | uniq -c | sort -nr | head -n 20 This new mode requires git to be compiled with SANITIZE=leak, rather than explaining that in the documentation let's make it self-documenting by bailing out if the user asks for this without git having been compiled with SANITIZE=leak, as we do with GIT_TEST_PASSING_SANITIZE_LEAK=true. Signed-off-by: Ævar Arnfjörð Bjarmason <avarab@gmail.com> Signed-off-by: Junio C Hamano <gitster@pobox.com>

Ævar Arnfjörð Bjarmason committed Jul 28, 2022 at 01:13 UTC 366bd129dc323d21fa830ca086162341a9b7c2d3
2 files changed +34 -1
t/README
+5
@@ -371,6 +371,11 @@ declared themselves as leak-free by setting
371 "TEST_PASSES_SANITIZE_LEAK=true" before sourcing "test-lib.sh". This
372 test mode is used by the "linux-leaks" CI target.
373
374 +GIT_TEST_SANITIZE_LEAK_LOG=true will log memory leaks to
375 +"test-results/$TEST_NAME.leak/trace.*" files. The logs include a
376 +"dedup_token" (see +"ASAN_OPTIONS=help=1 ./git") and other options to
377 +make logs +machine-readable.
378 +
379 GIT_TEST_PROTOCOL_VERSION=<n>, when set, makes 'protocol.version'
380 default to n.
381
t/test-lib.sh
+29 -1
@@ -305,6 +305,10 @@ TEST_NUMBER="${TEST_NAME%%-*}"
305 TEST_NUMBER="${TEST_NUMBER#t}"
306 TEST_RESULTS_DIR="$TEST_OUTPUT_DIRECTORY/test-results"
307 TEST_RESULTS_BASE="$TEST_RESULTS_DIR/$TEST_NAME$TEST_STRESS_JOB_SFX"
308 +TEST_RESULTS_SAN_FILE_PFX=trace
309 +TEST_RESULTS_SAN_DIR_SFX=leak
310 +TEST_RESULTS_SAN_FILE=
311 +TEST_RESULTS_SAN_DIR="$TEST_RESULTS_DIR/$TEST_NAME.$TEST_RESULTS_SAN_DIR_SFX"
312 TRASH_DIRECTORY="trash directory.$TEST_NAME$TEST_STRESS_JOB_SFX"
313 test -n "$root" && TRASH_DIRECTORY="$root/$TRASH_DIRECTORY"
314 case "$TRASH_DIRECTORY" in
@@ -1447,6 +1451,10 @@ then
1451 test_done
1452 fi
1453
1454 +BAIL_OUT_ENV_NEEDS_SANITIZE_LEAK () {
1455 + BAIL_OUT "$1 has no effect except when compiled with SANITIZE=leak"
1456 +}
1457 +
1458 if test -n "$SANITIZE_LEAK"
1459 then
1460 if test_bool_env GIT_TEST_PASSING_SANITIZE_LEAK false
@@ -1461,9 +1469,29 @@ then
1469 test_done
1470 fi
1471 fi
1472 +
1473 + if test_bool_env GIT_TEST_SANITIZE_LEAK_LOG false
1474 + then
1475 + if ! mkdir -p "$TEST_RESULTS_SAN_DIR"
1476 + then
1477 + BAIL_OUT "cannot create $TEST_RESULTS_SAN_DIR"
1478 + fi &&
1479 + TEST_RESULTS_SAN_FILE="$TEST_RESULTS_SAN_DIR/$TEST_RESULTS_SAN_FILE_PFX"
1480 +
1481 + # Don't litter *.leak dirs if there was nothing to report
1482 + test_atexit "rmdir \"$TEST_RESULTS_SAN_DIR\" 2>/dev/null || :"
1483 +
1484 + prepend_var LSAN_OPTIONS : dedup_token_length=9999
1485 + prepend_var LSAN_OPTIONS : log_exe_name=1
1486 + prepend_var LSAN_OPTIONS : log_path=\"$TEST_RESULTS_SAN_FILE\"
1487 + export LSAN_OPTIONS
1488 + fi
1489 elif test_bool_env GIT_TEST_PASSING_SANITIZE_LEAK false
1490 then
1466 - BAIL_OUT "GIT_TEST_PASSING_SANITIZE_LEAK=true has no effect except when compiled with SANITIZE=leak"
1491 + BAIL_OUT_ENV_NEEDS_SANITIZE_LEAK "GIT_TEST_PASSING_SANITIZE_LEAK=true"
1492 +elif test_bool_env GIT_TEST_SANITIZE_LEAK_LOG false
1493 +then
1494 + BAIL_OUT_ENV_NEEDS_SANITIZE_LEAK "GIT_TEST_SANITIZE_LEAK_LOG=true"
1495 fi
1496
1497 # Last-minute variable setup