[igt-dev] [PATCH 12/12] code_cov_parse_info: add support for generating html reports
Andrzej Hajda
andrzej.hajda at intel.com
Mon Apr 11 13:15:19 UTC 2022
On 04.04.2022 08:26, Mauro Carvalho Chehab wrote:
> From: Mauro Carvalho Chehab <mchehab at kernel.org>
>
> 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.
>
> Signed-off-by: Mauro Carvalho Chehab <mchehab at kernel.org>
> ---
> scripts/code_cov_parse_info | 438 +++++++++++++++++++++++++++++++-----
> 1 file changed, 386 insertions(+), 52 deletions(-)
>
> diff --git a/scripts/code_cov_parse_info b/scripts/code_cov_parse_info
> index 9624dc33468d..faa585ae5b18 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;
> @@ -307,8 +309,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
> @@ -416,71 +422,331 @@ 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;
> + # 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}})) {
> + $stats{"branch_count"}++;
> + $stats{"branch_reached"}++ if ($all_branch{$source}{$where} != 0);
> + }
> + }
> +
> + # 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, $func_used, $func_count;
> + $percent, $stats{"func_used"}, $stats{"func_count"};
> } else {
> print "No functions reported. Wrong filters?\n";
> return;
> }
>
> - # Output per-branch coverage statistics
> - my $branch_count = 0;
> - my $branch_reached = 0;
> + if ($stats{"branch_count"}) {
> + my $percent = 100. * $stats{"branch_reached"} / $stats{"branch_count"};
> + printf " branches...: %.1f%% (%d of %d branches)\n",
> + $percent, $stats{"branch_reached"}, $stats{"branch_count"};
> + } else {
> + print "No branch coverage data.\n";
> + }
> +}
>
> - foreach my $source (keys(%all_branch)) {
> - next if (!$used_source{$source});
> +my $gen_report;
> +my $css_file;
> +my $html_prolog;
> +my $html_epilog;
> +my %report;
>
> - foreach my $where (keys(%{$all_branch{$source}})) {
> - $branch_count++;
> - $branch_reached++ if ($all_branch{$source}{$where} != 0);
> +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_prolog) {
$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;
> }
> }
> - if ($branch_count) {
> - my $percent = 100. * $branch_reached / $branch_count;
> - printf " branches...: %.1f%% (%d of %d branches)\n",
> - $percent, $branch_reached, $branch_count;
> + 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 "No branch coverage data.\n";
> + 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;
> }
>
> #
> @@ -515,6 +781,11 @@ GetOptions(
> "exclude-source=s" => \$src_exclude,
> "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);
> @@ -528,7 +799,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;
> @@ -617,39 +890,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 "Ignored......:%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";
> @@ -670,8 +971,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
>
> @@ -700,6 +1003,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>.
Lots of code, I have always mixed feelings about code generating html.
It's maintainability is quite difficult.
I am not UI/presentation expert, so it is hard to decide, but if there
are no better options (XML/XSLT, perl frameworks to generate reports,
....), lets try it.
Regards
Andrzej
>
> =item B<--show-lines> or B<--show_lines>
>
More information about the igt-dev
mailing list