From mboxrd@z Thu Jan 1 00:00:00 1970 Return-Path: Received: from mga07.intel.com (mga07.intel.com [134.134.136.100]) by gabe.freedesktop.org (Postfix) with ESMTPS id C0C0B10E2AF for ; Thu, 14 Apr 2022 12:25:11 +0000 (UTC) From: Mauro Carvalho Chehab To: igt-dev@lists.freedesktop.org, Petri Latvala Date: Thu, 14 Apr 2022 14:25:02 +0200 Message-Id: <4bc114d50f6725702dd2969a66f8cbc1d1c631e2.1649939026.git.mchehab@kernel.org> In-Reply-To: References: MIME-Version: 1.0 Content-Transfer-Encoding: 8bit Subject: [igt-dev] [PATCH v3 12/12] code_cov_parse_info: add support for generating html reports List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Cc: Ch Sai Gowtham , Andrzej Hajda Errors-To: igt-dev-bounces@lists.freedesktop.org Sender: "igt-dev" List-ID: From: Mauro Carvalho Chehab 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 Signed-off-by: Mauro Carvalho Chehab --- To avoid mailbombing on a large number of people, only mailing lists were C/C on the cover. See [PATCH v3 00/12] at: https://lore.kernel.org/all/cover.1649939026.git.mchehab@kernel.org/ scripts/code_cov_parse_info | 442 +++++++++++++++++++++++++++++++----- 1 file changed, 388 insertions(+), 54 deletions(-) diff --git a/scripts/code_cov_parse_info b/scripts/code_cov_parse_info index 206145ef4925..77291eb69405 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; + # 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; - - 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); - } - } - if ($branch_count) { - my $percent = 100. * $branch_reached / $branch_count; + 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 (); + close IN; + } + + if ($html_epilog) { + open IN, $html_epilog or die "Can't open epilog file"; + $epilog .= $_ while (); + 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 "\n"; + + print OUT "\n\n"; + print OUT "\n"; + print OUT " \n"; + print OUT " $title\n"; + print OUT " \n" if ($css_file); + print OUT "\n\n\n$prolog"; + + print OUT "

$title

\n"; + + print OUT "

Summary

\n"; + # Generates a table containing the code coverage statistics per input + + print OUT "\n \n"; + print OUT " \n"; + foreach my $f (@info_files) { + print OUT " \n"; + } + print OUT " \n"; + print OUT " \n"; + print OUT " \n"; + + print OUT " \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 " \n", $percent; + } else { + print OUT " \n"; + } + } + if ($stats{"func_count"}) { + $percent = 100. * $stats{"func_used"} / $stats{"func_count"}; + + printf OUT " \n", $percent; + } else { + print OUT " \n"; + } + print OUT " "; + print OUT " \n"; + + print OUT " \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 " \n", $percent; + } else { + print OUT " \n"; + } + } + if ($stats{"branch_count"}) { + $percent = 100. * $stats{"branch_reached"} / $stats{"branch_count"}; + + printf OUT " \n", $percent; + } else { + print OUT " \n"; + } + print OUT " "; + print OUT " \n"; + + print OUT " \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 " \n", $percent; + } else { + print OUT " \n"; + } + } + if ($stats{"line_count"}) { + $percent = 100. * $stats{"line_reached"} / $stats{"line_count"}; + + printf OUT " \n", $percent; + } else { + print OUT " \n"; + } + print OUT " "; + + # If there are more than one tests per file, report them + my $total = scalar(keys %test_names); + if ($total > 1) { + print OUT " \n"; + print OUT " \n"; + foreach my $f (@info_files) { + my $count = scalar(keys %{$report{$f}{"test_names"}}); + + if ($count == 0) { + print OUT " \n"; + } elsif ($count < $total) { + print OUT " \n"; + } else { + print OUT " \n"; + } + } + print OUT " \n"; + + } + print OUT " \n
$fTOTALTotal count
Functions%.1f%%N. A.%.1f%%N. A." . $stats{"func_count"} . "
Branches%.1f%%N. A.%.1f%%N. A." . $stats{"branch_count"} . "
Lines%.1f%%N. A.%.1f%%N. A." . $stats{"line_count"} . "
Number of tests$count$count$count$total

\n\n"; + + if ($total > 1) { + print OUT "

Tests coverage

\n"; + + print OUT "\n \n"; + print OUT " \n"; + foreach my $f (@info_files) { + print OUT " \n"; + } + + foreach my $t (sort keys(%test_names)) { + print OUT " \n"; + printf OUT " \n", $t; + foreach my $f (@info_files) { + if (%{$report{$f}{"test_names"}}{$t}) { + print OUT " \n"; + } else { + print OUT " \n"; + } + } + } + print OUT "
Test name$f
%sYESNO
\n"; + } + + + # Generates a table containing per-function detailed data + + print OUT "

Functions coverage

\n"; + print OUT "\n \n"; + print OUT " \n"; + print OUT " \n"; + foreach my $f (@info_files) { + print OUT " \n"; + } + print OUT " \n"; + + foreach my $func (sort keys(%all_func)) { + my @keys = sort keys(%{$all_func{$func}}); + foreach my $file (@keys) { + print OUT " \n"; + print OUT " \n"; + if ($used_func{$func}->{$file}) { + print OUT " \n"; + } else { + print OUT " \n"; + } + foreach my $f (@info_files) { + if ($report{$f}{"used_func"}{$func}->{$file}) { + print OUT " \n"; + } else { + print OUT " \n"; + } + } + $file =~ s,$prefix,linux/,; + print OUT " \n"; + } + } + print OUT "
FunctionUsed?$fFile
$funcYESNOYESNO$file
\n"; + + print OUT "$epilog\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 = (); + } } +if ($gen_report) { + generate_report(); + exit 0; +} + +gen_stats(); + +die "Nothing counted. Wrong input files?" if (!$stats{"all_files"}); + print_code_coverage($print_used, $print_unused, $show_lines); print_summary() if ($stat); -my $all_files = scalar keys(%files); - -die "Nothing counted. Wrong input files?" if (!$all_files); - if ($has_filter) { - my $all_files = scalar keys(%files); - my $filtered_files = scalar keys(%record); - my $used_files = scalar keys(%used_source); - - my $percent = 100. * $used_files / $all_files; + 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 [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> -- 2.35.1