Skip to content

Commit 4543c36

Browse files
committed
Check coverage data consistency:
- function should have non-zero hit count only if at least one contained line has a non-zero hit count - and vice versa. - for line with non-zero hit count, at least one branch expression should be evaluated - and vice versa. Also add option to skip the consistency check (e.g., for legacy data). Signed-off-by: Henry Cox <henry.cox@mediatek.com>
1 parent 0843577 commit 4543c36

29 files changed

+6038
-92
lines changed

bin/genhtml

Lines changed: 55 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -95,6 +95,7 @@ use FindBin;
9595
use Time::HiRes; # for profiling
9696
use Storable;
9797
use POSIX;
98+
use Data::Dumper;
9899

99100
use lib "$FindBin::RealBin/../lib";
100101
use lcovutil qw (set_tool_name define_errors parse_ignore_errors
@@ -2570,24 +2571,6 @@ sub _categorizeLineCov
25702571
next;
25712572
}
25722573
# LCOV_EXCL_STOP
2573-
next if defined($lineCovCurrent->value($line));
2574-
2575-
lcovutil::ignorable_error($lcovutil::ERROR_INCONSISTENT_DATA,
2576-
"line $filename:$line has branchcov but no linecov data");
2577-
# maybe should skip this branch if no line data - rather
2578-
# than building fake line data.
2579-
my $hit = 0; # hit if any of the associated branches are hit
2580-
my $branchData = $branchCurrent->value($line);
2581-
BLOCKS: foreach my $block ($branchData->blocks()) {
2582-
my $b = $branchData->getBlock($block);
2583-
foreach my $expr (@$b) {
2584-
if ($expr->count() != 0) {
2585-
$hit = 1;
2586-
last BLOCKS;
2587-
}
2588-
}
2589-
}
2590-
$lineCovCurrent->append($line, $hit);
25912574
}
25922575

25932576
# it is sufficient to just walk the 'global' (merged) line
@@ -2597,9 +2580,15 @@ sub _categorizeLineCov
25972580
# coverages.)
25982581
foreach my $line ($lineCovCurrent->keylist()) {
25992582
my $type = $diffMap->type($filename, $diffMap->NEW, $line);
2600-
die("'current' line $filename:$line should not be marked 'delete'")
2601-
unless $type ne 'delete';
2602-
2583+
if ($type eq 'delete') {
2584+
# can happen in some inconsistent case, when there are certain
2585+
# out-of-range references in a file which contained diffs - and we
2586+
# ignored the error check
2587+
lcovutil::ignorable_error($lcovutil::ERROR_INCONSISTENT_DATA,
2588+
"'current' line $filename:$line should not be marked 'delete'");
2589+
$lineCovCurrent->remove($line);
2590+
next;
2591+
}
26032592
my $linedata;
26042593
if (!exists($lineDataMap->{$line})) {
26052594
$linedata = LineData->new($type);
@@ -2716,9 +2705,14 @@ sub _categorizeBranchCov
27162705
# Or because we did something bad and no longer reach what had been
27172706
# a high probability event?
27182707
foreach my $line ($branchCurrent->keylist()) {
2719-
# should have been caught or fixed, above
2720-
die("line $filename:$line has branchcov but no linecov data")
2721-
unless exists($lineDataMap->{$line});
2708+
unless (exists($lineDataMap->{$line})) {
2709+
# unless ignored, should have been caught or fixed during
2710+
# TraceInfo::_checkConsistency
2711+
lcovutil::ignorable_error($lcovutil::ERROR_INCONSISTENT_DATA,
2712+
"\"$filename\":$line line has branchcov but no linecov data (skipping)."
2713+
);
2714+
next;
2715+
}
27222716
my $data = $lineDataMap->{$line};
27232717

27242718
$branchCovLines{$line} = 1;
@@ -5123,7 +5117,6 @@ sub _synthesize
51235117

51245118
return $self if ($last_line < 1 ||
51255119
$currentLast >= $last_line);
5126-
51275120
my $why;
51285121
if (0 == $currentLast) {
51295122
# Synthesize source data from coverage trace to replace unreadable file
@@ -5148,7 +5141,6 @@ sub _synthesize
51485141
$last_line . $suffix);
51495142
$why = 'not long enough';
51505143
}
5151-
51525144
# Simulate gcov behavior
51535145
my $notFound = "/* " . $self->path() . " $why */";
51545146
my $synth = "/* (content generated from line coverage data) */";
@@ -5488,6 +5480,8 @@ sub add_dependency
54885480
lcovutil::debug(1,
54895481
"add depend $name' in -> " . $parent->[0]->[1]->[2] . "\n")
54905482
unless exists($parent->[1]->{$name});
5483+
print("add depend $name -> " . $parent->[0]->[1]->[2] . "\n")
5484+
if $main::debugScheduler > 1;
54915485
$parent->[1]->{$name} = 1;
54925486
}
54935487

@@ -5505,10 +5499,24 @@ sub completed_dependency
55055499
delete($pendingParent->[1]->{$name});
55065500
print("completed $name -> $parent " . scalar(%{$pendingParent->[1]}) . "\n")
55075501
if $main::debugScheduler > 1;
5508-
5502+
# LCOV_EXCL_START
5503+
if ($main::debugScheduler > 1) {
5504+
# print parent dependencies
5505+
while (my ($k, $d) = each(%{$pendingParent->[1]})) {
5506+
die("unexpected parent dependency $k ARRAY: " . $d->[0] . ' ' .
5507+
join('/', @{$d->[2]}))
5508+
if ('ARRAY' eq ref($d) || 1 != $d);
5509+
print(" $k\n");
5510+
}
5511+
}
5512+
# LCOV_EXCL_STOP
55095513
if (!%{$pendingParent->[1]}) {
55105514
# no more dependencies - schedule this one
5511-
push(@{$self->[WORKLIST]}, $pendingParent->[0]);
5515+
my $p = $pendingParent->[0];
5516+
print("completed dependencies - schedule " . $p->[0] . ' ' .
5517+
join('/', @{$p->[2]}) . "\n")
5518+
if $main::debugScheduler > 1;
5519+
push(@{$self->[WORKLIST]}, $p);
55125520
delete($pending->{$parent});
55135521
}
55145522
}
@@ -5914,7 +5922,25 @@ sub compute
59145922
next WORK;
59155923
}
59165924
}
5917-
die("unexpected empty worklist??") unless (@$worklist || @$joblist);
5925+
#LCOV_EXCL_START
5926+
unless (@$worklist || @$joblist) {
5927+
foreach my $e (keys %$pending) {
5928+
# something went wrong - print some debug data
5929+
lcovutil::info("me: $e: depends\n");
5930+
my $p = $pending->{$e};
5931+
while (my ($k, $d) = each(%{$p->[1]})) {
5932+
lcovutil::info(" $k\n");
5933+
die("unexpected data for depend $k: $d") unless $d == 1;
5934+
}
5935+
lcovutil::info(" parent: " . $p->[0]->[0] . ' ' .
5936+
join('/', @{$p->[0]->[2]}) . "\n");
5937+
}
5938+
# can get here if child process hit a 'die' (as opposed to an error)
5939+
# and "--keep-going" was specified.
5940+
die("unexpected empty worklist??");
5941+
}
5942+
#LCOV_EXCL_STOP
5943+
59185944
# schedule a few items from the worklist if we can..
59195945
$segmentID = $self->_segment_worklist($segmentID);
59205946

bin/perl2lcov

Lines changed: 88 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -79,11 +79,11 @@ For example:
7979
8080
# write Perl line, branch, condition, and subroutine coverage data to
8181
# 'myPerlDB' in the current directory
82-
\$ perl -MDevel::Cover=-db,./myPerlDB,-coverage,statement,branch,condition,subroutine,-silent,1 myScript.pl
82+
\$ perl -MDevel::Cover=-db,.\/myPerlDB,-coverage,statement,branch,condition,subroutine,-silent,1 myScript.pl
8383
# OR: write all the coverage types that Perl knows about to 'myPerlDB2' -
84-
# note that perl2lcov will ignore types it does not understand/does not use
85-
# (pod, time, and path)
86-
\$ perl -MDevel::Cover=-db,./myPerlDB2,-silent,1 myScript.pl
84+
# note that perl2lcov will ignore types it does not understand/does
85+
# not use (pod, time, and path)
86+
\$ perl -MDevel::Cover=-db,.\/myPerlDB2,-silent,1 myScript.pl
8787
# run 'cover' from the Devel::Cover installation - to extract runtime
8888
# data into a usable form. This will also generate an HTML report
8989
# in 'myCoverDB'
@@ -93,6 +93,28 @@ For example:
9393
# and generate a genhtml-format coverage report:
9494
\$ genhtml -o html_report perldata.info ...
9595
96+
Note that the data generateed by Devel::Cover is not always internally
97+
consistent. For example:
98+
99+
- some which are never called, do not appear in the coverage data.
100+
101+
- sometimes, a line will appear to be executed (non-zero hit count) but
102+
none of its contained branch expressions have been evaluated.
103+
(If the line was executed, then at least one branch condition must have
104+
been evaluated.
105+
106+
This can cause the various tools in the lcov package to generate errors of
107+
type 'inconsistent'.
108+
In that case, you can:
109+
110+
- skip consistency checks entirely: see the 'skip_consistency_checks' section
111+
in man lovrc(5)
112+
113+
- ignore the error: see the '--ignore-error' section in man genhtml(1)
114+
115+
- exclude the offending code: see the '--exclude', '--filter', and
116+
'--omit-lines' sections in man genhtml(1).
117+
96118
END_OF_USAGE
97119
}
98120

@@ -110,7 +132,7 @@ sub findPackage
110132
if ($line < $v->[0]) {
111133
$max = $mid - 1;
112134
} elsif ($line > $v->[0]) {
113-
$best = $v->[1];
135+
$best = $v;
114136
$min = $mid + 1;
115137
} else {
116138
# line number matched...which ought not to happen because
@@ -119,7 +141,7 @@ sub findPackage
119141
# That won't be the line containing "package ..." - unless the
120142
# user wrote the whole thing on one line. Not clever. Deserves
121143
# to lose, if something in here breaks.
122-
return $v->[1];
144+
return $v;
123145
}
124146
}
125147
return $best;
@@ -175,6 +197,9 @@ foreach my $db (@ARGV) {
175197
# use statement coverage to mark un-evaluated branches
176198
my ($stmts, $branches, $conditions, $subroutines);
177199
my @packageExtents;
200+
# Devel::Cover doesn't instrument all the functions in every file -
201+
# so need a workaround to find better extents for some of them
202+
my @functionExtents;
178203

179204
foreach my $criteria ($f->items) {
180205
# some types we don't use
@@ -188,11 +213,13 @@ foreach my $db (@ARGV) {
188213
$subroutines = $c;
189214
if (-f $file) {
190215
open(GREP, '-|', 'grep', '--line-number', '-E',
191-
'^\s*package ', $file) or
216+
'^\s*(package|sub) ', $file) or
192217
die("unable to grep $file: $!");
193218
while (<GREP>) {
194-
if (/^(\d+):\s*package\s+(\S+?);/) {
219+
if (/^(\d+):\s*package\s+(\S+)\s*;/) {
195220
push(@packageExtents, [$1, $2 . '::']);
221+
} elsif (/^(\d+):\s*sub\s+([^\s(]+)/) {
222+
push(@functionExtents, [$1, $2]);
196223
} else {
197224
die("unexpected grep output '$_'");
198225
}
@@ -248,7 +275,7 @@ foreach my $db (@ARGV) {
248275
if ($name !~ /(BEGIN|__ANON__)/) {
249276
my $p = findPackage(\@packageExtents, $line);
250277
if (defined($p)) {
251-
$name = $p . $name;
278+
$name = $p->[1] . $name;
252279
}
253280
$functionMap->define_function($name, $line);
254281
$functionMap->add_count($name, $count);
@@ -347,6 +374,58 @@ foreach my $db (@ARGV) {
347374
$fileData->sum()->union($lineMap);
348375
$fileData->sumbr()->union($branchMap);
349376
$fileData->func()->union($functionMap);
377+
378+
# have to do this manually due to some Perl quirks -
379+
# in particular, there may be code outside of the subroutine we are
380+
# walking...and we want to correct the end line
381+
TraceFile::_deriveFunctionEndLines($fileData);
382+
my $lineData = $fileData->sum();
383+
my $funcData = $fileData->testfnc();
384+
385+
foreach my $func ($fileData->func()->valuelist()) {
386+
# where is the nearest 'package' after my start line?
387+
my $first = $func->line();
388+
my $end = $func->end_line();
389+
next unless defined($end);
390+
# find package or function enclosing my end line..
391+
my $last = $end;
392+
foreach my $ext (\@packageExtents, \@functionExtents) {
393+
while (1) {
394+
my $p = findPackage($ext, $last);
395+
if (defined($p) && $p->[0] > $first) {
396+
$last = $p->[0] - 1;
397+
lcovutil::info(1,
398+
$func->name() .
399+
": found update end line $last in " .
400+
$p->[1] . "\n");
401+
# iterate in case there is another package above the first one
402+
} else {
403+
last;
404+
}
405+
}
406+
}
407+
next unless $last < $end;
408+
409+
# what is the last executable line before the 'package' or 'sub' decl?
410+
while ($last > $first) {
411+
if (defined($lineData->value($last))) {
412+
last;
413+
}
414+
--$last;
415+
}
416+
lcovutil::info(1,
417+
"resetting " . $func->name() .
418+
" end line to $last (from $end)\n");
419+
$func->set_end_line($last);
420+
421+
foreach my $tn ($funcData->keylist()) {
422+
my $d = $funcData->value($tn);
423+
my $f = $d->findKey($first);
424+
$f->set_end_line($last);
425+
}
426+
427+
} #foreach function
428+
350429
} # foreach file
351430
} #foreach cover db
352431

bin/py2lcov

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,17 @@ def main():
5656
usageString="""py2lcov: Translate Python coverage data to LCOV .info format.
5757
Please also see https://coverage.readthedocs.io
5858
59+
Note that the '--no-functions' argument may result in subtly inconsistent coverage
60+
data if a 'no-functions' coverage DB is merged with one which contains derived
61+
function data because the 'def myFunc(...)' line will acquire a 'hit' count
62+
of 1 because the python interpreter considers the 'def' to have been executed
63+
when the line is interpreted (i.e., when the function is defined).
64+
This will generate an 'inconsistent' error if the function is not executed in
65+
your tests becasue the (derived) function will have a zero hit count but the
66+
first line of the function has a non-zero count.
67+
Best practice is to either always specify '--no-functions' or never specify
68+
'--no-functions'.
69+
5970
Note that xml2lcov does not implement the full suite of LCOV features
6071
(e.g., filtering, substitutions, etc.).
6172
Please generate the translated LCOV format file and then read the data

bin/xml2lcovutil.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -121,7 +121,7 @@ def close(self):
121121
self._outf.close()
122122

123123
if self._args.version and None == self._versionScript:
124-
cmd = "%(lcov)s -a %(info)s -o %(info)s --version-script '%(vers)s' %(checksum)s--rc compute_file_version=1" % {
124+
cmd = "%(lcov)s -a %(info)s -o %(info)s --version-script '%(vers)s' %(checksum)s--rc compute_file_version=1 --branch-coverage --ignore inconsistent" % {
125125
'lcov': os.path.join(os.path.split(sys.argv[0])[0], 'lcov'),
126126
'checksum': "--checksum " if self._args.checksum else '',
127127
'info': self._args.output,

0 commit comments

Comments
 (0)