summaryrefslogtreecommitdiff
path: root/scripts
diff options
context:
space:
mode:
authorMauro Carvalho Chehab <mchehab@kernel.org>2022-04-14 14:25:02 +0200
committerPetri Latvala <petri.latvala@intel.com>2022-04-14 18:19:39 +0300
commite359750889b0caeac3a87d5fee4a16b653002173 (patch)
tree6cb2a0d9e8366a39b2034b2f8c789e2fdfcdf96f /scripts
parent20d6ae043286b38bbfa2ae4b6f4199bb30f459b8 (diff)
code_cov_parse_info: add support for generating html reports
While lcov has already its own report generator, it is interesting to be able to deal with multiple files exposing each input in separate. So, add new command line parameters to allow it to generate html reports. Also add some command lines to setup html title, add a css file and include a prolog/epilog at the html body. The title option can also be useful to rename the titles when merging multiple info files. Acked-by: Andrzej Hajda <andrzej.hajda@intel.com> Signed-off-by: Mauro Carvalho Chehab <mchehab@kernel.org>
Diffstat (limited to 'scripts')
-rwxr-xr-xscripts/code_cov_parse_info440
1 files changed, 387 insertions, 53 deletions
diff --git a/scripts/code_cov_parse_info b/scripts/code_cov_parse_info
index 206145ef..77291eb6 100755
--- a/scripts/code_cov_parse_info
+++ b/scripts/code_cov_parse_info
@@ -9,6 +9,8 @@ use Pod::Man;
my $prefix = qr ".*?(linux)\w*/";
+my $title = "";
+
my %used_func;
my %all_func;
my %all_branch;
@@ -284,8 +286,12 @@ sub write_filtered_file($)
my $filtered = "";
- foreach my $testname(sort keys %test_names) {
- $filtered .= "TN:$testname\n";
+ if ($title eq "") {
+ foreach my $testname(sort keys %test_names) {
+ $filtered .= "TN:$testname\n";
+ }
+ } else {
+ $filtered .= "TN:$title\n";
}
# Generates filtered data
@@ -390,68 +396,80 @@ sub print_code_coverage($$$)
}
}
-sub print_summary()
+my %stats;
+
+sub gen_stats()
{
- # Output per-line coverage statistics
- my $line_count = 0;
- my $line_reached = 0;
+ # per-line coverage statistics
+ $stats{"line_count"} = 0;
+ $stats{"line_reached"} = 0;
foreach my $source (keys(%all_line)) {
next if (!$used_source{$source});
foreach my $where (keys(%{$all_line{$source}})) {
- $line_count++;
- $line_reached++ if ($all_line{$source}{$where} != 0);
+ $stats{"line_count"}++;
+ $stats{"line_reached"}++ if ($all_line{$source}{$where} != 0);
}
}
- if ($line_count) {
- my $percent = 100. * $line_reached / $line_count;
- printf " lines......: %.1f%% (%d of %d lines)\n",
- $percent, $line_reached, $line_count;
- } else {
- print "No line coverage data.\n";
- }
- # Output per-function coverage statistics
- my $func_count = 0;
- my $func_used = 0;
+ # per-function coverage statistics
+ $stats{"func_count"} = 0;
+ $stats{"func_used"} = 0;
foreach my $func (keys(%all_func)) {
foreach my $file (keys(%{$all_func{$func}})) {
- $func_count++;
+ $stats{"func_count"}++;
if ($used_func{$func}) {
if ($used_func{$func}->{$file}) {
- $func_used++;
+ $stats{"func_used"}++;
}
}
}
}
- if ($func_count) {
- my $percent = 100. * $func_used / $func_count;
- printf " functions..: %.1f%% (%d of %d functions)\n",
- $percent, $func_used, $func_count;
- } else {
- print "No functions reported. Wrong filters?\n";
- return;
- }
-
- # Output per-branch coverage statistics
- my $branch_count = 0;
- my $branch_reached = 0;
+ # per-branch coverage statistics
+ $stats{"branch_count"} = 0;
+ $stats{"branch_reached"} = 0;
foreach my $source (keys(%all_branch)) {
next if (!$used_source{$source});
foreach my $where (keys(%{$all_branch{$source}})) {
- $branch_count++;
- $branch_reached++ if ($all_branch{$source}{$where} != 0);
+ $stats{"branch_count"}++;
+ $stats{"branch_reached"}++ if ($all_branch{$source}{$where} != 0);
}
}
- if ($branch_count) {
- my $percent = 100. * $branch_reached / $branch_count;
+
+ # per-file coverage stats
+ $stats{"all_files"} = scalar keys(%files);
+ $stats{"filtered_files"} = scalar keys(%record);
+ $stats{"used_files"} = scalar keys(%used_source);
+}
+
+sub print_summary()
+{
+ if ($stats{"line_count"}) {
+ my $percent = 100. * $stats{"line_reached"} / $stats{"line_count"};
+ printf " lines......: %.1f%% (%d of %d lines)\n",
+ $percent, $stats{"line_reached"}, $stats{"line_count"};
+ } else {
+ print "No line coverage data.\n";
+ }
+
+ if ($stats{"func_count"}) {
+ my $percent = 100. * $stats{"func_used"} / $stats{"func_count"};
+ printf " functions..: %.1f%% (%d of %d functions)\n",
+ $percent, $stats{"func_used"}, $stats{"func_count"};
+ } else {
+ print "No functions reported. Wrong filters?\n";
+ return;
+ }
+
+ if ($stats{"branch_count"}) {
+ my $percent = 100. * $stats{"branch_reached"} / $stats{"branch_count"};
printf " branches...: %.1f%% (%d of %d branches)\n",
- $percent, $branch_reached, $branch_count;
+ $percent, $stats{"branch_reached"}, $stats{"branch_count"};
} else {
print "No branch coverage data.\n";
}
@@ -514,6 +532,254 @@ sub open_filter_file($$$)
return $filter;
}
+my $gen_report;
+my $css_file;
+my $html_prolog;
+my $html_epilog;
+my %report;
+
+sub generate_report()
+{
+ my $percent;
+ my $prolog = "";
+ my $epilog = "";
+ my @info_files = sort(keys %report);
+
+ $title = "Code coverage results" if ($title eq "");
+
+ if ($html_prolog) {
+ open IN, $html_prolog or die "Can't open prolog file";
+ $prolog .= $_ while (<IN>);
+ close IN;
+ }
+
+ if ($html_epilog) {
+ open IN, $html_epilog or die "Can't open epilog file";
+ $epilog .= $_ while (<IN>);
+ close IN;
+ }
+
+ # Re-generate the hashes used to report stats in order to procuce the
+ # Total results
+
+ %used_func = ();
+ %all_func = ();
+ %all_branch = ();
+ %all_line = ();
+ %used_source = ();
+ %files = ();
+ %test_names = ();
+
+ foreach my $f (@info_files) {
+ foreach my $source (keys(%{$report{$f}{"all_line"}})) {
+ $used_source{$source} = 1 if ($report{$f}{"used_source"});
+ foreach my $where (keys(%{$report{$f}{"all_line"}{$source}})) {
+ $all_line{$source}{$where} += $report{$f}{"all_line"}{$source}{$where};
+ }
+ }
+ foreach my $func (keys(%{$report{$f}{"all_func"}})) {
+ foreach my $file (keys(%{$report{$f}{"all_func"}{$func}})) {
+ $all_func{$func}{$file}->{ln} = $report{$f}{"all_func"}{$func}{$file}->{ln};
+ $used_func{$func}->{$file} = 1 if ($report{$f}{"used_func"}{$func}->{$file});
+ }
+ }
+ foreach my $source (keys(%{$report{$f}{"all_branch"}})) {
+ foreach my $where (keys(%{$report{$f}{"all_branch"}{$source}})) {
+ $all_branch{$source}{"$where"} += $report{$f}{"all_branch"}{$source}{$where};
+ }
+ }
+ for my $source(keys(%{$report{$f}{"files"}})) {
+ $files{$source} = 1;
+ $used_source{$source} = 1 if ($report{$f}{"used_source"}{$source});
+ }
+ for my $test(keys(%{$report{$f}{"test_names"}})) {
+ $test_names{$test} = 1;
+ }
+ }
+ gen_stats();
+
+ # Colors for the html output
+
+ my $red = "style=\"background-color:#ffb3b3\"";
+ my $yellow = "style=\"background-color:#ffffb3\"";
+ my $green = "style=\"background-color:#d9ffd9\"";
+
+ # Open report file
+
+ open OUT, ">$gen_report" or die "Can't open $gen_report";
+
+ print OUT "<!DOCTYPE HTML PUBLIC \"-//W3C//DTD HTML 4.01 Transitional//EN\">\n";
+
+ print OUT "<html lang=\"en\">\n\n";
+ print OUT "<head>\n";
+ print OUT " <meta http-equiv=\"Content-Type\" content=\"text/html; charset=UTF-8\">\n";
+ print OUT " <title>$title</title>\n";
+ print OUT " <link rel=\"stylesheet\" type=\"text/css\" href=\"$css_file\">\n" if ($css_file);
+ print OUT "</head>\n\n<body>\n$prolog";
+
+ print OUT " <h1>$title</h1>\n";
+
+ print OUT " <h2>Summary</h2>\n";
+ # Generates a table containing the code coverage statistics per input
+
+ print OUT "<table width=\"100%\" border=1 cellspacing=0 cellpadding=0>\n <tr>\n";
+ print OUT " <th></th>\n";
+ foreach my $f (@info_files) {
+ print OUT " <th>$f</th>\n";
+ }
+ print OUT " <th>TOTAL</th>\n";
+ print OUT " <th>Total count</th>\n";
+ print OUT " </tr><tr>\n";
+
+ print OUT " <td><b>Functions</b></td>\n";
+ foreach my $f (@info_files) {
+ my %st = %{$report{$f}{"stats"}};
+ if ($st{"func_count"}) {
+ $percent = 100. * $st{"func_used"} / $st{"func_count"};
+
+ printf OUT " <td>%.1f%%</td>\n", $percent;
+ } else {
+ print OUT " <td>N. A.</td>\n";
+ }
+ }
+ if ($stats{"func_count"}) {
+ $percent = 100. * $stats{"func_used"} / $stats{"func_count"};
+
+ printf OUT " <td>%.1f%%</td>\n", $percent;
+ } else {
+ print OUT " <td>N. A.</td>\n";
+ }
+ print OUT " <td>" . $stats{"func_count"} . "</td>";
+ print OUT " </tr><tr>\n";
+
+ print OUT " <td><b>Branches</b></td>\n";
+ foreach my $f (@info_files) {
+ my %st = %{$report{$f}{"stats"}};
+ if ($st{"branch_count"}) {
+ $percent = 100. * $st{"branch_reached"} / $st{"branch_count"};
+
+ printf OUT " <td>%.1f%%</td>\n", $percent;
+ } else {
+ print OUT " <td>N. A.</td>\n";
+ }
+ }
+ if ($stats{"branch_count"}) {
+ $percent = 100. * $stats{"branch_reached"} / $stats{"branch_count"};
+
+ printf OUT " <td>%.1f%%</td>\n", $percent;
+ } else {
+ print OUT " <td>N. A.</td>\n";
+ }
+ print OUT " <td>" . $stats{"branch_count"} . "</td>";
+ print OUT " </tr><tr>\n";
+
+ print OUT " <td><b>Lines</b></td>\n";
+ foreach my $f (@info_files) {
+ my %st = %{$report{$f}{"stats"}};
+
+ if ($st{"line_count"}) {
+ $percent = 100. * $st{"line_reached"} / $st{"line_count"};
+
+ printf OUT " <td>%.1f%%</td>\n", $percent;
+ } else {
+ print OUT " <td>N. A.</td>\n";
+ }
+ }
+ if ($stats{"line_count"}) {
+ $percent = 100. * $stats{"line_reached"} / $stats{"line_count"};
+
+ printf OUT " <td>%.1f%%</td>\n", $percent;
+ } else {
+ print OUT " <td>N. A.</td>\n";
+ }
+ print OUT " <td>" . $stats{"line_count"} . "</td>";
+
+ # If there are more than one tests per file, report them
+ my $total = scalar(keys %test_names);
+ if ($total > 1) {
+ print OUT " </tr><tr>\n";
+ print OUT " <td><b>Number of tests</b></td>\n";
+ foreach my $f (@info_files) {
+ my $count = scalar(keys %{$report{$f}{"test_names"}});
+
+ if ($count == 0) {
+ print OUT " <td $red>$count</td>\n";
+ } elsif ($count < $total) {
+ print OUT " <td $yellow>$count</td>\n";
+ } else {
+ print OUT " <td $green>$count</td>\n";
+ }
+ }
+ print OUT " <td $green\>$total</td>\n";
+
+ }
+ print OUT " </tr>\n</table><p/>\n\n";
+
+ if ($total > 1) {
+ print OUT "<h2>Tests coverage</h2>\n";
+
+ print OUT "<table width=\"100%\" border=1 cellspacing=0 cellpadding=0>\n <tr>\n";
+ print OUT " <th>Test name</th>\n";
+ foreach my $f (@info_files) {
+ print OUT " <th>$f</th>\n";
+ }
+
+ foreach my $t (sort keys(%test_names)) {
+ print OUT " </tr><tr>\n";
+ printf OUT " <td>%s</td>\n", $t;
+ foreach my $f (@info_files) {
+ if (%{$report{$f}{"test_names"}}{$t}) {
+ print OUT " <td $green>YES</td>\n";
+ } else {
+ print OUT " <td $red>NO</td>\n";
+ }
+ }
+ }
+ print OUT "</tr></table>\n";
+ }
+
+
+ # Generates a table containing per-function detailed data
+
+ print OUT "<h2>Functions coverage</h2>\n";
+ print OUT "<table width=\"100%\" border=1 cellspacing=0 cellpadding=0>\n <tr>\n";
+ print OUT " <th>Function</th>\n";
+ print OUT " <th>Used?</th>\n";
+ foreach my $f (@info_files) {
+ print OUT " <th>$f</th>\n";
+ }
+ print OUT " <th>File</th>\n";
+
+ foreach my $func (sort keys(%all_func)) {
+ my @keys = sort keys(%{$all_func{$func}});
+ foreach my $file (@keys) {
+ print OUT " </tr><tr>\n";
+ print OUT " <td>$func</td>\n";
+ if ($used_func{$func}->{$file}) {
+ print OUT " <td $green>YES</td>\n";
+ } else {
+ print OUT " <td $red>NO</td>\n";
+ }
+ foreach my $f (@info_files) {
+ if ($report{$f}{"used_func"}{$func}->{$file}) {
+ print OUT " <td $green>YES</td>\n";
+ } else {
+ print OUT " <td $red>NO</td>\n";
+ }
+ }
+ $file =~ s,$prefix,linux/,;
+ print OUT " <td>$file</td>\n";
+ }
+ }
+ print OUT "</tr></table>\n";
+
+ print OUT "$epilog</body>\n";
+
+ # Close the file and exit
+
+ close OUT;
+}
+
#
# Argument handling
#
@@ -548,6 +814,11 @@ GetOptions(
"exclude-source=s" => \@src_exclude_regexes,
"show-files|show_files" => \$show_files,
"show-lines|show_lines" => \$show_lines,
+ "report|r=s" => \$gen_report,
+ "css-file|css|c=s" => \$css_file,
+ "title|t=s" => \$title,
+ "html-prolog|prolog=s" => \$html_prolog,
+ "html-epilog|epilog=s" => \$html_epilog,
"help" => \$help,
"man" => \$man,
) or pod2usage(2);
@@ -561,7 +832,9 @@ if ($#ARGV < 0) {
}
# At least one action should be specified
-pod2usage(1) if (!$print_used && !$filter && !$stat && !$print_unused);
+pod2usage(1) if (!$print_used && !$filter && !$stat && !$print_unused && !$gen_report);
+
+pod2usage(1) if ($gen_report && ($print_used || $filter || $stat || $print_unused));
my $filter_str = "";
my $has_filter;
@@ -605,39 +878,67 @@ if ($ignore_unused) {
foreach my $f (@ARGV) {
parse_info_data($f);
+
+ if ($gen_report) {
+ $f =~ s,.*/,,;
+ $f =~ s/\.info$//;
+
+ gen_stats();
+
+ $report{$f}{"stats"} = { %stats };
+ $report{$f}{"all_func"} = { %all_func };
+ $report{$f}{"used_func"} = { %used_func };
+ $report{$f}{"all_branch"} = { %all_branch };
+ $report{$f}{"all_line"} = { %all_line };
+ $report{$f}{"used_source"} = { %used_source };
+ $report{$f}{"files"} = { %files };
+ $report{$f}{"test_names"} = { %test_names };
+
+ %used_func = ();
+ %all_func = ();
+ %all_branch = ();
+ %all_line = ();
+ %used_source = ();
+ %files = ();
+ %test_names = ();
+ }
}
-print_code_coverage($print_used, $print_unused, $show_lines);
+if ($gen_report) {
+ generate_report();
+ exit 0;
+}
-print_summary() if ($stat);
+gen_stats();
-my $all_files = scalar keys(%files);
+die "Nothing counted. Wrong input files?" if (!$stats{"all_files"});
-die "Nothing counted. Wrong input files?" if (!$all_files);
+print_code_coverage($print_used, $print_unused, $show_lines);
-if ($has_filter) {
- my $all_files = scalar keys(%files);
- my $filtered_files = scalar keys(%record);
- my $used_files = scalar keys(%used_source);
+print_summary() if ($stat);
- my $percent = 100. * $used_files / $all_files;
+if ($has_filter) {
+ my $percent = 100. * $stats{"used_files"} / $stats{"all_files"};
$filter_str =~ s/(.*),/$1 and/;
printf "Filters......:%s.\n", $filter_str;
printf "Source files.: %.2f%% (%d of %d total)",
- $percent, $used_files, $all_files;
+ $percent, $stats{"used_files"}, $stats{"all_files"};
- if ($used_files != $filtered_files) {
- my $percent_filtered = 100. * $used_files / $filtered_files;
+ if ($stats{"used_files"} != $stats{"filtered_files"}) {
+ my $percent_filtered = 100. * $stats{"used_files"} / $stats{"filtered_files"};
printf ", %.2f%% (%d of %d filtered)",
- $percent_filtered, $used_files, $filtered_files;
+ $percent_filtered, $stats{"used_files"}, $stats{"filtered_files"};
}
print "\n";
} else {
printf "Source files: %d\n", scalar keys(%files) if($stat);
}
+my $ntests=scalar(%test_names);
+printf "Number of tests: %d\n", $ntests if ($ntests > 1);
+
if ($show_files) {
for my $f(sort keys %used_source) {
print "\t$f\n";
@@ -658,8 +959,10 @@ Parses lcov data from .info files.
code_cov_parse_info <options> [input file(s)]
-At least one of the options B<--stat>, B<--print> and/or B<--output>
-should be used.
+At least one of the output options should be used, e g.
+B<--stat>, B<--print>, B<--print-unused>, B<--report> and/or B<--output>.
+
+Also, B<--report> can't be used together with other output options.
=head1 OPTIONS
@@ -688,6 +991,37 @@ Prints the functions that were never reached.
The function coverage report is affected by the applied filters.
+=item B<--report> B<[output file]> or B<-r> B<[output file]>
+
+Generates an html report containing per-test and total statistics.
+
+The function coverage report is affected by the applied filters.
+
+=item B<--css-file> B<[css file]> or B<--css> B<[css file]> or B<-c> B<[css file]
+
+Adds an optional css file to the html report.
+Used only with B<--report>.
+
+=item B<--title> B<[title] or B<-t> B<[title]
+
+If used with B<--report>, it defines the title for the for the html report.
+
+If used with B<--output>, it replaces the test names with the title. This
+is useful when merging reports from multiple tests into a summarized file.
+If not used, the B<[output file]> will contain all test names on its
+beginning.
+
+Used with B<--report> AND B<--output>.
+
+=item B<--html-prolog> B<[html file] or B<--prolog> B<[html file]
+
+Adds a prolog at the beginning of the body of the html report.
+Used only with B<--report>.
+
+=item B<--html-epilog> B<[html file] or B<--epilog> B<[html file]
+
+Adds an epilog before the end of the body of the html report.
+Used only with B<--report>.
=item B<--show-lines> or B<--show_lines>