3 # Copyright (c) International Business Machines Corp., 2002,2010
5 # This program is free software; you can redistribute it and/or modify
6 # it under the terms of the GNU General Public License as published by
7 # the Free Software Foundation; either version 2 of the License, or (at
8 # your option) any later version.
10 # This program is distributed in the hope that it will be useful, but
11 # WITHOUT ANY WARRANTY; without even the implied warranty of
12 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
13 # General Public License for more details.
15 # You should have received a copy of the GNU General Public License
16 # along with this program; if not, write to the Free Software
17 # Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
22 # This script generates HTML output from .info files as created by the
23 # geninfo script. Call it with --help and refer to the genhtml man page
24 # to get information on usage and available options.
28 # 2002-08-23 created by Peter Oberparleiter <Peter.Oberparleiter@de.ibm.com>
30 # based on code by Manoj Iyer <manjo@mail.utexas.edu> and
31 # Megan Bock <mbock@us.ibm.com>
33 # 2002-08-27 / Peter Oberparleiter: implemented frame view
34 # 2002-08-29 / Peter Oberparleiter: implemented test description filtering
35 # so that by default only descriptions for test cases which
36 # actually hit some source lines are kept
37 # 2002-09-05 / Peter Oberparleiter: implemented --no-sourceview
38 # 2002-09-05 / Mike Kobler: One of my source file paths includes a "+" in
39 # the directory name. I found that genhtml.pl died when it
40 # encountered it. I was able to fix the problem by modifying
41 # the string with the escape character before parsing it.
42 # 2002-10-26 / Peter Oberparleiter: implemented --num-spaces
43 # 2003-04-07 / Peter Oberparleiter: fixed bug which resulted in an error
44 # when trying to combine .info files containing data without
46 # 2003-04-10 / Peter Oberparleiter: extended fix by Mike to also cover
47 # other special characters
48 # 2003-04-30 / Peter Oberparleiter: made info write to STDERR, not STDOUT
49 # 2003-07-10 / Peter Oberparleiter: added line checksum support
50 # 2004-08-09 / Peter Oberparleiter: added configuration file support
51 # 2005-03-04 / Cal Pierog: added legend to HTML output, fixed coloring of
52 # "good coverage" background
53 # 2006-03-18 / Marcus Boerger: added --custom-intro, --custom-outro and
54 # overwrite --no-prefix if --prefix is present
55 # 2006-03-20 / Peter Oberparleiter: changes to custom_* function (rename
56 # to html_prolog/_epilog, minor modifications to implementation),
57 # changed prefix/noprefix handling to be consistent with current
59 # 2006-03-20 / Peter Oberparleiter: added --html-extension option
60 # 2008-07-14 / Tom Zoerner: added --function-coverage command line option;
61 # added function table to source file page
62 # 2008-08-13 / Peter Oberparleiter: modified function coverage
63 # implementation (now enabled per default),
64 # introduced sorting option (enabled per default)
70 use Digest
::MD5
qw(md5_base64);
74 our $title = "LCOV - code coverage report";
75 our $lcov_version = 'LCOV version 1.9';
76 our $lcov_url = "http://ltp.sourceforge.net/coverage/lcov.php";
77 our $tool_name = basename
($0);
79 # Specify coverage rate limits (in %) for classifying file entries
80 # HI: $hi_limit <= rate <= 100 graph color: green
81 # MED: $med_limit <= rate < $hi_limit graph color: orange
82 # LO: 0 <= rate < $med_limit graph color: red
84 # For line coverage/all coverage types if not specified
88 # For function coverage
96 # Width of overview image
97 our $overview_width = 80;
99 # Resolution of overview navigation: this number specifies the maximum
100 # difference in lines between the position a user selected from the overview
101 # and the position the source code window is scrolled to.
102 our $nav_resolution = 4;
104 # Clicking a line in the overview image should show the source code view at
105 # a position a bit further up so that the requested line is not the first
106 # line in the window. This number specifies that offset in lines.
107 our $nav_offset = 10;
109 # Clicking on a function name should show the source code at a position a
110 # few lines before the first line of code of that function. This number
111 # specifies that offset in lines.
112 our $func_offset = 2;
114 our $overview_title = "top level";
116 # Width for line coverage information in the source code view
117 our $line_field_width = 12;
119 # Width for branch coverage information in the source code view
120 our $br_field_width = 16;
128 our $HDR_TESTDESC = 3;
135 our $SORT_BRANCH = 3;
137 # Fileview heading types
138 our $HEAD_NO_DETAIL = 1;
139 our $HEAD_DETAIL_HIDDEN = 2;
140 our $HEAD_DETAIL_SHOWN = 3;
142 # Offsets for storing branch coverage data in vectors
146 our $BR_VEC_ENTRIES = 3;
147 our $BR_VEC_WIDTH = 32;
149 # Additional offsets used when converting branch coverage data to HTML
154 # Branch data combination types
158 # Data related prototypes
163 sub process_file
($$$);
165 sub read_info_file
($);
166 sub get_info_entry
($);
167 sub set_info_entry
($$$$$$$$$;$$$$$$);
169 sub shorten_prefix
($);
171 sub get_relative_base_path
($);
172 sub read_testfile
($);
173 sub get_date_string
();
174 sub create_sub_dir
($);
175 sub subtract_counts
($$);
177 sub apply_baseline
($$);
178 sub remove_unused_descriptions
();
179 sub get_found_and_hit
($);
180 sub get_affecting_tests
($$$);
181 sub combine_info_files
($$);
182 sub merge_checksums
($$$);
183 sub combine_info_entries
($$$);
184 sub apply_prefix
($$);
185 sub system_no_output
($@
);
188 sub get_html_prolog
($);
189 sub get_html_epilog
($);
190 sub write_dir_page
($$$$$$$$$$$$$$$$$);
191 sub classify_rate
($$$$);
192 sub br_taken_add
($$);
193 sub br_taken_sub
($$);
196 sub br_ivec_push
($$$$);
197 sub combine_brcount
($$$);
198 sub get_br_found_and_hit
($);
203 # HTML related prototypes
205 sub get_bar_graph_code
($$$);
207 sub write_png_files
();
208 sub write_htaccess_file
();
209 sub write_css_file
();
210 sub write_description_file
($$$$$$$);
211 sub write_function_table
(*$$$$$$$$$$);
214 sub write_html_prolog
(*$$);
215 sub write_html_epilog
(*$;$);
217 sub write_header
(*$$$$$$$$$$);
218 sub write_header_prolog
(*$);
219 sub write_header_line
(*@
);
220 sub write_header_epilog
(*$);
222 sub write_file_table
(*$$$$$$$);
223 sub write_file_table_prolog
(*$@
);
224 sub write_file_table_entry
(*$$$@
);
225 sub write_file_table_detail_entry
(*$@
);
226 sub write_file_table_epilog
(*);
228 sub write_test_table_prolog
(*$);
229 sub write_test_table_entry
(*$$);
230 sub write_test_table_epilog
(*);
232 sub write_source
($$$$$$$);
233 sub write_source_prolog
(*);
234 sub write_source_line
(*$$$$$$);
235 sub write_source_epilog
(*);
237 sub write_frameset
(*$$$);
238 sub write_overview_line
(*$$$);
239 sub write_overview
(*$$$$);
241 # External prototype (defined in genpng)
245 # Global variables & initialization
246 our %info_data; # Hash containing all data from .info file
247 our $dir_prefix; # Prefix to remove from all sub directories
248 our %test_description; # Hash containing test descriptions if available
249 our $date = get_date_string
();
251 our @info_filenames; # List of .info files to use as data source
252 our $test_title; # Title for output as written to each page header
253 our $output_directory; # Name of directory in which to store output
254 our $base_filename; # Optional name of file containing baseline data
255 our $desc_filename; # Name of file containing test descriptions
256 our $css_filename; # Optional name of external stylesheet file to use
257 our $quiet; # If set, suppress information messages
258 our $help; # Help option flag
259 our $version; # Version option flag
260 our $show_details; # If set, generate detailed directory view
261 our $no_prefix; # If set, do not remove filename prefix
262 our $func_coverage = 1; # If set, generate function coverage statistics
263 our $no_func_coverage; # Disable func_coverage
264 our $br_coverage = 1; # If set, generate branch coverage statistics
265 our $no_br_coverage; # Disable br_coverage
266 our $sort = 1; # If set, provide directory listings with sorted entries
267 our $no_sort; # Disable sort
268 our $frames; # If set, use frames for source code view
269 our $keep_descriptions; # If set, do not remove unused test case descriptions
270 our $no_sourceview; # If set, do not create a source code view for each file
271 our $highlight; # If set, highlight lines covered by converted data only
272 our $legend; # If set, include legend in output
273 our $tab_size = 8; # Number of spaces to use in place of tab
274 our $config; # Configuration file contents
275 our $html_prolog_file; # Custom HTML prolog file (up to and including <body>)
276 our $html_epilog_file; # Custom HTML epilog file (from </body> onwards)
277 our $html_prolog; # Actual HTML prolog
278 our $html_epilog; # Actual HTML epilog
279 our $html_ext = "html"; # Extension for generated HTML files
280 our $html_gzip = 0; # Compress with gzip
281 our $demangle_cpp = 0; # Demangle C++ function names
282 our @fileview_sortlist;
283 our @fileview_sortname = ("", "-sort-l", "-sort-f", "-sort-b");
284 our @funcview_sortlist;
285 our @rate_name = ("Lo", "Med", "Hi");
286 our @rate_png = ("ruby.png", "amber.png", "emerald.png");
288 our $cwd = `pwd`; # Current working directory
290 our $tool_dir = dirname
($0); # Directory where genhtml tool is installed
297 $SIG{__WARN__
} = \
&warn_handler
;
298 $SIG{__DIE__
} = \
&die_handler
;
300 # Prettify version string
301 $lcov_version =~ s/\$\s*Revision\s*:?\s*(\S+)\s*\$/$1/;
303 # Add current working directory if $tool_dir is not already an absolute path
304 if (! ($tool_dir =~ /^\/(.*)$/))
306 $tool_dir = "$cwd/$tool_dir";
309 # Read configuration file if available
310 if (defined($ENV{"HOME"}) && (-r
$ENV{"HOME"}."/.lcovrc"))
312 $config = read_config
($ENV{"HOME"}."/.lcovrc");
314 elsif (-r
"/etc/lcovrc")
316 $config = read_config
("/etc/lcovrc");
321 # Copy configuration file values to variables
323 "genhtml_css_file" => \
$css_filename,
324 "genhtml_hi_limit" => \
$hi_limit,
325 "genhtml_med_limit" => \
$med_limit,
326 "genhtml_line_field_width" => \
$line_field_width,
327 "genhtml_overview_width" => \
$overview_width,
328 "genhtml_nav_resolution" => \
$nav_resolution,
329 "genhtml_nav_offset" => \
$nav_offset,
330 "genhtml_keep_descriptions" => \
$keep_descriptions,
331 "genhtml_no_prefix" => \
$no_prefix,
332 "genhtml_no_source" => \
$no_sourceview,
333 "genhtml_num_spaces" => \
$tab_size,
334 "genhtml_highlight" => \
$highlight,
335 "genhtml_legend" => \
$legend,
336 "genhtml_html_prolog" => \
$html_prolog_file,
337 "genhtml_html_epilog" => \
$html_epilog_file,
338 "genhtml_html_extension" => \
$html_ext,
339 "genhtml_html_gzip" => \
$html_gzip,
340 "genhtml_function_hi_limit" => \
$fn_hi_limit,
341 "genhtml_function_med_limit" => \
$fn_med_limit,
342 "genhtml_function_coverage" => \
$func_coverage,
343 "genhtml_branch_hi_limit" => \
$br_hi_limit,
344 "genhtml_branch_med_limit" => \
$br_med_limit,
345 "genhtml_branch_coverage" => \
$br_coverage,
346 "genhtml_branch_field_width" => \
$br_field_width,
347 "genhtml_sort" => \
$sort,
351 # Copy limit values if not specified
352 $fn_hi_limit = $hi_limit if (!defined($fn_hi_limit));
353 $fn_med_limit = $med_limit if (!defined($fn_med_limit));
354 $br_hi_limit = $hi_limit if (!defined($br_hi_limit));
355 $br_med_limit = $med_limit if (!defined($br_med_limit));
357 # Parse command line options
358 if (!GetOptions
("output-directory|o=s" => \
$output_directory,
359 "title|t=s" => \
$test_title,
360 "description-file|d=s" => \
$desc_filename,
361 "keep-descriptions|k" => \
$keep_descriptions,
362 "css-file|c=s" => \
$css_filename,
363 "baseline-file|b=s" => \
$base_filename,
364 "prefix|p=s" => \
$dir_prefix,
365 "num-spaces=i" => \
$tab_size,
366 "no-prefix" => \
$no_prefix,
367 "no-sourceview" => \
$no_sourceview,
368 "show-details|s" => \
$show_details,
369 "frames|f" => \
$frames,
370 "highlight" => \
$highlight,
371 "legend" => \
$legend,
372 "quiet|q" => \
$quiet,
373 "help|h|?" => \
$help,
374 "version|v" => \
$version,
375 "html-prolog=s" => \
$html_prolog_file,
376 "html-epilog=s" => \
$html_epilog_file,
377 "html-extension=s" => \
$html_ext,
378 "html-gzip" => \
$html_gzip,
379 "function-coverage" => \
$func_coverage,
380 "no-function-coverage" => \
$no_func_coverage,
381 "branch-coverage" => \
$br_coverage,
382 "no-branch-coverage" => \
$no_br_coverage,
384 "no-sort" => \
$no_sort,
385 "demangle-cpp" => \
$demangle_cpp,
388 print(STDERR
"Use $tool_name --help to get usage information\n");
392 if ($no_func_coverage) {
395 if ($no_br_coverage) {
405 @info_filenames = @ARGV;
407 # Check for help option
410 print_usage
(*STDOUT
);
414 # Check for version option
417 print("$tool_name: $lcov_version\n");
421 # Check for info filename
422 if (!@info_filenames)
424 die("No filename specified\n".
425 "Use $tool_name --help to get usage information\n");
428 # Generate a title if none is specified
431 if (scalar(@info_filenames) == 1)
433 # Only one filename specified, use it as title
434 $test_title = basename
($info_filenames[0]);
438 # More than one filename specified, used default title
439 $test_title = "unnamed";
443 # Make sure css_filename is an absolute path (in case we're changing
447 if (!($css_filename =~ /^\/(.*)$/))
449 $css_filename = $cwd."/".$css_filename;
453 # Make sure tab_size is within valid range
456 print(STDERR
"ERROR: invalid number of spaces specified: ".
461 # Get HTML prolog and epilog
462 $html_prolog = get_html_prolog
($html_prolog_file);
463 $html_epilog = get_html_epilog
($html_epilog_file);
465 # Issue a warning if --no-sourceview is enabled together with --frames
466 if ($no_sourceview && defined($frames))
468 warn("WARNING: option --frames disabled because --no-sourceview ".
473 # Issue a warning if --no-prefix is enabled together with --prefix
474 if ($no_prefix && defined($dir_prefix))
476 warn("WARNING: option --prefix disabled because --no-prefix was ".
481 @fileview_sortlist = ($SORT_FILE);
482 @funcview_sortlist = ($SORT_FILE);
485 push(@fileview_sortlist, $SORT_LINE);
486 push(@fileview_sortlist, $SORT_FUNC) if ($func_coverage);
487 push(@fileview_sortlist, $SORT_BRANCH) if ($br_coverage);
488 push(@funcview_sortlist, $SORT_LINE);
493 # Include genpng code needed for overview image generation
494 do("$tool_dir/genpng");
497 # Ensure that the c++filt tool is available when using --demangle-cpp
500 if (system_no_output
(3, "c++filt", "--version")) {
501 die("ERROR: could not find c++filt tool needed for ".
506 # Make sure output_directory exists, create it if necessary
507 if ($output_directory)
509 stat($output_directory);
513 create_sub_dir
($output_directory);
525 # print_usage(handle)
527 # Print usage information.
532 local *HANDLE
= $_[0];
534 print(HANDLE
<<END_OF_USAGE);
535 Usage: $tool_name [OPTIONS] INFOFILE(S)
537 Create HTML output for coverage data found in INFOFILE. Note that INFOFILE
538 may also be a list of filenames.
541 -h, --help Print this help, then exit
542 -v, --version Print version number, then exit
543 -q, --quiet Do not print progress messages
546 -o, --output-directory OUTDIR Write HTML output to OUTDIR
547 -s, --show-details Generate detailed directory view
548 -d, --description-file DESCFILE Read test case descriptions from DESCFILE
549 -k, --keep-descriptions Do not remove unused test descriptions
550 -b, --baseline-file BASEFILE Use BASEFILE as baseline file
551 -p, --prefix PREFIX Remove PREFIX from all directory names
552 --no-prefix Do not remove prefix from directory names
553 --(no-)function-coverage Enable (disable) function coverage display
554 --(no-)branch-coverage Enable (disable) branch coverage display
557 -f, --frames Use HTML frames for source code view
558 -t, --title TITLE Display TITLE in header of all pages
559 -c, --css-file CSSFILE Use external style sheet file CSSFILE
560 --no-source Do not create source code view
561 --num-spaces NUM Replace tabs with NUM spaces in source view
562 --highlight Highlight lines with converted-only data
563 --legend Include color legend in HTML output
564 --html-prolog FILE Use FILE as HTML prolog for generated pages
565 --html-epilog FILE Use FILE as HTML epilog for generated pages
566 --html-extension EXT Use EXT as filename extension for pages
567 --html-gzip Use gzip to compress HTML
568 --(no-)sort Enable (disable) sorted coverage views
569 --demangle-cpp Demangle C++ function names
571 For more information see: $lcov_url
578 # get_rate(found, hit)
580 # Return a relative value for the specified found&hit values
581 # which is used for sorting the corresponding entries in a
587 my ($found, $hit) = @_;
592 return int($hit * 1000 / $found) * 10 + 2 - (1 / $found);
597 # get_overall_line(found, hit, name_singular, name_plural)
599 # Return a string containing overall information for the specified
603 sub get_overall_line($$$$)
605 my ($found, $hit, $name_sn, $name_pl) = @_;
608 return "no data found" if (!defined($found) || $found == 0);
609 $name = ($found == 1) ? $name_sn : $name_pl;
610 return sprintf("%.1f%% (%d of %d %s)", $hit * 100 / $found, $hit,
616 # print_overall_rate(ln_do, ln_found, ln_hit, fn_do, fn_found, fn_hit, br_do
619 # Print overall coverage rates for the specified coverage types.
622 sub print_overall_rate($$$$$$$$$)
624 my ($ln_do, $ln_found, $ln_hit, $fn_do, $fn_found, $fn_hit,
625 $br_do, $br_found, $br_hit) = @_;
627 info("Overall coverage rate:\n");
628 info(" lines......: %s\n",
629 get_overall_line($ln_found, $ln_hit, "line", "lines"))
631 info(" functions..: %s\n",
632 get_overall_line($fn_found, $fn_hit, "function", "functions"))
634 info(" branches...: %s\n",
635 get_overall_line($br_found, $br_hit, "branch", "branches"))
643 # Generate a set of HTML pages from contents of .info file INFO_FILENAME.
644 # Files will be written to the current directory. If provided, test case
645 # descriptions will be read from .tests file TEST_FILENAME and included
662 my $overall_found = 0;
664 my $total_fn_found = 0;
665 my $total_fn_hit = 0;
666 my $total_br_found = 0;
667 my $total_br_hit = 0;
673 # Read in all specified .info files
674 foreach (@info_filenames)
676 %new_info = %{read_info_file($_)};
678 # Combine %new_info with %info_data
679 %info_data = %{combine_info_files(\%info_data, \%new_info)};
682 info("Found %d entries.\n", scalar(keys(%info_data)));
684 # Read and apply baseline data if specified
688 info("Reading baseline file $base_filename\n");
689 %base_data = %{read_info_file($base_filename)};
690 info("Found %d entries.\n", scalar(keys(%base_data)));
693 info("Subtracting baseline data.\n");
694 %info_data = %{apply_baseline(\%info_data, \%base_data)};
697 @dir_list = get_dir_list(keys(%info_data));
701 # User requested that we leave filenames alone
702 info("User asked not to remove filename prefix\n");
704 elsif (!defined($dir_prefix))
706 # Get prefix common to most directories in list
707 $dir_prefix = get_prefix(@dir_list);
711 info("Found common filename prefix \"$dir_prefix\"\n");
715 info("No common filename prefix found!\n");
721 info("Using user-specified filename prefix \"".
725 # Read in test description file if specified
728 info("Reading test description file $desc_filename\n");
729 %test_description = %{read_testfile($desc_filename)};
731 # Remove test descriptions which are not referenced
732 # from %info_data if user didn't tell us otherwise
733 if (!$keep_descriptions)
735 remove_unused_descriptions();
739 # Change to output directory if specified
740 if ($output_directory)
742 chdir($output_directory)
743 or die("ERROR: cannot change to directory ".
744 "$output_directory!\n");
747 info("Writing .css and .png files.\n");
753 info("Writing .htaccess file.\n");
754 write_htaccess_file();
757 info("Generating output.\n");
759 # Process each subdirectory and collect overview information
760 foreach $dir_name (@dir_list)
762 ($lines_found, $lines_hit, $fn_found, $fn_hit,
764 = process_dir($dir_name);
766 # Remove prefix if applicable
767 if (!$no_prefix && $dir_prefix)
769 # Match directory names beginning with $dir_prefix
770 $dir_name = apply_prefix($dir_name, $dir_prefix);
773 # Generate name for directory overview HTML page
774 if ($dir_name =~ /^\/(.*)$/)
776 $link_name = substr($dir_name, 1)."/index.$html_ext";
780 $link_name = $dir_name."/index.$html_ext";
783 $overview{$dir_name} = [$lines_found, $lines_hit, $fn_found,
784 $fn_hit, $br_found, $br_hit, $link_name,
785 get_rate($lines_found, $lines_hit),
786 get_rate($fn_found, $fn_hit),
787 get_rate($br_found, $br_hit)];
788 $overall_found += $lines_found;
789 $overall_hit += $lines_hit;
790 $total_fn_found += $fn_found;
791 $total_fn_hit += $fn_hit;
792 $total_br_found += $br_found;
793 $total_br_hit += $br_hit;
796 # Generate overview page
797 info("Writing directory view page.\n");
799 # Create sorted pages
800 foreach (@fileview_sortlist) {
801 write_dir_page($fileview_sortname[$_], ".", "", $test_title,
802 undef, $overall_found, $overall_hit,
803 $total_fn_found, $total_fn_hit, $total_br_found,
804 $total_br_hit, \%overview, {}, {}, {}, 0, $_);
807 # Check if there are any test case descriptions to write out
808 if (%test_description)
810 info("Writing test case description file.\n");
811 write_description_file( \%test_description,
812 $overall_found, $overall_hit,
813 $total_fn_found, $total_fn_hit,
814 $total_br_found, $total_br_hit);
817 print_overall_rate(1, $overall_found, $overall_hit,
818 $func_coverage, $total_fn_found, $total_fn_hit,
819 $br_coverage, $total_br_found, $total_br_hit);
825 # html_create(handle, filename)
831 my $filename = $_[1];
835 open($handle, "|gzip -c >$filename")
836 or die("ERROR: cannot open $filename for writing ".
841 open($handle, ">$filename")
842 or die("ERROR: cannot open $filename for writing!\n");
846 sub write_dir_page($$$$$$$$$$$$$$$$$)
848 my ($name, $rel_dir, $base_dir, $title, $trunc_dir, $overall_found,
849 $overall_hit, $total_fn_found, $total_fn_hit, $total_br_found,
850 $total_br_hit, $overview, $testhash, $testfnchash, $testbrhash,
851 $view_type, $sort_type) = @_;
853 # Generate directory overview page including details
854 html_create(*HTML_HANDLE, "$rel_dir/index$name.$html_ext");
855 if (!defined($trunc_dir)) {
858 write_html_prolog(*HTML_HANDLE, $base_dir, "LCOV - $title$trunc_dir");
859 write_header(*HTML_HANDLE, $view_type, $trunc_dir, $rel_dir,
860 $overall_found, $overall_hit, $total_fn_found,
861 $total_fn_hit, $total_br_found, $total_br_hit, $sort_type);
862 write_file_table(*HTML_HANDLE, $base_dir, $overview, $testhash,
863 $testfnchash, $testbrhash, $view_type, $sort_type);
864 write_html_epilog(*HTML_HANDLE, $base_dir);
870 # process_dir(dir_name)
877 my $rel_dir = $abs_dir;
889 my $total_fn_found=0;
891 my $total_br_found = 0;
892 my $total_br_hit = 0;
904 # Remove prefix if applicable
907 # Match directory name beginning with $dir_prefix
908 $rel_dir = apply_prefix($rel_dir, $dir_prefix);
911 $trunc_dir = $rel_dir;
914 if ($rel_dir =~ /^\/(.*)$/)
916 $rel_dir = substr($rel_dir, 1);
919 $base_dir = get_relative_base_path($rel_dir);
921 create_sub_dir($rel_dir);
923 # Match filenames which specify files in this directory, not including
925 foreach $filename (grep(/^\Q$abs_dir\E\/[^\/]*$/,keys(%info_data)))
930 ($lines_found, $lines_hit, $fn_found, $fn_hit, $br_found,
931 $br_hit, $testdata, $testfncdata, $testbrdata) =
932 process_file($trunc_dir, $rel_dir, $filename);
934 $base_name = basename($filename);
936 if ($no_sourceview) {
939 # Link to frameset page
940 $page_link = "$base_name.gcov.frameset.$html_ext";
942 # Link directory to source code view page
943 $page_link = "$base_name.gcov.$html_ext";
945 $overview{$base_name} = [$lines_found, $lines_hit, $fn_found,
946 $fn_hit, $br_found, $br_hit,
948 get_rate($lines_found, $lines_hit),
949 get_rate($fn_found, $fn_hit),
950 get_rate($br_found, $br_hit)];
952 $testhash{$base_name} = $testdata;
953 $testfnchash{$base_name} = $testfncdata;
954 $testbrhash{$base_name} = $testbrdata;
956 $overall_found += $lines_found;
957 $overall_hit += $lines_hit;
959 $total_fn_found += $fn_found;
960 $total_fn_hit += $fn_hit;
962 $total_br_found += $br_found;
963 $total_br_hit += $br_hit;
966 # Create sorted pages
967 foreach (@fileview_sortlist) {
968 # Generate directory overview page (without details)
969 write_dir_page($fileview_sortname[$_], $rel_dir, $base_dir,
970 $test_title, $trunc_dir, $overall_found,
971 $overall_hit, $total_fn_found, $total_fn_hit,
972 $total_br_found, $total_br_hit, \%overview, {},
974 if (!$show_details) {
977 # Generate directory overview page including details
978 write_dir_page("-detail".$fileview_sortname[$_], $rel_dir,
979 $base_dir, $test_title, $trunc_dir,
980 $overall_found, $overall_hit, $total_fn_found,
981 $total_fn_hit, $total_br_found, $total_br_hit,
982 \%overview, \%testhash, \%testfnchash,
983 \%testbrhash, 1, $_);
986 # Calculate resulting line counts
987 return ($overall_found, $overall_hit, $total_fn_found, $total_fn_hit,
988 $total_br_found, $total_br_hit);
993 # get_converted_lines(testdata)
995 # Return hash of line numbers of those lines which were only covered in
996 # converted data sets.
999 sub get_converted_lines($)
1001 my $testdata = $_[0];
1011 # Get a hash containing line numbers with positive counts both for
1012 # converted and original data sets
1013 foreach $testcase (keys(%{$testdata}))
1015 # Check to see if this is a converted data set
1016 if ($testcase =~ /,diff$/)
1018 $hash = \%converted;
1022 $hash = \%nonconverted;
1025 $testcount = $testdata->{$testcase};
1026 # Add lines with a positive count to hash
1027 foreach $line (keys%{$testcount})
1029 if ($testcount->{$line} > 0)
1036 # Combine both hashes to resulting list
1037 foreach $line (keys(%converted))
1039 if (!defined($nonconverted{$line}))
1049 sub write_function_page($$$$$$$$$$$$$$$$$$)
1051 my ($base_dir, $rel_dir, $trunc_dir, $base_name, $title,
1052 $lines_found, $lines_hit, $fn_found, $fn_hit, $br_found, $br_hit,
1053 $sumcount, $funcdata, $sumfnccount, $testfncdata, $sumbrcount,
1054 $testbrdata, $sort_type) = @_;
1058 # Generate function table for this file
1059 if ($sort_type == 0) {
1060 $filename = "$rel_dir/$base_name.func.$html_ext";
1062 $filename = "$rel_dir/$base_name.func-sort-c.$html_ext";
1064 html_create(*HTML_HANDLE, $filename);
1065 $pagetitle = "LCOV - $title - $trunc_dir/$base_name - functions";
1066 write_html_prolog(*HTML_HANDLE, $base_dir, $pagetitle);
1067 write_header(*HTML_HANDLE, 4, "$trunc_dir/$base_name",
1068 "$rel_dir/$base_name", $lines_found, $lines_hit,
1069 $fn_found, $fn_hit, $br_found, $br_hit, $sort_type);
1070 write_function_table(*HTML_HANDLE, "$base_name.gcov.$html_ext",
1071 $sumcount, $funcdata,
1072 $sumfnccount, $testfncdata, $sumbrcount,
1073 $testbrdata, $base_name,
1074 $base_dir, $sort_type);
1075 write_html_epilog(*HTML_HANDLE, $base_dir, 1);
1076 close(*HTML_HANDLE);
1081 # process_file(trunc_dir, rel_dir, filename)
1084 sub process_file($$$)
1086 info("Processing file ".apply_prefix($_[2], $dir_prefix)."\n");
1088 my $trunc_dir = $_[0];
1089 my $rel_dir = $_[1];
1090 my $filename = $_[2];
1091 my $base_name = basename($filename);
1092 my $base_dir = get_relative_base_path($rel_dir);
1113 ($testdata, $sumcount, $funcdata, $checkdata, $testfncdata,
1114 $sumfnccount, $testbrdata, $sumbrcount, $lines_found, $lines_hit,
1115 $fn_found, $fn_hit, $br_found, $br_hit)
1116 = get_info_entry($info_data{$filename});
1118 # Return after this point in case user asked us not to generate
1122 return ($lines_found, $lines_hit, $fn_found, $fn_hit,
1123 $br_found, $br_hit, $testdata, $testfncdata,
1127 $converted = get_converted_lines($testdata);
1128 # Generate source code view for this file
1129 html_create(*HTML_HANDLE, "$rel_dir/$base_name.gcov.$html_ext");
1130 $pagetitle = "LCOV - $test_title - $trunc_dir/$base_name";
1131 write_html_prolog(*HTML_HANDLE, $base_dir, $pagetitle);
1132 write_header(*HTML_HANDLE, 2, "$trunc_dir/$base_name",
1133 "$rel_dir/$base_name", $lines_found, $lines_hit,
1134 $fn_found, $fn_hit, $br_found, $br_hit, 0);
1135 @source = write_source(*HTML_HANDLE, $filename, $sumcount, $checkdata,
1136 $converted, $funcdata, $sumbrcount);
1138 write_html_epilog(*HTML_HANDLE, $base_dir, 1);
1139 close(*HTML_HANDLE);
1141 if ($func_coverage) {
1142 # Create function tables
1143 foreach (@funcview_sortlist) {
1144 write_function_page($base_dir, $rel_dir, $trunc_dir,
1145 $base_name, $test_title,
1146 $lines_found, $lines_hit,
1147 $fn_found, $fn_hit, $br_found,
1149 $funcdata, $sumfnccount,
1150 $testfncdata, $sumbrcount,
1155 # Additional files are needed in case of frame output
1158 return ($lines_found, $lines_hit, $fn_found, $fn_hit,
1159 $br_found, $br_hit, $testdata, $testfncdata,
1163 # Create overview png file
1164 gen_png("$rel_dir/$base_name.gcov.png", $overview_width, $tab_size,
1167 # Create frameset page
1168 html_create(*HTML_HANDLE,
1169 "$rel_dir/$base_name.gcov.frameset.$html_ext");
1170 write_frameset(*HTML_HANDLE, $base_dir, $base_name, $pagetitle);
1171 close(*HTML_HANDLE);
1173 # Write overview frame
1174 html_create(*HTML_HANDLE,
1175 "$rel_dir/$base_name.gcov.overview.$html_ext");
1176 write_overview(*HTML_HANDLE, $base_dir, $base_name, $pagetitle,
1178 close(*HTML_HANDLE);
1180 return ($lines_found, $lines_hit, $fn_found, $fn_hit, $br_found,
1181 $br_hit, $testdata, $testfncdata, $testbrdata);
1186 # read_info_file(info_filename)
1188 # Read in the contents of the .info file specified by INFO_FILENAME. Data will
1189 # be returned as a reference to a hash containing the following mappings:
1191 # %result: for each filename found in file -> \%data
1193 # %data: "test" -> \%testdata
1194 # "sum" -> \%sumcount
1195 # "func" -> \%funcdata
1196 # "found" -> $lines_found (number of instrumented lines found in file)
1197 # "hit" -> $lines_hit (number of executed lines in file)
1198 # "check" -> \%checkdata
1199 # "testfnc" -> \%testfncdata
1200 # "sumfnc" -> \%sumfnccount
1201 # "testbr" -> \%testbrdata
1202 # "sumbr" -> \%sumbrcount
1204 # %testdata : name of test affecting this file -> \%testcount
1205 # %testfncdata: name of test affecting this file -> \%testfnccount
1206 # %testbrdata: name of test affecting this file -> \%testbrcount
1208 # %testcount : line number -> execution count for a single test
1209 # %testfnccount: function name -> execution count for a single test
1210 # %testbrcount : line number -> branch coverage data for a single test
1211 # %sumcount : line number -> execution count for all tests
1212 # %sumfnccount : function name -> execution count for all tests
1213 # %sumbrcount : line number -> branch coverage data for all tests
1214 # %funcdata : function name -> line number
1215 # %checkdata : line number -> checksum of source code line
1216 # $brdata : vector of items: block, branch, taken
1218 # Note that .info file sections referring to the same file and test name
1219 # will automatically be combined by adding all execution counts.
1221 # Note that if INFO_FILENAME ends with ".gz", it is assumed that the file
1222 # is compressed using GZIP. If available, GUNZIP will be used to decompress
1228 sub read_info_file($)
1230 my $tracefile = $_[0]; # Name of tracefile
1231 my %result; # Resulting hash: file -> data
1232 my $data; # Data handle for current entry
1234 my $testcount; # " "
1237 my $checkdata; # " "
1244 my $line; # Current line read from .info file
1245 my $testname; # Current test name
1246 my $filename; # Current filename
1247 my $hitcount; # Count for lines hit
1248 my $count; # Execution count of current line
1249 my $negative; # If set, warn about negative counts
1250 my $changed_testname; # If set, warn about changed testname
1251 my $line_checksum; # Checksum of current line
1254 local *INFO_HANDLE; # Filehandle for .info file
1256 info("Reading data file $tracefile\n");
1258 # Check if file exists and is readable
1262 die("ERROR: cannot read file $_[0]!\n");
1265 # Check if this is really a plain file
1268 die("ERROR: not a plain file: $_[0]!\n");
1271 # Check for .gz extension
1272 if ($_[0] =~ /\.gz$/)
1274 # Check for availability of GZIP tool
1275 system_no_output(1, "gunzip" ,"-h")
1276 and die("ERROR: gunzip command not available!\n");
1278 # Check integrity of compressed file
1279 system_no_output(1, "gunzip", "-t", $_[0])
1280 and die("ERROR: integrity check failed for ".
1281 "compressed file $_[0]!\n");
1283 # Open compressed file
1284 open(INFO_HANDLE, "gunzip -c $_[0]|")
1285 or die("ERROR: cannot start gunzip to decompress ".
1290 # Open decompressed file
1291 open(INFO_HANDLE, $_[0])
1292 or die("ERROR: cannot read file $_[0]!\n");
1296 while (<INFO_HANDLE>)
1304 /^TN:([^,]*)(,diff)?/ && do
1306 # Test name information found
1307 $testname = defined($1) ? $1 : "";
1308 if ($testname =~ s/\W/_/g)
1310 $changed_testname = 1;
1312 $testname .= $2 if (defined($2));
1318 # Filename information found
1319 # Retrieve data for new entry
1322 $data = $result{$filename};
1323 ($testdata, $sumcount, $funcdata, $checkdata,
1324 $testfncdata, $sumfnccount, $testbrdata,
1326 get_info_entry($data);
1328 if (defined($testname))
1330 $testcount = $testdata->{$testname};
1331 $testfnccount = $testfncdata->{$testname};
1332 $testbrcount = $testbrdata->{$testname};
1343 /^DA:(\d+),(-?\d+)(,[^,\s]+)?/ && do
1345 # Fix negative counts
1346 $count = $2 < 0 ? 0 : $2;
1351 # Execution count found, add to structure
1352 # Add summary counts
1353 $sumcount->{$1} += $count;
1355 # Add test-specific counts
1356 if (defined($testname))
1358 $testcount->{$1} += $count;
1361 # Store line checksum if available
1364 $line_checksum = substr($3, 1);
1366 # Does it match a previous definition
1367 if (defined($checkdata->{$1}) &&
1368 ($checkdata->{$1} ne
1371 die("ERROR: checksum mismatch ".
1372 "at $filename:$1\n");
1375 $checkdata->{$1} = $line_checksum;
1380 /^FN:(\d+),([^,]+)/ && do
1382 # Function data found, add to structure
1383 $funcdata->{$2} = $1;
1385 # Also initialize function call data
1386 if (!defined($sumfnccount->{$2})) {
1387 $sumfnccount->{$2} = 0;
1389 if (defined($testname))
1391 if (!defined($testfnccount->{$2})) {
1392 $testfnccount->{$2} = 0;
1398 /^FNDA:(\d+),([^,]+)/ && do
1400 # Function call count found, add to structure
1401 # Add summary counts
1402 $sumfnccount->{$2} += $1;
1404 # Add test-specific counts
1405 if (defined($testname))
1407 $testfnccount->{$2} += $1;
1412 /^BRDA:(\d+),(\d+),(\d+),(\d+|-)/ && do {
1413 # Branch coverage data found
1414 my ($line, $block, $branch, $taken) =
1417 $sumbrcount->{$line} =
1418 br_ivec_push($sumbrcount->{$line},
1419 $block, $branch, $taken);
1421 # Add test-specific counts
1422 if (defined($testname)) {
1423 $testbrcount->{$line} =
1425 $testbrcount->{$line},
1432 /^end_of_record/ && do
1434 # Found end of section marker
1437 # Store current section data
1438 if (defined($testname))
1440 $testdata->{$testname} =
1442 $testfncdata->{$testname} =
1444 $testbrdata->{$testname} =
1448 set_info_entry($data, $testdata,
1449 $sumcount, $funcdata,
1450 $checkdata, $testfncdata,
1454 $result{$filename} = $data;
1465 # Calculate lines_found and lines_hit for each file
1466 foreach $filename (keys(%result))
1468 $data = $result{$filename};
1470 ($testdata, $sumcount, undef, undef, $testfncdata,
1471 $sumfnccount, $testbrdata, $sumbrcount) =
1472 get_info_entry($data);
1474 # Filter out empty files
1475 if (scalar(keys(%{$sumcount})) == 0)
1477 delete($result{$filename});
1480 # Filter out empty test cases
1481 foreach $testname (keys(%{$testdata}))
1483 if (!defined($testdata->{$testname}) ||
1484 scalar(keys(%{$testdata->{$testname}})) == 0)
1486 delete($testdata->{$testname});
1487 delete($testfncdata->{$testname});
1491 $data->{"found"} = scalar(keys(%{$sumcount}));
1494 foreach (keys(%{$sumcount}))
1496 if ($sumcount->{$_} > 0) { $hitcount++; }
1499 $data->{"hit"} = $hitcount;
1501 # Get found/hit values for function call data
1502 $data->{"f_found"} = scalar(keys(%{$sumfnccount}));
1505 foreach (keys(%{$sumfnccount})) {
1506 if ($sumfnccount->{$_} > 0) {
1510 $data->{"f_hit"} = $hitcount;
1512 # Get found/hit values for branch data
1513 ($br_found, $br_hit) = get_br_found_and_hit($sumbrcount);
1515 $data->{"b_found"} = $br_found;
1516 $data->{"b_hit"} = $br_hit;
1519 if (scalar(keys(%result)) == 0)
1521 die("ERROR: no valid records found in tracefile $tracefile\n");
1525 warn("WARNING: negative counts found in tracefile ".
1528 if ($changed_testname)
1530 warn("WARNING: invalid characters removed from testname in ".
1531 "tracefile $tracefile\n");
1539 # get_info_entry(hash_ref)
1541 # Retrieve data from an entry of the structure generated by read_info_file().
1542 # Return a list of references to hashes:
1543 # (test data hash ref, sum count hash ref, funcdata hash ref, checkdata hash
1544 # ref, testfncdata hash ref, sumfnccount hash ref, lines found, lines hit,
1545 # functions found, functions hit)
1548 sub get_info_entry($)
1550 my $testdata_ref = $_[0]->{"test"};
1551 my $sumcount_ref = $_[0]->{"sum"};
1552 my $funcdata_ref = $_[0]->{"func"};
1553 my $checkdata_ref = $_[0]->{"check"};
1554 my $testfncdata = $_[0]->{"testfnc"};
1555 my $sumfnccount = $_[0]->{"sumfnc"};
1556 my $testbrdata = $_[0]->{"testbr"};
1557 my $sumbrcount = $_[0]->{"sumbr"};
1558 my $lines_found = $_[0]->{"found"};
1559 my $lines_hit = $_[0]->{"hit"};
1560 my $fn_found = $_[0]->{"f_found"};
1561 my $fn_hit = $_[0]->{"f_hit"};
1562 my $br_found = $_[0]->{"b_found"};
1563 my $br_hit = $_[0]->{"b_hit"};
1565 return ($testdata_ref, $sumcount_ref, $funcdata_ref, $checkdata_ref,
1566 $testfncdata, $sumfnccount, $testbrdata, $sumbrcount,
1567 $lines_found, $lines_hit, $fn_found, $fn_hit,
1568 $br_found, $br_hit);
1573 # set_info_entry(hash_ref, testdata_ref, sumcount_ref, funcdata_ref,
1574 # checkdata_ref, testfncdata_ref, sumfcncount_ref,
1575 # testbrdata_ref, sumbrcount_ref[,lines_found,
1576 # lines_hit, f_found, f_hit, $b_found, $b_hit])
1578 # Update the hash referenced by HASH_REF with the provided data references.
1581 sub set_info_entry($$$$$$$$$;$$$$$$)
1583 my $data_ref = $_[0];
1585 $data_ref->{"test"} = $_[1];
1586 $data_ref->{"sum"} = $_[2];
1587 $data_ref->{"func"} = $_[3];
1588 $data_ref->{"check"} = $_[4];
1589 $data_ref->{"testfnc"} = $_[5];
1590 $data_ref->{"sumfnc"} = $_[6];
1591 $data_ref->{"testbr"} = $_[7];
1592 $data_ref->{"sumbr"} = $_[8];
1594 if (defined($_[9])) { $data_ref->{"found"} = $_[9]; }
1595 if (defined($_[10])) { $data_ref->{"hit"} = $_[10]; }
1596 if (defined($_[11])) { $data_ref->{"f_found"} = $_[11]; }
1597 if (defined($_[12])) { $data_ref->{"f_hit"} = $_[12]; }
1598 if (defined($_[13])) { $data_ref->{"b_found"} = $_[13]; }
1599 if (defined($_[14])) { $data_ref->{"b_hit"} = $_[14]; }
1604 # add_counts(data1_ref, data2_ref)
1606 # DATA1_REF and DATA2_REF are references to hashes containing a mapping
1608 # line number -> execution count
1610 # Return a list (RESULT_REF, LINES_FOUND, LINES_HIT) where RESULT_REF
1611 # is a reference to a hash containing the combined mapping in which
1612 # execution counts are added.
1617 my %data1 = %{$_[0]}; # Hash 1
1618 my %data2 = %{$_[1]}; # Hash 2
1619 my %result; # Resulting hash
1620 my $line; # Current line iteration scalar
1621 my $data1_count; # Count of line in hash1
1622 my $data2_count; # Count of line in hash2
1623 my $found = 0; # Total number of lines found
1624 my $hit = 0; # Number of lines with a count > 0
1626 foreach $line (keys(%data1))
1628 $data1_count = $data1{$line};
1629 $data2_count = $data2{$line};
1631 # Add counts if present in both hashes
1632 if (defined($data2_count)) { $data1_count += $data2_count; }
1634 # Store sum in %result
1635 $result{$line} = $data1_count;
1638 if ($data1_count > 0) { $hit++; }
1641 # Add lines unique to data2
1642 foreach $line (keys(%data2))
1644 # Skip lines already in data1
1645 if (defined($data1{$line})) { next; }
1647 # Copy count from data2
1648 $result{$line} = $data2{$line};
1651 if ($result{$line} > 0) { $hit++; }
1654 return (\%result, $found, $hit);
1659 # merge_checksums(ref1, ref2, filename)
1661 # REF1 and REF2 are references to hashes containing a mapping
1663 # line number -> checksum
1665 # Merge checksum lists defined in REF1 and REF2 and return reference to
1666 # resulting hash. Die if a checksum for a line is defined in both hashes
1667 # but does not match.
1670 sub merge_checksums($$$)
1674 my $filename = $_[2];
1678 foreach $line (keys(%{$ref1}))
1680 if (defined($ref2->{$line}) &&
1681 ($ref1->{$line} ne $ref2->{$line}))
1683 die("ERROR: checksum mismatch at $filename:$line\n");
1685 $result{$line} = $ref1->{$line};
1688 foreach $line (keys(%{$ref2}))
1690 $result{$line} = $ref2->{$line};
1698 # merge_func_data(funcdata1, funcdata2, filename)
1701 sub merge_func_data($$$)
1703 my ($funcdata1, $funcdata2, $filename) = @_;
1707 if (defined($funcdata1)) {
1708 %result = %{$funcdata1};
1711 foreach $func (keys(%{$funcdata2})) {
1712 my $line1 = $result{$func};
1713 my $line2 = $funcdata2->{$func};
1715 if (defined($line1) && ($line1 != $line2)) {
1716 warn("WARNING: function data mismatch at ".
1717 "$filename:$line2\n");
1720 $result{$func} = $line2;
1728 # add_fnccount(fnccount1, fnccount2)
1730 # Add function call count data. Return list (fnccount_added, f_found, f_hit)
1733 sub add_fnccount($$)
1735 my ($fnccount1, $fnccount2) = @_;
1741 if (defined($fnccount1)) {
1742 %result = %{$fnccount1};
1744 foreach $function (keys(%{$fnccount2})) {
1745 $result{$function} += $fnccount2->{$function};
1747 $fn_found = scalar(keys(%result));
1749 foreach $function (keys(%result)) {
1750 if ($result{$function} > 0) {
1755 return (\%result, $fn_found, $fn_hit);
1759 # add_testfncdata(testfncdata1, testfncdata2)
1761 # Add function call count data for several tests. Return reference to
1762 # added_testfncdata.
1765 sub add_testfncdata($$)
1767 my ($testfncdata1, $testfncdata2) = @_;
1771 foreach $testname (keys(%{$testfncdata1})) {
1772 if (defined($testfncdata2->{$testname})) {
1775 # Function call count data for this testname exists
1776 # in both data sets: add
1777 ($fnccount) = add_fnccount(
1778 $testfncdata1->{$testname},
1779 $testfncdata2->{$testname});
1780 $result{$testname} = $fnccount;
1783 # Function call count data for this testname is unique to
1785 $result{$testname} = $testfncdata1->{$testname};
1788 # Add count data for testnames unique to data set 2
1789 foreach $testname (keys(%{$testfncdata2})) {
1790 if (!defined($result{$testname})) {
1791 $result{$testname} = $testfncdata2->{$testname};
1799 # brcount_to_db(brcount)
1801 # Convert brcount data to the following format:
1803 # db: line number -> block hash
1804 # block hash: block number -> branch hash
1805 # branch hash: branch number -> taken value
1808 sub brcount_to_db($)
1814 # Add branches from first count to database
1815 foreach $line (keys(%{$brcount})) {
1816 my $brdata = $brcount->{$line};
1818 my $num = br_ivec_len($brdata);
1820 for ($i = 0; $i < $num; $i++) {
1821 my ($block, $branch, $taken) = br_ivec_get($brdata, $i);
1823 $db->{$line}->{$block}->{$branch} = $taken;
1834 # Convert branch coverage data back to brcount format.
1837 sub db_to_brcount($)
1845 # Convert database back to brcount format
1846 foreach $line (sort({$a <=> $b} keys(%{$db}))) {
1847 my $ldata = $db->{$line};
1851 foreach $block (sort({$a <=> $b} keys(%{$ldata}))) {
1852 my $bdata = $ldata->{$block};
1855 foreach $branch (sort({$a <=> $b} keys(%{$bdata}))) {
1856 my $taken = $bdata->{$branch};
1859 $br_hit++ if ($taken ne "-" && $taken > 0);
1860 $brdata = br_ivec_push($brdata, $block,
1864 $brcount->{$line} = $brdata;
1867 return ($brcount, $br_found, $br_hit);
1872 # combine_brcount(brcount1, brcount2, type)
1874 # If add is BR_ADD, add branch coverage data and return list (brcount_added,
1875 # br_found, br_hit). If add is BR_SUB, subtract the taken values of brcount2
1876 # from brcount1 and return (brcount_sub, br_found, br_hit).
1879 sub combine_brcount($$$)
1881 my ($brcount1, $brcount2, $type) = @_;
1891 # Convert branches from first count to database
1892 $db = brcount_to_db($brcount1);
1893 # Combine values from database and second count
1894 foreach $line (keys(%{$brcount2})) {
1895 my $brdata = $brcount2->{$line};
1896 my $num = br_ivec_len($brdata);
1899 for ($i = 0; $i < $num; $i++) {
1900 ($block, $branch, $taken) = br_ivec_get($brdata, $i);
1901 my $new_taken = $db->{$line}->{$block}->{$branch};
1903 if ($type == $BR_ADD) {
1904 $new_taken = br_taken_add($new_taken, $taken);
1905 } elsif ($type == $BR_SUB) {
1906 $new_taken = br_taken_sub($new_taken, $taken);
1908 $db->{$line}->{$block}->{$branch} = $new_taken
1909 if (defined($new_taken));
1912 # Convert database back to brcount format
1913 ($result, $br_found, $br_hit) = db_to_brcount($db);
1915 return ($result, $br_found, $br_hit);
1920 # add_testbrdata(testbrdata1, testbrdata2)
1922 # Add branch coverage data for several tests. Return reference to
1926 sub add_testbrdata($$)
1928 my ($testbrdata1, $testbrdata2) = @_;
1932 foreach $testname (keys(%{$testbrdata1})) {
1933 if (defined($testbrdata2->{$testname})) {
1936 # Branch coverage data for this testname exists
1937 # in both data sets: add
1938 ($brcount) = combine_brcount($testbrdata1->{$testname},
1939 $testbrdata2->{$testname}, $BR_ADD);
1940 $result{$testname} = $brcount;
1943 # Branch coverage data for this testname is unique to
1945 $result{$testname} = $testbrdata1->{$testname};
1948 # Add count data for testnames unique to data set 2
1949 foreach $testname (keys(%{$testbrdata2})) {
1950 if (!defined($result{$testname})) {
1951 $result{$testname} = $testbrdata2->{$testname};
1959 # combine_info_entries(entry_ref1, entry_ref2, filename)
1961 # Combine .info data entry hashes referenced by ENTRY_REF1 and ENTRY_REF2.
1962 # Return reference to resulting hash.
1965 sub combine_info_entries($$$)
1967 my $entry1 = $_[0]; # Reference to hash containing first entry
1977 my $entry2 = $_[1]; # Reference to hash containing second entry
1987 my %result; # Hash containing combined entry
1988 my %result_testdata;
1989 my $result_sumcount = {};
1990 my $result_funcdata;
1991 my $result_testfncdata;
1992 my $result_sumfnccount;
1993 my $result_testbrdata;
1994 my $result_sumbrcount;
2003 my $filename = $_[2];
2006 ($testdata1, $sumcount1, $funcdata1, $checkdata1, $testfncdata1,
2007 $sumfnccount1, $testbrdata1, $sumbrcount1) = get_info_entry($entry1);
2008 ($testdata2, $sumcount2, $funcdata2, $checkdata2, $testfncdata2,
2009 $sumfnccount2, $testbrdata2, $sumbrcount2) = get_info_entry($entry2);
2012 $checkdata1 = merge_checksums($checkdata1, $checkdata2, $filename);
2015 $result_funcdata = merge_func_data($funcdata1, $funcdata2, $filename);
2017 # Combine function call count data
2018 $result_testfncdata = add_testfncdata($testfncdata1, $testfncdata2);
2019 ($result_sumfnccount, $fn_found, $fn_hit) =
2020 add_fnccount($sumfnccount1, $sumfnccount2);
2022 # Combine branch coverage data
2023 $result_testbrdata = add_testbrdata($testbrdata1, $testbrdata2);
2024 ($result_sumbrcount, $br_found, $br_hit) =
2025 combine_brcount($sumbrcount1, $sumbrcount2, $BR_ADD);
2028 foreach $testname (keys(%{$testdata1}))
2030 if (defined($testdata2->{$testname}))
2032 # testname is present in both entries, requires
2034 ($result_testdata{$testname}) =
2035 add_counts($testdata1->{$testname},
2036 $testdata2->{$testname});
2040 # testname only present in entry1, add to result
2041 $result_testdata{$testname} = $testdata1->{$testname};
2044 # update sum count hash
2045 ($result_sumcount, $lines_found, $lines_hit) =
2046 add_counts($result_sumcount,
2047 $result_testdata{$testname});
2050 foreach $testname (keys(%{$testdata2}))
2052 # Skip testnames already covered by previous iteration
2053 if (defined($testdata1->{$testname})) { next; }
2055 # testname only present in entry2, add to result hash
2056 $result_testdata{$testname} = $testdata2->{$testname};
2058 # update sum count hash
2059 ($result_sumcount, $lines_found, $lines_hit) =
2060 add_counts($result_sumcount,
2061 $result_testdata{$testname});
2064 # Calculate resulting sumcount
2067 set_info_entry(\%result, \%result_testdata, $result_sumcount,
2068 $result_funcdata, $checkdata1, $result_testfncdata,
2069 $result_sumfnccount, $result_testbrdata,
2070 $result_sumbrcount, $lines_found, $lines_hit,
2071 $fn_found, $fn_hit, $br_found, $br_hit);
2078 # combine_info_files(info_ref1, info_ref2)
2080 # Combine .info data in hashes referenced by INFO_REF1 and INFO_REF2. Return
2081 # reference to resulting hash.
2084 sub combine_info_files($$)
2086 my %hash1 = %{$_[0]};
2087 my %hash2 = %{$_[1]};
2090 foreach $filename (keys(%hash2))
2092 if ($hash1{$filename})
2094 # Entry already exists in hash1, combine them
2096 combine_info_entries($hash1{$filename},
2102 # Entry is unique in both hashes, simply add to
2104 $hash1{$filename} = $hash2{$filename};
2113 # get_prefix(filename_list)
2115 # Search FILENAME_LIST for a directory prefix which is common to as many
2116 # list entries as possible, so that removing this prefix will minimize the
2117 # sum of the lengths of all resulting shortened filenames.
2122 my @filename_list = @_; # provided list of filenames
2123 my %prefix; # mapping: prefix -> sum of lengths
2124 my $current; # Temporary iteration variable
2126 # Find list of prefixes
2127 foreach (@filename_list)
2129 # Need explicit assignment to get a copy of $_ so that
2130 # shortening the contained prefix does not affect the list
2131 $current = shorten_prefix($_);
2132 while ($current = shorten_prefix($current))
2134 # Skip rest if the remaining prefix has already been
2136 if ($prefix{$current}) { last; }
2139 $prefix{$current}="0";
2144 # Calculate sum of lengths for all prefixes
2145 foreach $current (keys(%prefix))
2147 foreach (@filename_list)
2149 # Add original length
2150 $prefix{$current} += length($_);
2152 # Check whether prefix matches
2153 if (substr($_, 0, length($current)) eq $current)
2155 # Subtract prefix length for this filename
2156 $prefix{$current} -= length($current);
2161 # Find and return prefix with minimal sum
2162 $current = (keys(%prefix))[0];
2164 foreach (keys(%prefix))
2166 if ($prefix{$_} < $prefix{$current})
2177 # shorten_prefix(prefix)
2179 # Return PREFIX shortened by last directory component.
2182 sub shorten_prefix($)
2184 my @list = split("/", $_[0]);
2187 return join("/", @list);
2193 # get_dir_list(filename_list)
2195 # Return sorted list of directories for each entry in given FILENAME_LIST.
2204 $result{shorten_prefix($_)} = "";
2207 return(sort(keys(%result)));
2212 # get_relative_base_path(subdirectory)
2214 # Return a relative path string which references the base path when applied
2217 # Example: get_relative_base_path("fs/mm") -> "../../"
2220 sub get_relative_base_path($)
2225 # Make an empty directory path a special case
2226 if (!$_[0]) { return(""); }
2228 # Count number of /s in path
2229 $index = ($_[0] =~ s/\//\//g);
2231 # Add a ../ to $result for each / in the directory path + 1
2232 for (; $index>=0; $index--)
2242 # read_testfile(test_filename)
2244 # Read in file TEST_FILENAME which contains test descriptions in the format:
2246 # TN:<whitespace><test name>
2247 # TD:<whitespace><test description>
2249 # for each test case. Return a reference to a hash containing a mapping
2251 # test name -> test description.
2256 sub read_testfile($)
2260 my $changed_testname;
2263 open(TEST_HANDLE, "<".$_[0])
2264 or die("ERROR: cannot open $_[0]!\n");
2266 while (<TEST_HANDLE>)
2270 # Match lines beginning with TN:<whitespace(s)>
2271 if (/^TN:\s+(.*?)\s*$/)
2273 # Store name for later use
2275 if ($test_name =~ s/\W/_/g)
2277 $changed_testname = 1;
2281 # Match lines beginning with TD:<whitespace(s)>
2282 if (/^TD:\s+(.*?)\s*$/)
2284 # Check for empty line
2287 # Add description to hash
2288 $result{$test_name} .= " $1";
2293 $result{$test_name} .= "\n\n";
2300 if ($changed_testname)
2302 warn("WARNING: invalid characters removed from testname in ".
2303 "descriptions file $_[0]\n");
2311 # escape_html(STRING)
2313 # Return a copy of STRING in which all occurrences of HTML special characters
2321 if (!$string) { return ""; }
2323 $string =~ s/&/&/g; # & -> &
2324 $string =~ s/</</g; # < -> <
2325 $string =~ s/>/>/g; # > -> >
2326 $string =~ s/\"/"/g; # " -> "
2328 while ($string =~ /^([^\t]*)(\t)/)
2330 my $replacement = " "x($tab_size - (length($1) % $tab_size));
2331 $string =~ s/^([^\t]*)(\t)/$1$replacement/;
2334 $string =~ s/\n/<br>/g; # \n -> <br>
2343 # Return the current date in the form: yyyy-mm-dd
2346 sub get_date_string()
2352 ($year, $month, $day) = (localtime())[5, 4, 3];
2354 return sprintf("%d-%02d-%02d", $year+1900, $month+1, $day);
2359 # create_sub_dir(dir_name)
2361 # Create subdirectory DIR_NAME if it does not already exist, including all its
2362 # parent directories.
2367 sub create_sub_dir($)
2371 system("mkdir", "-p" ,$dir)
2372 and die("ERROR: cannot create directory $dir!\n");
2377 # write_description_file(descriptions, overall_found, overall_hit,
2378 # total_fn_found, total_fn_hit, total_br_found,
2381 # Write HTML file containing all test case descriptions. DESCRIPTIONS is a
2382 # reference to a hash containing a mapping
2384 # test case name -> test case description
2389 sub write_description_file($$$$$$$)
2391 my %description = %{$_[0]};
2394 my $fn_found = $_[3];
2396 my $br_found = $_[5];
2401 html_create(*HTML_HANDLE,"descriptions.$html_ext");
2402 write_html_prolog(*HTML_HANDLE, "", "LCOV - test case descriptions");
2403 write_header(*HTML_HANDLE, 3, "", "", $found, $hit, $fn_found,
2404 $fn_hit, $br_found, $br_hit, 0);
2406 write_test_table_prolog(*HTML_HANDLE,
2407 "Test case descriptions - alphabetical list");
2409 foreach $test_name (sort(keys(%description)))
2411 write_test_table_entry(*HTML_HANDLE, $test_name,
2412 escape_html($description{$test_name}));
2415 write_test_table_epilog(*HTML_HANDLE);
2416 write_html_epilog(*HTML_HANDLE, "");
2418 close(*HTML_HANDLE);
2426 # Create all necessary .png files for the HTML-output in the current
2427 # directory. .png-files are used as bar graphs.
2432 sub write_png_files()
2438 [0x89, 0x50, 0x4e, 0x47, 0x0d, 0x0a, 0x1a, 0x0a, 0x00, 0x00,
2439 0x00, 0x0d, 0x49, 0x48, 0x44, 0x52, 0x00, 0x00, 0x00, 0x01,
2440 0x00, 0x00, 0x00, 0x01, 0x01, 0x03, 0x00, 0x00, 0x00, 0x25,
2441 0xdb, 0x56, 0xca, 0x00, 0x00, 0x00, 0x07, 0x74, 0x49, 0x4d,
2442 0x45, 0x07, 0xd2, 0x07, 0x11, 0x0f, 0x18, 0x10, 0x5d, 0x57,
2443 0x34, 0x6e, 0x00, 0x00, 0x00, 0x09, 0x70, 0x48, 0x59, 0x73,
2444 0x00, 0x00, 0x0b, 0x12, 0x00, 0x00, 0x0b, 0x12, 0x01, 0xd2,
2445 0xdd, 0x7e, 0xfc, 0x00, 0x00, 0x00, 0x04, 0x67, 0x41, 0x4d,
2446 0x41, 0x00, 0x00, 0xb1, 0x8f, 0x0b, 0xfc, 0x61, 0x05, 0x00,
2447 0x00, 0x00, 0x06, 0x50, 0x4c, 0x54, 0x45, 0xff, 0x35, 0x2f,
2448 0x00, 0x00, 0x00, 0xd0, 0x33, 0x9a, 0x9d, 0x00, 0x00, 0x00,
2449 0x0a, 0x49, 0x44, 0x41, 0x54, 0x78, 0xda, 0x63, 0x60, 0x00,
2450 0x00, 0x00, 0x02, 0x00, 0x01, 0xe5, 0x27, 0xde, 0xfc, 0x00,
2451 0x00, 0x00, 0x00, 0x49, 0x45, 0x4e, 0x44, 0xae, 0x42, 0x60,
2453 $data{"amber.png"} =
2454 [0x89, 0x50, 0x4e, 0x47, 0x0d, 0x0a, 0x1a, 0x0a, 0x00, 0x00,
2455 0x00, 0x0d, 0x49, 0x48, 0x44, 0x52, 0x00, 0x00, 0x00, 0x01,
2456 0x00, 0x00, 0x00, 0x01, 0x01, 0x03, 0x00, 0x00, 0x00, 0x25,
2457 0xdb, 0x56, 0xca, 0x00, 0x00, 0x00, 0x07, 0x74, 0x49, 0x4d,
2458 0x45, 0x07, 0xd2, 0x07, 0x11, 0x0f, 0x28, 0x04, 0x98, 0xcb,
2459 0xd6, 0xe0, 0x00, 0x00, 0x00, 0x09, 0x70, 0x48, 0x59, 0x73,
2460 0x00, 0x00, 0x0b, 0x12, 0x00, 0x00, 0x0b, 0x12, 0x01, 0xd2,
2461 0xdd, 0x7e, 0xfc, 0x00, 0x00, 0x00, 0x04, 0x67, 0x41, 0x4d,
2462 0x41, 0x00, 0x00, 0xb1, 0x8f, 0x0b, 0xfc, 0x61, 0x05, 0x00,
2463 0x00, 0x00, 0x06, 0x50, 0x4c, 0x54, 0x45, 0xff, 0xe0, 0x50,
2464 0x00, 0x00, 0x00, 0xa2, 0x7a, 0xda, 0x7e, 0x00, 0x00, 0x00,
2465 0x0a, 0x49, 0x44, 0x41, 0x54, 0x78, 0xda, 0x63, 0x60, 0x00,
2466 0x00, 0x00, 0x02, 0x00, 0x01, 0xe5, 0x27, 0xde, 0xfc, 0x00,
2467 0x00, 0x00, 0x00, 0x49, 0x45, 0x4e, 0x44, 0xae, 0x42, 0x60,
2469 $data{"emerald.png"} =
2470 [0x89, 0x50, 0x4e, 0x47, 0x0d, 0x0a, 0x1a, 0x0a, 0x00, 0x00,
2471 0x00, 0x0d, 0x49, 0x48, 0x44, 0x52, 0x00, 0x00, 0x00, 0x01,
2472 0x00, 0x00, 0x00, 0x01, 0x01, 0x03, 0x00, 0x00, 0x00, 0x25,
2473 0xdb, 0x56, 0xca, 0x00, 0x00, 0x00, 0x07, 0x74, 0x49, 0x4d,
2474 0x45, 0x07, 0xd2, 0x07, 0x11, 0x0f, 0x22, 0x2b, 0xc9, 0xf5,
2475 0x03, 0x33, 0x00, 0x00, 0x00, 0x09, 0x70, 0x48, 0x59, 0x73,
2476 0x00, 0x00, 0x0b, 0x12, 0x00, 0x00, 0x0b, 0x12, 0x01, 0xd2,
2477 0xdd, 0x7e, 0xfc, 0x00, 0x00, 0x00, 0x04, 0x67, 0x41, 0x4d,
2478 0x41, 0x00, 0x00, 0xb1, 0x8f, 0x0b, 0xfc, 0x61, 0x05, 0x00,
2479 0x00, 0x00, 0x06, 0x50, 0x4c, 0x54, 0x45, 0x1b, 0xea, 0x59,
2480 0x0a, 0x0a, 0x0a, 0x0f, 0xba, 0x50, 0x83, 0x00, 0x00, 0x00,
2481 0x0a, 0x49, 0x44, 0x41, 0x54, 0x78, 0xda, 0x63, 0x60, 0x00,
2482 0x00, 0x00, 0x02, 0x00, 0x01, 0xe5, 0x27, 0xde, 0xfc, 0x00,
2483 0x00, 0x00, 0x00, 0x49, 0x45, 0x4e, 0x44, 0xae, 0x42, 0x60,
2486 [0x89, 0x50, 0x4e, 0x47, 0x0d, 0x0a, 0x1a, 0x0a, 0x00, 0x00,
2487 0x00, 0x0d, 0x49, 0x48, 0x44, 0x52, 0x00, 0x00, 0x00, 0x01,
2488 0x00, 0x00, 0x00, 0x01, 0x01, 0x03, 0x00, 0x00, 0x00, 0x25,
2489 0xdb, 0x56, 0xca, 0x00, 0x00, 0x00, 0x07, 0x74, 0x49, 0x4d,
2490 0x45, 0x07, 0xd2, 0x07, 0x11, 0x0f, 0x1e, 0x1d, 0x75, 0xbc,
2491 0xef, 0x55, 0x00, 0x00, 0x00, 0x09, 0x70, 0x48, 0x59, 0x73,
2492 0x00, 0x00, 0x0b, 0x12, 0x00, 0x00, 0x0b, 0x12, 0x01, 0xd2,
2493 0xdd, 0x7e, 0xfc, 0x00, 0x00, 0x00, 0x04, 0x67, 0x41, 0x4d,
2494 0x41, 0x00, 0x00, 0xb1, 0x8f, 0x0b, 0xfc, 0x61, 0x05, 0x00,
2495 0x00, 0x00, 0x06, 0x50, 0x4c, 0x54, 0x45, 0xff, 0xff, 0xff,
2496 0x00, 0x00, 0x00, 0x55, 0xc2, 0xd3, 0x7e, 0x00, 0x00, 0x00,
2497 0x0a, 0x49, 0x44, 0x41, 0x54, 0x78, 0xda, 0x63, 0x60, 0x00,
2498 0x00, 0x00, 0x02, 0x00, 0x01, 0xe5, 0x27, 0xde, 0xfc, 0x00,
2499 0x00, 0x00, 0x00, 0x49, 0x45, 0x4e, 0x44, 0xae, 0x42, 0x60,
2501 $data{"glass.png"} =
2502 [0x89, 0x50, 0x4e, 0x47, 0x0d, 0x0a, 0x1a, 0x0a, 0x00, 0x00,
2503 0x00, 0x0d, 0x49, 0x48, 0x44, 0x52, 0x00, 0x00, 0x00, 0x01,
2504 0x00, 0x00, 0x00, 0x01, 0x01, 0x03, 0x00, 0x00, 0x00, 0x25,
2505 0xdb, 0x56, 0xca, 0x00, 0x00, 0x00, 0x04, 0x67, 0x41, 0x4d,
2506 0x41, 0x00, 0x00, 0xb1, 0x8f, 0x0b, 0xfc, 0x61, 0x05, 0x00,
2507 0x00, 0x00, 0x06, 0x50, 0x4c, 0x54, 0x45, 0xff, 0xff, 0xff,
2508 0x00, 0x00, 0x00, 0x55, 0xc2, 0xd3, 0x7e, 0x00, 0x00, 0x00,
2509 0x01, 0x74, 0x52, 0x4e, 0x53, 0x00, 0x40, 0xe6, 0xd8, 0x66,
2510 0x00, 0x00, 0x00, 0x01, 0x62, 0x4b, 0x47, 0x44, 0x00, 0x88,
2511 0x05, 0x1d, 0x48, 0x00, 0x00, 0x00, 0x09, 0x70, 0x48, 0x59,
2512 0x73, 0x00, 0x00, 0x0b, 0x12, 0x00, 0x00, 0x0b, 0x12, 0x01,
2513 0xd2, 0xdd, 0x7e, 0xfc, 0x00, 0x00, 0x00, 0x07, 0x74, 0x49,
2514 0x4d, 0x45, 0x07, 0xd2, 0x07, 0x13, 0x0f, 0x08, 0x19, 0xc4,
2515 0x40, 0x56, 0x10, 0x00, 0x00, 0x00, 0x0a, 0x49, 0x44, 0x41,
2516 0x54, 0x78, 0x9c, 0x63, 0x60, 0x00, 0x00, 0x00, 0x02, 0x00,
2517 0x01, 0x48, 0xaf, 0xa4, 0x71, 0x00, 0x00, 0x00, 0x00, 0x49,
2518 0x45, 0x4e, 0x44, 0xae, 0x42, 0x60, 0x82];
2519 $data{"updown.png"} =
2520 [0x89, 0x50, 0x4e, 0x47, 0x0d, 0x0a, 0x1a, 0x0a, 0x00, 0x00,
2521 0x00, 0x0d, 0x49, 0x48, 0x44, 0x52, 0x00, 0x00, 0x00, 0x0a,
2522 0x00, 0x00, 0x00, 0x0e, 0x08, 0x06, 0x00, 0x00, 0x00, 0x16,
2523 0xa3, 0x8d, 0xab, 0x00, 0x00, 0x00, 0x3c, 0x49, 0x44, 0x41,
2524 0x54, 0x28, 0xcf, 0x63, 0x60, 0x40, 0x03, 0xff, 0xa1, 0x00,
2525 0x5d, 0x9c, 0x11, 0x5d, 0x11, 0x8a, 0x24, 0x23, 0x23, 0x23,
2526 0x86, 0x42, 0x6c, 0xa6, 0x20, 0x2b, 0x66, 0xc4, 0xa7, 0x08,
2527 0x59, 0x31, 0x23, 0x21, 0x45, 0x30, 0xc0, 0xc4, 0x30, 0x60,
2528 0x80, 0xfa, 0x6e, 0x24, 0x3e, 0x78, 0x48, 0x0a, 0x70, 0x62,
2529 0xa2, 0x90, 0x81, 0xd8, 0x44, 0x01, 0x00, 0xe9, 0x5c, 0x2f,
2530 0xf5, 0xe2, 0x9d, 0x0f, 0xf9, 0x00, 0x00, 0x00, 0x00, 0x49,
2531 0x45, 0x4e, 0x44, 0xae, 0x42, 0x60, 0x82] if ($sort);
2532 foreach (keys(%data))
2534 open(PNG_HANDLE, ">".$_)
2535 or die("ERROR: cannot create $_!\n");
2536 binmode(PNG_HANDLE);
2537 print(PNG_HANDLE map(chr,@{$data{$_}}));
2544 # write_htaccess_file()
2547 sub write_htaccess_file()
2549 local *HTACCESS_HANDLE;
2552 open(*HTACCESS_HANDLE, ">.htaccess")
2553 or die("ERROR: cannot open .htaccess for writing!\n");
2555 $htaccess_data = (<<"END_OF_HTACCESS")
2556 AddEncoding x-gzip .html
2560 print(HTACCESS_HANDLE $htaccess_data);
2561 close(*HTACCESS_HANDLE);
2568 # Write the cascading style sheet file gcov.css to the current directory.
2569 # This file defines basic layout attributes of all generated HTML pages.
2572 sub write_css_file()
2576 # Check for a specified external style sheet file
2579 # Simply copy that file
2580 system("cp", $css_filename, "gcov.css")
2581 and die("ERROR: cannot copy file $css_filename!\n");
2585 open(CSS_HANDLE, ">gcov.css")
2586 or die ("ERROR: cannot open gcov.css for writing!\n");
2589 # *************************************************************
2591 my $css_data = ($_=<<"END_OF_CSS")
2592 /* All views: initial background and text color */
2596 background-color: #FFFFFF;
2599 /* All views: standard link format*/
2603 text-decoration: underline;
2606 /* All views: standard link - visited format */
2610 text-decoration: underline;
2613 /* All views: standard link - activated format */
2617 text-decoration: underline;
2620 /* All views: main title format */
2624 padding-bottom: 10px;
2625 font-family: sans-serif;
2631 /* All views: header item format */
2636 font-family: sans-serif;
2638 vertical-align: top;
2639 white-space: nowrap;
2642 /* All views: header item value format */
2647 font-family: sans-serif;
2649 white-space: nowrap;
2652 /* All views: header item coverage table heading */
2653 td.headerCovTableHead
2658 padding-bottom: 0px;
2659 font-family: sans-serif;
2661 white-space: nowrap;
2664 /* All views: header item coverage table entry */
2665 td.headerCovTableEntry
2669 font-family: sans-serif;
2671 white-space: nowrap;
2674 background-color: #DAE7FE;
2677 /* All views: header item coverage table entry for high coverage rate */
2678 td.headerCovTableEntryHi
2682 font-family: sans-serif;
2684 white-space: nowrap;
2687 background-color: #A7FC9D;
2690 /* All views: header item coverage table entry for medium coverage rate */
2691 td.headerCovTableEntryMed
2695 font-family: sans-serif;
2697 white-space: nowrap;
2700 background-color: #FFEA20;
2703 /* All views: header item coverage table entry for ow coverage rate */
2704 td.headerCovTableEntryLo
2708 font-family: sans-serif;
2710 white-space: nowrap;
2713 background-color: #FF0000;
2716 /* All views: header legend value for legend entry */
2721 font-family: sans-serif;
2723 white-space: nowrap;
2727 /* All views: color of horizontal ruler */
2730 background-color: #6688D4;
2733 /* All views: version string format */
2738 font-family: sans-serif;
2742 /* Directory view/File view (all)/Test case descriptions:
2743 table headline format */
2748 background-color: #6688D4;
2749 font-family: sans-serif;
2752 white-space: nowrap;
2762 /* Directory view/File view (all): filename entry format */
2767 padding-right: 20px;
2769 background-color: #DAE7FE;
2770 font-family: monospace;
2773 /* Directory view/File view (all): bar-graph entry format*/
2777 padding-right: 10px;
2778 background-color: #DAE7FE;
2781 /* Directory view/File view (all): bar-graph outline color */
2784 background-color: #000000;
2787 /* Directory view/File view (all): percentage entry for files with
2788 high coverage rate */
2793 padding-right: 10px;
2794 background-color: #A7FC9D;
2796 font-family: sans-serif;
2799 /* Directory view/File view (all): line count entry for files with
2800 high coverage rate */
2805 padding-right: 10px;
2806 background-color: #A7FC9D;
2807 white-space: nowrap;
2808 font-family: sans-serif;
2811 /* Directory view/File view (all): percentage entry for files with
2812 medium coverage rate */
2817 padding-right: 10px;
2818 background-color: #FFEA20;
2820 font-family: sans-serif;
2823 /* Directory view/File view (all): line count entry for files with
2824 medium coverage rate */
2829 padding-right: 10px;
2830 background-color: #FFEA20;
2831 white-space: nowrap;
2832 font-family: sans-serif;
2835 /* Directory view/File view (all): percentage entry for files with
2836 low coverage rate */
2841 padding-right: 10px;
2842 background-color: #FF0000;
2844 font-family: sans-serif;
2847 /* Directory view/File view (all): line count entry for files with
2848 low coverage rate */
2853 padding-right: 10px;
2854 background-color: #FF0000;
2855 white-space: nowrap;
2856 font-family: sans-serif;
2859 /* File view (all): "show/hide details" link format */
2866 /* File view (all): "show/hide details" link - visited format */
2873 /* File view (all): "show/hide details" link - activated format */
2880 /* File view (detail): test name entry */
2884 padding-right: 10px;
2885 background-color: #DAE7FE;
2886 font-family: sans-serif;
2889 /* File view (detail): test percentage entry */
2894 padding-right: 10px;
2895 background-color: #DAE7FE;
2896 font-family: sans-serif;
2899 /* File view (detail): test lines count entry */
2904 padding-right: 10px;
2905 background-color: #DAE7FE;
2906 font-family: sans-serif;
2909 /* Test case descriptions: test name format*/
2912 font-family: sans-serif;
2916 /* Test case descriptions: description table body */
2921 padding-bottom: 10px;
2922 padding-right: 30px;
2923 background-color: #DAE7FE;
2926 /* Source code view: function entry */
2931 padding-right: 20px;
2933 background-color: #DAE7FE;
2934 font-family: monospace;
2937 /* Source code view: function entry zero count*/
2942 padding-right: 10px;
2943 background-color: #FF0000;
2945 font-family: sans-serif;
2948 /* Source code view: function entry nonzero count*/
2953 padding-right: 10px;
2954 background-color: #DAE7FE;
2956 font-family: sans-serif;
2959 /* Source code view: source code format */
2962 font-family: monospace;
2967 /* Source code view: line number format */
2970 background-color: #EFE383;
2973 /* Source code view: format for lines which were executed */
2977 background-color: #CAD7FE;
2980 /* Source code view: format for Cov legend */
2984 padding-right: 10px;
2985 padding-bottom: 2px;
2986 background-color: #CAD7FE;
2989 /* Source code view: format for lines which were not executed */
2993 background-color: #FF6230;
2996 /* Source code view: format for NoCov legend */
2997 span.coverLegendNoCov
3000 padding-right: 10px;
3001 padding-bottom: 2px;
3002 background-color: #FF6230;
3005 /* Source code view (function table): standard link - visited format */
3006 td.lineNoCov > a:visited,
3007 td.lineCov > a:visited
3010 text-decoration: underline;
3013 /* Source code view: format for lines which were executed only in a
3017 background-color: #B5F7AF;
3020 /* Source code view: format for branches which were executed
3024 background-color: #CAD7FE;
3027 /* Source code view: format for branches which were executed
3031 background-color: #FF6230;
3034 /* Source code view: format for branches which were not executed */
3037 background-color: #FF6230;
3040 /* Source code view: format for the source code heading line */
3044 font-family: monospace;
3049 /* All views: header legend value for low rate */
3052 font-family: sans-serif;
3054 white-space: nowrap;
3057 background-color: #FF0000;
3061 /* All views: header legend value for med rate */
3064 font-family: sans-serif;
3066 white-space: nowrap;
3069 background-color: #FFEA20;
3073 /* All views: header legend value for hi rate */
3076 font-family: sans-serif;
3078 white-space: nowrap;
3081 background-color: #A7FC9D;
3085 /* All views except source code view: legend format for low coverage */
3086 span.coverLegendCovLo
3089 padding-right: 10px;
3091 background-color: #FF0000;
3094 /* All views except source code view: legend format for med coverage */
3095 span.coverLegendCovMed
3098 padding-right: 10px;
3100 background-color: #FFEA20;
3103 /* All views except source code view: legend format for hi coverage */
3104 span.coverLegendCovHi
3107 padding-right: 10px;
3109 background-color: #A7FC9D;
3114 # *************************************************************
3117 # Remove leading tab from all lines
3118 $css_data =~ s/^\t//gm;
3120 print(CSS_HANDLE $css_data);
3127 # get_bar_graph_code(base_dir, cover_found, cover_hit)
3129 # Return a string containing HTML code which implements a bar graph display
3130 # for a coverage rate of cover_hit * 100 / cover_found.
3133 sub get_bar_graph_code($$$)
3142 # Check number of instrumented lines
3143 if ($_[1] == 0) { return ""; }
3145 $rate = $_[2] * 100 / $_[1];
3146 $alt = sprintf("%.1f", $rate)."%";
3147 $width = sprintf("%.0f", $rate);
3148 $remainder = sprintf("%d", 100-$width);
3150 # Decide which .png file to use
3151 $png_name = $rate_png[classify_rate($_[1], $_[2], $med_limit,
3157 $graph_code = (<<END_OF_HTML
)
3158 <table border
=0 cellspacing
=0 cellpadding
=1><tr
><td
class="coverBarOutline"><img src
="$_[0]snow.png" width
=100 height
=10 alt
="$alt"></td></tr
></table
>
3162 elsif ($width == 100)
3165 $graph_code = (<<END_OF_HTML)
3166 <table border=0 cellspacing=0 cellpadding=1><tr><td class="coverBarOutline"><img src="$_[0]$png_name" width=100 height=10 alt="$alt"></td></tr></table>
3173 $graph_code = (<<END_OF_HTML
)
3174 <table border
=0 cellspacing
=0 cellpadding
=1><tr
><td
class="coverBarOutline"><img src
="$_[0]$png_name" width
=$width height
=10 alt
="$alt"><img src
="$_[0]snow.png" width
=$remainder height
=10 alt
="$alt"></td></tr
></table
>
3179 # Remove leading tabs from all lines
3180 $graph_code =~ s/^\t+//gm;
3183 return($graph_code);
3187 # sub classify_rate(found, hit, med_limit, high_limit)
3189 # Return 0 for low rate, 1 for medium rate and 2 for hi rate.
3192 sub classify_rate
($$$$)
3194 my ($found, $hit, $med, $hi) = @_;
3200 $rate = $hit * 100 / $found;
3203 } elsif ($rate < $hi) {
3211 # write_html(filehandle, html_code)
3213 # Write out HTML_CODE to FILEHANDLE while removing a leading tabulator mark
3214 # in each line of HTML_CODE.
3219 local *HTML_HANDLE
= $_[0];
3220 my $html_code = $_[1];
3222 # Remove leading tab from all lines
3223 $html_code =~ s/^\t//gm;
3225 print(HTML_HANDLE
$html_code)
3226 or die("ERROR: cannot write HTML data ($!)\n");
3231 # write_html_prolog(filehandle, base_dir, pagetitle)
3233 # Write an HTML prolog common to all HTML files to FILEHANDLE. PAGETITLE will
3234 # be used as HTML page title. BASE_DIR contains a relative path which points
3235 # to the base directory.
3238 sub write_html_prolog
(*$$)
3240 my $basedir = $_[1];
3241 my $pagetitle = $_[2];
3244 $prolog = $html_prolog;
3245 $prolog =~ s/\@pagetitle\@/$pagetitle/g;
3246 $prolog =~ s/\@basedir\@/$basedir/g;
3248 write_html
($_[0], $prolog);
3253 # write_header_prolog(filehandle, base_dir)
3255 # Write beginning of page header HTML code.
3258 sub write_header_prolog
(*$)
3260 # *************************************************************
3262 write_html
($_[0], <<END_OF_HTML)
3263 <table width="100%" border=0 cellspacing=0 cellpadding=0>
3264 <tr><td class="title">$title</td></tr>
3265 <tr><td class="ruler"><img src="$_[1]glass.png" width=3 height=3 alt=""></td></tr>
3269 <table cellpadding=1 border=0 width="100%">
3273 # *************************************************************
3278 # write_header_line(handle, content)
3280 # Write a header line with the specified table contents.
3283 sub write_header_line(*@)
3285 my ($handle, @content) = @_;
3288 write_html($handle, " <tr>\n");
3289 foreach $entry (@content) {
3290 my ($width, $class, $text, $colspan) = @{$entry};
3292 if (defined($width)) {
3293 $width = " width=\"$width\"";
3297 if (defined($class)) {
3298 $class = " class=\"$class\"";
3302 if (defined($colspan)) {
3303 $colspan = " colspan=\"$colspan\"";
3307 $text = "" if (!defined($text));
3309 " <td$width$class$colspan>$text</td>\n");
3311 write_html($handle, " </tr>\n");
3316 # write_header_epilog(filehandle, base_dir)
3318 # Write end of page header HTML code.
3321 sub write_header_epilog(*$)
3323 # *************************************************************
3325 write_html($_[0], <<END_OF_HTML
)
3326 <tr
><td
><img src
="$_[1]glass.png" width
=3 height
=3 alt
=""></td></tr
>
3331 <tr
><td
class="ruler"><img src
="$_[1]glass.png" width
=3 height
=3 alt
=""></td></tr
>
3337 # *************************************************************
3342 # write_file_table_prolog(handle, file_heading, ([heading, num_cols], ...))
3344 # Write heading for file table.
3347 sub write_file_table_prolog
(*$@
)
3349 my ($handle, $file_heading, @columns) = @_;
3350 my $num_columns = 0;
3355 $width = 20 if (scalar(@columns) == 1);
3356 $width = 10 if (scalar(@columns) == 2);
3357 $width = 8 if (scalar(@columns) > 2);
3359 foreach $col (@columns) {
3360 my ($heading, $cols) = @
{$col};
3362 $num_columns += $cols;
3364 $file_width = 100 - $num_columns * $width;
3367 write_html
($handle, <<END_OF_HTML);
3369 <table width="80%" cellpadding=1 cellspacing=1 border=0>
3372 <td width="$file_width%"><br></td>
3375 foreach $col (@columns) {
3376 my ($heading, $cols) = @{$col};
3378 while ($cols-- > 0) {
3379 write_html($handle, <<END_OF_HTML
);
3380 <td width
="$width%"></td
>
3385 write_html
($handle, <<END_OF_HTML);
3389 <td class="tableHead">$file_heading</td>
3392 foreach $col (@columns) {
3393 my ($heading, $cols) = @{$col};
3396 $colspan = " colspan=$cols" if ($cols > 1);
3397 write_html($handle, <<END_OF_HTML
);
3398 <td
class="tableHead"$colspan>$heading</td
>
3401 write_html
($handle, <<END_OF_HTML);
3407 # write_file_table_entry(handle, base_dir, filename, page_link,
3408 # ([ found, hit, med_limit, hi_limit, graph ], ..)
3410 # Write an entry of the file table.
3413 sub write_file_table_entry(*$$$@)
3415 my ($handle, $base_dir, $filename, $page_link, @entries) = @_;
3419 # Add link to source if provided
3420 if (defined($page_link) && $page_link ne "") {
3421 $file_code = "<a href=\"$page_link\">$filename</a>";
3423 $file_code = $filename;
3426 # First column: filename
3427 write_html($handle, <<END_OF_HTML
);
3429 <td
class="coverFile">$file_code</td
>
3431 # Columns as defined
3432 foreach $entry (@entries) {
3433 my ($found, $hit, $med, $hi, $graph) = @
{$entry};
3438 # Generate bar graph if requested
3440 $bar_graph = get_bar_graph_code
($base_dir, $found,
3442 write_html
($handle, <<END_OF_HTML);
3443 <td class="coverBar" align="center">
3448 # Get rate color and text
3453 $rate = sprintf("%.1f %%", $hit * 100 / $found);
3454 $class = $rate_name[classify_rate($found, $hit,
3457 write_html($handle, <<END_OF_HTML
);
3458 <td
class="coverPer$class">$rate</td
>
3459 <td
class="coverNum$class">$hit / $found</td
>
3463 write_html
($handle, <<END_OF_HTML);
3470 # write_file_table_detail_entry(filehandle, test_name, ([found, hit], ...))
3472 # Write entry for detail section in file table.
3475 sub write_file_table_detail_entry(*$@)
3477 my ($handle, $test, @entries) = @_;
3481 $test = "<span style=\"font-style:italic\"><unnamed></span>";
3482 } elsif ($test =~ /^(.*),diff$/) {
3483 $test = $1." (converted)";
3486 write_html($handle, <<END_OF_HTML
);
3488 <td
class="testName" colspan
=2>$test</td
>
3491 foreach $entry (@entries) {
3492 my ($found, $hit) = @
{$entry};
3496 $rate = sprintf("%.1f %%", $hit * 100 / $found);
3498 write_html
($handle, <<END_OF_HTML);
3499 <td class="testPer">$rate</td>
3500 <td class="testNum">$hit / $found</td>
3504 write_html($handle, <<END_OF_HTML
);
3509 # *************************************************************
3514 # write_file_table_epilog(filehandle)
3516 # Write end of file table HTML code.
3519 sub write_file_table_epilog
(*)
3521 # *************************************************************
3523 write_html
($_[0], <<END_OF_HTML)
3531 # *************************************************************
3536 # write_test_table_prolog(filehandle, table_heading)
3538 # Write heading for test case description table.
3541 sub write_test_table_prolog(*$)
3543 # *************************************************************
3545 write_html($_[0], <<END_OF_HTML
)
3547 <table width
="80%" cellpadding
=2 cellspacing
=1 border
=0>
3554 <td
class="tableHead">$_[1]</td
>
3558 <td
class="testDescription">
3563 # *************************************************************
3568 # write_test_table_entry(filehandle, test_name, test_description)
3570 # Write entry for the test table.
3573 sub write_test_table_entry
(*$$)
3575 # *************************************************************
3577 write_html
($_[0], <<END_OF_HTML)
3578 <dt>$_[1]<a name="$_[1]"> </a></dt>
3579 <dd>$_[2]<br><br></dd>
3583 # *************************************************************
3588 # write_test_table_epilog(filehandle)
3590 # Write end of test description table HTML code.
3593 sub write_test_table_epilog(*)
3595 # *************************************************************
3597 write_html($_[0], <<END_OF_HTML
)
3608 # *************************************************************
3612 sub fmt_centered
($$)
3614 my ($width, $text) = @_;
3615 my $w0 = length($text);
3616 my $w1 = int(($width - $w0) / 2);
3617 my $w2 = $width - $w0 - $w1;
3619 return (" "x
$w1).$text.(" "x
$w2);
3624 # write_source_prolog(filehandle)
3626 # Write start of source code table.
3629 sub write_source_prolog
(*)
3631 my $lineno_heading = " ";
3632 my $branch_heading = "";
3633 my $line_heading = fmt_centered
($line_field_width, "Line data");
3634 my $source_heading = " Source code";
3637 $branch_heading = fmt_centered
($br_field_width, "Branch data").
3640 # *************************************************************
3642 write_html
($_[0], <<END_OF_HTML)
3643 <table cellpadding=0 cellspacing=0 border=0>
3649 <pre class="sourceHeading">${lineno_heading}${branch_heading}${line_heading} ${source_heading}</pre>
3650 <pre class="source">
3654 # *************************************************************
3659 # get_branch_blocks(brdata)
3661 # Group branches that belong to the same basic block.
3663 # Returns: [block1, block2, ...]
3664 # block: [branch1, branch2, ...]
3665 # branch: [block_num, branch_num, taken_count, text_length, open, close]
3668 sub get_branch_blocks($)
3675 my $num = br_ivec_len($brdata);
3678 for ($i = 0; $i < $num; $i++) {
3679 my ($block_num, $branch, $taken) = br_ivec_get($brdata, $i);
3682 if (defined($last_block_num) && $block_num != $last_block_num) {
3683 push(@blocks, $block);
3686 $br = [$block_num, $branch, $taken, 3, 0, 0];
3687 push(@{$block}, $br);
3688 $last_block_num = $block_num;
3690 push(@blocks, $block) if (scalar(@{$block}) > 0);
3692 # Add braces to first and last branch in group
3693 foreach $block (@blocks) {
3694 $block->[0]->[$BR_OPEN] = 1;
3695 $block->[0]->[$BR_LEN]++;
3696 $block->[scalar(@{$block}) - 1]->[$BR_CLOSE] = 1;
3697 $block->[scalar(@{$block}) - 1]->[$BR_LEN]++;
3704 # get_block_len(block)
3706 # Calculate total text length of all branches in a block of branches.
3709 sub get_block_len($)
3715 foreach $branch (@{$block}) {
3716 $len += $branch->[$BR_LEN];
3724 # get_branch_html(brdata)
3726 # Return a list of HTML lines which represent the specified branch coverage
3727 # data in source code view.
3730 sub get_branch_html($)
3733 my @blocks = get_branch_blocks($brdata);
3737 my $line = []; # [branch2|" ", branch|" ", ...]
3738 my @lines; # [line1, line2, ...]
3741 # Distribute blocks to lines
3742 foreach $block (@blocks) {
3743 my $block_len = get_block_len($block);
3745 # Does this block fit into the current line?
3746 if ($line_len + $block_len <= $br_field_width) {
3748 $line_len += $block_len;
3749 push(@{$line}, @{$block});
3751 } elsif ($block_len <= $br_field_width) {
3752 # It would fit if the line was empty - add it to new
3754 push(@lines, $line);
3755 $line_len = $block_len;
3756 $line = [ @{$block} ];
3759 # Split the block into several lines
3760 foreach $branch (@{$block}) {
3761 if ($line_len + $branch->[$BR_LEN] >= $br_field_width) {
3763 if (($line_len + 1 <= $br_field_width) &&
3764 scalar(@{$line}) > 0 &&
3765 !$line->[scalar(@$line) - 1]->[$BR_CLOSE]) {
3766 # Try to align branch symbols to be in
3768 push(@{$line}, " ");
3770 push(@lines, $line);
3774 push(@{$line}, $branch);
3775 $line_len += $branch->[$BR_LEN];
3778 push(@lines, $line);
3781 foreach $line (@lines) {
3783 my $current_len = 0;
3785 foreach $branch (@$line) {
3786 # Skip alignment space
3787 if ($branch eq " ") {
3793 my ($block_num, $br_num, $taken, $len, $open, $close) =
3799 if ($taken eq '-') {
3800 $class = "branchNoExec";
3802 $title = "Branch $br_num was not executed";
3803 } elsif ($taken == 0) {
3804 $class = "branchNoCov";
3806 $title = "Branch $br_num was not taken";
3808 $class = "branchCov";
3810 $title = "Branch $br_num was taken $taken ".
3812 $title .= "s" if ($taken > 1);
3814 $current .= "[" if ($open);
3815 $current .= "<span class=\"$class\" title=\"$title\">";
3816 $current .= $text."</span>";
3817 $current .= "]" if ($close);
3818 $current_len += $len;
3821 # Right-align result text
3822 if ($current_len < $br_field_width) {
3823 $current = (" "x($br_field_width - $current_len)).
3826 push(@result, $current);
3834 # format_count(count, width)
3836 # Return a right-aligned representation of count that fits in width characters.
3839 sub format_count($$)
3841 my ($count, $width) = @_;
3845 $result = sprintf("%*.0f", $width, $count);
3846 while (length($result) > $width) {
3847 last if ($count < 10);
3849 $count = int($count/10);
3850 $result = sprintf("%*s", $width, ">$count*10^$exp");
3856 # write_source_line(filehandle, line_num, source, hit_count, converted,
3857 # brdata, add_anchor)
3859 # Write formatted source code line. Return a line in a format as needed
3863 sub write_source_line(*$$$$$$)
3865 my ($handle, $line, $source, $count, $converted, $brdata,
3870 my $anchor_start = "";
3871 my $anchor_end = "";
3872 my $count_field_width = $line_field_width - 1;
3876 # Get branch HTML data for this line
3877 @br_html = get_branch_html($brdata) if ($br_coverage);
3879 if (!defined($count)) {
3881 $source_format = "";
3882 $count_format = " "x$count_field_width;
3884 elsif ($count == 0) {
3886 $source_format = '<span class="lineNoCov">';
3887 $count_format = format_count($count, $count_field_width);
3889 elsif ($converted && defined($highlight)) {
3890 $result = "*".$count;
3891 $source_format = '<span class="lineDiffCov">';
3892 $count_format = format_count($count, $count_field_width);
3896 $source_format = '<span class="lineCov">';
3897 $count_format = format_count($count, $count_field_width);
3899 $result .= ":".$source;
3901 # Write out a line number navigation anchor every $nav_resolution
3902 # lines if necessary
3905 $anchor_start = "<a name=\"$_[1]\">";
3906 $anchor_end = "</a>";
3910 # *************************************************************
3912 $html = $anchor_start;
3913 $html .= "<span class=\"lineNum\">".sprintf("%8d", $line)." </span>";
3914 $html .= shift(@br_html).":" if ($br_coverage);
3915 $html .= "$source_format$count_format : ";
3916 $html .= escape_html($source);
3917 $html .= "</span>" if ($source_format);
3918 $html .= $anchor_end."\n";
3920 write_html($handle, $html);
3923 # Add lines for overlong branch information
3924 foreach (@br_html) {
3925 write_html($handle, "<span class=\"lineNum\">".
3929 # *************************************************************
3936 # write_source_epilog(filehandle)
3938 # Write end of source code table.
3941 sub write_source_epilog(*)
3943 # *************************************************************
3945 write_html($_[0], <<END_OF_HTML
)
3955 # *************************************************************
3960 # write_html_epilog(filehandle, base_dir[, break_frames])
3962 # Write HTML page footer to FILEHANDLE. BREAK_FRAMES should be set when
3963 # this page is embedded in a frameset, clicking the URL link will then
3964 # break this frameset.
3967 sub write_html_epilog
(*$;$)
3969 my $basedir = $_[1];
3970 my $break_code = "";
3975 $break_code = " target=\"_parent\"";
3978 # *************************************************************
3980 write_html
($_[0], <<END_OF_HTML)
3981 <table width="100%" border=0 cellspacing=0 cellpadding=0>
3982 <tr><td class="ruler"><img src="$_[1]glass.png" width=3 height=3 alt=""></td></tr>
3983 <tr><td class="versionInfo">Generated by: <a href="$lcov_url"$break_code>$lcov_version</a></td></tr>
3989 $epilog = $html_epilog;
3990 $epilog =~ s/\@basedir\@/$basedir/g;
3992 write_html($_[0], $epilog);
3997 # write_frameset(filehandle, basedir, basename, pagetitle)
4001 sub write_frameset(*$$$)
4003 my $frame_width = $overview_width + 40;
4005 # *************************************************************
4007 write_html($_[0], <<END_OF_HTML
)
4008 <!DOCTYPE HTML PUBLIC
"-//W3C//DTD HTML 4.01 Frameset//EN">
4013 <meta http
-equiv
="Content-Type" content
="text/html; charset=ISO-8859-1">
4014 <title
>$_[3]</title
>
4015 <link rel
="stylesheet" type
="text/css" href
="$_[1]gcov.css">
4018 <frameset cols
="$frame_width,*">
4019 <frame src
="$_[2].gcov.overview.$html_ext" name
="overview">
4020 <frame src
="$_[2].gcov.$html_ext" name
="source">
4022 <center
>Frames
not supported by your browser
!<br
></center
>
4030 # *************************************************************
4035 # sub write_overview_line(filehandle, basename, line, link)
4039 sub write_overview_line
(*$$$)
4042 my $y2 = $y1 + $nav_resolution - 1;
4043 my $x2 = $overview_width - 1;
4045 # *************************************************************
4047 write_html
($_[0], <<END_OF_HTML)
4048 <area shape="rect" coords="0,$y1,$x2,$y2" href="$_[1].gcov.$html_ext#$_[3]" target="source" alt="overview">
4052 # *************************************************************
4057 # write_overview(filehandle, basedir, basename, pagetitle, lines)
4061 sub write_overview(*$$$$)
4064 my $max_line = $_[4] - 1;
4067 # *************************************************************
4069 write_html($_[0], <<END_OF_HTML
)
4070 <!DOCTYPE HTML PUBLIC
"-//W3C//DTD HTML 4.01 Transitional//EN">
4075 <title
>$_[3]</title
>
4076 <meta http
-equiv
="Content-Type" content
="text/html; charset=ISO-8859-1">
4077 <link rel
="stylesheet" type
="text/css" href
="$_[1]gcov.css">
4081 <map name
="overview">
4085 # *************************************************************
4087 # Make $offset the next higher multiple of $nav_resolution
4088 $offset = ($nav_offset + $nav_resolution - 1) / $nav_resolution;
4089 $offset = sprintf("%d", $offset ) * $nav_resolution;
4091 # Create image map for overview image
4092 for ($index = 1; $index <= $_[4]; $index += $nav_resolution)
4094 # Enforce nav_offset
4095 if ($index < $offset + 1)
4097 write_overview_line
($_[0], $_[2], $index, 1);
4101 write_overview_line
($_[0], $_[2], $index, $index - $offset);
4105 # *************************************************************
4107 write_html
($_[0], <<END_OF_HTML)
4111 <a href="$_[2].gcov.$html_ext#top" target="source">Top</a><br><br>
4112 <img src="$_[2].gcov.png" width=$overview_width height=$max_line alt="Overview" border=0 usemap="#overview">
4119 # *************************************************************
4123 # format_rate(found, hit)
4125 # Return formatted percent string for coverage rate.
4130 return $_[0] == 0 ? "-" : sprintf("%.1f", $_[1] * 100 / $_[0])." %";
4138 return $a if ($a > $b);
4144 # write_header(filehandle, type, trunc_file_name, rel_file_name, lines_found,
4145 # lines_hit, funcs_found, funcs_hit, sort_type)
4147 # Write a complete standard page header. TYPE may be (0, 1, 2, 3, 4)
4148 # corresponding to (directory view header, file view header, source view
4149 # header, test case description header, function view header)
4152 sub write_header(*$$$$$$$$$$)
4154 local *HTML_HANDLE = $_[0];
4156 my $trunc_name = $_[2];
4157 my $rel_filename = $_[3];
4158 my $lines_found = $_[4];
4159 my $lines_hit = $_[5];
4160 my $fn_found = $_[6];
4162 my $br_found = $_[8];
4164 my $sort_type = $_[10];
4176 $base_name = basename($rel_filename);
4178 # Prepare text for "current view" field
4179 if ($type == $HDR_DIR)
4183 $view = $overview_title;
4185 elsif ($type == $HDR_FILE)
4187 # Directory overview
4188 $base_dir = get_relative_base_path($rel_filename);
4189 $view = "<a href=\"$base_dir"."index.$html_ext\">".
4190 "$overview_title</a> - $trunc_name";
4192 elsif ($type == $HDR_SOURCE || $type == $HDR_FUNC)
4195 my $dir_name = dirname($rel_filename);
4197 $base_dir = get_relative_base_path($dir_name);
4200 # Need to break frameset when clicking any of these
4202 $view = "<a href=\"$base_dir"."index.$html_ext\" ".
4203 "target=\"_parent\">$overview_title</a> - ".
4204 "<a href=\"index.$html_ext\" target=\"_parent\">".
4205 "$dir_name</a> - $base_name";
4209 $view = "<a href=\"$base_dir"."index.$html_ext\">".
4210 "$overview_title</a> - ".
4211 "<a href=\"index.$html_ext\">".
4212 "$dir_name</a> - $base_name";
4215 # Add function suffix
4216 if ($func_coverage) {
4217 $view .= "<span style=\"font-size: 80%;\">";
4218 if ($type == $HDR_SOURCE) {
4219 $view .= " (source / <a href=\"$base_name.func.$html_ext\">functions</a>)";
4220 } elsif ($type == $HDR_FUNC) {
4221 $view .= " (<a href=\"$base_name.gcov.$html_ext\">source</a> / functions)";
4226 elsif ($type == $HDR_TESTDESC)
4228 # Test description header
4230 $view = "<a href=\"$base_dir"."index.$html_ext\">".
4231 "$overview_title</a> - test case descriptions";
4234 # Prepare text for "test" field
4235 $test = escape_html($test_title);
4237 # Append link to test description page if available
4238 if (%test_description && ($type != $HDR_TESTDESC))
4240 if ($frames && ($type == $HDR_SOURCE || $type == $HDR_FUNC))
4242 # Need to break frameset when clicking this link
4243 $test .= " ( <span style=\"font-size:80%;\">".
4244 "<a href=\"$base_dir".
4245 "descriptions.$html_ext\" target=\"_parent\">".
4246 "view descriptions</a></span> )";
4250 $test .= " ( <span style=\"font-size:80%;\">".
4251 "<a href=\"$base_dir".
4252 "descriptions.$html_ext\">".
4253 "view descriptions</a></span> )";
4258 write_header_prolog(*HTML_HANDLE, $base_dir);
4261 push(@row_left, [[ "10%", "headerItem", "Current view:" ],
4262 [ "35%", "headerValue", $view ]]);
4263 push(@row_left, [[undef, "headerItem", "Test:"],
4264 [undef, "headerValue", $test]]);
4265 push(@row_left, [[undef, "headerItem", "Date:"],
4266 [undef, "headerValue", $date]]);
4269 if ($legend && ($type == $HDR_SOURCE || $type == $HDR_FUNC)) {
4270 my $text = <<END_OF_HTML
;
4272 <span
class="coverLegendCov">hit
</span
>
4273 <span
class="coverLegendNoCov">not hit
</span
>
4276 $text .= <<END_OF_HTML;
4278 <span class="coverLegendCov">+</span> taken
4279 <span class="coverLegendNoCov">-</span> not taken
4280 <span class="coverLegendNoCov">#</span> not executed
4283 push(@row_left, [[undef, "headerItem", "Legend:"],
4284 [undef, "headerValueLeg", $text]]);
4285 } elsif ($legend && ($type != $HDR_TESTDESC)) {
4286 my $text = <<END_OF_HTML
;
4288 <span
class="coverLegendCovLo" title
="Coverage rates below $med_limit % are classified as low">low
: < $med_limit %</span
>
4289 <span
class="coverLegendCovMed" title
="Coverage rates between $med_limit % and $hi_limit % are classified as medium">medium
: >= $med_limit %</span
>
4290 <span
class="coverLegendCovHi" title
="Coverage rates of $hi_limit % and more are classified as high">high
: >= $hi_limit %</span
>
4292 push(@row_left, [[undef, "headerItem", "Legend:"],
4293 [undef, "headerValueLeg", $text]]);
4295 if ($type == $HDR_TESTDESC) {
4296 push(@row_right, [[ "55%" ]]);
4298 push(@row_right, [["15%", undef, undef ],
4299 ["10%", "headerCovTableHead", "Hit" ],
4300 ["10%", "headerCovTableHead", "Total" ],
4301 ["15%", "headerCovTableHead", "Coverage"]]);
4304 $style = $rate_name[classify_rate
($lines_found, $lines_hit,
4305 $med_limit, $hi_limit)];
4306 $rate = format_rate
($lines_found, $lines_hit);
4307 push(@row_right, [[undef, "headerItem", "Lines:"],
4308 [undef, "headerCovTableEntry", $lines_hit],
4309 [undef, "headerCovTableEntry", $lines_found],
4310 [undef, "headerCovTableEntry$style", $rate]])
4311 if ($type != $HDR_TESTDESC);
4313 if ($func_coverage) {
4314 $style = $rate_name[classify_rate
($fn_found, $fn_hit,
4315 $fn_med_limit, $fn_hi_limit)];
4316 $rate = format_rate
($fn_found, $fn_hit);
4317 push(@row_right, [[undef, "headerItem", "Functions:"],
4318 [undef, "headerCovTableEntry", $fn_hit],
4319 [undef, "headerCovTableEntry", $fn_found],
4320 [undef, "headerCovTableEntry$style", $rate]])
4321 if ($type != $HDR_TESTDESC);
4325 $style = $rate_name[classify_rate
($br_found, $br_hit,
4326 $br_med_limit, $br_hi_limit)];
4327 $rate = format_rate
($br_found, $br_hit);
4328 push(@row_right, [[undef, "headerItem", "Branches:"],
4329 [undef, "headerCovTableEntry", $br_hit],
4330 [undef, "headerCovTableEntry", $br_found],
4331 [undef, "headerCovTableEntry$style", $rate]])
4332 if ($type != $HDR_TESTDESC);
4336 $num_rows = max
(scalar(@row_left), scalar(@row_right));
4337 for ($i = 0; $i < $num_rows; $i++) {
4338 my $left = $row_left[$i];
4339 my $right = $row_right[$i];
4341 if (!defined($left)) {
4342 $left = [[undef, undef, undef], [undef, undef, undef]];
4344 if (!defined($right)) {
4347 write_header_line
(*HTML_HANDLE
, @
{$left},
4348 [ $i == 0 ?
"5%" : undef, undef, undef],
4353 write_header_epilog
(*HTML_HANDLE
, $base_dir);
4358 # get_sorted_keys(hash_ref, sort_type)
4361 sub get_sorted_keys
($$)
4363 my ($hash, $type) = @_;
4365 if ($type == $SORT_FILE) {
4367 return sort(keys(%{$hash}));
4368 } elsif ($type == $SORT_LINE) {
4369 # Sort by line coverage
4370 return sort({$hash->{$a}[7] <=> $hash->{$b}[7]} keys(%{$hash}));
4371 } elsif ($type == $SORT_FUNC) {
4372 # Sort by function coverage;
4373 return sort({$hash->{$a}[8] <=> $hash->{$b}[8]} keys(%{$hash}));
4374 } elsif ($type == $SORT_BRANCH) {
4375 # Sort by br coverage;
4376 return sort({$hash->{$a}[9] <=> $hash->{$b}[9]} keys(%{$hash}));
4380 sub get_sort_code
($$$)
4382 my ($link, $alt, $base) = @_;
4387 if (!defined($link)) {
4392 $png = "updown.png";
4393 $link_start = '<a href="'.$link.'">';
4397 return ' <span class="tableHeadSort">'.$link_start.
4398 '<img src="'.$base.$png.'" width=10 height=14 '.
4399 'alt="'.$alt.'" title="'.$alt.'" border=0>'.$link_end.'</span>';
4402 sub get_file_code
($$$$)
4404 my ($type, $text, $sort_button, $base) = @_;
4409 if ($type == $HEAD_NO_DETAIL) {
4410 $link = "index.$html_ext";
4412 $link = "index-detail.$html_ext";
4415 $result .= get_sort_code
($link, "Sort by name", $base);
4420 sub get_line_code
($$$$$)
4422 my ($type, $sort_type, $text, $sort_button, $base) = @_;
4426 if ($type == $HEAD_NO_DETAIL) {
4429 $sort_link = "index-sort-l.$html_ext";
4431 } elsif ($type == $HEAD_DETAIL_HIDDEN) {
4432 # Text + link to detail view
4433 $result .= ' ( <a class="detail" href="index-detail'.
4434 $fileview_sortname[$sort_type].'.'.$html_ext.
4435 '">show details</a> )';
4437 $sort_link = "index-sort-l.$html_ext";
4440 # Text + link to standard view
4441 $result .= ' ( <a class="detail" href="index'.
4442 $fileview_sortname[$sort_type].'.'.$html_ext.
4443 '">hide details</a> )';
4445 $sort_link = "index-detail-sort-l.$html_ext";
4449 $result .= get_sort_code
($sort_link, "Sort by line coverage", $base);
4454 sub get_func_code
($$$$)
4456 my ($type, $text, $sort_button, $base) = @_;
4461 if ($type == $HEAD_NO_DETAIL) {
4462 $link = "index-sort-f.$html_ext";
4464 $link = "index-detail-sort-f.$html_ext";
4467 $result .= get_sort_code
($link, "Sort by function coverage", $base);
4471 sub get_br_code
($$$$)
4473 my ($type, $text, $sort_button, $base) = @_;
4478 if ($type == $HEAD_NO_DETAIL) {
4479 $link = "index-sort-b.$html_ext";
4481 $link = "index-detail-sort-b.$html_ext";
4484 $result .= get_sort_code
($link, "Sort by branch coverage", $base);
4489 # write_file_table(filehandle, base_dir, overview, testhash, testfnchash,
4490 # testbrhash, fileview, sort_type)
4492 # Write a complete file table. OVERVIEW is a reference to a hash containing
4493 # the following mapping:
4495 # filename -> "lines_found,lines_hit,funcs_found,funcs_hit,page_link,
4498 # TESTHASH is a reference to the following hash:
4500 # filename -> \%testdata
4501 # %testdata: name of test affecting this file -> \%testcount
4502 # %testcount: line number -> execution count for a single test
4504 # Heading of first column is "Filename" if FILEVIEW is true, "Directory name"
4508 sub write_file_table
(*$$$$$$$)
4510 local *HTML_HANDLE
= $_[0];
4511 my $base_dir = $_[1];
4512 my $overview = $_[2];
4513 my $testhash = $_[3];
4514 my $testfnchash = $_[4];
4515 my $testbrhash = $_[5];
4516 my $fileview = $_[6];
4517 my $sort_type = $_[7];
4531 my %affecting_tests;
4538 # Determine HTML code for column headings
4539 if (($base_dir ne "") && $show_details)
4541 my $detailed = keys(%{$testhash});
4543 $file_code = get_file_code
($detailed ?
$HEAD_DETAIL_HIDDEN :
4545 $fileview ?
"Filename" : "Directory",
4546 $sort && $sort_type != $SORT_FILE,
4548 $line_code = get_line_code
($detailed ?
$HEAD_DETAIL_SHOWN :
4549 $HEAD_DETAIL_HIDDEN,
4552 $sort && $sort_type != $SORT_LINE,
4554 $func_code = get_func_code
($detailed ?
$HEAD_DETAIL_HIDDEN :
4557 $sort && $sort_type != $SORT_FUNC,
4559 $br_code = get_br_code
($detailed ?
$HEAD_DETAIL_HIDDEN :
4562 $sort && $sort_type != $SORT_BRANCH,
4565 $file_code = get_file_code
($HEAD_NO_DETAIL,
4566 $fileview ?
"Filename" : "Directory",
4567 $sort && $sort_type != $SORT_FILE,
4569 $line_code = get_line_code
($HEAD_NO_DETAIL, $sort_type, "Line Coverage",
4570 $sort && $sort_type != $SORT_LINE,
4572 $func_code = get_func_code
($HEAD_NO_DETAIL, "Functions",
4573 $sort && $sort_type != $SORT_FUNC,
4575 $br_code = get_br_code
($HEAD_NO_DETAIL, "Branches",
4576 $sort && $sort_type != $SORT_BRANCH,
4579 push(@head_columns, [ $line_code, 3 ]);
4580 push(@head_columns, [ $func_code, 2]) if ($func_coverage);
4581 push(@head_columns, [ $br_code, 2]) if ($br_coverage);
4583 write_file_table_prolog
(*HTML_HANDLE
, $file_code, @head_columns);
4585 foreach $filename (get_sorted_keys
($overview, $sort_type))
4588 ($found, $hit, $fn_found, $fn_hit, $br_found, $br_hit,
4589 $page_link) = @
{$overview->{$filename}};
4592 push(@columns, [$found, $hit, $med_limit, $hi_limit, 1]);
4594 if ($func_coverage) {
4595 push(@columns, [$fn_found, $fn_hit, $fn_med_limit,
4600 push(@columns, [$br_found, $br_hit, $br_med_limit,
4603 write_file_table_entry
(*HTML_HANDLE
, $base_dir, $filename,
4604 $page_link, @columns);
4606 $testdata = $testhash->{$filename};
4607 $testfncdata = $testfnchash->{$filename};
4608 $testbrdata = $testbrhash->{$filename};
4610 # Check whether we should write test specific coverage
4612 if (!($show_details && $testdata)) { next; }
4614 # Filter out those tests that actually affect this file
4615 %affecting_tests = %{ get_affecting_tests
($testdata,
4616 $testfncdata, $testbrdata) };
4618 # Does any of the tests affect this file at all?
4619 if (!%affecting_tests) { next; }
4621 foreach $testname (keys(%affecting_tests))
4624 ($found, $hit, $fn_found, $fn_hit, $br_found, $br_hit) =
4625 split(",", $affecting_tests{$testname});
4627 # Insert link to description of available
4628 if ($test_description{$testname})
4630 $testname = "<a href=\"$base_dir".
4631 "descriptions.$html_ext#$testname\">".
4635 push(@results, [$found, $hit]);
4636 push(@results, [$fn_found, $fn_hit]) if ($func_coverage);
4637 push(@results, [$br_found, $br_hit]) if ($br_coverage);
4638 write_file_table_detail_entry
(*HTML_HANDLE
, $testname,
4643 write_file_table_epilog
(*HTML_HANDLE
);
4648 # get_found_and_hit(hash)
4650 # Return the count for entries (found) and entries with an execution count
4651 # greater than zero (hit) in a hash (linenumber -> execution count) as
4652 # a list (found, hit)
4655 sub get_found_and_hit
($)
4657 my %hash = %{$_[0]};
4665 foreach (keys(%hash))
4668 if ($hash{$_}>0) { $hit++; }
4671 return ($found, $hit);
4676 # get_func_found_and_hit(sumfnccount)
4678 # Return (f_found, f_hit) for sumfnccount
4681 sub get_func_found_and_hit
($)
4683 my ($sumfnccount) = @_;
4688 $fn_found = scalar(keys(%{$sumfnccount}));
4690 foreach $function (keys(%{$sumfnccount})) {
4691 if ($sumfnccount->{$function} > 0) {
4695 return ($fn_found, $fn_hit);
4700 # br_taken_to_num(taken)
4702 # Convert a branch taken value .info format to number format.
4705 sub br_taken_to_num
($)
4709 return 0 if ($taken eq '-');
4715 # br_num_to_taken(taken)
4717 # Convert a branch taken value in number format to .info format.
4720 sub br_num_to_taken
($)
4724 return '-' if ($taken == 0);
4730 # br_taken_add(taken1, taken2)
4732 # Return the result of taken1 + taken2 for 'branch taken' values.
4735 sub br_taken_add
($$)
4739 return $t1 if (!defined($t2));
4740 return $t2 if (!defined($t1));
4741 return $t1 if ($t2 eq '-');
4742 return $t2 if ($t1 eq '-');
4748 # br_taken_sub(taken1, taken2)
4750 # Return the result of taken1 - taken2 for 'branch taken' values. Return 0
4751 # if the result would become negative.
4754 sub br_taken_sub
($$)
4758 return $t1 if (!defined($t2));
4759 return undef if (!defined($t1));
4760 return $t1 if ($t1 eq '-');
4761 return $t1 if ($t2 eq '-');
4762 return 0 if $t2 > $t1;
4768 # br_ivec_len(vector)
4770 # Return the number of entries in the branch coverage vector.
4777 return 0 if (!defined($vec));
4778 return (length($vec) * 8 / $BR_VEC_WIDTH) / $BR_VEC_ENTRIES;
4783 # br_ivec_get(vector, number)
4785 # Return an entry from the branch coverage vector.
4790 my ($vec, $num) = @_;
4794 my $offset = $num * $BR_VEC_ENTRIES;
4796 # Retrieve data from vector
4797 $block = vec($vec, $offset + $BR_BLOCK, $BR_VEC_WIDTH);
4798 $branch = vec($vec, $offset + $BR_BRANCH, $BR_VEC_WIDTH);
4799 $taken = vec($vec, $offset + $BR_TAKEN, $BR_VEC_WIDTH);
4801 # Decode taken value from an integer
4802 $taken = br_num_to_taken
($taken);
4804 return ($block, $branch, $taken);
4809 # br_ivec_push(vector, block, branch, taken)
4811 # Add an entry to the branch coverage vector. If an entry with the same
4812 # branch ID already exists, add the corresponding taken values.
4815 sub br_ivec_push
($$$$)
4817 my ($vec, $block, $branch, $taken) = @_;
4819 my $num = br_ivec_len
($vec);
4822 $vec = "" if (!defined($vec));
4824 # Check if branch already exists in vector
4825 for ($i = 0; $i < $num; $i++) {
4826 my ($v_block, $v_branch, $v_taken) = br_ivec_get
($vec, $i);
4828 next if ($v_block != $block || $v_branch != $branch);
4831 $taken = br_taken_add
($taken, $v_taken);
4835 $offset = $i * $BR_VEC_ENTRIES;
4836 $taken = br_taken_to_num
($taken);
4839 vec($vec, $offset + $BR_BLOCK, $BR_VEC_WIDTH) = $block;
4840 vec($vec, $offset + $BR_BRANCH, $BR_VEC_WIDTH) = $branch;
4841 vec($vec, $offset + $BR_TAKEN, $BR_VEC_WIDTH) = $taken;
4848 # get_br_found_and_hit(sumbrcount)
4850 # Return (br_found, br_hit) for sumbrcount
4853 sub get_br_found_and_hit
($)
4855 my ($sumbrcount) = @_;
4860 foreach $line (keys(%{$sumbrcount})) {
4861 my $brdata = $sumbrcount->{$line};
4863 my $num = br_ivec_len
($brdata);
4865 for ($i = 0; $i < $num; $i++) {
4868 (undef, undef, $taken) = br_ivec_get
($brdata, $i);
4871 $br_hit++ if ($taken ne "-" && $taken > 0);
4875 return ($br_found, $br_hit);
4880 # get_affecting_tests(testdata, testfncdata, testbrdata)
4882 # HASHREF contains a mapping filename -> (linenumber -> exec count). Return
4883 # a hash containing mapping filename -> "lines found, lines hit" for each
4884 # filename which has a nonzero hit count.
4887 sub get_affecting_tests
($$$)
4889 my ($testdata, $testfncdata, $testbrdata) = @_;
4902 foreach $testname (keys(%{$testdata}))
4904 # Get (line number -> count) hash for this test case
4905 $testcount = $testdata->{$testname};
4906 $testfnccount = $testfncdata->{$testname};
4907 $testbrcount = $testbrdata->{$testname};
4910 ($found, $hit) = get_found_and_hit
($testcount);
4911 ($fn_found, $fn_hit) = get_func_found_and_hit
($testfnccount);
4912 ($br_found, $br_hit) = get_br_found_and_hit
($testbrcount);
4916 $result{$testname} = "$found,$hit,$fn_found,$fn_hit,".
4917 "$br_found,$br_hit";
4925 sub get_hash_reverse
($)
4930 foreach (keys(%{$hash})) {
4931 $result{$hash->{$_}} = $_;
4938 # write_source(filehandle, source_filename, count_data, checksum_data,
4939 # converted_data, func_data, sumbrcount)
4941 # Write an HTML view of a source code file. Returns a list containing
4942 # data as needed by gen_png().
4947 sub write_source
($$$$$$$)
4949 local *HTML_HANDLE
= $_[0];
4950 local *SOURCE_HANDLE
;
4951 my $source_filename = $_[1];
4955 my $checkdata = $_[3];
4956 my $converted = $_[4];
4957 my $funcdata = $_[5];
4958 my $sumbrcount = $_[6];
4959 my $datafunc = get_hash_reverse
($funcdata);
4964 %count_data = %{$_[2]};
4967 open(SOURCE_HANDLE
, "<".$source_filename)
4968 or die("ERROR: cannot open $source_filename for reading!\n");
4970 write_source_prolog
(*HTML_HANDLE
);
4972 for ($line_number = 1; <SOURCE_HANDLE
> ; $line_number++)
4976 # Also remove CR from line-end
4979 # Source code matches coverage data?
4980 if (defined($checkdata->{$line_number}) &&
4981 ($checkdata->{$line_number} ne md5_base64
($_)))
4983 die("ERROR: checksum mismatch at $source_filename:".
4989 if (($line_number - 1) % $nav_resolution == 0) {
4993 if ($func_coverage) {
4994 if ($line_number == 1) {
4996 } elsif (defined($datafunc->{$line_number +
5002 write_source_line
(HTML_HANDLE
, $line_number,
5003 $_, $count_data{$line_number},
5004 $converted->{$line_number},
5005 $sumbrcount->{$line_number}, $add_anchor));
5008 close(SOURCE_HANDLE
);
5009 write_source_epilog
(*HTML_HANDLE
);
5014 sub funcview_get_func_code
($$$)
5016 my ($name, $base, $type) = @_;
5020 if ($sort && $type == 1) {
5021 $link = "$name.func.$html_ext";
5023 $result = "Function Name";
5024 $result .= get_sort_code
($link, "Sort by function name", $base);
5029 sub funcview_get_count_code
($$$)
5031 my ($name, $base, $type) = @_;
5035 if ($sort && $type == 0) {
5036 $link = "$name.func-sort-c.$html_ext";
5038 $result = "Hit count";
5039 $result .= get_sort_code
($link, "Sort by hit count", $base);
5045 # funcview_get_sorted(funcdata, sumfncdata, sort_type)
5047 # Depending on the value of sort_type, return a list of functions sorted
5048 # by name (type 0) or by the associated call count (type 1).
5051 sub funcview_get_sorted
($$$)
5053 my ($funcdata, $sumfncdata, $type) = @_;
5056 return sort(keys(%{$funcdata}));
5058 return sort({$sumfncdata->{$b} <=> $sumfncdata->{$a}}
5059 keys(%{$sumfncdata}));
5063 # write_function_table(filehandle, source_file, sumcount, funcdata,
5064 # sumfnccount, testfncdata, sumbrcount, testbrdata,
5065 # base_name, base_dir, sort_type)
5067 # Write an HTML table listing all functions in a source file, including
5068 # also function call counts and line coverages inside of each function.
5073 sub write_function_table
(*$$$$$$$$$$)
5075 local *HTML_HANDLE
= $_[0];
5077 my $sumcount = $_[2];
5078 my $funcdata = $_[3];
5079 my $sumfncdata = $_[4];
5080 my $testfncdata = $_[5];
5081 my $sumbrcount = $_[6];
5082 my $testbrdata = $_[7];
5090 # Get HTML code for headings
5091 $func_code = funcview_get_func_code
($name, $base, $type);
5092 $count_code = funcview_get_count_code
($name, $base, $type);
5093 write_html
(*HTML_HANDLE
, <<END_OF_HTML)
5095 <table width="60%" cellpadding=1 cellspacing=1 border=0>
5096 <tr><td><br></td></tr>
5098 <td width="80%" class="tableHead">$func_code</td>
5099 <td width="20%" class="tableHead">$count_code</td>
5104 # Get a sorted table
5105 foreach $func (funcview_get_sorted($funcdata, $sumfncdata, $type)) {
5106 if (!defined($funcdata->{$func}))
5111 my $startline = $funcdata->{$func} - $func_offset;
5113 my $count = $sumfncdata->{$name};
5116 # Demangle C++ function names if requested
5117 if ($demangle_cpp) {
5118 $name = `c++filt "$name"`;
5121 # Escape any remaining special characters
5122 $name = escape_html($name);
5123 if ($startline < 1) {
5127 $countstyle = "coverFnLo";
5129 $countstyle = "coverFnHi";
5132 write_html(*HTML_HANDLE, <<END_OF_HTML
)
5134 <td
class="coverFn"><a href
="$source#$startline">$name</a></td
>
5135 <td
class="$countstyle">$count</td
>
5140 write_html
(*HTML_HANDLE
, <<END_OF_HTML)
5150 # info(printf_parameter)
5152 # Use printf to write PRINTF_PARAMETER to stdout only when the $quiet flag
5167 # subtract_counts(data_ref, base_ref)
5170 sub subtract_counts($$)
5172 my %data = %{$_[0]};
5173 my %base = %{$_[1]};
5180 foreach $line (keys(%data))
5183 $data_count = $data{$line};
5184 $base_count = $base{$line};
5186 if (defined($base_count))
5188 $data_count -= $base_count;
5190 # Make sure we don't get negative numbers
5191 if ($data_count<0) { $data_count = 0; }
5194 $data{$line} = $data_count;
5195 if ($data_count > 0) { $hit++; }
5198 return (\%data, $found, $hit);
5203 # subtract_fnccounts(data, base)
5205 # Subtract function call counts found in base from those in data.
5206 # Return (data, f_found, f_hit).
5209 sub subtract_fnccounts($$)
5219 %data = %{$_[0]} if (defined($_[0]));
5220 %base = %{$_[1]} if (defined($_[1]));
5221 foreach $func (keys(%data)) {
5223 $data_count = $data{$func};
5224 $base_count = $base{$func};
5226 if (defined($base_count)) {
5227 $data_count -= $base_count;
5229 # Make sure we don't get negative numbers
5230 if ($data_count < 0) {
5235 $data{$func} = $data_count;
5236 if ($data_count > 0) {
5241 return (\%data, $fn_found, $fn_hit);
5246 # apply_baseline(data_ref, baseline_ref)
5248 # Subtract the execution counts found in the baseline hash referenced by
5249 # BASELINE_REF from actual data in DATA_REF.
5252 sub apply_baseline($$)
5254 my %data_hash = %{$_[0]};
5255 my %base_hash = %{$_[1]};
5262 my $data_testfncdata;
5263 my $data_testbrdata;
5265 my $data_testfnccount;
5266 my $data_testbrcount;
5269 my $base_sumfnccount;
5270 my $base_sumbrcount;
5282 foreach $filename (keys(%data_hash))
5284 # Get data set for data and baseline
5285 $data = $data_hash{$filename};
5286 $base = $base_hash{$filename};
5288 # Skip data entries for which no base entry exists
5289 if (!defined($base))
5294 # Get set entries for data and baseline
5295 ($data_testdata, undef, $data_funcdata, $data_checkdata,
5296 $data_testfncdata, undef, $data_testbrdata) =
5297 get_info_entry($data);
5298 (undef, $base_count, undef, $base_checkdata, undef,
5299 $base_sumfnccount, undef, $base_sumbrcount) =
5300 get_info_entry($base);
5302 # Check for compatible checksums
5303 merge_checksums($data_checkdata, $base_checkdata, $filename);
5305 # sumcount has to be calculated anew
5310 # For each test case, subtract test specific counts
5311 foreach $testname (keys(%{$data_testdata}))
5313 # Get counts of both data and baseline
5314 $data_count = $data_testdata->{$testname};
5315 $data_testfnccount = $data_testfncdata->{$testname};
5316 $data_testbrcount = $data_testbrdata->{$testname};
5318 ($data_count, undef, $hit) =
5319 subtract_counts($data_count, $base_count);
5320 ($data_testfnccount) =
5321 subtract_fnccounts($data_testfnccount,
5323 ($data_testbrcount) =
5324 combine_brcount($data_testbrcount,
5325 $base_sumbrcount, $BR_SUB);
5328 # Check whether this test case did hit any line at all
5331 # Write back resulting hash
5332 $data_testdata->{$testname} = $data_count;
5333 $data_testfncdata->{$testname} =
5335 $data_testbrdata->{$testname} =
5340 # Delete test case which did not impact this
5342 delete($data_testdata->{$testname});
5343 delete($data_testfncdata->{$testname});
5344 delete($data_testbrdata->{$testname});
5347 # Add counts to sum of counts
5348 ($sumcount, $found, $hit) =
5349 add_counts($sumcount, $data_count);
5350 ($sumfnccount, $fn_found, $fn_hit) =
5351 add_fnccount($sumfnccount, $data_testfnccount);
5352 ($sumbrcount, $br_found, $br_hit) =
5353 combine_brcount($sumbrcount, $data_testbrcount,
5357 # Write back resulting entry
5358 set_info_entry($data, $data_testdata, $sumcount, $data_funcdata,
5359 $data_checkdata, $data_testfncdata, $sumfnccount,
5360 $data_testbrdata, $sumbrcount, $found, $hit,
5361 $fn_found, $fn_hit, $br_found, $br_hit);
5363 $data_hash{$filename} = $data;
5366 return (\%data_hash);
5371 # remove_unused_descriptions()
5373 # Removes all test descriptions from the global hash %test_description which
5374 # are not present in %info_data.
5377 sub remove_unused_descriptions()
5379 my $filename; # The current filename
5380 my %test_list; # Hash containing found test names
5381 my $test_data; # Reference to hash test_name -> count_data
5382 my $before; # Initial number of descriptions
5383 my $after; # Remaining number of descriptions
5385 $before = scalar(keys(%test_description));
5387 foreach $filename (keys(%info_data))
5389 ($test_data) = get_info_entry($info_data{$filename});
5390 foreach (keys(%{$test_data}))
5392 $test_list{$_} = "";
5396 # Remove descriptions for tests which are not in our list
5397 foreach (keys(%test_description))
5399 if (!defined($test_list{$_}))
5401 delete($test_description{$_});
5405 $after = scalar(keys(%test_description));
5406 if ($after < $before)
5408 info("Removed ".($before - $after).
5409 " unused descriptions, $after remaining.\n");
5415 # apply_prefix(filename, prefix)
5417 # If FILENAME begins with PREFIX, remove PREFIX from FILENAME and return
5418 # resulting string, otherwise return FILENAME.
5421 sub apply_prefix($$)
5423 my $filename = $_[0];
5426 if (defined($prefix) && ($prefix ne ""))
5428 if ($filename =~ /^\Q$prefix\E\/(.*)$/)
5430 return substr($filename, length($prefix) + 1);
5439 # system_no_output(mode, parameters)
5441 # Call an external program using PARAMETERS while suppressing depending on
5442 # the value of MODE:
5444 # MODE & 1: suppress STDOUT
5445 # MODE & 2: suppress STDERR
5447 # Return 0 on success, non-zero otherwise.
5450 sub system_no_output($@)
5457 # Save old stdout and stderr handles
5458 ($mode & 1) && open(OLD_STDOUT, ">>&STDOUT");
5459 ($mode & 2) && open(OLD_STDERR, ">>&STDERR");
5461 # Redirect to /dev/null
5462 ($mode & 1) && open(STDOUT, ">/dev/null");
5463 ($mode & 2) && open(STDERR, ">/dev/null");
5468 # Close redirected handles
5469 ($mode & 1) && close(STDOUT);
5470 ($mode & 2) && close(STDERR);
5472 # Restore old handles
5473 ($mode & 1) && open(STDOUT, ">>&OLD_STDOUT");
5474 ($mode & 2) && open(STDERR, ">>&OLD_STDERR");
5481 # read_config(filename)
5483 # Read configuration file FILENAME and return a reference to a hash containing
5484 # all valid key=value pairs found.
5489 my $filename = $_[0];
5495 if (!open(HANDLE, "<$filename"))
5497 warn("WARNING: cannot read configuration file $filename\n");
5505 # Remove leading blanks
5507 # Remove trailing blanks
5510 ($key, $value) = split(/\s*=\s*/, $_, 2);
5511 if (defined($key) && defined($value))
5513 $result{$key} = $value;
5517 warn("WARNING: malformed statement in line $. ".
5518 "of configuration file $filename\n");
5529 # REF is a reference to a hash containing the following mapping:
5531 # key_string => var_ref
5533 # where KEY_STRING is a keyword and VAR_REF is a reference to an associated
5534 # variable. If the global configuration hash CONFIG contains a value for
5535 # keyword KEY_STRING, VAR_REF will be assigned the value for that keyword.
5542 foreach (keys(%{$ref}))
5544 if (defined($config->{$_}))
5546 ${$ref->{$_}} = $config->{$_};
5553 # get_html_prolog(FILENAME)
5555 # If FILENAME is defined, return contents of file. Otherwise return default
5556 # HTML prolog. Die on error.
5559 sub get_html_prolog($)
5561 my $filename = $_[0];
5564 if (defined($filename))
5568 open(HANDLE, "<".$filename)
5569 or die("ERROR: cannot open html prolog $filename!\n");
5578 $result = <<END_OF_HTML
5579 <!DOCTYPE HTML PUBLIC
"-//W3C//DTD HTML 4.01 Transitional//EN">
5584 <meta http
-equiv
="Content-Type" content
="text/html; charset=ISO-8859-1">
5585 <title
>\
@pagetitle\@
</title
>
5586 <link rel
="stylesheet" type
="text/css" href
="\@basedir\@gcov.css">
5600 # get_html_epilog(FILENAME)
5602 # If FILENAME is defined, return contents of file. Otherwise return default
5603 # HTML epilog. Die on error.
5605 sub get_html_epilog
($)
5607 my $filename = $_[0];
5610 if (defined($filename))
5614 open(HANDLE
, "<".$filename)
5615 or die("ERROR: cannot open html epilog $filename!\n");
5624 $result = <<END_OF_HTML
5640 warn("$tool_name: $msg");
5647 die("$tool_name: $msg");