diff --git a/README.rst b/README.rst index 443ea81c..88f621df 100644 --- a/README.rst +++ b/README.rst @@ -184,6 +184,7 @@ These perl packages include: - Devel::StackTrace - Digest::MD5 - File::Spec +- GD - at least one flavor of JSON module. In order of performance/preference: - JSON::XS @@ -213,13 +214,23 @@ To execute ``make install ...`` or ``make doc``, you will need: Some of the applications provided with the lcov module are written in Python - and may require additional Python packages. In particular, 'xlsxwriter' is required in order to generate any -of the spreadsheet reports, and 'xml' and 'zipfile' are required in +of the spreadsheet reports, and Python packages 'xml' and 'zipfile' are +required in order to be able to run regression tests. To measure Python code coverage, users will need Python packages: - Coverage.py - + +Certain tests use additional tools: + +- ``jq`` - the command-line json parser - is required by some tests. + ``make check`` fail if it is not found. +- ``py2lcov`` tests use Coverage.py; these tests will be skipped if + the tool is not found in your environment. +- some LLVM-related tests (*e.g.*, the ``llvm2lcov`` tests) require + ``clang`` and/or ``clang++``. If these are not present in your + environment, then the LLVM-related tests are skipped. In addition, contributors will need: diff --git a/bin/lcov b/bin/lcov index 4b0fe9c6..c8c53a1a 100755 --- a/bin/lcov +++ b/bin/lcov @@ -136,6 +136,7 @@ sub setup_gkv(); sub lcov_geninfo(@); sub create_package($$$;$); sub summary(); +sub count_data_cb($$$); # Global variables & initialization @@ -1039,6 +1040,22 @@ sub read_file($) return $content; } +# +# count_data_cb(dir, relative_name, count_ref) +# +# Count the data files found while searching an unpacked package. Returns +# undefined: a defined value would stop 'lcov_find' at the first match. +# + +sub count_data_cb($$$) +{ + my ($dir, $rel, $count_ref) = @_; + + $$count_ref++; + + return; +} + # # get_package(package_file) # @@ -1060,16 +1077,16 @@ sub get_package($) info("Reading package $file:\n"); $file = abs_path($file); chdir($dir); - open(HANDLE, "-|", "tar xvfz " . quotemeta($file) . " 2>$lcovutil::devnull") + # Extract quietly: the format of the verbose listing depends on which + # tar implementation is installed (GNU tar prints just the path, while + # bsdtar appends the file size to each line), so the data files are + # counted in the expanded directory rather than from the tar listing. + open(HANDLE, "-|", "tar xfz " . quotemeta($file) . " 2>$lcovutil::devnull") or die("could not process package $file: $!\n"); - $count = 0; - while () { - if (/\.da$/ || /\.gcda$/) { - $count++; - } - } close(HANDLE); + $count = 0; + lcov_find($dir, \&count_data_cb, \$count, '\.gcda$', '\.da$'); if ($count == 0) { die("no data file found in package $file\n"); } @@ -1126,7 +1143,8 @@ sub count_package_data($) open(HANDLE, "-|", "tar tfz " . quotemeta($filename)) or return undef; while () { - if (/\.da$/ || /\.gcda$/) { + # tolerate 'bsdtar', which appends the file size to each listing line + if (/\.(?:gcda|da)(?:\s|\z)/) { $count++; } } diff --git a/tests/common.tst b/tests/common.tst index 53501dc5..40546745 100644 --- a/tests/common.tst +++ b/tests/common.tst @@ -8,6 +8,46 @@ PROFILE="--profile" LOCAL_COVERAGE=1 KEEP_GOING=0 +# --------------------------------------------------------------------------- +# Portable helpers +# --------------------------------------------------------------------------- + +# BSD 'wc -l' left-pads its output, so a literal string comparison against an +# expected count fails even when the counts are numerically equal. Always +# compare '$(count_lines FILE)' - awk prints a clean number everywhere. +# BSD/macOS has 'md5' (stdout) but not the GNU 'md5sum' that was forked in +# legacy scripts; compute the identical lowercase hex digest in-process using +# perl Digest::MD5 so tests agree with the tools on every platform. +compute_md5_hex() +{ + perl -MDigest::MD5 -e ' + $SIG{PIPE} = q{IGNORE}; + my $name = @ARGV && $ARGV[0] ne q{-} ? $ARGV[0] : q{-}; + binmode STDIN; + my $self = Digest::MD5->new; + $self->add(do { local $/; my $fh = $name eq q{-} ? *STDIN : do { open my $h, q{<}, $name or die qq{md5: $name: $!}; $h }; <$fh> }); + print $self->hexdigest, chr(10); + ' "$@" +} + +count_lines() +{ + awk 'END { print NR }' "$@" +} + +# Prints the best way to invoke the python Coverage.py module - empty when +# the package isn't installed (callers should then SKIP, not fail). +find_python_coverage() +{ + if command -v coverage >/dev/null 2> /dev/null ; then + echo "coverage" + elif command -v python3-coverage >/dev/null 2> /dev/null ; then + echo "python3-coverage" + elif python3 -m coverage --version >/dev/null 2> /dev/null ; then + echo "python3 -m coverage" + fi +} + #echo "CMD: $0 $@" while [ $# -gt 0 ] ; do @@ -68,14 +108,20 @@ while [ $# -gt 0 ] ; do CMD='python3-coverage' # ubuntu? fi fi - which $CMD - if [ 0 != $? ] ; then - echo "cannot find 'coverage' or 'python3-coverage'" - echo "unable to run py2lcov - please install python Coverage.py package" - exit 1 + if ! command -v "$CMD" > /dev/null 2> /dev/null ; then + if [ "python3 -m coverage" != "$CMD" ] && + ! python3 -m coverage --version >/dev/null 2> /dev/null ; then + # Not a bug in the tools - run without coverage + # instrumentation (see runtests.py: a missing Coverage.py + # also counts as a platform skip there). + echo "python Coverage.py package not installed - running without runtime instrumentation" + CMD='' + fi fi - PYCOVER="COVERAGE_FILE=$PYCOV_DB $CMD run --branch --append" + if [ '' != "$CMD" ] ; then + PYCOVER="COVERAGE_FILE=$PYCOV_DB $CMD run --branch --append" + fi ;; --home | -home ) @@ -266,7 +312,7 @@ function check_tla_css() local rc=0 local class for class in `find $dir -name '*.html' -print0 | - xargs -0 --no-run-if-empty grep -h -o -E 'class="[^"]+"' | + xargs -0 -r grep -h -o -E 'class="[^"]+"' | sed -e 's/class="//' -e 's/"$//' | tr ' ' '\n' | grep -E '^tla(Bg)?[A-Z]' | sort -u` ; do if ! grep -E -q "(^|[[:space:]])(td|span|a)\.$class([^A-Za-z0-9]|\$)" \ diff --git a/tests/genhtml/htmlgen/htmlgen.sh b/tests/genhtml/htmlgen/htmlgen.sh index f7ca4557..6804fb5b 100755 --- a/tests/genhtml/htmlgen/htmlgen.sh +++ b/tests/genhtml/htmlgen/htmlgen.sh @@ -430,8 +430,8 @@ for mode in plain nav ; do done # the source text has no brackets in it, so every '[' in the page opened a # coverpoint group and every ']' closed one - OPEN=`grep -o '\[' $PAGE | wc -l` - CLOSE=`grep -o '\]' $PAGE | wc -l` + OPEN=`grep -o '\[' $PAGE | grep -c ''` + CLOSE=`grep -o '\]' $PAGE | grep -c ''` if [ "$OPEN" != "$CLOSE" ] ; then fail unreach_$mode "$OPEN '[' but $CLOSE ']' in the source page" fi @@ -448,7 +448,7 @@ for mode in plain nav ; do # matches and not lines - and the page is found first, because backticks # inside backticks do not nest PAGE=`find html_$mode -name 't.c.gcov.html'` - COUNT=`grep -o -F -e 'tlaEUC' $PAGE | wc -l` + COUNT=`grep -o -F -e 'tlaEUC' $PAGE | grep -c ''` eval "COUNT_$mode=$COUNT" done if [ "$COUNT_plain" != "$COUNT_nav" ] ; then @@ -817,7 +817,7 @@ for mode in nfb nfb_off nfb_noann nfb_olderbase ; do # the MC/DC coverpoints are on the page at all: four markers on line 5, # which are dropped entirely if the categorizer walks only the baseline. # All four are on one line, so this counts matches and not lines - COUNT=`grep -o -F -e 'class="mcdcTla"' $PAGE | wc -l` + COUNT=`grep -o -F -e 'class="mcdcTla"' $PAGE | grep -c ''` if [ 4 != "$COUNT" ] ; then fail $mode "expected 4 MC/DC markers in the $mode page, found $COUNT" fi diff --git a/tests/genpng/genpng.sh b/tests/genpng/genpng.sh index 13a7fd2f..ecc73351 100755 --- a/tests/genpng/genpng.sh +++ b/tests/genpng/genpng.sh @@ -183,7 +183,7 @@ elif [ -s genpng2.log ] ; then else dump hand.png > hand.dump # nine source lines, and no row for either of the two metadata lines - ROWS=`wc -l < hand.dump` + ROWS=`count_lines hand.dump` if [ "$ROWS" != 9 ] ; then fail "Test 2 layout: expected 9 rows, got $ROWS: `cat hand.dump`" else @@ -230,7 +230,7 @@ if [ 0 != ${PIPESTATUS[0]} ] ; then fail "Test 3 plain text: genpng failed" else dump plain.png > plain.dump - if [ "`wc -l < plain.dump`" != 3 ] ; then + if [ "`count_lines plain.dump`" != 3 ] ; then fail "Test 3 plain text: expected 3 rows: `cat plain.dump`" elif [ "`grep -c \" $PLAIN \" plain.dump`" != 3 ] ; then fail "Test 3 plain text: not every row is plain: `cat plain.dump`" diff --git a/tests/lcov/extract/extract1.sh b/tests/lcov/extract/extract1.sh index c7f2881c..55591436 100755 --- a/tests/lcov/extract/extract1.sh +++ b/tests/lcov/extract/extract1.sh @@ -181,7 +181,13 @@ if [ 0 != $? ] ; then fi # exclude some un-covered standard includes -EXCLUDE='--exclude */include/c++/* --exclude */include/g++-v* --ignore unused' +# Depending on platform, the compiler's private headers live under +# '/usr/include/c++/N' (Linux), '/usr/include/g++/bits' or +# '/usr/pkg/include/gcc/N' - exclude every spelling, tolerating the unused +# ones. +EXCLUDE='--exclude */include/c++/* --exclude */include/g++-v* + --exclude */include/g++/* --exclude */include/gcc/* + --exclude */include/gcc[0-9]*/* --ignore unused' # callback tests echo $COVER $CAPTURE . $LCOV_OPTS -o callback.info $FILTER $IGNORE --criteria $SCRIPT_DIR/threshold.pm,--line,90,--branch,65,--function,100 $EXCLUDE diff --git a/tests/lcov/extract/extract2.sh b/tests/lcov/extract/extract2.sh index a9aac16b..9ff05813 100755 --- a/tests/lcov/extract/extract2.sh +++ b/tests/lcov/extract/extract2.sh @@ -761,7 +761,7 @@ fi # test filename containing spaces rm -rf ./mytest -mkdir -pv ./mytest +mkdir -p ./mytest echo "int main(){}" > './mytest/main space.cpp' ( cd ./mytest ; ${CXX} -c 'main space.cpp' --coverage ) diff --git a/tests/lcov/fork_throttle/fork_throttle.pl b/tests/lcov/fork_throttle/fork_throttle.pl index bf5a2d8c..969ac82c 100755 --- a/tests/lcov/fork_throttle/fork_throttle.pl +++ b/tests/lcov/fork_throttle/fork_throttle.pl @@ -28,6 +28,22 @@ lcovutil::parseOptions({}, {}); +# The throttle's whole subject is a memory governor: it learns a byte-per- +# unit rate from a child's peak RSS, which is only measurable where the OS +# reports one - the same fact which makes lcovutil emit no per-job +# 'memory' profile data at all on platforms which don't expose it (their +# read_proc_peak_memory returns zeroes). Without that signal every check +# below is moot - and worse, the 'learnedBytes / learnedWeight' rate +# divides by a never-learned weight, which is the crash this guard exists +# to prevent - so skip rather than fail. +{ + my ($rss, $vsize) = lcovutil::read_proc_peak_memory(); + if (!$rss && !$vsize) { + print("skipped: platform does not report peak memory\n"); + exit(0); + } +} + my $tempdir = 'fork_throttle.tmp'; rmtree($tempdir) if -d $tempdir; mkdir($tempdir) or die("cannot create $tempdir: $!"); diff --git a/tests/lcov/parallel_parse/parallel_parse.sh b/tests/lcov/parallel_parse/parallel_parse.sh index 0193dcba..7edb2a65 100755 --- a/tests/lcov/parallel_parse/parallel_parse.sh +++ b/tests/lcov/parallel_parse/parallel_parse.sh @@ -372,7 +372,7 @@ fi # 9. truncated final section: the same format error as a serial read, with # the same line numbers #----------------------------------------------------------------------- -head -n -1 multi.info > trunc.info +sed '$ d' multi.info > trunc.info run_pair trunc trunc.info --ignore-errors corrupt,format for f in trunc_serial.log trunc_split.log ; do if ! grep -E "unexpected end of file: missing 'end_of_record'" $f \ diff --git a/tests/lcov/scheduling/scheduling.sh b/tests/lcov/scheduling/scheduling.sh index 18b8ff2a..393dad22 100755 --- a/tests/lcov/scheduling/scheduling.sh +++ b/tests/lcov/scheduling/scheduling.sh @@ -323,16 +323,22 @@ check_memory agg_prof.json filter filt_child .filt_child check_memory agg_prof.json aggregate 'group total' \ '(with_entries(select(.key | test("^[0-9]+$"))))' -# Both phases must be present simultaneously in that one profile: under the -# earlier pid keying they were indistinguishable, and under a flat numeric -# keying they would have overwritten each other. -for phase in filter aggregate ; do - n=`jq -r --arg p "$phase" '[.memory | keys[] - | select(startswith($p + "_"))] | length' agg_prof.json` - if [ "${n:-0}" -lt 1 ] ; then - fail_memory $phase "no $phase worker in agg_prof.json" - fi -done +if [ "`jq -r 'has("memory")' agg_prof.json`" != "true" ] ; then + echo "memory data absent in agg_prof.json (platform does not expose peak memory)" +else + { + # Both phases must be present simultaneously in that one profile: under the + # earlier pid keying they were indistinguishable, and under a flat numeric + # keying they would have overwritten each other. + for phase in filter aggregate ; do + n=`jq -r --arg p "$phase" '[.memory | keys[] + | select(startswith($p + "_"))] | length' agg_prof.json` + if [ "${n:-0}" -lt 1 ] ; then + fail_memory $phase "no $phase worker in agg_prof.json" + fi + done + } +fi # ...and a collision would have been reported by merge_child_profile if grep -i 'unexpected duplicate key' agg.log geninfo_hist.log genhtml_hist.log ; then fail_memory collision \ @@ -364,13 +370,19 @@ if grep -i 'unexpected duplicate key' nested.log ; then fi # the qualified filter workers, and the check that memory{filter_} still # lines up with filt_child{} for them -nested=`jq -r '[.memory | keys[] | select(test("^filter_aggregate_"))] - | sort | join(" ")' nested_prof.json` -if [ "`echo $nested | wc -w`" -lt 2 ] ; then - jq -r '.memory | keys' nested_prof.json - fail_memory nested "no nested filter worker in nested_prof.json" +if [ "`jq -r 'has("memory")' nested_prof.json`" != "true" ] ; then + echo "memory data absent in nested_prof.json (platform does not expose peak memory)" else - echo "OK (memory nested): [$nested]" + { + nested=`jq -r '[.memory | keys[] | select(test("^filter_aggregate_"))] + | sort | join(" ")' nested_prof.json` + if [ "`echo $nested | wc -w`" -lt 2 ] ; then + jq -r '.memory | keys' nested_prof.json + fail_memory nested "no nested filter worker in nested_prof.json" + else + echo "OK (memory nested): [$nested]" + fi + } fi check_memory nested_prof.json filter filt_child .filt_child diff --git a/tests/py2lcov/py2lcov.sh b/tests/py2lcov/py2lcov.sh index 9628b903..d36e887d 100755 --- a/tests/py2lcov/py2lcov.sh +++ b/tests/py2lcov/py2lcov.sh @@ -51,17 +51,14 @@ LCOV_OPTS="--branch-coverage $PARALLEL $PROFILE" if [ '' != "${COVERAGE_COMMAND}" ] ; then CMD=${COVERAGE_COMMAND} else - CMD='coverage' - which $CMD - if [ 0 != $? ] ; then - CMD='python3-coverage' # ubuntu? - fi + CMD=$(find_python_coverage) fi -which $CMD -if [ 0 != $? ] ; then - echo "cannot find 'coverage' or 'python3-coverage'" - echo "unable to run py2lcov - please install python Coverage.py package" - exit 1 +if [ '' == "$CMD" ] ; then + # The python Coverage.py package is not installed on this platform: + # py2lcov itself is not implicated - skip instead of failing + # (same treatment as other platform-constraint skips in this suite). + echo "python Coverage.py package not installed - skipping py2lcov test" + exit 0 fi # some corner cases: diff --git a/tests/scripts/batchgitversion_test.sh b/tests/scripts/batchgitversion_test.sh index 0111c999..8f5c31ff 100755 --- a/tests/scripts/batchgitversion_test.sh +++ b/tests/scripts/batchgitversion_test.sh @@ -55,6 +55,8 @@ if [[ "x" == "${LCOV_HOME}x" ]] ; then fi source ../common.tst +rm -f batch_git_*.pl + if [ -z "$SCRIPT_DIR" ] ; then echo "SCRIPT_DIR not set" >&2 exit 1 @@ -107,6 +109,10 @@ run_pl() { local pl_file="$1"; shift OUTPUT=$($PERL -I"$SCRIPT_DIR" "$pl_file" "$@" 2>&1) RC=$? + # Devel::Cover status lines can leak into the captured output even with + # -silent (its 'Deleting old coverage for changed file' message is + # printed unconditionally), so they must not reach the comparisons. + OUTPUT=$(sed '/^Devel::Cover:/d' <<<"$OUTPUT") } # --------------------------------------------------------------------------- @@ -136,7 +142,7 @@ ENDSCRIPT # =========================================================================== read -r REPODIR FOO_SHA <<< "$(make_repo)" MD5_FOO=$(md5sum "$REPODIR/foo.c" | awk '{print $1}') -trap 'rm -rf "$REPODIR"' EXIT +trap 'rm -rf "$REPODIR"; rm -f batch_git_*.pl' EXIT # =========================================================================== # Tests 1-2: standalone invocation via call_get_version @@ -179,7 +185,8 @@ fi # =========================================================================== # Test 4: bad option to new() returns undef -PL=$(mktemp --suffix=.pl) +TESTIDX=4 +PL="batch_git_${TESTIDX}.pl" cat > "$PL" << 'PLEOF' use batchGitVersion; my $obj = batchGitVersion->new('/fake/script', '--bad-option'); @@ -194,7 +201,7 @@ fi rm -f "$PL" # Test 5: --help to new() returns undef -PL=$(mktemp --suffix=.pl) +PL="batch_git_$(( ++TESTIDX )).pl" cat > "$PL" << 'PLEOF' use batchGitVersion; my $obj = batchGitVersion->new('/fake/script', '--help'); @@ -213,7 +220,7 @@ rm -f "$PL" # =========================================================================== # Test 6: file found via prefix stripping -> "BLOB " -PL=$(mktemp --suffix=.pl) +PL="batch_git_$(( ++TESTIDX )).pl" cat > "$PL" << PLEOF use batchGitVersion; my (\$repo, \$sha) = @ARGV; @@ -232,7 +239,7 @@ fi rm -f "$PL" # Test 7: file found directly in DB (no prefix needed) -> "BLOB " -PL=$(mktemp --suffix=.pl) +PL="batch_git_$(( ++TESTIDX )).pl" cat > "$PL" << PLEOF use batchGitVersion; my (\$repo, \$sha) = @ARGV; @@ -251,7 +258,7 @@ fi rm -f "$PL" # Test 8: file not in DB, exists on disk -> mtime (ISO8601) -PL=$(mktemp --suffix=.pl) +PL="batch_git_$(( ++TESTIDX )).pl" cat > "$PL" << PLEOF use batchGitVersion; my \$repo = \$ARGV[0]; @@ -273,7 +280,7 @@ fi rm -f "$PL" # Test 9: file not in DB, exists on disk, --md5 -> mtime + " md5:" -PL=$(mktemp --suffix=.pl) +PL="batch_git_$(( ++TESTIDX )).pl" cat > "$PL" << PLEOF use batchGitVersion; my \$repo = \$ARGV[0]; @@ -295,7 +302,7 @@ fi rm -f "$PL" # Test 10: file not in DB, not on disk, --allow-missing -> empty string -PL=$(mktemp --suffix=.pl) +PL="batch_git_$(( ++TESTIDX )).pl" cat > "$PL" << PLEOF use batchGitVersion; my \$repo = \$ARGV[0]; @@ -314,7 +321,7 @@ fi rm -f "$PL" # Test 11: file not in DB, not on disk, no --allow-missing -> dies -PL=$(mktemp --suffix=.pl) +PL="batch_git_$(( ++TESTIDX )).pl" cat > "$PL" << PLEOF use batchGitVersion; my \$repo = \$ARGV[0]; @@ -337,7 +344,7 @@ rm -f "$PL" # =========================================================================== # Test 12: --prepend path prepends to all DB keys; lookup via prepend/file -PL=$(mktemp --suffix=.pl) +PL="batch_git_$(( ++TESTIDX )).pl" cat > "$PL" << PLEOF use batchGitVersion; my (\$repo, \$sha) = @ARGV; @@ -360,7 +367,7 @@ rm -f "$PL" # =========================================================================== # Test 13: --token SHA uses SHA instead of BLOB as the token prefix -PL=$(mktemp --suffix=.pl) +PL="batch_git_$(( ++TESTIDX )).pl" cat > "$PL" << PLEOF use batchGitVersion; my (\$repo, \$sha) = @ARGV; @@ -383,7 +390,7 @@ rm -f "$PL" # =========================================================================== # Test 14: without --repo, new() falls back to getcwd(); chdir to repo first -PL=$(mktemp --suffix=.pl) +PL="batch_git_$(( ++TESTIDX )).pl" cat > "$PL" << PLEOF use batchGitVersion; use Cwd qw(chdir); @@ -408,7 +415,7 @@ rm -f "$PL" # =========================================================================== # Test 15: compare_version same BLOB strings -> 0 (false) -PL=$(mktemp --suffix=.pl) +PL="batch_git_$(( ++TESTIDX )).pl" cat > "$PL" << PLEOF use batchGitVersion; my \$repo = \$ARGV[0]; @@ -425,7 +432,7 @@ fi rm -f "$PL" # Test 16: compare_version different BLOB strings -> 1 (true) -PL=$(mktemp --suffix=.pl) +PL="batch_git_$(( ++TESTIDX )).pl" cat > "$PL" << PLEOF use batchGitVersion; my \$repo = \$ARGV[0]; @@ -442,7 +449,7 @@ fi rm -f "$PL" # Test 17: compare_version --md5, old has md5 (no BLOB prefix), both same -> 0 -PL=$(mktemp --suffix=.pl) +PL="batch_git_$(( ++TESTIDX )).pl" cat > "$PL" << PLEOF use batchGitVersion; my (\$repo, \$md5) = @ARGV; @@ -460,7 +467,7 @@ fi rm -f "$PL" # Test 18: compare_version --md5, old has md5, new has md5, differ -> 1 -PL=$(mktemp --suffix=.pl) +PL="batch_git_$(( ++TESTIDX )).pl" cat > "$PL" << PLEOF use batchGitVersion; my (\$repo, \$md5) = @ARGV; @@ -479,7 +486,7 @@ fi rm -f "$PL" # Test 19: compare_version --md5, old starts BLOB -> bypass md5 branch, exact match -PL=$(mktemp --suffix=.pl) +PL="batch_git_$(( ++TESTIDX )).pl" cat > "$PL" << PLEOF use batchGitVersion; my \$repo = \$ARGV[0]; @@ -497,7 +504,7 @@ fi rm -f "$PL" # Test 20: compare_version --md5, old has md5 but new has none -> fall through, 1 -PL=$(mktemp --suffix=.pl) +PL="batch_git_$(( ++TESTIDX )).pl" cat > "$PL" << PLEOF use batchGitVersion; my (\$repo, \$md5) = @ARGV; @@ -520,7 +527,7 @@ rm -f "$PL" # =========================================================================== # Test 21: -v prints extract_version trace (prefix check / match / found) -PL=$(mktemp --suffix=.pl) +PL="batch_git_$(( ++TESTIDX )).pl" cat > "$PL" << PLEOF use batchGitVersion; my (\$repo, \$sha) = @ARGV; @@ -561,7 +568,7 @@ chmod +x "$MOCKDIR/git" FAKEREPO=$(mktemp -d) export PATH="$MOCKDIR:$PATH" -PL=$(mktemp --suffix=.pl) +PL="batch_git_$(( ++TESTIDX )).pl" cat > "$PL" << PLEOF use batchGitVersion; my \$repo = \$ARGV[0]; @@ -600,7 +607,7 @@ chmod +x "$MOCKDIR/git" FAKEREPO=$(mktemp -d) export PATH="$MOCKDIR:$PATH" -PL=$(mktemp --suffix=.pl) +PL="batch_git_$(( ++TESTIDX )).pl" cat > "$PL" << PLEOF use batchGitVersion; my \$repo = \$ARGV[0]; @@ -642,7 +649,7 @@ chmod +x "$MOCKDIR/git" FAKEREPO=$(mktemp -d) export PATH="$MOCKDIR:$PATH" -PL=$(mktemp --suffix=.pl) +PL="batch_git_$(( ++TESTIDX )).pl" cat > "$PL" << PLEOF use batchGitVersion; my \$repo = \$ARGV[0]; @@ -679,7 +686,7 @@ chmod +x "$MOCKDIR/git" FAKEREPO=$(mktemp -d) export PATH="$MOCKDIR:$PATH" -PL=$(mktemp --suffix=.pl) +PL="batch_git_$(( ++TESTIDX )).pl" cat > "$PL" << PLEOF use batchGitVersion; my \$repo = \$ARGV[0]; @@ -721,7 +728,7 @@ chmod +x "$MOCKDIR/git" FAKEREPO=$(mktemp -d) export PATH="$MOCKDIR:$PATH" -PL=$(mktemp --suffix=.pl) +PL="batch_git_$(( ++TESTIDX )).pl" cat > "$PL" << PLEOF use batchGitVersion; my \$repo = \$ARGV[0]; diff --git a/tests/scripts/gitblame_test.sh b/tests/scripts/gitblame_test.sh index 53949c7c..2b3496d1 100755 --- a/tests/scripts/gitblame_test.sh +++ b/tests/scripts/gitblame_test.sh @@ -42,6 +42,9 @@ if [[ "x" == "${LCOV_HOME}x" ]] ; then fi source ../common.tst +# cleanup: fixed-name inputs from previous runs +rm -f gitblame_*.c gitblame_*.pl + if [ -z "$SCRIPT_DIR" ] ; then echo "SCRIPT_DIR not set" >&2 exit 1 @@ -517,7 +520,7 @@ rm -rf "$REPO17" # with annotation data which differs in a token instead. Three lines, two # of which differ only in whitespace and must stay silent, so the count is # asserted and not just the presence of the message. -TMP19=$(mktemp --suffix=.c) +TMP19="gitblame_19.c" printf 'int main( ) {\n\treturn 0; \n}\n' > "$TMP19" OUTPUT=$($PERL -I"$LCOV_LIB" -I"$SCRIPT_DIR" -e \ 'use lcovutil; use annotateutil; @@ -679,7 +682,7 @@ fi # file is not in a git repo at all - so it is annotated from the filesystem, # and the pattern would never have been reached. # ----------------------------------------------------------------------- -TMP23=$(mktemp --suffix=.c) +TMP23="gitblame_23.c" printf 'no repo here\n' > "$TMP23" OUTPUT=$($GITBLAME --abbrev 's/[/' "$TMP23" 2>&1) RC=$? diff --git a/tests/scripts/gitversion_test.sh b/tests/scripts/gitversion_test.sh index eae7b9e5..5447f312 100755 --- a/tests/scripts/gitversion_test.sh +++ b/tests/scripts/gitversion_test.sh @@ -352,7 +352,8 @@ rm -f "$TF15" # Branch: MD5 set, old !~ /^SHA/, !P4, old =~ / md5:/, new =~ / md5:/ -> compare md5s # --------------------------------------------------------------------------- TF16=$(mktemp) ; echo "x" > "$TF16" -MD5_16=$(md5sum "$TF16" | awk '{print $1}') + +MD5_16=$(compute_md5_hex "$TF16" | awk '{print $1}') $GITVERSION --md5 --compare \ "2024-01-01T00:00:00+00:00 md5:${MD5_16}" \ "2024-01-01T00:00:00+00:00 md5:${MD5_16}" \ @@ -366,7 +367,7 @@ rm -f "$TF16" # Test 17: --compare --md5, both versions carry different md5 -> exit 1 # --------------------------------------------------------------------------- TF17=$(mktemp) ; echo "x" > "$TF17" -MD5_17=$(md5sum "$TF17" | awk '{print $1}') +MD5_17=$(compute_md5_hex "$TF17" | awk '{print $1}') $GITVERSION --md5 --compare \ "2024-01-01T00:00:00+00:00 md5:${MD5_17}" \ "2024-01-01T00:00:00+00:00 md5:DIFFERENTHASH" \ @@ -381,7 +382,7 @@ rm -f "$TF17" # exact string match -> exit 1 (strings differ) # --------------------------------------------------------------------------- TF18=$(mktemp) ; echo "x" > "$TF18" -MD5_18=$(md5sum "$TF18" | awk '{print $1}') +MD5_18=$(compute_md5_hex "$TF18" | awk '{print $1}') $GITVERSION --md5 --compare \ "2024-01-01T00:00:00+00:00 md5:${MD5_18}" \ "2024-01-01T00:00:00+00:00" \ @@ -397,7 +398,7 @@ rm -f "$TF18" # -> overall (old !~ /^CL/) part causes the whole MD5 block to be skipped. # --------------------------------------------------------------------------- TF19=$(mktemp) ; echo "x" > "$TF19" -MD5_19=$(md5sum "$TF19" | awk '{print $1}') +MD5_19=$(compute_md5_hex "$TF19" | awk '{print $1}') # Same CL number -> exact match -> exit 0 $GITVERSION --md5 --p4 --compare \ "CL 12345 md5:${MD5_19}" \ diff --git a/tests/scripts/p4annotate_test.sh b/tests/scripts/p4annotate_test.sh index 1250d101..afbbd860 100755 --- a/tests/scripts/p4annotate_test.sh +++ b/tests/scripts/p4annotate_test.sh @@ -33,6 +33,8 @@ if [[ "x" == "${LCOV_HOME}x" ]] ; then fi source ../common.tst +rm -f p4annotate_*.c p4annotate_*.pl + if [ -z "$SCRIPT_DIR" ] ; then echo "SCRIPT_DIR not set" >&2 exit 1 @@ -119,8 +121,16 @@ export PATH="$MOCKDIR:$PATH" # Create a temp source file; printf-style content string is the first argument mk_target() { - local f - f=$(mktemp --suffix=.c) + local i=1 f + # This function is always called via $(mk_target ...), so an index + # variable incremented here would die with the subshell and every + # caller would keep getting the same name (which let Devel::Cover's + # changed-file check fire on the reused name). Pick the first free + # name by scanning what is already on disk instead. + while [ -e "p4annotate_${i}.c" ] ; do + i=$(( i + 1 )) + done + f="p4annotate_${i}.c" printf "${1:-line one\nline two\nline three\n}" > "$f" echo "$f" } @@ -133,6 +143,8 @@ mk_diff() { local f ; f=$(mktemp) ; cat > "$f" ; echo "$f" ; } run_p4annotate() { OUTPUT=$($P4ANNOTATE "$@" 2>&1) RC=$? + # Strip possible Devel::Cover status lines (not all are gated by -silent). + OUTPUT=$(sed '/^Devel::Cover:/d' <<<"$OUTPUT") } # --------------------------------------------------------------------------- @@ -401,7 +413,7 @@ fi # --------------------------------------------------------------------------- # Test 15: Path normalisation logic from annotate_callback resolves symlink # --------------------------------------------------------------------------- -NORM_SCRIPT=$(mktemp --suffix=.pl) +NORM_SCRIPT="p4annotate_15.pl" cat > "$NORM_SCRIPT" << 'NORMPL' use File::Spec; use File::Basename qw(dirname); @@ -433,7 +445,7 @@ fi # --------------------------------------------------------------------------- # Test 16: Path normalisation handles .. components in symlink target # --------------------------------------------------------------------------- -NORM_SCRIPT=$(mktemp --suffix=.pl) +NORM_SCRIPT="p4annotate_16.pl" cat > "$NORM_SCRIPT" << 'NORMPL' use File::Spec; use File::Basename qw(dirname); @@ -487,7 +499,8 @@ SYMLINK_TESTDIR="" # =========================================================================== # Tests 18-34: End-to-end annotation output (from annotate_check.sh) # =========================================================================== - +# mk_target() numbers its files by scanning the directory, starting again +# at p4annotate_1.c since the startup cleanup removed any leftovers. # --------------------------------------------------------------------------- # Test 18: File not in p4 -> not_in_repo fallback -> NONE annotation # --------------------------------------------------------------------------- @@ -1100,7 +1113,7 @@ fi # Test 35: normalize_path returns original for non-existent path (no symlink # resolution attempted when -e is false) # --------------------------------------------------------------------------- -NORM_SCRIPT=$(mktemp --suffix=.pl) +NORM_SCRIPT="p4annotate_35.pl" cat > "$NORM_SCRIPT" << 'NORMPL' use File::Spec; use File::Basename qw(dirname); diff --git a/tests/scripts/p4version_test.sh b/tests/scripts/p4version_test.sh index eea6223c..2c0ec971 100755 --- a/tests/scripts/p4version_test.sh +++ b/tests/scripts/p4version_test.sh @@ -70,6 +70,16 @@ if [[ "x" == "${LCOV_HOME}x" ]] ; then fi source ../common.tst +# Remove any helper scripts a killed or failing run left behind. The +# p4v_test_*.pl files are written into this directory; a run which dies +# (fail(), a timeout, Ctrl-C) exits before its 'rm -f "$PL"', and a stale +# copy under a name the Devel::Cover database still knows re-hashes to a +# different digest, which makes Devel::Cover print +# 'Devel::Cover: Deleting old coverage for changed file ...' +# on stderr - not suppressed by -silent, and it would pollute the captured +# output of the exact-match tests. +rm -f p4v_test_*.sh p4v_test_*.pl + if [ -z "$SCRIPT_DIR" ] ; then echo "SCRIPT_DIR not set" >&2 exit 1 @@ -100,7 +110,7 @@ fail() { echo "FAIL: $1" ; ((FAIL++)) ; if [ "$KEEP_GOING" != 1 ] ; then exit 1 # --------------------------------------------------------------------------- MOCKDIR=$(mktemp -d) WORKSPACE=$(mktemp -d) -trap 'rm -rf "$MOCKDIR" "$WORKSPACE"' EXIT +trap 'rm -rf "$MOCKDIR" "$WORKSPACE"; rm -f p4v_test_*.pl' EXIT # The fake p4 script handles both getp4version and P4version.pm invocations. # For getp4version: uses P4V_NO_SUCH, P4V_HAVE_OUT, P4V_OPENED_OUT @@ -153,16 +163,20 @@ TF1="$WORKSPACE/foo.c" TF2="$WORKSPACE/bar.c" echo "int main(){}" > "$TF1" echo "// bar" > "$TF2" -MD5_TF1=$(md5sum "$TF1" | awk '{print $1}') +MD5_TF1=$(compute_md5_hex "$TF1" | awk '{print $1}') export P4V_WORKSPACE="$WORKSPACE" # --------------------------------------------------------------------------- # Helper: run getp4version, capture OUTPUT and RC # --------------------------------------------------------------------------- +# Devel::Cover status lines can leak into the captured output even with +# -silent (its 'Deleting old coverage for changed file' message is printed +# unconditionally), so they must not reach the exact-match comparisons. run_getp4version() { OUTPUT=$($GETP4VERSION "$@" 2>&1) RC=$? + OUTPUT=$(sed '/^Devel::Cover:/d' <<<"$OUTPUT") } # --------------------------------------------------------------------------- @@ -172,6 +186,7 @@ run_p4version_pl() { local pl_file="$1"; shift OUTPUT=$($PERL -I"$SCRIPT_DIR" -I"$LCOV_LIB" "$pl_file" "$@" 2>&1) RC=$? + OUTPUT=$(sed '/^Devel::Cover:/d' <<<"$OUTPUT") } # =========================================================================== @@ -391,7 +406,8 @@ fi # --------------------------------------------------------------------------- # Test 20: new() bad option -> undef (script ne $0 so returns undef not exit) # --------------------------------------------------------------------------- -PL=$(mktemp --suffix=.pl) +TESTIDX=20 +PL="p4v_test_${TESTIDX}.pl" cat > "$PL" << 'PLEOF' use P4version; # Pass a fake script name different from $0 so new() returns undef instead of exiting @@ -409,7 +425,7 @@ rm -f "$PL" # --------------------------------------------------------------------------- # Test 21: new() --help -> undef # --------------------------------------------------------------------------- -PL=$(mktemp --suffix=.pl) +PL="p4v_test_$(( ++TESTIDX )).pl" cat > "$PL" << 'PLEOF' use P4version; my $obj = P4version->new('/fake/caller', '--help'); @@ -426,7 +442,7 @@ rm -f "$PL" # --------------------------------------------------------------------------- # Test 22: new() depot arg not a directory -> dies # --------------------------------------------------------------------------- -PL=$(mktemp --suffix=.pl) +PL="p4v_test_$(( ++TESTIDX )).pl" cat > "$PL" << 'PLEOF' use P4version; eval { P4version->new('/fake/caller', '/nonexistent/depot/path'); }; @@ -447,7 +463,7 @@ unset P4V_HAVE_LINES P4V_WHERE_LINE P4V_OPENED_LINES P4V_NO_SUCH export P4V_HAVE_LINES="" # empty -> no filehash entries export P4V_SUBCOMMAND_EXIT=0 -PL=$(mktemp --suffix=.pl) +PL="p4v_test_$(( ++TESTIDX )).pl" cat > "$PL" << PLEOF use P4version; my \$file = \$ARGV[0]; @@ -473,7 +489,7 @@ export P4V_HAVE_LINES="//depot/foo.c#3 - $TF1" export P4V_WHERE_LINE="//depot/... //ws/... $WORKSPACE/..." export P4V_OPENED_LINES="" -PL=$(mktemp --suffix=.pl) +PL="p4v_test_$(( ++TESTIDX )).pl" cat > "$PL" << PLEOF use P4version; my (\$depot, \$file) = @ARGV; @@ -497,7 +513,7 @@ export P4V_HAVE_LINES="//depot/bar.c#5 - $TF2" export P4V_WHERE_LINE="//depot/... //ws/... $WORKSPACE/..." export P4V_OPENED_LINES="" -PL=$(mktemp --suffix=.pl) +PL="p4v_test_$(( ++TESTIDX )).pl" cat > "$PL" << PLEOF use P4version; my (\$depot, \$file) = @ARGV; @@ -523,7 +539,7 @@ export P4V_HAVE_LINES="//depot/bar.c#5 - $TF2" export P4V_WHERE_LINE="//depot/... //ws/... $WORKSPACE/..." export P4V_OPENED_LINES="" -PL=$(mktemp --suffix=.pl) +PL="p4v_test_$(( ++TESTIDX )).pl" cat > "$PL" << PLEOF use P4version; my (\$depot, \$file, \$md5) = @ARGV; @@ -549,7 +565,7 @@ export P4V_HAVE_LINES="//depot/foo.c#3 - $TF1" export P4V_WHERE_LINE="//depot/... //ws/... $WORKSPACE/..." export P4V_OPENED_LINES="//depot/foo.c#3 - edit default change (text)" -PL=$(mktemp --suffix=.pl) +PL="p4v_test_$(( ++TESTIDX )).pl" cat > "$PL" << PLEOF use P4version; my (\$depot) = @ARGV; @@ -571,7 +587,7 @@ export P4V_HAVE_LINES="//depot/foo.c#3 - $TF1" export P4V_WHERE_LINE="//depot/... //ws/... $WORKSPACE/..." export P4V_OPENED_LINES="//depot/foo.c#3 - edit default change (text)" -PL=$(mktemp --suffix=.pl) +PL="p4v_test_$(( ++TESTIDX )).pl" cat > "$PL" << PLEOF use P4version; my (\$depot, \$file) = @ARGV; @@ -597,7 +613,7 @@ export P4V_HAVE_LINES="//depot/foo.c#3 - $TF1" export P4V_WHERE_LINE="//depot/... //ws/... $WORKSPACE/..." export P4V_OPENED_LINES="//depot/foo.c#3 - integrate change 42 (text)" -PL=$(mktemp --suffix=.pl) +PL="p4v_test_$(( ++TESTIDX )).pl" cat > "$PL" << PLEOF use P4version; my (\$depot, \$file) = @ARGV; @@ -624,7 +640,7 @@ export P4V_HAVE_LINES="//depot/bar.c#5 - $TF2" export P4V_WHERE_LINE="//depot/... //ws/... $WORKSPACE/..." export P4V_OPENED_LINES="//depot/foo.c#1 - add default change (text)" -PL=$(mktemp --suffix=.pl) +PL="p4v_test_$(( ++TESTIDX )).pl" cat > "$PL" << PLEOF use P4version; my (\$depot, \$file) = @ARGV; @@ -652,7 +668,7 @@ export P4V_HAVE_LINES="//depot/bar.c#5 - $TF2" export P4V_WHERE_LINE="//depot/... //ws/... $WORKSPACE/..." export P4V_OPENED_LINES="//depot/gone.c#3 - delete default change (text)" -PL=$(mktemp --suffix=.pl) +PL="p4v_test_$(( ++TESTIDX )).pl" cat > "$PL" << PLEOF use P4version; my (\$depot, \$file) = @ARGV; @@ -678,7 +694,7 @@ export P4V_HAVE_LINES="//depot/foo.c#3 - $TF1" export P4V_WHERE_LINE="//depot/... //ws/... $WORKSPACE/..." export P4V_OPENED_LINES="//depot/foo.c#3 - edit default change (text)" -PL=$(mktemp --suffix=.pl) +PL="p4v_test_$(( ++TESTIDX )).pl" cat > "$PL" << PLEOF use P4version; my (\$depot, \$file, \$md5) = @ARGV; @@ -704,7 +720,7 @@ export P4V_HAVE_LINES="//depot/foo.c#3 - $TF1" export P4V_WHERE_LINE="//depot/... //ws/... $WORKSPACE/..." export P4V_OPENED_LINES="" -PL=$(mktemp --suffix=.pl) +PL="p4v_test_$(( ++TESTIDX )).pl" cat > "$PL" << PLEOF use P4version; my (\$depot, \$missing) = @ARGV; @@ -726,7 +742,7 @@ rm -f "$PL" # --------------------------------------------------------------------------- # Test 34: extract_version without --allow-missing, missing file -> dies # --------------------------------------------------------------------------- -PL=$(mktemp --suffix=.pl) +PL="p4v_test_$(( ++TESTIDX )).pl" cat > "$PL" << PLEOF use P4version; my (\$depot, \$missing) = @ARGV; @@ -752,7 +768,7 @@ export P4V_HAVE_LINES="//depot/foo.c#3 - $TF1" export P4V_WHERE_LINE="//depot/... //ws/... $WORKSPACE/..." export P4V_OPENED_LINES="" -PL=$(mktemp --suffix=.pl) +PL="p4v_test_$(( ++TESTIDX )).pl" cat > "$PL" << PLEOF use P4version; my (\$depot, \$prefix) = @ARGV; @@ -773,7 +789,7 @@ rm -f "$PL" # --------------------------------------------------------------------------- # Test 36: compare_version same strings -> 0 (false) # --------------------------------------------------------------------------- -PL=$(mktemp --suffix=.pl) +PL="p4v_test_$(( ++TESTIDX )).pl" cat > "$PL" << PLEOF use P4version; my (\$depot, \$file) = @ARGV; @@ -795,7 +811,7 @@ rm -f "$PL" # --------------------------------------------------------------------------- # Test 37: compare_version different strings -> 1 (true) # --------------------------------------------------------------------------- -PL=$(mktemp --suffix=.pl) +PL="p4v_test_$(( ++TESTIDX )).pl" cat > "$PL" << PLEOF use P4version; my (\$depot, \$file) = @ARGV; @@ -818,7 +834,7 @@ export P4V_SUBCOMMAND_EXIT=1 # make all p4 sub-commands exit 1 (have fails) unset P4V_HAVE_LINES export P4V_HAVE_LINES="" # empty output + bad exit -> close fails, empty hash -PL=$(mktemp --suffix=.pl) +PL="p4v_test_$(( ++TESTIDX )).pl" cat > "$PL" << PLEOF use lcovutil; lcovutil::parse_ignore_errors("usage"); @@ -860,7 +876,7 @@ export P4V_HAVE_LINES="//depot/foo.c#3 - $TF1" export P4V_WHERE_LINE="//depot/... //ws/... $WORKSPACE/..." export P4V_OPENED_LINES="//depot/foo.c#3 - delete default change (text)" -PL=$(mktemp --suffix=.pl) +PL="p4v_test_$(( ++TESTIDX )).pl" cat > "$PL" << PLEOF use P4version; my (\$depot, \$file) = @ARGV; @@ -885,6 +901,7 @@ rm -f "$PL" # reached p4 as two arguments; p4 answered 'no such file' and the file was # reported by modification time as if it were not in perforce at all. # =========================================================================== +TESTIDX=$(( TESTIDX + 1 )) TF40="$WORKSPACE/has space.c" echo "int spaced(){}" > "$TF40" unset P4V_NO_SUCH P4V_OPENED_OUT P4V_SUBCOMMAND_EXIT @@ -909,7 +926,7 @@ export P4V_HAVE_LINES="//depot/foo.c - no revision here" export P4V_WHERE_LINE="//depot/... //ws/... $WORKSPACE/..." export P4V_OPENED_LINES="" -PL=$(mktemp --suffix=.pl) +PL="p4v_test_$(( ++TESTIDX )).pl" cat > "$PL" << 'PLEOF' use P4version; my ($depot) = @ARGV; @@ -930,7 +947,7 @@ rm -f "$PL" export P4V_HAVE_LINES="//depot/foo.c#3 - $TF1" export P4V_OPENED_LINES="//depot/foo.c - opened somehow" -PL=$(mktemp --suffix=.pl) +PL="p4v_test_$(( ++TESTIDX )).pl" cat > "$PL" << 'PLEOF' use P4version; my ($depot) = @ARGV;