Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 13 additions & 2 deletions README.rst
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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:

Expand Down
34 changes: 26 additions & 8 deletions bin/lcov
Original file line number Diff line number Diff line change
Expand Up @@ -136,6 +136,7 @@ sub setup_gkv();
sub lcov_geninfo(@);
sub create_package($$$;$);
sub summary();
sub count_data_cb($$$);

# Global variables & initialization

Expand Down Expand Up @@ -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)
#
Expand All @@ -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 (<HANDLE>) {
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");
}
Expand Down Expand Up @@ -1126,7 +1143,8 @@ sub count_package_data($)

open(HANDLE, "-|", "tar tfz " . quotemeta($filename)) or return undef;
while (<HANDLE>) {
if (/\.da$/ || /\.gcda$/) {
# tolerate 'bsdtar', which appends the file size to each listing line
if (/\.(?:gcda|da)(?:\s|\z)/) {
$count++;
}
}
Expand Down
60 changes: 53 additions & 7 deletions tests/common.tst
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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 )
Expand Down Expand Up @@ -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]|\$)" \
Expand Down
8 changes: 4 additions & 4 deletions tests/genhtml/htmlgen/htmlgen.sh
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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
Expand Down Expand Up @@ -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
Expand Down
4 changes: 2 additions & 2 deletions tests/genpng/genpng.sh
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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`"
Expand Down
8 changes: 7 additions & 1 deletion tests/lcov/extract/extract1.sh
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
2 changes: 1 addition & 1 deletion tests/lcov/extract/extract2.sh
Original file line number Diff line number Diff line change
Expand Up @@ -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 )

Expand Down
16 changes: 16 additions & 0 deletions tests/lcov/fork_throttle/fork_throttle.pl
Original file line number Diff line number Diff line change
Expand Up @@ -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: $!");
Expand Down
2 changes: 1 addition & 1 deletion tests/lcov/parallel_parse/parallel_parse.sh
Original file line number Diff line number Diff line change
Expand Up @@ -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 \
Expand Down
44 changes: 28 additions & 16 deletions tests/lcov/scheduling/scheduling.sh
Original file line number Diff line number Diff line change
Expand Up @@ -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 \
Expand Down Expand Up @@ -364,13 +370,19 @@ if grep -i 'unexpected duplicate key' nested.log ; then
fi
# the qualified filter workers, and the check that memory{filter_<id>} still
# lines up with filt_child{<id>} 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

Expand Down
17 changes: 7 additions & 10 deletions tests/py2lcov/py2lcov.sh
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand Down
Loading
Loading