3 # Copyright (c) International Business Machines Corp., 2002,2012
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.10';
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 # Error classes which users may specify to ignore during processing
159 our $ERROR_SOURCE = 0;
161 "source" => $ERROR_SOURCE,
164 # Data related prototypes
169 sub process_file
($$$);
171 sub read_info_file
($);
172 sub get_info_entry
($);
173 sub set_info_entry
($$$$$$$$$;$$$$$$);
175 sub shorten_prefix
($);
177 sub get_relative_base_path
($);
178 sub read_testfile
($);
179 sub get_date_string
();
180 sub create_sub_dir
($);
181 sub subtract_counts
($$);
183 sub apply_baseline
($$);
184 sub remove_unused_descriptions
();
185 sub get_found_and_hit
($);
186 sub get_affecting_tests
($$$);
187 sub combine_info_files
($$);
188 sub merge_checksums
($$$);
189 sub combine_info_entries
($$$);
190 sub apply_prefix
($$);
191 sub system_no_output
($@
);
194 sub get_html_prolog
($);
195 sub get_html_epilog
($);
196 sub write_dir_page
($$$$$$$$$$$$$$$$$);
197 sub classify_rate
($$$$);
198 sub br_taken_add
($$);
199 sub br_taken_sub
($$);
202 sub br_ivec_push
($$$$);
203 sub combine_brcount
($$$);
204 sub get_br_found_and_hit
($);
207 sub parse_ignore_errors
(@
);
211 # HTML related prototypes
213 sub get_bar_graph_code
($$$);
215 sub write_png_files
();
216 sub write_htaccess_file
();
217 sub write_css_file
();
218 sub write_description_file
($$$$$$$);
219 sub write_function_table
(*$$$$$$$$$$);
222 sub write_html_prolog
(*$$);
223 sub write_html_epilog
(*$;$);
225 sub write_header
(*$$$$$$$$$$);
226 sub write_header_prolog
(*$);
227 sub write_header_line
(*@
);
228 sub write_header_epilog
(*$);
230 sub write_file_table
(*$$$$$$$);
231 sub write_file_table_prolog
(*$@
);
232 sub write_file_table_entry
(*$$$@
);
233 sub write_file_table_detail_entry
(*$@
);
234 sub write_file_table_epilog
(*);
236 sub write_test_table_prolog
(*$);
237 sub write_test_table_entry
(*$$);
238 sub write_test_table_epilog
(*);
240 sub write_source
($$$$$$$);
241 sub write_source_prolog
(*);
242 sub write_source_line
(*$$$$$$);
243 sub write_source_epilog
(*);
245 sub write_frameset
(*$$$);
246 sub write_overview_line
(*$$$);
247 sub write_overview
(*$$$$);
249 # External prototype (defined in genpng)
253 # Global variables & initialization
254 our %info_data; # Hash containing all data from .info file
255 our $dir_prefix; # Prefix to remove from all sub directories
256 our %test_description; # Hash containing test descriptions if available
257 our $date = get_date_string
();
259 our @info_filenames; # List of .info files to use as data source
260 our $test_title; # Title for output as written to each page header
261 our $output_directory; # Name of directory in which to store output
262 our $base_filename; # Optional name of file containing baseline data
263 our $desc_filename; # Name of file containing test descriptions
264 our $css_filename; # Optional name of external stylesheet file to use
265 our $quiet; # If set, suppress information messages
266 our $help; # Help option flag
267 our $version; # Version option flag
268 our $show_details; # If set, generate detailed directory view
269 our $no_prefix; # If set, do not remove filename prefix
270 our $func_coverage; # If set, generate function coverage statistics
271 our $no_func_coverage; # Disable func_coverage
272 our $br_coverage; # If set, generate branch coverage statistics
273 our $no_br_coverage; # Disable br_coverage
274 our $sort = 1; # If set, provide directory listings with sorted entries
275 our $no_sort; # Disable sort
276 our $frames; # If set, use frames for source code view
277 our $keep_descriptions; # If set, do not remove unused test case descriptions
278 our $no_sourceview; # If set, do not create a source code view for each file
279 our $highlight; # If set, highlight lines covered by converted data only
280 our $legend; # If set, include legend in output
281 our $tab_size = 8; # Number of spaces to use in place of tab
282 our $config; # Configuration file contents
283 our $html_prolog_file; # Custom HTML prolog file (up to and including <body>)
284 our $html_epilog_file; # Custom HTML epilog file (from </body> onwards)
285 our $html_prolog; # Actual HTML prolog
286 our $html_epilog; # Actual HTML epilog
287 our $html_ext = "html"; # Extension for generated HTML files
288 our $html_gzip = 0; # Compress with gzip
289 our $demangle_cpp = 0; # Demangle C++ function names
290 our @opt_ignore_errors; # Ignore certain error classes during processing
292 our $opt_config_file; # User-specified configuration file location
294 our $charset = "UTF-8"; # Default charset for HTML pages
295 our @fileview_sortlist;
296 our @fileview_sortname = ("", "-sort-l", "-sort-f", "-sort-b");
297 our @funcview_sortlist;
298 our @rate_name = ("Lo", "Med", "Hi");
299 our @rate_png = ("ruby.png", "amber.png", "emerald.png");
300 our $lcov_func_coverage = 1;
301 our $lcov_branch_coverage = 0;
303 our $cwd = `pwd`; # Current working directory
305 our $tool_dir = dirname
($0); # Directory where genhtml tool is installed
312 $SIG{__WARN__
} = \
&warn_handler
;
313 $SIG{__DIE__
} = \
&die_handler
;
315 # Prettify version string
316 $lcov_version =~ s/\$\s*Revision\s*:?\s*(\S+)\s*\$/$1/;
318 # Add current working directory if $tool_dir is not already an absolute path
319 if (! ($tool_dir =~ /^\/(.*)$/))
321 $tool_dir = "$cwd/$tool_dir";
324 # Check command line for a configuration file name
325 Getopt
::Long
::Configure
("pass_through", "no_auto_abbrev");
326 GetOptions
("config-file=s" => \
$opt_config_file,
327 "rc=s%" => \
%opt_rc);
328 Getopt
::Long
::Configure
("default");
330 # Read configuration file if available
331 if (defined($opt_config_file)) {
332 $config = read_config
($opt_config_file);
333 } elsif (defined($ENV{"HOME"}) && (-r
$ENV{"HOME"}."/.lcovrc"))
335 $config = read_config
($ENV{"HOME"}."/.lcovrc");
337 elsif (-r
"/etc/lcovrc")
339 $config = read_config
("/etc/lcovrc");
342 if ($config || %opt_rc)
344 # Copy configuration file and --rc values to variables
346 "genhtml_css_file" => \
$css_filename,
347 "genhtml_hi_limit" => \
$hi_limit,
348 "genhtml_med_limit" => \
$med_limit,
349 "genhtml_line_field_width" => \
$line_field_width,
350 "genhtml_overview_width" => \
$overview_width,
351 "genhtml_nav_resolution" => \
$nav_resolution,
352 "genhtml_nav_offset" => \
$nav_offset,
353 "genhtml_keep_descriptions" => \
$keep_descriptions,
354 "genhtml_no_prefix" => \
$no_prefix,
355 "genhtml_no_source" => \
$no_sourceview,
356 "genhtml_num_spaces" => \
$tab_size,
357 "genhtml_highlight" => \
$highlight,
358 "genhtml_legend" => \
$legend,
359 "genhtml_html_prolog" => \
$html_prolog_file,
360 "genhtml_html_epilog" => \
$html_epilog_file,
361 "genhtml_html_extension" => \
$html_ext,
362 "genhtml_html_gzip" => \
$html_gzip,
363 "genhtml_function_hi_limit" => \
$fn_hi_limit,
364 "genhtml_function_med_limit" => \
$fn_med_limit,
365 "genhtml_function_coverage" => \
$func_coverage,
366 "genhtml_branch_hi_limit" => \
$br_hi_limit,
367 "genhtml_branch_med_limit" => \
$br_med_limit,
368 "genhtml_branch_coverage" => \
$br_coverage,
369 "genhtml_branch_field_width" => \
$br_field_width,
370 "genhtml_sort" => \
$sort,
371 "genhtml_charset" => \
$charset,
372 "lcov_function_coverage" => \
$lcov_func_coverage,
373 "lcov_branch_coverage" => \
$lcov_branch_coverage,
377 # Copy related values if not specified
378 $fn_hi_limit = $hi_limit if (!defined($fn_hi_limit));
379 $fn_med_limit = $med_limit if (!defined($fn_med_limit));
380 $br_hi_limit = $hi_limit if (!defined($br_hi_limit));
381 $br_med_limit = $med_limit if (!defined($br_med_limit));
382 $func_coverage = $lcov_func_coverage if (!defined($func_coverage));
383 $br_coverage = $lcov_branch_coverage if (!defined($br_coverage));
385 # Parse command line options
386 if (!GetOptions
("output-directory|o=s" => \
$output_directory,
387 "title|t=s" => \
$test_title,
388 "description-file|d=s" => \
$desc_filename,
389 "keep-descriptions|k" => \
$keep_descriptions,
390 "css-file|c=s" => \
$css_filename,
391 "baseline-file|b=s" => \
$base_filename,
392 "prefix|p=s" => \
$dir_prefix,
393 "num-spaces=i" => \
$tab_size,
394 "no-prefix" => \
$no_prefix,
395 "no-sourceview" => \
$no_sourceview,
396 "show-details|s" => \
$show_details,
397 "frames|f" => \
$frames,
398 "highlight" => \
$highlight,
399 "legend" => \
$legend,
400 "quiet|q" => \
$quiet,
401 "help|h|?" => \
$help,
402 "version|v" => \
$version,
403 "html-prolog=s" => \
$html_prolog_file,
404 "html-epilog=s" => \
$html_epilog_file,
405 "html-extension=s" => \
$html_ext,
406 "html-gzip" => \
$html_gzip,
407 "function-coverage" => \
$func_coverage,
408 "no-function-coverage" => \
$no_func_coverage,
409 "branch-coverage" => \
$br_coverage,
410 "no-branch-coverage" => \
$no_br_coverage,
412 "no-sort" => \
$no_sort,
413 "demangle-cpp" => \
$demangle_cpp,
414 "ignore-errors=s" => \
@opt_ignore_errors,
415 "config-file=s" => \
$opt_config_file,
419 print(STDERR
"Use $tool_name --help to get usage information\n");
423 if ($no_func_coverage) {
426 if ($no_br_coverage) {
436 @info_filenames = @ARGV;
438 # Check for help option
441 print_usage
(*STDOUT
);
445 # Check for version option
448 print("$tool_name: $lcov_version\n");
452 # Determine which errors the user wants us to ignore
453 parse_ignore_errors
(@opt_ignore_errors);
455 # Check for info filename
456 if (!@info_filenames)
458 die("No filename specified\n".
459 "Use $tool_name --help to get usage information\n");
462 # Generate a title if none is specified
465 if (scalar(@info_filenames) == 1)
467 # Only one filename specified, use it as title
468 $test_title = basename
($info_filenames[0]);
472 # More than one filename specified, used default title
473 $test_title = "unnamed";
477 # Make sure css_filename is an absolute path (in case we're changing
481 if (!($css_filename =~ /^\/(.*)$/))
483 $css_filename = $cwd."/".$css_filename;
487 # Make sure tab_size is within valid range
490 print(STDERR
"ERROR: invalid number of spaces specified: ".
495 # Get HTML prolog and epilog
496 $html_prolog = get_html_prolog
($html_prolog_file);
497 $html_epilog = get_html_epilog
($html_epilog_file);
499 # Issue a warning if --no-sourceview is enabled together with --frames
500 if ($no_sourceview && defined($frames))
502 warn("WARNING: option --frames disabled because --no-sourceview ".
507 # Issue a warning if --no-prefix is enabled together with --prefix
508 if ($no_prefix && defined($dir_prefix))
510 warn("WARNING: option --prefix disabled because --no-prefix was ".
515 @fileview_sortlist = ($SORT_FILE);
516 @funcview_sortlist = ($SORT_FILE);
519 push(@fileview_sortlist, $SORT_LINE);
520 push(@fileview_sortlist, $SORT_FUNC) if ($func_coverage);
521 push(@fileview_sortlist, $SORT_BRANCH) if ($br_coverage);
522 push(@funcview_sortlist, $SORT_LINE);
527 # Include genpng code needed for overview image generation
528 do("$tool_dir/genpng");
531 # Ensure that the c++filt tool is available when using --demangle-cpp
534 if (system_no_output
(3, "c++filt", "--version")) {
535 die("ERROR: could not find c++filt tool needed for ".
540 # Make sure output_directory exists, create it if necessary
541 if ($output_directory)
543 stat($output_directory);
547 create_sub_dir
($output_directory);
559 # print_usage(handle)
561 # Print usage information.
566 local *HANDLE
= $_[0];
568 print(HANDLE
<<END_OF_USAGE);
569 Usage: $tool_name [OPTIONS] INFOFILE(S)
571 Create HTML output for coverage data found in INFOFILE. Note that INFOFILE
572 may also be a list of filenames.
575 -h, --help Print this help, then exit
576 -v, --version Print version number, then exit
577 -q, --quiet Do not print progress messages
578 --config-file FILENAME Specify configuration file location
579 --rc SETTING=VALUE Override configuration file setting
580 --ignore-errors ERRORS Continue after ERRORS (source)
583 -o, --output-directory OUTDIR Write HTML output to OUTDIR
584 -s, --show-details Generate detailed directory view
585 -d, --description-file DESCFILE Read test case descriptions from DESCFILE
586 -k, --keep-descriptions Do not remove unused test descriptions
587 -b, --baseline-file BASEFILE Use BASEFILE as baseline file
588 -p, --prefix PREFIX Remove PREFIX from all directory names
589 --no-prefix Do not remove prefix from directory names
590 --(no-)function-coverage Enable (disable) function coverage display
591 --(no-)branch-coverage Enable (disable) branch coverage display
594 -f, --frames Use HTML frames for source code view
595 -t, --title TITLE Display TITLE in header of all pages
596 -c, --css-file CSSFILE Use external style sheet file CSSFILE
597 --no-source Do not create source code view
598 --num-spaces NUM Replace tabs with NUM spaces in source view
599 --highlight Highlight lines with converted-only data
600 --legend Include color legend in HTML output
601 --html-prolog FILE Use FILE as HTML prolog for generated pages
602 --html-epilog FILE Use FILE as HTML epilog for generated pages
603 --html-extension EXT Use EXT as filename extension for pages
604 --html-gzip Use gzip to compress HTML
605 --(no-)sort Enable (disable) sorted coverage views
606 --demangle-cpp Demangle C++ function names
608 For more information see: $lcov_url
615 # get_rate(found, hit)
617 # Return a relative value for the specified found&hit values
618 # which is used for sorting the corresponding entries in a
624 my ($found, $hit) = @_;
629 return int($hit * 1000 / $found) * 10 + 2 - (1 / $found);
634 # get_overall_line(found, hit, name_singular, name_plural)
636 # Return a string containing overall information for the specified
640 sub get_overall_line($$$$)
642 my ($found, $hit, $name_sn, $name_pl) = @_;
645 return "no data found" if (!defined($found) || $found == 0);
646 $name = ($found == 1) ? $name_sn : $name_pl;
647 return rate($hit, $found, "% ($hit of $found $name)");
652 # print_overall_rate(ln_do, ln_found, ln_hit, fn_do, fn_found, fn_hit, br_do
655 # Print overall coverage rates for the specified coverage types.
658 sub print_overall_rate($$$$$$$$$)
660 my ($ln_do, $ln_found, $ln_hit, $fn_do, $fn_found, $fn_hit,
661 $br_do, $br_found, $br_hit) = @_;
663 info("Overall coverage rate:\n");
664 info(" lines......: %s\n",
665 get_overall_line($ln_found, $ln_hit, "line", "lines"))
667 info(" functions..: %s\n",
668 get_overall_line($fn_found, $fn_hit, "function", "functions"))
670 info(" branches...: %s\n",
671 get_overall_line($br_found, $br_hit, "branch", "branches"))
679 # Generate a set of HTML pages from contents of .info file INFO_FILENAME.
680 # Files will be written to the current directory. If provided, test case
681 # descriptions will be read from .tests file TEST_FILENAME and included
698 my $overall_found = 0;
700 my $total_fn_found = 0;
701 my $total_fn_hit = 0;
702 my $total_br_found = 0;
703 my $total_br_hit = 0;
709 # Read in all specified .info files
710 foreach (@info_filenames)
712 %new_info = %{read_info_file($_)};
714 # Combine %new_info with %info_data
715 %info_data = %{combine_info_files(\%info_data, \%new_info)};
718 info("Found %d entries.\n", scalar(keys(%info_data)));
720 # Read and apply baseline data if specified
724 info("Reading baseline file $base_filename\n");
725 %base_data = %{read_info_file($base_filename)};
726 info("Found %d entries.\n", scalar(keys(%base_data)));
729 info("Subtracting baseline data.\n");
730 %info_data = %{apply_baseline(\%info_data, \%base_data)};
733 @dir_list = get_dir_list(keys(%info_data));
737 # User requested that we leave filenames alone
738 info("User asked not to remove filename prefix\n");
740 elsif (!defined($dir_prefix))
742 # Get prefix common to most directories in list
743 $dir_prefix = get_prefix(1, keys(%info_data));
747 info("Found common filename prefix \"$dir_prefix\"\n");
751 info("No common filename prefix found!\n");
757 info("Using user-specified filename prefix \"".
761 # Read in test description file if specified
764 info("Reading test description file $desc_filename\n");
765 %test_description = %{read_testfile($desc_filename)};
767 # Remove test descriptions which are not referenced
768 # from %info_data if user didn't tell us otherwise
769 if (!$keep_descriptions)
771 remove_unused_descriptions();
775 # Change to output directory if specified
776 if ($output_directory)
778 chdir($output_directory)
779 or die("ERROR: cannot change to directory ".
780 "$output_directory!\n");
783 info("Writing .css and .png files.\n");
789 info("Writing .htaccess file.\n");
790 write_htaccess_file();
793 info("Generating output.\n");
795 # Process each subdirectory and collect overview information
796 foreach $dir_name (@dir_list)
798 ($lines_found, $lines_hit, $fn_found, $fn_hit,
800 = process_dir($dir_name);
802 # Handle files in root directory gracefully
803 $dir_name = "root" if ($dir_name eq "");
805 # Remove prefix if applicable
806 if (!$no_prefix && $dir_prefix)
808 # Match directory names beginning with $dir_prefix
809 $dir_name = apply_prefix($dir_name, $dir_prefix);
812 # Generate name for directory overview HTML page
813 if ($dir_name =~ /^\/(.*)$/)
815 $link_name = substr($dir_name, 1)."/index.$html_ext";
819 $link_name = $dir_name."/index.$html_ext";
822 $overview{$dir_name} = [$lines_found, $lines_hit, $fn_found,
823 $fn_hit, $br_found, $br_hit, $link_name,
824 get_rate($lines_found, $lines_hit),
825 get_rate($fn_found, $fn_hit),
826 get_rate($br_found, $br_hit)];
827 $overall_found += $lines_found;
828 $overall_hit += $lines_hit;
829 $total_fn_found += $fn_found;
830 $total_fn_hit += $fn_hit;
831 $total_br_found += $br_found;
832 $total_br_hit += $br_hit;
835 # Generate overview page
836 info("Writing directory view page.\n");
838 # Create sorted pages
839 foreach (@fileview_sortlist) {
840 write_dir_page($fileview_sortname[$_], ".", "", $test_title,
841 undef, $overall_found, $overall_hit,
842 $total_fn_found, $total_fn_hit, $total_br_found,
843 $total_br_hit, \%overview, {}, {}, {}, 0, $_);
846 # Check if there are any test case descriptions to write out
847 if (%test_description)
849 info("Writing test case description file.\n");
850 write_description_file( \%test_description,
851 $overall_found, $overall_hit,
852 $total_fn_found, $total_fn_hit,
853 $total_br_found, $total_br_hit);
856 print_overall_rate(1, $overall_found, $overall_hit,
857 $func_coverage, $total_fn_found, $total_fn_hit,
858 $br_coverage, $total_br_found, $total_br_hit);
864 # html_create(handle, filename)
870 my $filename = $_[1];
874 open($handle, "|-", "gzip -c >'$filename'")
875 or die("ERROR: cannot open $filename for writing ".
880 open($handle, ">", $filename)
881 or die("ERROR: cannot open $filename for writing!\n");
885 sub write_dir_page($$$$$$$$$$$$$$$$$)
887 my ($name, $rel_dir, $base_dir, $title, $trunc_dir, $overall_found,
888 $overall_hit, $total_fn_found, $total_fn_hit, $total_br_found,
889 $total_br_hit, $overview, $testhash, $testfnchash, $testbrhash,
890 $view_type, $sort_type) = @_;
892 # Generate directory overview page including details
893 html_create(*HTML_HANDLE, "$rel_dir/index$name.$html_ext");
894 if (!defined($trunc_dir)) {
897 $title .= " - " if ($trunc_dir ne "");
898 write_html_prolog(*HTML_HANDLE, $base_dir, "LCOV - $title$trunc_dir");
899 write_header(*HTML_HANDLE, $view_type, $trunc_dir, $rel_dir,
900 $overall_found, $overall_hit, $total_fn_found,
901 $total_fn_hit, $total_br_found, $total_br_hit, $sort_type);
902 write_file_table(*HTML_HANDLE, $base_dir, $overview, $testhash,
903 $testfnchash, $testbrhash, $view_type, $sort_type);
904 write_html_epilog(*HTML_HANDLE, $base_dir);
910 # process_dir(dir_name)
917 my $rel_dir = $abs_dir;
929 my $total_fn_found=0;
931 my $total_br_found = 0;
932 my $total_br_hit = 0;
944 # Remove prefix if applicable
947 # Match directory name beginning with $dir_prefix
948 $rel_dir = apply_prefix($rel_dir, $dir_prefix);
951 $trunc_dir = $rel_dir;
954 if ($rel_dir =~ /^\/(.*)$/)
956 $rel_dir = substr($rel_dir, 1);
959 # Handle files in root directory gracefully
960 $rel_dir = "root" if ($rel_dir eq "");
961 $trunc_dir = "root" if ($trunc_dir eq "");
963 $base_dir = get_relative_base_path($rel_dir);
965 create_sub_dir($rel_dir);
967 # Match filenames which specify files in this directory, not including
969 foreach $filename (grep(/^\Q$abs_dir\E\/[^\/]*$/,keys(%info_data)))
974 ($lines_found, $lines_hit, $fn_found, $fn_hit, $br_found,
975 $br_hit, $testdata, $testfncdata, $testbrdata) =
976 process_file($trunc_dir, $rel_dir, $filename);
978 $base_name = basename($filename);
980 if ($no_sourceview) {
983 # Link to frameset page
984 $page_link = "$base_name.gcov.frameset.$html_ext";
986 # Link directory to source code view page
987 $page_link = "$base_name.gcov.$html_ext";
989 $overview{$base_name} = [$lines_found, $lines_hit, $fn_found,
990 $fn_hit, $br_found, $br_hit,
992 get_rate($lines_found, $lines_hit),
993 get_rate($fn_found, $fn_hit),
994 get_rate($br_found, $br_hit)];
996 $testhash{$base_name} = $testdata;
997 $testfnchash{$base_name} = $testfncdata;
998 $testbrhash{$base_name} = $testbrdata;
1000 $overall_found += $lines_found;
1001 $overall_hit += $lines_hit;
1003 $total_fn_found += $fn_found;
1004 $total_fn_hit += $fn_hit;
1006 $total_br_found += $br_found;
1007 $total_br_hit += $br_hit;
1010 # Create sorted pages
1011 foreach (@fileview_sortlist) {
1012 # Generate directory overview page (without details)
1013 write_dir_page($fileview_sortname[$_], $rel_dir, $base_dir,
1014 $test_title, $trunc_dir, $overall_found,
1015 $overall_hit, $total_fn_found, $total_fn_hit,
1016 $total_br_found, $total_br_hit, \%overview, {},
1018 if (!$show_details) {
1021 # Generate directory overview page including details
1022 write_dir_page("-detail".$fileview_sortname[$_], $rel_dir,
1023 $base_dir, $test_title, $trunc_dir,
1024 $overall_found, $overall_hit, $total_fn_found,
1025 $total_fn_hit, $total_br_found, $total_br_hit,
1026 \%overview, \%testhash, \%testfnchash,
1027 \%testbrhash, 1, $_);
1030 # Calculate resulting line counts
1031 return ($overall_found, $overall_hit, $total_fn_found, $total_fn_hit,
1032 $total_br_found, $total_br_hit);
1037 # get_converted_lines(testdata)
1039 # Return hash of line numbers of those lines which were only covered in
1040 # converted data sets.
1043 sub get_converted_lines($)
1045 my $testdata = $_[0];
1055 # Get a hash containing line numbers with positive counts both for
1056 # converted and original data sets
1057 foreach $testcase (keys(%{$testdata}))
1059 # Check to see if this is a converted data set
1060 if ($testcase =~ /,diff$/)
1062 $hash = \%converted;
1066 $hash = \%nonconverted;
1069 $testcount = $testdata->{$testcase};
1070 # Add lines with a positive count to hash
1071 foreach $line (keys%{$testcount})
1073 if ($testcount->{$line} > 0)
1080 # Combine both hashes to resulting list
1081 foreach $line (keys(%converted))
1083 if (!defined($nonconverted{$line}))
1093 sub write_function_page($$$$$$$$$$$$$$$$$$)
1095 my ($base_dir, $rel_dir, $trunc_dir, $base_name, $title,
1096 $lines_found, $lines_hit, $fn_found, $fn_hit, $br_found, $br_hit,
1097 $sumcount, $funcdata, $sumfnccount, $testfncdata, $sumbrcount,
1098 $testbrdata, $sort_type) = @_;
1102 # Generate function table for this file
1103 if ($sort_type == 0) {
1104 $filename = "$rel_dir/$base_name.func.$html_ext";
1106 $filename = "$rel_dir/$base_name.func-sort-c.$html_ext";
1108 html_create(*HTML_HANDLE, $filename);
1109 $pagetitle = "LCOV - $title - $trunc_dir/$base_name - functions";
1110 write_html_prolog(*HTML_HANDLE, $base_dir, $pagetitle);
1111 write_header(*HTML_HANDLE, 4, "$trunc_dir/$base_name",
1112 "$rel_dir/$base_name", $lines_found, $lines_hit,
1113 $fn_found, $fn_hit, $br_found, $br_hit, $sort_type);
1114 write_function_table(*HTML_HANDLE, "$base_name.gcov.$html_ext",
1115 $sumcount, $funcdata,
1116 $sumfnccount, $testfncdata, $sumbrcount,
1117 $testbrdata, $base_name,
1118 $base_dir, $sort_type);
1119 write_html_epilog(*HTML_HANDLE, $base_dir, 1);
1120 close(*HTML_HANDLE);
1125 # process_file(trunc_dir, rel_dir, filename)
1128 sub process_file($$$)
1130 info("Processing file ".apply_prefix($_[2], $dir_prefix)."\n");
1132 my $trunc_dir = $_[0];
1133 my $rel_dir = $_[1];
1134 my $filename = $_[2];
1135 my $base_name = basename($filename);
1136 my $base_dir = get_relative_base_path($rel_dir);
1157 ($testdata, $sumcount, $funcdata, $checkdata, $testfncdata,
1158 $sumfnccount, $testbrdata, $sumbrcount, $lines_found, $lines_hit,
1159 $fn_found, $fn_hit, $br_found, $br_hit)
1160 = get_info_entry($info_data{$filename});
1162 # Return after this point in case user asked us not to generate
1166 return ($lines_found, $lines_hit, $fn_found, $fn_hit,
1167 $br_found, $br_hit, $testdata, $testfncdata,
1171 $converted = get_converted_lines($testdata);
1172 # Generate source code view for this file
1173 html_create(*HTML_HANDLE, "$rel_dir/$base_name.gcov.$html_ext");
1174 $pagetitle = "LCOV - $test_title - $trunc_dir/$base_name";
1175 write_html_prolog(*HTML_HANDLE, $base_dir, $pagetitle);
1176 write_header(*HTML_HANDLE, 2, "$trunc_dir/$base_name",
1177 "$rel_dir/$base_name", $lines_found, $lines_hit,
1178 $fn_found, $fn_hit, $br_found, $br_hit, 0);
1179 @source = write_source(*HTML_HANDLE, $filename, $sumcount, $checkdata,
1180 $converted, $funcdata, $sumbrcount);
1182 write_html_epilog(*HTML_HANDLE, $base_dir, 1);
1183 close(*HTML_HANDLE);
1185 if ($func_coverage) {
1186 # Create function tables
1187 foreach (@funcview_sortlist) {
1188 write_function_page($base_dir, $rel_dir, $trunc_dir,
1189 $base_name, $test_title,
1190 $lines_found, $lines_hit,
1191 $fn_found, $fn_hit, $br_found,
1193 $funcdata, $sumfnccount,
1194 $testfncdata, $sumbrcount,
1199 # Additional files are needed in case of frame output
1202 return ($lines_found, $lines_hit, $fn_found, $fn_hit,
1203 $br_found, $br_hit, $testdata, $testfncdata,
1207 # Create overview png file
1208 gen_png("$rel_dir/$base_name.gcov.png", $overview_width, $tab_size,
1211 # Create frameset page
1212 html_create(*HTML_HANDLE,
1213 "$rel_dir/$base_name.gcov.frameset.$html_ext");
1214 write_frameset(*HTML_HANDLE, $base_dir, $base_name, $pagetitle);
1215 close(*HTML_HANDLE);
1217 # Write overview frame
1218 html_create(*HTML_HANDLE,
1219 "$rel_dir/$base_name.gcov.overview.$html_ext");
1220 write_overview(*HTML_HANDLE, $base_dir, $base_name, $pagetitle,
1222 close(*HTML_HANDLE);
1224 return ($lines_found, $lines_hit, $fn_found, $fn_hit, $br_found,
1225 $br_hit, $testdata, $testfncdata, $testbrdata);
1230 # read_info_file(info_filename)
1232 # Read in the contents of the .info file specified by INFO_FILENAME. Data will
1233 # be returned as a reference to a hash containing the following mappings:
1235 # %result: for each filename found in file -> \%data
1237 # %data: "test" -> \%testdata
1238 # "sum" -> \%sumcount
1239 # "func" -> \%funcdata
1240 # "found" -> $lines_found (number of instrumented lines found in file)
1241 # "hit" -> $lines_hit (number of executed lines in file)
1242 # "check" -> \%checkdata
1243 # "testfnc" -> \%testfncdata
1244 # "sumfnc" -> \%sumfnccount
1245 # "testbr" -> \%testbrdata
1246 # "sumbr" -> \%sumbrcount
1248 # %testdata : name of test affecting this file -> \%testcount
1249 # %testfncdata: name of test affecting this file -> \%testfnccount
1250 # %testbrdata: name of test affecting this file -> \%testbrcount
1252 # %testcount : line number -> execution count for a single test
1253 # %testfnccount: function name -> execution count for a single test
1254 # %testbrcount : line number -> branch coverage data for a single test
1255 # %sumcount : line number -> execution count for all tests
1256 # %sumfnccount : function name -> execution count for all tests
1257 # %sumbrcount : line number -> branch coverage data for all tests
1258 # %funcdata : function name -> line number
1259 # %checkdata : line number -> checksum of source code line
1260 # $brdata : vector of items: block, branch, taken
1262 # Note that .info file sections referring to the same file and test name
1263 # will automatically be combined by adding all execution counts.
1265 # Note that if INFO_FILENAME ends with ".gz", it is assumed that the file
1266 # is compressed using GZIP. If available, GUNZIP will be used to decompress
1272 sub read_info_file($)
1274 my $tracefile = $_[0]; # Name of tracefile
1275 my %result; # Resulting hash: file -> data
1276 my $data; # Data handle for current entry
1278 my $testcount; # " "
1281 my $checkdata; # " "
1288 my $line; # Current line read from .info file
1289 my $testname; # Current test name
1290 my $filename; # Current filename
1291 my $hitcount; # Count for lines hit
1292 my $count; # Execution count of current line
1293 my $negative; # If set, warn about negative counts
1294 my $changed_testname; # If set, warn about changed testname
1295 my $line_checksum; # Checksum of current line
1298 local *INFO_HANDLE; # Filehandle for .info file
1300 info("Reading data file $tracefile\n");
1302 # Check if file exists and is readable
1306 die("ERROR: cannot read file $_[0]!\n");
1309 # Check if this is really a plain file
1312 die("ERROR: not a plain file: $_[0]!\n");
1315 # Check for .gz extension
1316 if ($_[0] =~ /\.gz$/)
1318 # Check for availability of GZIP tool
1319 system_no_output(1, "gunzip" ,"-h")
1320 and die("ERROR: gunzip command not available!\n");
1322 # Check integrity of compressed file
1323 system_no_output(1, "gunzip", "-t", $_[0])
1324 and die("ERROR: integrity check failed for ".
1325 "compressed file $_[0]!\n");
1327 # Open compressed file
1328 open(INFO_HANDLE, "-|", "gunzip -c '$_[0]'")
1329 or die("ERROR: cannot start gunzip to decompress ".
1334 # Open decompressed file
1335 open(INFO_HANDLE, "<", $_[0])
1336 or die("ERROR: cannot read file $_[0]!\n");
1340 while (<INFO_HANDLE>)
1348 /^TN:([^,]*)(,diff)?/ && do
1350 # Test name information found
1351 $testname = defined($1) ? $1 : "";
1352 if ($testname =~ s/\W/_/g)
1354 $changed_testname = 1;
1356 $testname .= $2 if (defined($2));
1362 # Filename information found
1363 # Retrieve data for new entry
1366 $data = $result{$filename};
1367 ($testdata, $sumcount, $funcdata, $checkdata,
1368 $testfncdata, $sumfnccount, $testbrdata,
1370 get_info_entry($data);
1372 if (defined($testname))
1374 $testcount = $testdata->{$testname};
1375 $testfnccount = $testfncdata->{$testname};
1376 $testbrcount = $testbrdata->{$testname};
1387 /^DA:(\d+),(-?\d+)(,[^,\s]+)?/ && do
1389 # Fix negative counts
1390 $count = $2 < 0 ? 0 : $2;
1395 # Execution count found, add to structure
1396 # Add summary counts
1397 $sumcount->{$1} += $count;
1399 # Add test-specific counts
1400 if (defined($testname))
1402 $testcount->{$1} += $count;
1405 # Store line checksum if available
1408 $line_checksum = substr($3, 1);
1410 # Does it match a previous definition
1411 if (defined($checkdata->{$1}) &&
1412 ($checkdata->{$1} ne
1415 die("ERROR: checksum mismatch ".
1416 "at $filename:$1\n");
1419 $checkdata->{$1} = $line_checksum;
1424 /^FN:(\d+),([^,]+)/ && do
1426 last if (!$func_coverage);
1428 # Function data found, add to structure
1429 $funcdata->{$2} = $1;
1431 # Also initialize function call data
1432 if (!defined($sumfnccount->{$2})) {
1433 $sumfnccount->{$2} = 0;
1435 if (defined($testname))
1437 if (!defined($testfnccount->{$2})) {
1438 $testfnccount->{$2} = 0;
1444 /^FNDA:(\d+),([^,]+)/ && do
1446 last if (!$func_coverage);
1447 # Function call count found, add to structure
1448 # Add summary counts
1449 $sumfnccount->{$2} += $1;
1451 # Add test-specific counts
1452 if (defined($testname))
1454 $testfnccount->{$2} += $1;
1459 /^BRDA:(\d+),(\d+),(\d+),(\d+|-)/ && do {
1460 # Branch coverage data found
1461 my ($line, $block, $branch, $taken) =
1464 last if (!$br_coverage);
1465 $sumbrcount->{$line} =
1466 br_ivec_push($sumbrcount->{$line},
1467 $block, $branch, $taken);
1469 # Add test-specific counts
1470 if (defined($testname)) {
1471 $testbrcount->{$line} =
1473 $testbrcount->{$line},
1480 /^end_of_record/ && do
1482 # Found end of section marker
1485 # Store current section data
1486 if (defined($testname))
1488 $testdata->{$testname} =
1490 $testfncdata->{$testname} =
1492 $testbrdata->{$testname} =
1496 set_info_entry($data, $testdata,
1497 $sumcount, $funcdata,
1498 $checkdata, $testfncdata,
1502 $result{$filename} = $data;
1513 # Calculate lines_found and lines_hit for each file
1514 foreach $filename (keys(%result))
1516 $data = $result{$filename};
1518 ($testdata, $sumcount, undef, undef, $testfncdata,
1519 $sumfnccount, $testbrdata, $sumbrcount) =
1520 get_info_entry($data);
1522 # Filter out empty files
1523 if (scalar(keys(%{$sumcount})) == 0)
1525 delete($result{$filename});
1528 # Filter out empty test cases
1529 foreach $testname (keys(%{$testdata}))
1531 if (!defined($testdata->{$testname}) ||
1532 scalar(keys(%{$testdata->{$testname}})) == 0)
1534 delete($testdata->{$testname});
1535 delete($testfncdata->{$testname});
1539 $data->{"found"} = scalar(keys(%{$sumcount}));
1542 foreach (keys(%{$sumcount}))
1544 if ($sumcount->{$_} > 0) { $hitcount++; }
1547 $data->{"hit"} = $hitcount;
1549 # Get found/hit values for function call data
1550 $data->{"f_found"} = scalar(keys(%{$sumfnccount}));
1553 foreach (keys(%{$sumfnccount})) {
1554 if ($sumfnccount->{$_} > 0) {
1558 $data->{"f_hit"} = $hitcount;
1560 # Get found/hit values for branch data
1561 ($br_found, $br_hit) = get_br_found_and_hit($sumbrcount);
1563 $data->{"b_found"} = $br_found;
1564 $data->{"b_hit"} = $br_hit;
1567 if (scalar(keys(%result)) == 0)
1569 die("ERROR: no valid records found in tracefile $tracefile\n");
1573 warn("WARNING: negative counts found in tracefile ".
1576 if ($changed_testname)
1578 warn("WARNING: invalid characters removed from testname in ".
1579 "tracefile $tracefile\n");
1587 # get_info_entry(hash_ref)
1589 # Retrieve data from an entry of the structure generated by read_info_file().
1590 # Return a list of references to hashes:
1591 # (test data hash ref, sum count hash ref, funcdata hash ref, checkdata hash
1592 # ref, testfncdata hash ref, sumfnccount hash ref, lines found, lines hit,
1593 # functions found, functions hit)
1596 sub get_info_entry($)
1598 my $testdata_ref = $_[0]->{"test"};
1599 my $sumcount_ref = $_[0]->{"sum"};
1600 my $funcdata_ref = $_[0]->{"func"};
1601 my $checkdata_ref = $_[0]->{"check"};
1602 my $testfncdata = $_[0]->{"testfnc"};
1603 my $sumfnccount = $_[0]->{"sumfnc"};
1604 my $testbrdata = $_[0]->{"testbr"};
1605 my $sumbrcount = $_[0]->{"sumbr"};
1606 my $lines_found = $_[0]->{"found"};
1607 my $lines_hit = $_[0]->{"hit"};
1608 my $fn_found = $_[0]->{"f_found"};
1609 my $fn_hit = $_[0]->{"f_hit"};
1610 my $br_found = $_[0]->{"b_found"};
1611 my $br_hit = $_[0]->{"b_hit"};
1613 return ($testdata_ref, $sumcount_ref, $funcdata_ref, $checkdata_ref,
1614 $testfncdata, $sumfnccount, $testbrdata, $sumbrcount,
1615 $lines_found, $lines_hit, $fn_found, $fn_hit,
1616 $br_found, $br_hit);
1621 # set_info_entry(hash_ref, testdata_ref, sumcount_ref, funcdata_ref,
1622 # checkdata_ref, testfncdata_ref, sumfcncount_ref,
1623 # testbrdata_ref, sumbrcount_ref[,lines_found,
1624 # lines_hit, f_found, f_hit, $b_found, $b_hit])
1626 # Update the hash referenced by HASH_REF with the provided data references.
1629 sub set_info_entry($$$$$$$$$;$$$$$$)
1631 my $data_ref = $_[0];
1633 $data_ref->{"test"} = $_[1];
1634 $data_ref->{"sum"} = $_[2];
1635 $data_ref->{"func"} = $_[3];
1636 $data_ref->{"check"} = $_[4];
1637 $data_ref->{"testfnc"} = $_[5];
1638 $data_ref->{"sumfnc"} = $_[6];
1639 $data_ref->{"testbr"} = $_[7];
1640 $data_ref->{"sumbr"} = $_[8];
1642 if (defined($_[9])) { $data_ref->{"found"} = $_[9]; }
1643 if (defined($_[10])) { $data_ref->{"hit"} = $_[10]; }
1644 if (defined($_[11])) { $data_ref->{"f_found"} = $_[11]; }
1645 if (defined($_[12])) { $data_ref->{"f_hit"} = $_[12]; }
1646 if (defined($_[13])) { $data_ref->{"b_found"} = $_[13]; }
1647 if (defined($_[14])) { $data_ref->{"b_hit"} = $_[14]; }
1652 # add_counts(data1_ref, data2_ref)
1654 # DATA1_REF and DATA2_REF are references to hashes containing a mapping
1656 # line number -> execution count
1658 # Return a list (RESULT_REF, LINES_FOUND, LINES_HIT) where RESULT_REF
1659 # is a reference to a hash containing the combined mapping in which
1660 # execution counts are added.
1665 my %data1 = %{$_[0]}; # Hash 1
1666 my %data2 = %{$_[1]}; # Hash 2
1667 my %result; # Resulting hash
1668 my $line; # Current line iteration scalar
1669 my $data1_count; # Count of line in hash1
1670 my $data2_count; # Count of line in hash2
1671 my $found = 0; # Total number of lines found
1672 my $hit = 0; # Number of lines with a count > 0
1674 foreach $line (keys(%data1))
1676 $data1_count = $data1{$line};
1677 $data2_count = $data2{$line};
1679 # Add counts if present in both hashes
1680 if (defined($data2_count)) { $data1_count += $data2_count; }
1682 # Store sum in %result
1683 $result{$line} = $data1_count;
1686 if ($data1_count > 0) { $hit++; }
1689 # Add lines unique to data2
1690 foreach $line (keys(%data2))
1692 # Skip lines already in data1
1693 if (defined($data1{$line})) { next; }
1695 # Copy count from data2
1696 $result{$line} = $data2{$line};
1699 if ($result{$line} > 0) { $hit++; }
1702 return (\%result, $found, $hit);
1707 # merge_checksums(ref1, ref2, filename)
1709 # REF1 and REF2 are references to hashes containing a mapping
1711 # line number -> checksum
1713 # Merge checksum lists defined in REF1 and REF2 and return reference to
1714 # resulting hash. Die if a checksum for a line is defined in both hashes
1715 # but does not match.
1718 sub merge_checksums($$$)
1722 my $filename = $_[2];
1726 foreach $line (keys(%{$ref1}))
1728 if (defined($ref2->{$line}) &&
1729 ($ref1->{$line} ne $ref2->{$line}))
1731 die("ERROR: checksum mismatch at $filename:$line\n");
1733 $result{$line} = $ref1->{$line};
1736 foreach $line (keys(%{$ref2}))
1738 $result{$line} = $ref2->{$line};
1746 # merge_func_data(funcdata1, funcdata2, filename)
1749 sub merge_func_data($$$)
1751 my ($funcdata1, $funcdata2, $filename) = @_;
1755 if (defined($funcdata1)) {
1756 %result = %{$funcdata1};
1759 foreach $func (keys(%{$funcdata2})) {
1760 my $line1 = $result{$func};
1761 my $line2 = $funcdata2->{$func};
1763 if (defined($line1) && ($line1 != $line2)) {
1764 warn("WARNING: function data mismatch at ".
1765 "$filename:$line2\n");
1768 $result{$func} = $line2;
1776 # add_fnccount(fnccount1, fnccount2)
1778 # Add function call count data. Return list (fnccount_added, f_found, f_hit)
1781 sub add_fnccount($$)
1783 my ($fnccount1, $fnccount2) = @_;
1789 if (defined($fnccount1)) {
1790 %result = %{$fnccount1};
1792 foreach $function (keys(%{$fnccount2})) {
1793 $result{$function} += $fnccount2->{$function};
1795 $fn_found = scalar(keys(%result));
1797 foreach $function (keys(%result)) {
1798 if ($result{$function} > 0) {
1803 return (\%result, $fn_found, $fn_hit);
1807 # add_testfncdata(testfncdata1, testfncdata2)
1809 # Add function call count data for several tests. Return reference to
1810 # added_testfncdata.
1813 sub add_testfncdata($$)
1815 my ($testfncdata1, $testfncdata2) = @_;
1819 foreach $testname (keys(%{$testfncdata1})) {
1820 if (defined($testfncdata2->{$testname})) {
1823 # Function call count data for this testname exists
1824 # in both data sets: add
1825 ($fnccount) = add_fnccount(
1826 $testfncdata1->{$testname},
1827 $testfncdata2->{$testname});
1828 $result{$testname} = $fnccount;
1831 # Function call count data for this testname is unique to
1833 $result{$testname} = $testfncdata1->{$testname};
1836 # Add count data for testnames unique to data set 2
1837 foreach $testname (keys(%{$testfncdata2})) {
1838 if (!defined($result{$testname})) {
1839 $result{$testname} = $testfncdata2->{$testname};
1847 # brcount_to_db(brcount)
1849 # Convert brcount data to the following format:
1851 # db: line number -> block hash
1852 # block hash: block number -> branch hash
1853 # branch hash: branch number -> taken value
1856 sub brcount_to_db($)
1862 # Add branches from first count to database
1863 foreach $line (keys(%{$brcount})) {
1864 my $brdata = $brcount->{$line};
1866 my $num = br_ivec_len($brdata);
1868 for ($i = 0; $i < $num; $i++) {
1869 my ($block, $branch, $taken) = br_ivec_get($brdata, $i);
1871 $db->{$line}->{$block}->{$branch} = $taken;
1882 # Convert branch coverage data back to brcount format.
1885 sub db_to_brcount($)
1893 # Convert database back to brcount format
1894 foreach $line (sort({$a <=> $b} keys(%{$db}))) {
1895 my $ldata = $db->{$line};
1899 foreach $block (sort({$a <=> $b} keys(%{$ldata}))) {
1900 my $bdata = $ldata->{$block};
1903 foreach $branch (sort({$a <=> $b} keys(%{$bdata}))) {
1904 my $taken = $bdata->{$branch};
1907 $br_hit++ if ($taken ne "-" && $taken > 0);
1908 $brdata = br_ivec_push($brdata, $block,
1912 $brcount->{$line} = $brdata;
1915 return ($brcount, $br_found, $br_hit);
1920 # combine_brcount(brcount1, brcount2, type)
1922 # If add is BR_ADD, add branch coverage data and return list (brcount_added,
1923 # br_found, br_hit). If add is BR_SUB, subtract the taken values of brcount2
1924 # from brcount1 and return (brcount_sub, br_found, br_hit).
1927 sub combine_brcount($$$)
1929 my ($brcount1, $brcount2, $type) = @_;
1939 # Convert branches from first count to database
1940 $db = brcount_to_db($brcount1);
1941 # Combine values from database and second count
1942 foreach $line (keys(%{$brcount2})) {
1943 my $brdata = $brcount2->{$line};
1944 my $num = br_ivec_len($brdata);
1947 for ($i = 0; $i < $num; $i++) {
1948 ($block, $branch, $taken) = br_ivec_get($brdata, $i);
1949 my $new_taken = $db->{$line}->{$block}->{$branch};
1951 if ($type == $BR_ADD) {
1952 $new_taken = br_taken_add($new_taken, $taken);
1953 } elsif ($type == $BR_SUB) {
1954 $new_taken = br_taken_sub($new_taken, $taken);
1956 $db->{$line}->{$block}->{$branch} = $new_taken
1957 if (defined($new_taken));
1960 # Convert database back to brcount format
1961 ($result, $br_found, $br_hit) = db_to_brcount($db);
1963 return ($result, $br_found, $br_hit);
1968 # add_testbrdata(testbrdata1, testbrdata2)
1970 # Add branch coverage data for several tests. Return reference to
1974 sub add_testbrdata($$)
1976 my ($testbrdata1, $testbrdata2) = @_;
1980 foreach $testname (keys(%{$testbrdata1})) {
1981 if (defined($testbrdata2->{$testname})) {
1984 # Branch coverage data for this testname exists
1985 # in both data sets: add
1986 ($brcount) = combine_brcount($testbrdata1->{$testname},
1987 $testbrdata2->{$testname}, $BR_ADD);
1988 $result{$testname} = $brcount;
1991 # Branch coverage data for this testname is unique to
1993 $result{$testname} = $testbrdata1->{$testname};
1996 # Add count data for testnames unique to data set 2
1997 foreach $testname (keys(%{$testbrdata2})) {
1998 if (!defined($result{$testname})) {
1999 $result{$testname} = $testbrdata2->{$testname};
2007 # combine_info_entries(entry_ref1, entry_ref2, filename)
2009 # Combine .info data entry hashes referenced by ENTRY_REF1 and ENTRY_REF2.
2010 # Return reference to resulting hash.
2013 sub combine_info_entries($$$)
2015 my $entry1 = $_[0]; # Reference to hash containing first entry
2025 my $entry2 = $_[1]; # Reference to hash containing second entry
2035 my %result; # Hash containing combined entry
2036 my %result_testdata;
2037 my $result_sumcount = {};
2038 my $result_funcdata;
2039 my $result_testfncdata;
2040 my $result_sumfnccount;
2041 my $result_testbrdata;
2042 my $result_sumbrcount;
2051 my $filename = $_[2];
2054 ($testdata1, $sumcount1, $funcdata1, $checkdata1, $testfncdata1,
2055 $sumfnccount1, $testbrdata1, $sumbrcount1) = get_info_entry($entry1);
2056 ($testdata2, $sumcount2, $funcdata2, $checkdata2, $testfncdata2,
2057 $sumfnccount2, $testbrdata2, $sumbrcount2) = get_info_entry($entry2);
2060 $checkdata1 = merge_checksums($checkdata1, $checkdata2, $filename);
2063 $result_funcdata = merge_func_data($funcdata1, $funcdata2, $filename);
2065 # Combine function call count data
2066 $result_testfncdata = add_testfncdata($testfncdata1, $testfncdata2);
2067 ($result_sumfnccount, $fn_found, $fn_hit) =
2068 add_fnccount($sumfnccount1, $sumfnccount2);
2070 # Combine branch coverage data
2071 $result_testbrdata = add_testbrdata($testbrdata1, $testbrdata2);
2072 ($result_sumbrcount, $br_found, $br_hit) =
2073 combine_brcount($sumbrcount1, $sumbrcount2, $BR_ADD);
2076 foreach $testname (keys(%{$testdata1}))
2078 if (defined($testdata2->{$testname}))
2080 # testname is present in both entries, requires
2082 ($result_testdata{$testname}) =
2083 add_counts($testdata1->{$testname},
2084 $testdata2->{$testname});
2088 # testname only present in entry1, add to result
2089 $result_testdata{$testname} = $testdata1->{$testname};
2092 # update sum count hash
2093 ($result_sumcount, $lines_found, $lines_hit) =
2094 add_counts($result_sumcount,
2095 $result_testdata{$testname});
2098 foreach $testname (keys(%{$testdata2}))
2100 # Skip testnames already covered by previous iteration
2101 if (defined($testdata1->{$testname})) { next; }
2103 # testname only present in entry2, add to result hash
2104 $result_testdata{$testname} = $testdata2->{$testname};
2106 # update sum count hash
2107 ($result_sumcount, $lines_found, $lines_hit) =
2108 add_counts($result_sumcount,
2109 $result_testdata{$testname});
2112 # Calculate resulting sumcount
2115 set_info_entry(\%result, \%result_testdata, $result_sumcount,
2116 $result_funcdata, $checkdata1, $result_testfncdata,
2117 $result_sumfnccount, $result_testbrdata,
2118 $result_sumbrcount, $lines_found, $lines_hit,
2119 $fn_found, $fn_hit, $br_found, $br_hit);
2126 # combine_info_files(info_ref1, info_ref2)
2128 # Combine .info data in hashes referenced by INFO_REF1 and INFO_REF2. Return
2129 # reference to resulting hash.
2132 sub combine_info_files($$)
2134 my %hash1 = %{$_[0]};
2135 my %hash2 = %{$_[1]};
2138 foreach $filename (keys(%hash2))
2140 if ($hash1{$filename})
2142 # Entry already exists in hash1, combine them
2144 combine_info_entries($hash1{$filename},
2150 # Entry is unique in both hashes, simply add to
2152 $hash1{$filename} = $hash2{$filename};
2161 # get_prefix(min_dir, filename_list)
2163 # Search FILENAME_LIST for a directory prefix which is common to as many
2164 # list entries as possible, so that removing this prefix will minimize the
2165 # sum of the lengths of all resulting shortened filenames while observing
2166 # that no filename has less than MIN_DIR parent directories.
2171 my ($min_dir, @filename_list) = @_;
2172 my %prefix; # mapping: prefix -> sum of lengths
2173 my $current; # Temporary iteration variable
2175 # Find list of prefixes
2176 foreach (@filename_list)
2178 # Need explicit assignment to get a copy of $_ so that
2179 # shortening the contained prefix does not affect the list
2181 while ($current = shorten_prefix($current))
2185 # Skip rest if the remaining prefix has already been
2187 if (exists($prefix{$current})) { last; }
2190 $prefix{$current}="0";
2195 # Remove all prefixes that would cause filenames to have less than
2196 # the minimum number of parent directories
2197 foreach my $filename (@filename_list) {
2198 my $dir = dirname($filename);
2200 for (my $i = 0; $i < $min_dir; $i++) {
2201 delete($prefix{$dir."/"});
2202 $dir = shorten_prefix($dir);
2206 # Check if any prefix remains
2207 return undef if (!%prefix);
2209 # Calculate sum of lengths for all prefixes
2210 foreach $current (keys(%prefix))
2212 foreach (@filename_list)
2214 # Add original length
2215 $prefix{$current} += length($_);
2217 # Check whether prefix matches
2218 if (substr($_, 0, length($current)) eq $current)
2220 # Subtract prefix length for this filename
2221 $prefix{$current} -= length($current);
2226 # Find and return prefix with minimal sum
2227 $current = (keys(%prefix))[0];
2229 foreach (keys(%prefix))
2231 if ($prefix{$_} < $prefix{$current})
2237 $current =~ s/\/$//;
2244 # shorten_prefix(prefix)
2246 # Return PREFIX shortened by last directory component.
2249 sub shorten_prefix($)
2251 my @list = split("/", $_[0]);
2254 return join("/", @list);
2260 # get_dir_list(filename_list)
2262 # Return sorted list of directories for each entry in given FILENAME_LIST.
2271 $result{shorten_prefix($_)} = "";
2274 return(sort(keys(%result)));
2279 # get_relative_base_path(subdirectory)
2281 # Return a relative path string which references the base path when applied
2284 # Example: get_relative_base_path("fs/mm") -> "../../"
2287 sub get_relative_base_path($)
2292 # Make an empty directory path a special case
2293 if (!$_[0]) { return(""); }
2295 # Count number of /s in path
2296 $index = ($_[0] =~ s/\//\//g);
2298 # Add a ../ to $result for each / in the directory path + 1
2299 for (; $index>=0; $index--)
2309 # read_testfile(test_filename)
2311 # Read in file TEST_FILENAME which contains test descriptions in the format:
2313 # TN:<whitespace><test name>
2314 # TD:<whitespace><test description>
2316 # for each test case. Return a reference to a hash containing a mapping
2318 # test name -> test description.
2323 sub read_testfile($)
2327 my $changed_testname;
2330 open(TEST_HANDLE, "<", $_[0])
2331 or die("ERROR: cannot open $_[0]!\n");
2333 while (<TEST_HANDLE>)
2337 # Match lines beginning with TN:<whitespace(s)>
2338 if (/^TN:\s+(.*?)\s*$/)
2340 # Store name for later use
2342 if ($test_name =~ s/\W/_/g)
2344 $changed_testname = 1;
2348 # Match lines beginning with TD:<whitespace(s)>
2349 if (/^TD:\s+(.*?)\s*$/)
2351 # Check for empty line
2354 # Add description to hash
2355 $result{$test_name} .= " $1";
2360 $result{$test_name} .= "\n\n";
2367 if ($changed_testname)
2369 warn("WARNING: invalid characters removed from testname in ".
2370 "descriptions file $_[0]\n");
2378 # escape_html(STRING)
2380 # Return a copy of STRING in which all occurrences of HTML special characters
2388 if (!$string) { return ""; }
2390 $string =~ s/&/&/g; # & -> &
2391 $string =~ s/</</g; # < -> <
2392 $string =~ s/>/>/g; # > -> >
2393 $string =~ s/\"/"/g; # " -> "
2395 while ($string =~ /^([^\t]*)(\t)/)
2397 my $replacement = " "x($tab_size - (length($1) % $tab_size));
2398 $string =~ s/^([^\t]*)(\t)/$1$replacement/;
2401 $string =~ s/\n/<br>/g; # \n -> <br>
2410 # Return the current date in the form: yyyy-mm-dd
2413 sub get_date_string()
2419 ($year, $month, $day) = (localtime())[5, 4, 3];
2421 return sprintf("%d-%02d-%02d", $year+1900, $month+1, $day);
2426 # create_sub_dir(dir_name)
2428 # Create subdirectory DIR_NAME if it does not already exist, including all its
2429 # parent directories.
2434 sub create_sub_dir($)
2438 system("mkdir", "-p" ,$dir)
2439 and die("ERROR: cannot create directory $dir!\n");
2444 # write_description_file(descriptions, overall_found, overall_hit,
2445 # total_fn_found, total_fn_hit, total_br_found,
2448 # Write HTML file containing all test case descriptions. DESCRIPTIONS is a
2449 # reference to a hash containing a mapping
2451 # test case name -> test case description
2456 sub write_description_file($$$$$$$)
2458 my %description = %{$_[0]};
2461 my $fn_found = $_[3];
2463 my $br_found = $_[5];
2468 html_create(*HTML_HANDLE,"descriptions.$html_ext");
2469 write_html_prolog(*HTML_HANDLE, "", "LCOV - test case descriptions");
2470 write_header(*HTML_HANDLE, 3, "", "", $found, $hit, $fn_found,
2471 $fn_hit, $br_found, $br_hit, 0);
2473 write_test_table_prolog(*HTML_HANDLE,
2474 "Test case descriptions - alphabetical list");
2476 foreach $test_name (sort(keys(%description)))
2478 write_test_table_entry(*HTML_HANDLE, $test_name,
2479 escape_html($description{$test_name}));
2482 write_test_table_epilog(*HTML_HANDLE);
2483 write_html_epilog(*HTML_HANDLE, "");
2485 close(*HTML_HANDLE);
2493 # Create all necessary .png files for the HTML-output in the current
2494 # directory. .png-files are used as bar graphs.
2499 sub write_png_files()
2505 [0x89, 0x50, 0x4e, 0x47, 0x0d, 0x0a, 0x1a, 0x0a, 0x00, 0x00,
2506 0x00, 0x0d, 0x49, 0x48, 0x44, 0x52, 0x00, 0x00, 0x00, 0x01,
2507 0x00, 0x00, 0x00, 0x01, 0x01, 0x03, 0x00, 0x00, 0x00, 0x25,
2508 0xdb, 0x56, 0xca, 0x00, 0x00, 0x00, 0x07, 0x74, 0x49, 0x4d,
2509 0x45, 0x07, 0xd2, 0x07, 0x11, 0x0f, 0x18, 0x10, 0x5d, 0x57,
2510 0x34, 0x6e, 0x00, 0x00, 0x00, 0x09, 0x70, 0x48, 0x59, 0x73,
2511 0x00, 0x00, 0x0b, 0x12, 0x00, 0x00, 0x0b, 0x12, 0x01, 0xd2,
2512 0xdd, 0x7e, 0xfc, 0x00, 0x00, 0x00, 0x04, 0x67, 0x41, 0x4d,
2513 0x41, 0x00, 0x00, 0xb1, 0x8f, 0x0b, 0xfc, 0x61, 0x05, 0x00,
2514 0x00, 0x00, 0x06, 0x50, 0x4c, 0x54, 0x45, 0xff, 0x35, 0x2f,
2515 0x00, 0x00, 0x00, 0xd0, 0x33, 0x9a, 0x9d, 0x00, 0x00, 0x00,
2516 0x0a, 0x49, 0x44, 0x41, 0x54, 0x78, 0xda, 0x63, 0x60, 0x00,
2517 0x00, 0x00, 0x02, 0x00, 0x01, 0xe5, 0x27, 0xde, 0xfc, 0x00,
2518 0x00, 0x00, 0x00, 0x49, 0x45, 0x4e, 0x44, 0xae, 0x42, 0x60,
2520 $data{"amber.png"} =
2521 [0x89, 0x50, 0x4e, 0x47, 0x0d, 0x0a, 0x1a, 0x0a, 0x00, 0x00,
2522 0x00, 0x0d, 0x49, 0x48, 0x44, 0x52, 0x00, 0x00, 0x00, 0x01,
2523 0x00, 0x00, 0x00, 0x01, 0x01, 0x03, 0x00, 0x00, 0x00, 0x25,
2524 0xdb, 0x56, 0xca, 0x00, 0x00, 0x00, 0x07, 0x74, 0x49, 0x4d,
2525 0x45, 0x07, 0xd2, 0x07, 0x11, 0x0f, 0x28, 0x04, 0x98, 0xcb,
2526 0xd6, 0xe0, 0x00, 0x00, 0x00, 0x09, 0x70, 0x48, 0x59, 0x73,
2527 0x00, 0x00, 0x0b, 0x12, 0x00, 0x00, 0x0b, 0x12, 0x01, 0xd2,
2528 0xdd, 0x7e, 0xfc, 0x00, 0x00, 0x00, 0x04, 0x67, 0x41, 0x4d,
2529 0x41, 0x00, 0x00, 0xb1, 0x8f, 0x0b, 0xfc, 0x61, 0x05, 0x00,
2530 0x00, 0x00, 0x06, 0x50, 0x4c, 0x54, 0x45, 0xff, 0xe0, 0x50,
2531 0x00, 0x00, 0x00, 0xa2, 0x7a, 0xda, 0x7e, 0x00, 0x00, 0x00,
2532 0x0a, 0x49, 0x44, 0x41, 0x54, 0x78, 0xda, 0x63, 0x60, 0x00,
2533 0x00, 0x00, 0x02, 0x00, 0x01, 0xe5, 0x27, 0xde, 0xfc, 0x00,
2534 0x00, 0x00, 0x00, 0x49, 0x45, 0x4e, 0x44, 0xae, 0x42, 0x60,
2536 $data{"emerald.png"} =
2537 [0x89, 0x50, 0x4e, 0x47, 0x0d, 0x0a, 0x1a, 0x0a, 0x00, 0x00,
2538 0x00, 0x0d, 0x49, 0x48, 0x44, 0x52, 0x00, 0x00, 0x00, 0x01,
2539 0x00, 0x00, 0x00, 0x01, 0x01, 0x03, 0x00, 0x00, 0x00, 0x25,
2540 0xdb, 0x56, 0xca, 0x00, 0x00, 0x00, 0x07, 0x74, 0x49, 0x4d,
2541 0x45, 0x07, 0xd2, 0x07, 0x11, 0x0f, 0x22, 0x2b, 0xc9, 0xf5,
2542 0x03, 0x33, 0x00, 0x00, 0x00, 0x09, 0x70, 0x48, 0x59, 0x73,
2543 0x00, 0x00, 0x0b, 0x12, 0x00, 0x00, 0x0b, 0x12, 0x01, 0xd2,
2544 0xdd, 0x7e, 0xfc, 0x00, 0x00, 0x00, 0x04, 0x67, 0x41, 0x4d,
2545 0x41, 0x00, 0x00, 0xb1, 0x8f, 0x0b, 0xfc, 0x61, 0x05, 0x00,
2546 0x00, 0x00, 0x06, 0x50, 0x4c, 0x54, 0x45, 0x1b, 0xea, 0x59,
2547 0x0a, 0x0a, 0x0a, 0x0f, 0xba, 0x50, 0x83, 0x00, 0x00, 0x00,
2548 0x0a, 0x49, 0x44, 0x41, 0x54, 0x78, 0xda, 0x63, 0x60, 0x00,
2549 0x00, 0x00, 0x02, 0x00, 0x01, 0xe5, 0x27, 0xde, 0xfc, 0x00,
2550 0x00, 0x00, 0x00, 0x49, 0x45, 0x4e, 0x44, 0xae, 0x42, 0x60,
2553 [0x89, 0x50, 0x4e, 0x47, 0x0d, 0x0a, 0x1a, 0x0a, 0x00, 0x00,
2554 0x00, 0x0d, 0x49, 0x48, 0x44, 0x52, 0x00, 0x00, 0x00, 0x01,
2555 0x00, 0x00, 0x00, 0x01, 0x01, 0x03, 0x00, 0x00, 0x00, 0x25,
2556 0xdb, 0x56, 0xca, 0x00, 0x00, 0x00, 0x07, 0x74, 0x49, 0x4d,
2557 0x45, 0x07, 0xd2, 0x07, 0x11, 0x0f, 0x1e, 0x1d, 0x75, 0xbc,
2558 0xef, 0x55, 0x00, 0x00, 0x00, 0x09, 0x70, 0x48, 0x59, 0x73,
2559 0x00, 0x00, 0x0b, 0x12, 0x00, 0x00, 0x0b, 0x12, 0x01, 0xd2,
2560 0xdd, 0x7e, 0xfc, 0x00, 0x00, 0x00, 0x04, 0x67, 0x41, 0x4d,
2561 0x41, 0x00, 0x00, 0xb1, 0x8f, 0x0b, 0xfc, 0x61, 0x05, 0x00,
2562 0x00, 0x00, 0x06, 0x50, 0x4c, 0x54, 0x45, 0xff, 0xff, 0xff,
2563 0x00, 0x00, 0x00, 0x55, 0xc2, 0xd3, 0x7e, 0x00, 0x00, 0x00,
2564 0x0a, 0x49, 0x44, 0x41, 0x54, 0x78, 0xda, 0x63, 0x60, 0x00,
2565 0x00, 0x00, 0x02, 0x00, 0x01, 0xe5, 0x27, 0xde, 0xfc, 0x00,
2566 0x00, 0x00, 0x00, 0x49, 0x45, 0x4e, 0x44, 0xae, 0x42, 0x60,
2568 $data{"glass.png"} =
2569 [0x89, 0x50, 0x4e, 0x47, 0x0d, 0x0a, 0x1a, 0x0a, 0x00, 0x00,
2570 0x00, 0x0d, 0x49, 0x48, 0x44, 0x52, 0x00, 0x00, 0x00, 0x01,
2571 0x00, 0x00, 0x00, 0x01, 0x01, 0x03, 0x00, 0x00, 0x00, 0x25,
2572 0xdb, 0x56, 0xca, 0x00, 0x00, 0x00, 0x04, 0x67, 0x41, 0x4d,
2573 0x41, 0x00, 0x00, 0xb1, 0x8f, 0x0b, 0xfc, 0x61, 0x05, 0x00,
2574 0x00, 0x00, 0x06, 0x50, 0x4c, 0x54, 0x45, 0xff, 0xff, 0xff,
2575 0x00, 0x00, 0x00, 0x55, 0xc2, 0xd3, 0x7e, 0x00, 0x00, 0x00,
2576 0x01, 0x74, 0x52, 0x4e, 0x53, 0x00, 0x40, 0xe6, 0xd8, 0x66,
2577 0x00, 0x00, 0x00, 0x01, 0x62, 0x4b, 0x47, 0x44, 0x00, 0x88,
2578 0x05, 0x1d, 0x48, 0x00, 0x00, 0x00, 0x09, 0x70, 0x48, 0x59,
2579 0x73, 0x00, 0x00, 0x0b, 0x12, 0x00, 0x00, 0x0b, 0x12, 0x01,
2580 0xd2, 0xdd, 0x7e, 0xfc, 0x00, 0x00, 0x00, 0x07, 0x74, 0x49,
2581 0x4d, 0x45, 0x07, 0xd2, 0x07, 0x13, 0x0f, 0x08, 0x19, 0xc4,
2582 0x40, 0x56, 0x10, 0x00, 0x00, 0x00, 0x0a, 0x49, 0x44, 0x41,
2583 0x54, 0x78, 0x9c, 0x63, 0x60, 0x00, 0x00, 0x00, 0x02, 0x00,
2584 0x01, 0x48, 0xaf, 0xa4, 0x71, 0x00, 0x00, 0x00, 0x00, 0x49,
2585 0x45, 0x4e, 0x44, 0xae, 0x42, 0x60, 0x82];
2586 $data{"updown.png"} =
2587 [0x89, 0x50, 0x4e, 0x47, 0x0d, 0x0a, 0x1a, 0x0a, 0x00, 0x00,
2588 0x00, 0x0d, 0x49, 0x48, 0x44, 0x52, 0x00, 0x00, 0x00, 0x0a,
2589 0x00, 0x00, 0x00, 0x0e, 0x08, 0x06, 0x00, 0x00, 0x00, 0x16,
2590 0xa3, 0x8d, 0xab, 0x00, 0x00, 0x00, 0x3c, 0x49, 0x44, 0x41,
2591 0x54, 0x28, 0xcf, 0x63, 0x60, 0x40, 0x03, 0xff, 0xa1, 0x00,
2592 0x5d, 0x9c, 0x11, 0x5d, 0x11, 0x8a, 0x24, 0x23, 0x23, 0x23,
2593 0x86, 0x42, 0x6c, 0xa6, 0x20, 0x2b, 0x66, 0xc4, 0xa7, 0x08,
2594 0x59, 0x31, 0x23, 0x21, 0x45, 0x30, 0xc0, 0xc4, 0x30, 0x60,
2595 0x80, 0xfa, 0x6e, 0x24, 0x3e, 0x78, 0x48, 0x0a, 0x70, 0x62,
2596 0xa2, 0x90, 0x81, 0xd8, 0x44, 0x01, 0x00, 0xe9, 0x5c, 0x2f,
2597 0xf5, 0xe2, 0x9d, 0x0f, 0xf9, 0x00, 0x00, 0x00, 0x00, 0x49,
2598 0x45, 0x4e, 0x44, 0xae, 0x42, 0x60, 0x82] if ($sort);
2599 foreach (keys(%data))
2601 open(PNG_HANDLE, ">", $_)
2602 or die("ERROR: cannot create $_!\n");
2603 binmode(PNG_HANDLE);
2604 print(PNG_HANDLE map(chr,@{$data{$_}}));
2611 # write_htaccess_file()
2614 sub write_htaccess_file()
2616 local *HTACCESS_HANDLE;
2619 open(*HTACCESS_HANDLE, ">", ".htaccess")
2620 or die("ERROR: cannot open .htaccess for writing!\n");
2622 $htaccess_data = (<<"END_OF_HTACCESS")
2623 AddEncoding x-gzip .html
2627 print(HTACCESS_HANDLE $htaccess_data);
2628 close(*HTACCESS_HANDLE);
2635 # Write the cascading style sheet file gcov.css to the current directory.
2636 # This file defines basic layout attributes of all generated HTML pages.
2639 sub write_css_file()
2643 # Check for a specified external style sheet file
2646 # Simply copy that file
2647 system("cp", $css_filename, "gcov.css")
2648 and die("ERROR: cannot copy file $css_filename!\n");
2652 open(CSS_HANDLE, ">", "gcov.css")
2653 or die ("ERROR: cannot open gcov.css for writing!\n");
2656 # *************************************************************
2658 my $css_data = ($_=<<"END_OF_CSS")
2659 /* All views: initial background and text color */
2663 background-color: #FFFFFF;
2666 /* All views: standard link format*/
2670 text-decoration: underline;
2673 /* All views: standard link - visited format */
2677 text-decoration: underline;
2680 /* All views: standard link - activated format */
2684 text-decoration: underline;
2687 /* All views: main title format */
2691 padding-bottom: 10px;
2692 font-family: sans-serif;
2698 /* All views: header item format */
2703 font-family: sans-serif;
2705 vertical-align: top;
2706 white-space: nowrap;
2709 /* All views: header item value format */
2714 font-family: sans-serif;
2716 white-space: nowrap;
2719 /* All views: header item coverage table heading */
2720 td.headerCovTableHead
2725 padding-bottom: 0px;
2726 font-family: sans-serif;
2728 white-space: nowrap;
2731 /* All views: header item coverage table entry */
2732 td.headerCovTableEntry
2736 font-family: sans-serif;
2738 white-space: nowrap;
2741 background-color: #DAE7FE;
2744 /* All views: header item coverage table entry for high coverage rate */
2745 td.headerCovTableEntryHi
2749 font-family: sans-serif;
2751 white-space: nowrap;
2754 background-color: #A7FC9D;
2757 /* All views: header item coverage table entry for medium coverage rate */
2758 td.headerCovTableEntryMed
2762 font-family: sans-serif;
2764 white-space: nowrap;
2767 background-color: #FFEA20;
2770 /* All views: header item coverage table entry for ow coverage rate */
2771 td.headerCovTableEntryLo
2775 font-family: sans-serif;
2777 white-space: nowrap;
2780 background-color: #FF0000;
2783 /* All views: header legend value for legend entry */
2788 font-family: sans-serif;
2790 white-space: nowrap;
2794 /* All views: color of horizontal ruler */
2797 background-color: #6688D4;
2800 /* All views: version string format */
2805 font-family: sans-serif;
2809 /* Directory view/File view (all)/Test case descriptions:
2810 table headline format */
2815 background-color: #6688D4;
2816 font-family: sans-serif;
2819 white-space: nowrap;
2829 /* Directory view/File view (all): filename entry format */
2834 padding-right: 20px;
2836 background-color: #DAE7FE;
2837 font-family: monospace;
2840 /* Directory view/File view (all): bar-graph entry format*/
2844 padding-right: 10px;
2845 background-color: #DAE7FE;
2848 /* Directory view/File view (all): bar-graph outline color */
2851 background-color: #000000;
2854 /* Directory view/File view (all): percentage entry for files with
2855 high coverage rate */
2860 padding-right: 10px;
2861 background-color: #A7FC9D;
2863 font-family: sans-serif;
2866 /* Directory view/File view (all): line count entry for files with
2867 high coverage rate */
2872 padding-right: 10px;
2873 background-color: #A7FC9D;
2874 white-space: nowrap;
2875 font-family: sans-serif;
2878 /* Directory view/File view (all): percentage entry for files with
2879 medium coverage rate */
2884 padding-right: 10px;
2885 background-color: #FFEA20;
2887 font-family: sans-serif;
2890 /* Directory view/File view (all): line count entry for files with
2891 medium coverage rate */
2896 padding-right: 10px;
2897 background-color: #FFEA20;
2898 white-space: nowrap;
2899 font-family: sans-serif;
2902 /* Directory view/File view (all): percentage entry for files with
2903 low coverage rate */
2908 padding-right: 10px;
2909 background-color: #FF0000;
2911 font-family: sans-serif;
2914 /* Directory view/File view (all): line count entry for files with
2915 low coverage rate */
2920 padding-right: 10px;
2921 background-color: #FF0000;
2922 white-space: nowrap;
2923 font-family: sans-serif;
2926 /* File view (all): "show/hide details" link format */
2933 /* File view (all): "show/hide details" link - visited format */
2940 /* File view (all): "show/hide details" link - activated format */
2947 /* File view (detail): test name entry */
2951 padding-right: 10px;
2952 background-color: #DAE7FE;
2953 font-family: sans-serif;
2956 /* File view (detail): test percentage entry */
2961 padding-right: 10px;
2962 background-color: #DAE7FE;
2963 font-family: sans-serif;
2966 /* File view (detail): test lines count entry */
2971 padding-right: 10px;
2972 background-color: #DAE7FE;
2973 font-family: sans-serif;
2976 /* Test case descriptions: test name format*/
2979 font-family: sans-serif;
2983 /* Test case descriptions: description table body */
2988 padding-bottom: 10px;
2989 padding-right: 30px;
2990 background-color: #DAE7FE;
2993 /* Source code view: function entry */
2998 padding-right: 20px;
3000 background-color: #DAE7FE;
3001 font-family: monospace;
3004 /* Source code view: function entry zero count*/
3009 padding-right: 10px;
3010 background-color: #FF0000;
3012 font-family: sans-serif;
3015 /* Source code view: function entry nonzero count*/
3020 padding-right: 10px;
3021 background-color: #DAE7FE;
3023 font-family: sans-serif;
3026 /* Source code view: source code format */
3029 font-family: monospace;
3034 /* Source code view: line number format */
3037 background-color: #EFE383;
3040 /* Source code view: format for lines which were executed */
3044 background-color: #CAD7FE;
3047 /* Source code view: format for Cov legend */
3051 padding-right: 10px;
3052 padding-bottom: 2px;
3053 background-color: #CAD7FE;
3056 /* Source code view: format for lines which were not executed */
3060 background-color: #FF6230;
3063 /* Source code view: format for NoCov legend */
3064 span.coverLegendNoCov
3067 padding-right: 10px;
3068 padding-bottom: 2px;
3069 background-color: #FF6230;
3072 /* Source code view (function table): standard link - visited format */
3073 td.lineNoCov > a:visited,
3074 td.lineCov > a:visited
3077 text-decoration: underline;
3080 /* Source code view: format for lines which were executed only in a
3084 background-color: #B5F7AF;
3087 /* Source code view: format for branches which were executed
3091 background-color: #CAD7FE;
3094 /* Source code view: format for branches which were executed
3098 background-color: #FF6230;
3101 /* Source code view: format for branches which were not executed */
3104 background-color: #FF6230;
3107 /* Source code view: format for the source code heading line */
3111 font-family: monospace;
3116 /* All views: header legend value for low rate */
3119 font-family: sans-serif;
3121 white-space: nowrap;
3124 background-color: #FF0000;
3128 /* All views: header legend value for med rate */
3131 font-family: sans-serif;
3133 white-space: nowrap;
3136 background-color: #FFEA20;
3140 /* All views: header legend value for hi rate */
3143 font-family: sans-serif;
3145 white-space: nowrap;
3148 background-color: #A7FC9D;
3152 /* All views except source code view: legend format for low coverage */
3153 span.coverLegendCovLo
3156 padding-right: 10px;
3158 background-color: #FF0000;
3161 /* All views except source code view: legend format for med coverage */
3162 span.coverLegendCovMed
3165 padding-right: 10px;
3167 background-color: #FFEA20;
3170 /* All views except source code view: legend format for hi coverage */
3171 span.coverLegendCovHi
3174 padding-right: 10px;
3176 background-color: #A7FC9D;
3181 # *************************************************************
3184 # Remove leading tab from all lines
3185 $css_data =~ s/^\t//gm;
3187 print(CSS_HANDLE $css_data);
3194 # get_bar_graph_code(base_dir, cover_found, cover_hit)
3196 # Return a string containing HTML code which implements a bar graph display
3197 # for a coverage rate of cover_hit * 100 / cover_found.
3200 sub get_bar_graph_code($$$)
3202 my ($base_dir, $found, $hit) = @_;
3210 # Check number of instrumented lines
3211 if ($_[1] == 0) { return ""; }
3213 $alt = rate($hit, $found, "%");
3214 $width = rate($hit, $found, undef, 0);
3215 $remainder = 100 - $width;
3217 # Decide which .png file to use
3218 $png_name = $rate_png[classify_rate($found, $hit, $med_limit,
3224 $graph_code = (<<END_OF_HTML
)
3225 <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
>
3229 elsif ($width == 100)
3232 $graph_code = (<<END_OF_HTML)
3233 <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>
3240 $graph_code = (<<END_OF_HTML
)
3241 <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
>
3246 # Remove leading tabs from all lines
3247 $graph_code =~ s/^\t+//gm;
3250 return($graph_code);
3254 # sub classify_rate(found, hit, med_limit, high_limit)
3256 # Return 0 for low rate, 1 for medium rate and 2 for hi rate.
3259 sub classify_rate
($$$$)
3261 my ($found, $hit, $med, $hi) = @_;
3267 $rate = rate
($hit, $found);
3270 } elsif ($rate < $hi) {
3278 # write_html(filehandle, html_code)
3280 # Write out HTML_CODE to FILEHANDLE while removing a leading tabulator mark
3281 # in each line of HTML_CODE.
3286 local *HTML_HANDLE
= $_[0];
3287 my $html_code = $_[1];
3289 # Remove leading tab from all lines
3290 $html_code =~ s/^\t//gm;
3292 print(HTML_HANDLE
$html_code)
3293 or die("ERROR: cannot write HTML data ($!)\n");
3298 # write_html_prolog(filehandle, base_dir, pagetitle)
3300 # Write an HTML prolog common to all HTML files to FILEHANDLE. PAGETITLE will
3301 # be used as HTML page title. BASE_DIR contains a relative path which points
3302 # to the base directory.
3305 sub write_html_prolog
(*$$)
3307 my $basedir = $_[1];
3308 my $pagetitle = $_[2];
3311 $prolog = $html_prolog;
3312 $prolog =~ s/\@pagetitle\@/$pagetitle/g;
3313 $prolog =~ s/\@basedir\@/$basedir/g;
3315 write_html
($_[0], $prolog);
3320 # write_header_prolog(filehandle, base_dir)
3322 # Write beginning of page header HTML code.
3325 sub write_header_prolog
(*$)
3327 # *************************************************************
3329 write_html
($_[0], <<END_OF_HTML)
3330 <table width="100%" border=0 cellspacing=0 cellpadding=0>
3331 <tr><td class="title">$title</td></tr>
3332 <tr><td class="ruler"><img src="$_[1]glass.png" width=3 height=3 alt=""></td></tr>
3336 <table cellpadding=1 border=0 width="100%">
3340 # *************************************************************
3345 # write_header_line(handle, content)
3347 # Write a header line with the specified table contents.
3350 sub write_header_line(*@)
3352 my ($handle, @content) = @_;
3355 write_html($handle, " <tr>\n");
3356 foreach $entry (@content) {
3357 my ($width, $class, $text, $colspan) = @{$entry};
3359 if (defined($width)) {
3360 $width = " width=\"$width\"";
3364 if (defined($class)) {
3365 $class = " class=\"$class\"";
3369 if (defined($colspan)) {
3370 $colspan = " colspan=\"$colspan\"";
3374 $text = "" if (!defined($text));
3376 " <td$width$class$colspan>$text</td>\n");
3378 write_html($handle, " </tr>\n");
3383 # write_header_epilog(filehandle, base_dir)
3385 # Write end of page header HTML code.
3388 sub write_header_epilog(*$)
3390 # *************************************************************
3392 write_html($_[0], <<END_OF_HTML
)
3393 <tr
><td
><img src
="$_[1]glass.png" width
=3 height
=3 alt
=""></td></tr
>
3398 <tr
><td
class="ruler"><img src
="$_[1]glass.png" width
=3 height
=3 alt
=""></td></tr
>
3404 # *************************************************************
3409 # write_file_table_prolog(handle, file_heading, ([heading, num_cols], ...))
3411 # Write heading for file table.
3414 sub write_file_table_prolog
(*$@
)
3416 my ($handle, $file_heading, @columns) = @_;
3417 my $num_columns = 0;
3422 $width = 20 if (scalar(@columns) == 1);
3423 $width = 10 if (scalar(@columns) == 2);
3424 $width = 8 if (scalar(@columns) > 2);
3426 foreach $col (@columns) {
3427 my ($heading, $cols) = @
{$col};
3429 $num_columns += $cols;
3431 $file_width = 100 - $num_columns * $width;
3434 write_html
($handle, <<END_OF_HTML);
3436 <table width="80%" cellpadding=1 cellspacing=1 border=0>
3439 <td width="$file_width%"><br></td>
3442 foreach $col (@columns) {
3443 my ($heading, $cols) = @{$col};
3445 while ($cols-- > 0) {
3446 write_html($handle, <<END_OF_HTML
);
3447 <td width
="$width%"></td
>
3452 write_html
($handle, <<END_OF_HTML);
3456 <td class="tableHead">$file_heading</td>
3459 foreach $col (@columns) {
3460 my ($heading, $cols) = @{$col};
3463 $colspan = " colspan=$cols" if ($cols > 1);
3464 write_html($handle, <<END_OF_HTML
);
3465 <td
class="tableHead"$colspan>$heading</td
>
3468 write_html
($handle, <<END_OF_HTML);
3474 # write_file_table_entry(handle, base_dir, filename, page_link,
3475 # ([ found, hit, med_limit, hi_limit, graph ], ..)
3477 # Write an entry of the file table.
3480 sub write_file_table_entry(*$$$@)
3482 my ($handle, $base_dir, $filename, $page_link, @entries) = @_;
3485 my $esc_filename = escape_html($filename);
3487 # Add link to source if provided
3488 if (defined($page_link) && $page_link ne "") {
3489 $file_code = "<a href=\"$page_link\">$esc_filename</a>";
3491 $file_code = $esc_filename;
3494 # First column: filename
3495 write_html($handle, <<END_OF_HTML
);
3497 <td
class="coverFile">$file_code</td
>
3499 # Columns as defined
3500 foreach $entry (@entries) {
3501 my ($found, $hit, $med, $hi, $graph) = @
{$entry};
3506 # Generate bar graph if requested
3508 $bar_graph = get_bar_graph_code
($base_dir, $found,
3510 write_html
($handle, <<END_OF_HTML);
3511 <td class="coverBar" align="center">
3516 # Get rate color and text
3521 $rate = rate($hit, $found, " %");
3522 $class = $rate_name[classify_rate($found, $hit,
3525 write_html($handle, <<END_OF_HTML
);
3526 <td
class="coverPer$class">$rate</td
>
3527 <td
class="coverNum$class">$hit / $found</td
>
3531 write_html
($handle, <<END_OF_HTML);
3538 # write_file_table_detail_entry(filehandle, test_name, ([found, hit], ...))
3540 # Write entry for detail section in file table.
3543 sub write_file_table_detail_entry(*$@)
3545 my ($handle, $test, @entries) = @_;
3549 $test = "<span style=\"font-style:italic\"><unnamed></span>";
3550 } elsif ($test =~ /^(.*),diff$/) {
3551 $test = $1." (converted)";
3554 write_html($handle, <<END_OF_HTML
);
3556 <td
class="testName" colspan
=2>$test</td
>
3559 foreach $entry (@entries) {
3560 my ($found, $hit) = @
{$entry};
3561 my $rate = rate
($hit, $found, " %");
3563 write_html
($handle, <<END_OF_HTML);
3564 <td class="testPer">$rate</td>
3565 <td class="testNum">$hit / $found</td>
3569 write_html($handle, <<END_OF_HTML
);
3574 # *************************************************************
3579 # write_file_table_epilog(filehandle)
3581 # Write end of file table HTML code.
3584 sub write_file_table_epilog
(*)
3586 # *************************************************************
3588 write_html
($_[0], <<END_OF_HTML)
3596 # *************************************************************
3601 # write_test_table_prolog(filehandle, table_heading)
3603 # Write heading for test case description table.
3606 sub write_test_table_prolog(*$)
3608 # *************************************************************
3610 write_html($_[0], <<END_OF_HTML
)
3612 <table width
="80%" cellpadding
=2 cellspacing
=1 border
=0>
3619 <td
class="tableHead">$_[1]</td
>
3623 <td
class="testDescription">
3628 # *************************************************************
3633 # write_test_table_entry(filehandle, test_name, test_description)
3635 # Write entry for the test table.
3638 sub write_test_table_entry
(*$$)
3640 # *************************************************************
3642 write_html
($_[0], <<END_OF_HTML)
3643 <dt>$_[1]<a name="$_[1]"> </a></dt>
3644 <dd>$_[2]<br><br></dd>
3648 # *************************************************************
3653 # write_test_table_epilog(filehandle)
3655 # Write end of test description table HTML code.
3658 sub write_test_table_epilog(*)
3660 # *************************************************************
3662 write_html($_[0], <<END_OF_HTML
)
3673 # *************************************************************
3677 sub fmt_centered
($$)
3679 my ($width, $text) = @_;
3680 my $w0 = length($text);
3681 my $w1 = int(($width - $w0) / 2);
3682 my $w2 = $width - $w0 - $w1;
3684 return (" "x
$w1).$text.(" "x
$w2);
3689 # write_source_prolog(filehandle)
3691 # Write start of source code table.
3694 sub write_source_prolog
(*)
3696 my $lineno_heading = " ";
3697 my $branch_heading = "";
3698 my $line_heading = fmt_centered
($line_field_width, "Line data");
3699 my $source_heading = " Source code";
3702 $branch_heading = fmt_centered
($br_field_width, "Branch data").
3705 # *************************************************************
3707 write_html
($_[0], <<END_OF_HTML)
3708 <table cellpadding=0 cellspacing=0 border=0>
3714 <pre class="sourceHeading">${lineno_heading}${branch_heading}${line_heading} ${source_heading}</pre>
3715 <pre class="source">
3719 # *************************************************************
3724 # get_branch_blocks(brdata)
3726 # Group branches that belong to the same basic block.
3728 # Returns: [block1, block2, ...]
3729 # block: [branch1, branch2, ...]
3730 # branch: [block_num, branch_num, taken_count, text_length, open, close]
3733 sub get_branch_blocks($)
3740 my $num = br_ivec_len($brdata);
3743 for ($i = 0; $i < $num; $i++) {
3744 my ($block_num, $branch, $taken) = br_ivec_get($brdata, $i);
3747 if (defined($last_block_num) && $block_num != $last_block_num) {
3748 push(@blocks, $block);
3751 $br = [$block_num, $branch, $taken, 3, 0, 0];
3752 push(@{$block}, $br);
3753 $last_block_num = $block_num;
3755 push(@blocks, $block) if (scalar(@{$block}) > 0);
3757 # Add braces to first and last branch in group
3758 foreach $block (@blocks) {
3759 $block->[0]->[$BR_OPEN] = 1;
3760 $block->[0]->[$BR_LEN]++;
3761 $block->[scalar(@{$block}) - 1]->[$BR_CLOSE] = 1;
3762 $block->[scalar(@{$block}) - 1]->[$BR_LEN]++;
3769 # get_block_len(block)
3771 # Calculate total text length of all branches in a block of branches.
3774 sub get_block_len($)
3780 foreach $branch (@{$block}) {
3781 $len += $branch->[$BR_LEN];
3789 # get_branch_html(brdata)
3791 # Return a list of HTML lines which represent the specified branch coverage
3792 # data in source code view.
3795 sub get_branch_html($)
3798 my @blocks = get_branch_blocks($brdata);
3802 my $line = []; # [branch2|" ", branch|" ", ...]
3803 my @lines; # [line1, line2, ...]
3806 # Distribute blocks to lines
3807 foreach $block (@blocks) {
3808 my $block_len = get_block_len($block);
3810 # Does this block fit into the current line?
3811 if ($line_len + $block_len <= $br_field_width) {
3813 $line_len += $block_len;
3814 push(@{$line}, @{$block});
3816 } elsif ($block_len <= $br_field_width) {
3817 # It would fit if the line was empty - add it to new
3819 push(@lines, $line);
3820 $line_len = $block_len;
3821 $line = [ @{$block} ];
3824 # Split the block into several lines
3825 foreach $branch (@{$block}) {
3826 if ($line_len + $branch->[$BR_LEN] >= $br_field_width) {
3828 if (($line_len + 1 <= $br_field_width) &&
3829 scalar(@{$line}) > 0 &&
3830 !$line->[scalar(@$line) - 1]->[$BR_CLOSE]) {
3831 # Try to align branch symbols to be in
3833 push(@{$line}, " ");
3835 push(@lines, $line);
3839 push(@{$line}, $branch);
3840 $line_len += $branch->[$BR_LEN];
3843 push(@lines, $line);
3846 foreach $line (@lines) {
3848 my $current_len = 0;
3850 foreach $branch (@$line) {
3851 # Skip alignment space
3852 if ($branch eq " ") {
3858 my ($block_num, $br_num, $taken, $len, $open, $close) =
3864 if ($taken eq '-') {
3865 $class = "branchNoExec";
3867 $title = "Branch $br_num was not executed";
3868 } elsif ($taken == 0) {
3869 $class = "branchNoCov";
3871 $title = "Branch $br_num was not taken";
3873 $class = "branchCov";
3875 $title = "Branch $br_num was taken $taken ".
3877 $title .= "s" if ($taken > 1);
3879 $current .= "[" if ($open);
3880 $current .= "<span class=\"$class\" title=\"$title\">";
3881 $current .= $text."</span>";
3882 $current .= "]" if ($close);
3883 $current_len += $len;
3886 # Right-align result text
3887 if ($current_len < $br_field_width) {
3888 $current = (" "x($br_field_width - $current_len)).
3891 push(@result, $current);
3899 # format_count(count, width)
3901 # Return a right-aligned representation of count that fits in width characters.
3904 sub format_count($$)
3906 my ($count, $width) = @_;
3910 $result = sprintf("%*.0f", $width, $count);
3911 while (length($result) > $width) {
3912 last if ($count < 10);
3914 $count = int($count/10);
3915 $result = sprintf("%*s", $width, ">$count*10^$exp");
3921 # write_source_line(filehandle, line_num, source, hit_count, converted,
3922 # brdata, add_anchor)
3924 # Write formatted source code line. Return a line in a format as needed
3928 sub write_source_line(*$$$$$$)
3930 my ($handle, $line, $source, $count, $converted, $brdata,
3935 my $anchor_start = "";
3936 my $anchor_end = "";
3937 my $count_field_width = $line_field_width - 1;
3941 # Get branch HTML data for this line
3942 @br_html = get_branch_html($brdata) if ($br_coverage);
3944 if (!defined($count)) {
3946 $source_format = "";
3947 $count_format = " "x$count_field_width;
3949 elsif ($count == 0) {
3951 $source_format = '<span class="lineNoCov">';
3952 $count_format = format_count($count, $count_field_width);
3954 elsif ($converted && defined($highlight)) {
3955 $result = "*".$count;
3956 $source_format = '<span class="lineDiffCov">';
3957 $count_format = format_count($count, $count_field_width);
3961 $source_format = '<span class="lineCov">';
3962 $count_format = format_count($count, $count_field_width);
3964 $result .= ":".$source;
3966 # Write out a line number navigation anchor every $nav_resolution
3967 # lines if necessary
3970 $anchor_start = "<a name=\"$_[1]\">";
3971 $anchor_end = "</a>";
3975 # *************************************************************
3977 $html = $anchor_start;
3978 $html .= "<span class=\"lineNum\">".sprintf("%8d", $line)." </span>";
3979 $html .= shift(@br_html).":" if ($br_coverage);
3980 $html .= "$source_format$count_format : ";
3981 $html .= escape_html($source);
3982 $html .= "</span>" if ($source_format);
3983 $html .= $anchor_end."\n";
3985 write_html($handle, $html);
3988 # Add lines for overlong branch information
3989 foreach (@br_html) {
3990 write_html($handle, "<span class=\"lineNum\">".
3994 # *************************************************************
4001 # write_source_epilog(filehandle)
4003 # Write end of source code table.
4006 sub write_source_epilog(*)
4008 # *************************************************************
4010 write_html($_[0], <<END_OF_HTML
)
4020 # *************************************************************
4025 # write_html_epilog(filehandle, base_dir[, break_frames])
4027 # Write HTML page footer to FILEHANDLE. BREAK_FRAMES should be set when
4028 # this page is embedded in a frameset, clicking the URL link will then
4029 # break this frameset.
4032 sub write_html_epilog
(*$;$)
4034 my $basedir = $_[1];
4035 my $break_code = "";
4040 $break_code = " target=\"_parent\"";
4043 # *************************************************************
4045 write_html
($_[0], <<END_OF_HTML)
4046 <table width="100%" border=0 cellspacing=0 cellpadding=0>
4047 <tr><td class="ruler"><img src="$_[1]glass.png" width=3 height=3 alt=""></td></tr>
4048 <tr><td class="versionInfo">Generated by: <a href="$lcov_url"$break_code>$lcov_version</a></td></tr>
4054 $epilog = $html_epilog;
4055 $epilog =~ s/\@basedir\@/$basedir/g;
4057 write_html($_[0], $epilog);
4062 # write_frameset(filehandle, basedir, basename, pagetitle)
4066 sub write_frameset(*$$$)
4068 my $frame_width = $overview_width + 40;
4070 # *************************************************************
4072 write_html($_[0], <<END_OF_HTML
)
4073 <!DOCTYPE HTML PUBLIC
"-//W3C//DTD HTML 4.01 Frameset//EN">
4078 <meta http
-equiv
="Content-Type" content
="text/html; charset=$charset">
4079 <title
>$_[3]</title
>
4080 <link rel
="stylesheet" type
="text/css" href
="$_[1]gcov.css">
4083 <frameset cols
="$frame_width,*">
4084 <frame src
="$_[2].gcov.overview.$html_ext" name
="overview">
4085 <frame src
="$_[2].gcov.$html_ext" name
="source">
4087 <center
>Frames
not supported by your browser
!<br
></center
>
4095 # *************************************************************
4100 # sub write_overview_line(filehandle, basename, line, link)
4104 sub write_overview_line
(*$$$)
4107 my $y2 = $y1 + $nav_resolution - 1;
4108 my $x2 = $overview_width - 1;
4110 # *************************************************************
4112 write_html
($_[0], <<END_OF_HTML)
4113 <area shape="rect" coords="0,$y1,$x2,$y2" href="$_[1].gcov.$html_ext#$_[3]" target="source" alt="overview">
4117 # *************************************************************
4122 # write_overview(filehandle, basedir, basename, pagetitle, lines)
4126 sub write_overview(*$$$$)
4129 my $max_line = $_[4] - 1;
4132 # *************************************************************
4134 write_html($_[0], <<END_OF_HTML
)
4135 <!DOCTYPE HTML PUBLIC
"-//W3C//DTD HTML 4.01 Transitional//EN">
4140 <title
>$_[3]</title
>
4141 <meta http
-equiv
="Content-Type" content
="text/html; charset=$charset">
4142 <link rel
="stylesheet" type
="text/css" href
="$_[1]gcov.css">
4146 <map name
="overview">
4150 # *************************************************************
4152 # Make $offset the next higher multiple of $nav_resolution
4153 $offset = ($nav_offset + $nav_resolution - 1) / $nav_resolution;
4154 $offset = sprintf("%d", $offset ) * $nav_resolution;
4156 # Create image map for overview image
4157 for ($index = 1; $index <= $_[4]; $index += $nav_resolution)
4159 # Enforce nav_offset
4160 if ($index < $offset + 1)
4162 write_overview_line
($_[0], $_[2], $index, 1);
4166 write_overview_line
($_[0], $_[2], $index, $index - $offset);
4170 # *************************************************************
4172 write_html
($_[0], <<END_OF_HTML)
4176 <a href="$_[2].gcov.$html_ext#top" target="source">Top</a><br><br>
4177 <img src="$_[2].gcov.png" width=$overview_width height=$max_line alt="Overview" border=0 usemap="#overview">
4184 # *************************************************************
4192 return $a if ($a > $b);
4198 # write_header(filehandle, type, trunc_file_name, rel_file_name, lines_found,
4199 # lines_hit, funcs_found, funcs_hit, sort_type)
4201 # Write a complete standard page header. TYPE may be (0, 1, 2, 3, 4)
4202 # corresponding to (directory view header, file view header, source view
4203 # header, test case description header, function view header)
4206 sub write_header(*$$$$$$$$$$)
4208 local *HTML_HANDLE = $_[0];
4210 my $trunc_name = $_[2];
4211 my $rel_filename = $_[3];
4212 my $lines_found = $_[4];
4213 my $lines_hit = $_[5];
4214 my $fn_found = $_[6];
4216 my $br_found = $_[8];
4218 my $sort_type = $_[10];
4229 my $esc_trunc_name = escape_html($trunc_name);
4231 $base_name = basename($rel_filename);
4233 # Prepare text for "current view" field
4234 if ($type == $HDR_DIR)
4238 $view = $overview_title;
4240 elsif ($type == $HDR_FILE)
4242 # Directory overview
4243 $base_dir = get_relative_base_path($rel_filename);
4244 $view = "<a href=\"$base_dir"."index.$html_ext\">".
4245 "$overview_title</a> - $esc_trunc_name";
4247 elsif ($type == $HDR_SOURCE || $type == $HDR_FUNC)
4250 my $dir_name = dirname($rel_filename);
4251 my $esc_base_name = escape_html($base_name);
4252 my $esc_dir_name = escape_html($dir_name);
4254 $base_dir = get_relative_base_path($dir_name);
4257 # Need to break frameset when clicking any of these
4259 $view = "<a href=\"$base_dir"."index.$html_ext\" ".
4260 "target=\"_parent\">$overview_title</a> - ".
4261 "<a href=\"index.$html_ext\" target=\"_parent\">".
4262 "$esc_dir_name</a> - $esc_base_name";
4266 $view = "<a href=\"$base_dir"."index.$html_ext\">".
4267 "$overview_title</a> - ".
4268 "<a href=\"index.$html_ext\">".
4269 "$esc_dir_name</a> - $esc_base_name";
4272 # Add function suffix
4273 if ($func_coverage) {
4274 $view .= "<span style=\"font-size: 80%;\">";
4275 if ($type == $HDR_SOURCE) {
4276 $view .= " (source / <a href=\"$base_name.func.$html_ext\">functions</a>)";
4277 } elsif ($type == $HDR_FUNC) {
4278 $view .= " (<a href=\"$base_name.gcov.$html_ext\">source</a> / functions)";
4283 elsif ($type == $HDR_TESTDESC)
4285 # Test description header
4287 $view = "<a href=\"$base_dir"."index.$html_ext\">".
4288 "$overview_title</a> - test case descriptions";
4291 # Prepare text for "test" field
4292 $test = escape_html($test_title);
4294 # Append link to test description page if available
4295 if (%test_description && ($type != $HDR_TESTDESC))
4297 if ($frames && ($type == $HDR_SOURCE || $type == $HDR_FUNC))
4299 # Need to break frameset when clicking this link
4300 $test .= " ( <span style=\"font-size:80%;\">".
4301 "<a href=\"$base_dir".
4302 "descriptions.$html_ext\" target=\"_parent\">".
4303 "view descriptions</a></span> )";
4307 $test .= " ( <span style=\"font-size:80%;\">".
4308 "<a href=\"$base_dir".
4309 "descriptions.$html_ext\">".
4310 "view descriptions</a></span> )";
4315 write_header_prolog(*HTML_HANDLE, $base_dir);
4318 push(@row_left, [[ "10%", "headerItem", "Current view:" ],
4319 [ "35%", "headerValue", $view ]]);
4320 push(@row_left, [[undef, "headerItem", "Test:"],
4321 [undef, "headerValue", $test]]);
4322 push(@row_left, [[undef, "headerItem", "Date:"],
4323 [undef, "headerValue", $date]]);
4326 if ($legend && ($type == $HDR_SOURCE || $type == $HDR_FUNC)) {
4327 my $text = <<END_OF_HTML
;
4329 <span
class="coverLegendCov">hit
</span
>
4330 <span
class="coverLegendNoCov">not hit
</span
>
4333 $text .= <<END_OF_HTML;
4335 <span class="coverLegendCov">+</span> taken
4336 <span class="coverLegendNoCov">-</span> not taken
4337 <span class="coverLegendNoCov">#</span> not executed
4340 push(@row_left, [[undef, "headerItem", "Legend:"],
4341 [undef, "headerValueLeg", $text]]);
4342 } elsif ($legend && ($type != $HDR_TESTDESC)) {
4343 my $text = <<END_OF_HTML
;
4345 <span
class="coverLegendCovLo" title
="Coverage rates below $med_limit % are classified as low">low
: < $med_limit %</span
>
4346 <span
class="coverLegendCovMed" title
="Coverage rates between $med_limit % and $hi_limit % are classified as medium">medium
: >= $med_limit %</span
>
4347 <span
class="coverLegendCovHi" title
="Coverage rates of $hi_limit % and more are classified as high">high
: >= $hi_limit %</span
>
4349 push(@row_left, [[undef, "headerItem", "Legend:"],
4350 [undef, "headerValueLeg", $text]]);
4352 if ($type == $HDR_TESTDESC) {
4353 push(@row_right, [[ "55%" ]]);
4355 push(@row_right, [["15%", undef, undef ],
4356 ["10%", "headerCovTableHead", "Hit" ],
4357 ["10%", "headerCovTableHead", "Total" ],
4358 ["15%", "headerCovTableHead", "Coverage"]]);
4361 $style = $rate_name[classify_rate
($lines_found, $lines_hit,
4362 $med_limit, $hi_limit)];
4363 $rate = rate
($lines_hit, $lines_found, " %");
4364 push(@row_right, [[undef, "headerItem", "Lines:"],
4365 [undef, "headerCovTableEntry", $lines_hit],
4366 [undef, "headerCovTableEntry", $lines_found],
4367 [undef, "headerCovTableEntry$style", $rate]])
4368 if ($type != $HDR_TESTDESC);
4370 if ($func_coverage) {
4371 $style = $rate_name[classify_rate
($fn_found, $fn_hit,
4372 $fn_med_limit, $fn_hi_limit)];
4373 $rate = rate
($fn_hit, $fn_found, " %");
4374 push(@row_right, [[undef, "headerItem", "Functions:"],
4375 [undef, "headerCovTableEntry", $fn_hit],
4376 [undef, "headerCovTableEntry", $fn_found],
4377 [undef, "headerCovTableEntry$style", $rate]])
4378 if ($type != $HDR_TESTDESC);
4382 $style = $rate_name[classify_rate
($br_found, $br_hit,
4383 $br_med_limit, $br_hi_limit)];
4384 $rate = rate
($br_hit, $br_found, " %");
4385 push(@row_right, [[undef, "headerItem", "Branches:"],
4386 [undef, "headerCovTableEntry", $br_hit],
4387 [undef, "headerCovTableEntry", $br_found],
4388 [undef, "headerCovTableEntry$style", $rate]])
4389 if ($type != $HDR_TESTDESC);
4393 $num_rows = max
(scalar(@row_left), scalar(@row_right));
4394 for ($i = 0; $i < $num_rows; $i++) {
4395 my $left = $row_left[$i];
4396 my $right = $row_right[$i];
4398 if (!defined($left)) {
4399 $left = [[undef, undef, undef], [undef, undef, undef]];
4401 if (!defined($right)) {
4404 write_header_line
(*HTML_HANDLE
, @
{$left},
4405 [ $i == 0 ?
"5%" : undef, undef, undef],
4410 write_header_epilog
(*HTML_HANDLE
, $base_dir);
4415 # get_sorted_keys(hash_ref, sort_type)
4418 sub get_sorted_keys
($$)
4420 my ($hash, $type) = @_;
4422 if ($type == $SORT_FILE) {
4424 return sort(keys(%{$hash}));
4425 } elsif ($type == $SORT_LINE) {
4426 # Sort by line coverage
4427 return sort({$hash->{$a}[7] <=> $hash->{$b}[7]} keys(%{$hash}));
4428 } elsif ($type == $SORT_FUNC) {
4429 # Sort by function coverage;
4430 return sort({$hash->{$a}[8] <=> $hash->{$b}[8]} keys(%{$hash}));
4431 } elsif ($type == $SORT_BRANCH) {
4432 # Sort by br coverage;
4433 return sort({$hash->{$a}[9] <=> $hash->{$b}[9]} keys(%{$hash}));
4437 sub get_sort_code
($$$)
4439 my ($link, $alt, $base) = @_;
4444 if (!defined($link)) {
4449 $png = "updown.png";
4450 $link_start = '<a href="'.$link.'">';
4454 return ' <span class="tableHeadSort">'.$link_start.
4455 '<img src="'.$base.$png.'" width=10 height=14 '.
4456 'alt="'.$alt.'" title="'.$alt.'" border=0>'.$link_end.'</span>';
4459 sub get_file_code
($$$$)
4461 my ($type, $text, $sort_button, $base) = @_;
4466 if ($type == $HEAD_NO_DETAIL) {
4467 $link = "index.$html_ext";
4469 $link = "index-detail.$html_ext";
4472 $result .= get_sort_code
($link, "Sort by name", $base);
4477 sub get_line_code
($$$$$)
4479 my ($type, $sort_type, $text, $sort_button, $base) = @_;
4483 if ($type == $HEAD_NO_DETAIL) {
4486 $sort_link = "index-sort-l.$html_ext";
4488 } elsif ($type == $HEAD_DETAIL_HIDDEN) {
4489 # Text + link to detail view
4490 $result .= ' ( <a class="detail" href="index-detail'.
4491 $fileview_sortname[$sort_type].'.'.$html_ext.
4492 '">show details</a> )';
4494 $sort_link = "index-sort-l.$html_ext";
4497 # Text + link to standard view
4498 $result .= ' ( <a class="detail" href="index'.
4499 $fileview_sortname[$sort_type].'.'.$html_ext.
4500 '">hide details</a> )';
4502 $sort_link = "index-detail-sort-l.$html_ext";
4506 $result .= get_sort_code
($sort_link, "Sort by line coverage", $base);
4511 sub get_func_code
($$$$)
4513 my ($type, $text, $sort_button, $base) = @_;
4518 if ($type == $HEAD_NO_DETAIL) {
4519 $link = "index-sort-f.$html_ext";
4521 $link = "index-detail-sort-f.$html_ext";
4524 $result .= get_sort_code
($link, "Sort by function coverage", $base);
4528 sub get_br_code
($$$$)
4530 my ($type, $text, $sort_button, $base) = @_;
4535 if ($type == $HEAD_NO_DETAIL) {
4536 $link = "index-sort-b.$html_ext";
4538 $link = "index-detail-sort-b.$html_ext";
4541 $result .= get_sort_code
($link, "Sort by branch coverage", $base);
4546 # write_file_table(filehandle, base_dir, overview, testhash, testfnchash,
4547 # testbrhash, fileview, sort_type)
4549 # Write a complete file table. OVERVIEW is a reference to a hash containing
4550 # the following mapping:
4552 # filename -> "lines_found,lines_hit,funcs_found,funcs_hit,page_link,
4555 # TESTHASH is a reference to the following hash:
4557 # filename -> \%testdata
4558 # %testdata: name of test affecting this file -> \%testcount
4559 # %testcount: line number -> execution count for a single test
4561 # Heading of first column is "Filename" if FILEVIEW is true, "Directory name"
4565 sub write_file_table
(*$$$$$$$)
4567 local *HTML_HANDLE
= $_[0];
4568 my $base_dir = $_[1];
4569 my $overview = $_[2];
4570 my $testhash = $_[3];
4571 my $testfnchash = $_[4];
4572 my $testbrhash = $_[5];
4573 my $fileview = $_[6];
4574 my $sort_type = $_[7];
4588 my %affecting_tests;
4595 # Determine HTML code for column headings
4596 if (($base_dir ne "") && $show_details)
4598 my $detailed = keys(%{$testhash});
4600 $file_code = get_file_code
($detailed ?
$HEAD_DETAIL_HIDDEN :
4602 $fileview ?
"Filename" : "Directory",
4603 $sort && $sort_type != $SORT_FILE,
4605 $line_code = get_line_code
($detailed ?
$HEAD_DETAIL_SHOWN :
4606 $HEAD_DETAIL_HIDDEN,
4609 $sort && $sort_type != $SORT_LINE,
4611 $func_code = get_func_code
($detailed ?
$HEAD_DETAIL_HIDDEN :
4614 $sort && $sort_type != $SORT_FUNC,
4616 $br_code = get_br_code
($detailed ?
$HEAD_DETAIL_HIDDEN :
4619 $sort && $sort_type != $SORT_BRANCH,
4622 $file_code = get_file_code
($HEAD_NO_DETAIL,
4623 $fileview ?
"Filename" : "Directory",
4624 $sort && $sort_type != $SORT_FILE,
4626 $line_code = get_line_code
($HEAD_NO_DETAIL, $sort_type, "Line Coverage",
4627 $sort && $sort_type != $SORT_LINE,
4629 $func_code = get_func_code
($HEAD_NO_DETAIL, "Functions",
4630 $sort && $sort_type != $SORT_FUNC,
4632 $br_code = get_br_code
($HEAD_NO_DETAIL, "Branches",
4633 $sort && $sort_type != $SORT_BRANCH,
4636 push(@head_columns, [ $line_code, 3 ]);
4637 push(@head_columns, [ $func_code, 2]) if ($func_coverage);
4638 push(@head_columns, [ $br_code, 2]) if ($br_coverage);
4640 write_file_table_prolog
(*HTML_HANDLE
, $file_code, @head_columns);
4642 foreach $filename (get_sorted_keys
($overview, $sort_type))
4645 ($found, $hit, $fn_found, $fn_hit, $br_found, $br_hit,
4646 $page_link) = @
{$overview->{$filename}};
4649 push(@columns, [$found, $hit, $med_limit, $hi_limit, 1]);
4651 if ($func_coverage) {
4652 push(@columns, [$fn_found, $fn_hit, $fn_med_limit,
4657 push(@columns, [$br_found, $br_hit, $br_med_limit,
4660 write_file_table_entry
(*HTML_HANDLE
, $base_dir, $filename,
4661 $page_link, @columns);
4663 $testdata = $testhash->{$filename};
4664 $testfncdata = $testfnchash->{$filename};
4665 $testbrdata = $testbrhash->{$filename};
4667 # Check whether we should write test specific coverage
4669 if (!($show_details && $testdata)) { next; }
4671 # Filter out those tests that actually affect this file
4672 %affecting_tests = %{ get_affecting_tests
($testdata,
4673 $testfncdata, $testbrdata) };
4675 # Does any of the tests affect this file at all?
4676 if (!%affecting_tests) { next; }
4678 foreach $testname (keys(%affecting_tests))
4681 ($found, $hit, $fn_found, $fn_hit, $br_found, $br_hit) =
4682 split(",", $affecting_tests{$testname});
4684 # Insert link to description of available
4685 if ($test_description{$testname})
4687 $testname = "<a href=\"$base_dir".
4688 "descriptions.$html_ext#$testname\">".
4692 push(@results, [$found, $hit]);
4693 push(@results, [$fn_found, $fn_hit]) if ($func_coverage);
4694 push(@results, [$br_found, $br_hit]) if ($br_coverage);
4695 write_file_table_detail_entry
(*HTML_HANDLE
, $testname,
4700 write_file_table_epilog
(*HTML_HANDLE
);
4705 # get_found_and_hit(hash)
4707 # Return the count for entries (found) and entries with an execution count
4708 # greater than zero (hit) in a hash (linenumber -> execution count) as
4709 # a list (found, hit)
4712 sub get_found_and_hit
($)
4714 my %hash = %{$_[0]};
4722 foreach (keys(%hash))
4725 if ($hash{$_}>0) { $hit++; }
4728 return ($found, $hit);
4733 # get_func_found_and_hit(sumfnccount)
4735 # Return (f_found, f_hit) for sumfnccount
4738 sub get_func_found_and_hit
($)
4740 my ($sumfnccount) = @_;
4745 $fn_found = scalar(keys(%{$sumfnccount}));
4747 foreach $function (keys(%{$sumfnccount})) {
4748 if ($sumfnccount->{$function} > 0) {
4752 return ($fn_found, $fn_hit);
4757 # br_taken_to_num(taken)
4759 # Convert a branch taken value .info format to number format.
4762 sub br_taken_to_num
($)
4766 return 0 if ($taken eq '-');
4772 # br_num_to_taken(taken)
4774 # Convert a branch taken value in number format to .info format.
4777 sub br_num_to_taken
($)
4781 return '-' if ($taken == 0);
4787 # br_taken_add(taken1, taken2)
4789 # Return the result of taken1 + taken2 for 'branch taken' values.
4792 sub br_taken_add
($$)
4796 return $t1 if (!defined($t2));
4797 return $t2 if (!defined($t1));
4798 return $t1 if ($t2 eq '-');
4799 return $t2 if ($t1 eq '-');
4805 # br_taken_sub(taken1, taken2)
4807 # Return the result of taken1 - taken2 for 'branch taken' values. Return 0
4808 # if the result would become negative.
4811 sub br_taken_sub
($$)
4815 return $t1 if (!defined($t2));
4816 return undef if (!defined($t1));
4817 return $t1 if ($t1 eq '-');
4818 return $t1 if ($t2 eq '-');
4819 return 0 if $t2 > $t1;
4825 # br_ivec_len(vector)
4827 # Return the number of entries in the branch coverage vector.
4834 return 0 if (!defined($vec));
4835 return (length($vec) * 8 / $BR_VEC_WIDTH) / $BR_VEC_ENTRIES;
4840 # br_ivec_get(vector, number)
4842 # Return an entry from the branch coverage vector.
4847 my ($vec, $num) = @_;
4851 my $offset = $num * $BR_VEC_ENTRIES;
4853 # Retrieve data from vector
4854 $block = vec($vec, $offset + $BR_BLOCK, $BR_VEC_WIDTH);
4855 $branch = vec($vec, $offset + $BR_BRANCH, $BR_VEC_WIDTH);
4856 $taken = vec($vec, $offset + $BR_TAKEN, $BR_VEC_WIDTH);
4858 # Decode taken value from an integer
4859 $taken = br_num_to_taken
($taken);
4861 return ($block, $branch, $taken);
4866 # br_ivec_push(vector, block, branch, taken)
4868 # Add an entry to the branch coverage vector. If an entry with the same
4869 # branch ID already exists, add the corresponding taken values.
4872 sub br_ivec_push
($$$$)
4874 my ($vec, $block, $branch, $taken) = @_;
4876 my $num = br_ivec_len
($vec);
4879 $vec = "" if (!defined($vec));
4881 # Check if branch already exists in vector
4882 for ($i = 0; $i < $num; $i++) {
4883 my ($v_block, $v_branch, $v_taken) = br_ivec_get
($vec, $i);
4885 next if ($v_block != $block || $v_branch != $branch);
4888 $taken = br_taken_add
($taken, $v_taken);
4892 $offset = $i * $BR_VEC_ENTRIES;
4893 $taken = br_taken_to_num
($taken);
4896 vec($vec, $offset + $BR_BLOCK, $BR_VEC_WIDTH) = $block;
4897 vec($vec, $offset + $BR_BRANCH, $BR_VEC_WIDTH) = $branch;
4898 vec($vec, $offset + $BR_TAKEN, $BR_VEC_WIDTH) = $taken;
4905 # get_br_found_and_hit(sumbrcount)
4907 # Return (br_found, br_hit) for sumbrcount
4910 sub get_br_found_and_hit
($)
4912 my ($sumbrcount) = @_;
4917 foreach $line (keys(%{$sumbrcount})) {
4918 my $brdata = $sumbrcount->{$line};
4920 my $num = br_ivec_len
($brdata);
4922 for ($i = 0; $i < $num; $i++) {
4925 (undef, undef, $taken) = br_ivec_get
($brdata, $i);
4928 $br_hit++ if ($taken ne "-" && $taken > 0);
4932 return ($br_found, $br_hit);
4937 # get_affecting_tests(testdata, testfncdata, testbrdata)
4939 # HASHREF contains a mapping filename -> (linenumber -> exec count). Return
4940 # a hash containing mapping filename -> "lines found, lines hit" for each
4941 # filename which has a nonzero hit count.
4944 sub get_affecting_tests
($$$)
4946 my ($testdata, $testfncdata, $testbrdata) = @_;
4959 foreach $testname (keys(%{$testdata}))
4961 # Get (line number -> count) hash for this test case
4962 $testcount = $testdata->{$testname};
4963 $testfnccount = $testfncdata->{$testname};
4964 $testbrcount = $testbrdata->{$testname};
4967 ($found, $hit) = get_found_and_hit
($testcount);
4968 ($fn_found, $fn_hit) = get_func_found_and_hit
($testfnccount);
4969 ($br_found, $br_hit) = get_br_found_and_hit
($testbrcount);
4973 $result{$testname} = "$found,$hit,$fn_found,$fn_hit,".
4974 "$br_found,$br_hit";
4982 sub get_hash_reverse
($)
4987 foreach (keys(%{$hash})) {
4988 $result{$hash->{$_}} = $_;
4995 # write_source(filehandle, source_filename, count_data, checksum_data,
4996 # converted_data, func_data, sumbrcount)
4998 # Write an HTML view of a source code file. Returns a list containing
4999 # data as needed by gen_png().
5004 sub write_source
($$$$$$$)
5006 local *HTML_HANDLE
= $_[0];
5007 local *SOURCE_HANDLE
;
5008 my $source_filename = $_[1];
5012 my $checkdata = $_[3];
5013 my $converted = $_[4];
5014 my $funcdata = $_[5];
5015 my $sumbrcount = $_[6];
5016 my $datafunc = get_hash_reverse
($funcdata);
5022 %count_data = %{$_[2]};
5025 if (!open(SOURCE_HANDLE
, "<", $source_filename)) {
5029 if (!$ignore[$ERROR_SOURCE]) {
5030 die("ERROR: cannot read $source_filename\n");
5033 # Continue without source file
5034 warn("WARNING: cannot read $source_filename!\n");
5036 @lines = sort( { $a <=> $b } keys(%count_data));
5038 $last_line = $lines[scalar(@lines) - 1];
5040 return ( ":" ) if ($last_line < 1);
5042 # Simulate gcov behavior
5043 for ($line_number = 1; $line_number <= $last_line;
5045 push(@file, "/* EOF */");
5048 @file = <SOURCE_HANDLE
>;
5051 write_source_prolog
(*HTML_HANDLE
);
5057 # Also remove CR from line-end
5060 # Source code matches coverage data?
5061 if (defined($checkdata->{$line_number}) &&
5062 ($checkdata->{$line_number} ne md5_base64
($_)))
5064 die("ERROR: checksum mismatch at $source_filename:".
5070 if (($line_number - 1) % $nav_resolution == 0) {
5074 if ($func_coverage) {
5075 if ($line_number == 1) {
5077 } elsif (defined($datafunc->{$line_number +
5083 write_source_line
(HTML_HANDLE
, $line_number,
5084 $_, $count_data{$line_number},
5085 $converted->{$line_number},
5086 $sumbrcount->{$line_number}, $add_anchor));
5089 close(SOURCE_HANDLE
);
5090 write_source_epilog
(*HTML_HANDLE
);
5095 sub funcview_get_func_code
($$$)
5097 my ($name, $base, $type) = @_;
5101 if ($sort && $type == 1) {
5102 $link = "$name.func.$html_ext";
5104 $result = "Function Name";
5105 $result .= get_sort_code
($link, "Sort by function name", $base);
5110 sub funcview_get_count_code
($$$)
5112 my ($name, $base, $type) = @_;
5116 if ($sort && $type == 0) {
5117 $link = "$name.func-sort-c.$html_ext";
5119 $result = "Hit count";
5120 $result .= get_sort_code
($link, "Sort by hit count", $base);
5126 # funcview_get_sorted(funcdata, sumfncdata, sort_type)
5128 # Depending on the value of sort_type, return a list of functions sorted
5129 # by name (type 0) or by the associated call count (type 1).
5132 sub funcview_get_sorted
($$$)
5134 my ($funcdata, $sumfncdata, $type) = @_;
5137 return sort(keys(%{$funcdata}));
5139 return sort({$sumfncdata->{$b} <=> $sumfncdata->{$a}}
5140 keys(%{$sumfncdata}));
5144 # write_function_table(filehandle, source_file, sumcount, funcdata,
5145 # sumfnccount, testfncdata, sumbrcount, testbrdata,
5146 # base_name, base_dir, sort_type)
5148 # Write an HTML table listing all functions in a source file, including
5149 # also function call counts and line coverages inside of each function.
5154 sub write_function_table
(*$$$$$$$$$$)
5156 local *HTML_HANDLE
= $_[0];
5158 my $sumcount = $_[2];
5159 my $funcdata = $_[3];
5160 my $sumfncdata = $_[4];
5161 my $testfncdata = $_[5];
5162 my $sumbrcount = $_[6];
5163 my $testbrdata = $_[7];
5171 # Get HTML code for headings
5172 $func_code = funcview_get_func_code
($name, $base, $type);
5173 $count_code = funcview_get_count_code
($name, $base, $type);
5174 write_html
(*HTML_HANDLE
, <<END_OF_HTML)
5176 <table width="60%" cellpadding=1 cellspacing=1 border=0>
5177 <tr><td><br></td></tr>
5179 <td width="80%" class="tableHead">$func_code</td>
5180 <td width="20%" class="tableHead">$count_code</td>
5185 # Get a sorted table
5186 foreach $func (funcview_get_sorted($funcdata, $sumfncdata, $type)) {
5187 if (!defined($funcdata->{$func}))
5192 my $startline = $funcdata->{$func} - $func_offset;
5194 my $count = $sumfncdata->{$name};
5197 # Demangle C++ function names if requested
5198 if ($demangle_cpp) {
5199 $name = `c++filt "$name"`;
5202 # Escape any remaining special characters
5203 $name = escape_html($name);
5204 if ($startline < 1) {
5208 $countstyle = "coverFnLo";
5210 $countstyle = "coverFnHi";
5213 write_html(*HTML_HANDLE, <<END_OF_HTML
)
5215 <td
class="coverFn"><a href
="$source#$startline">$name</a></td
>
5216 <td
class="$countstyle">$count</td
>
5221 write_html
(*HTML_HANDLE
, <<END_OF_HTML)
5231 # info(printf_parameter)
5233 # Use printf to write PRINTF_PARAMETER to stdout only when the $quiet flag
5248 # subtract_counts(data_ref, base_ref)
5251 sub subtract_counts($$)
5253 my %data = %{$_[0]};
5254 my %base = %{$_[1]};
5261 foreach $line (keys(%data))
5264 $data_count = $data{$line};
5265 $base_count = $base{$line};
5267 if (defined($base_count))
5269 $data_count -= $base_count;
5271 # Make sure we don't get negative numbers
5272 if ($data_count<0) { $data_count = 0; }
5275 $data{$line} = $data_count;
5276 if ($data_count > 0) { $hit++; }
5279 return (\%data, $found, $hit);
5284 # subtract_fnccounts(data, base)
5286 # Subtract function call counts found in base from those in data.
5287 # Return (data, f_found, f_hit).
5290 sub subtract_fnccounts($$)
5300 %data = %{$_[0]} if (defined($_[0]));
5301 %base = %{$_[1]} if (defined($_[1]));
5302 foreach $func (keys(%data)) {
5304 $data_count = $data{$func};
5305 $base_count = $base{$func};
5307 if (defined($base_count)) {
5308 $data_count -= $base_count;
5310 # Make sure we don't get negative numbers
5311 if ($data_count < 0) {
5316 $data{$func} = $data_count;
5317 if ($data_count > 0) {
5322 return (\%data, $fn_found, $fn_hit);
5327 # apply_baseline(data_ref, baseline_ref)
5329 # Subtract the execution counts found in the baseline hash referenced by
5330 # BASELINE_REF from actual data in DATA_REF.
5333 sub apply_baseline($$)
5335 my %data_hash = %{$_[0]};
5336 my %base_hash = %{$_[1]};
5343 my $data_testfncdata;
5344 my $data_testbrdata;
5346 my $data_testfnccount;
5347 my $data_testbrcount;
5350 my $base_sumfnccount;
5351 my $base_sumbrcount;
5363 foreach $filename (keys(%data_hash))
5365 # Get data set for data and baseline
5366 $data = $data_hash{$filename};
5367 $base = $base_hash{$filename};
5369 # Skip data entries for which no base entry exists
5370 if (!defined($base))
5375 # Get set entries for data and baseline
5376 ($data_testdata, undef, $data_funcdata, $data_checkdata,
5377 $data_testfncdata, undef, $data_testbrdata) =
5378 get_info_entry($data);
5379 (undef, $base_count, undef, $base_checkdata, undef,
5380 $base_sumfnccount, undef, $base_sumbrcount) =
5381 get_info_entry($base);
5383 # Check for compatible checksums
5384 merge_checksums($data_checkdata, $base_checkdata, $filename);
5386 # sumcount has to be calculated anew
5391 # For each test case, subtract test specific counts
5392 foreach $testname (keys(%{$data_testdata}))
5394 # Get counts of both data and baseline
5395 $data_count = $data_testdata->{$testname};
5396 $data_testfnccount = $data_testfncdata->{$testname};
5397 $data_testbrcount = $data_testbrdata->{$testname};
5399 ($data_count, undef, $hit) =
5400 subtract_counts($data_count, $base_count);
5401 ($data_testfnccount) =
5402 subtract_fnccounts($data_testfnccount,
5404 ($data_testbrcount) =
5405 combine_brcount($data_testbrcount,
5406 $base_sumbrcount, $BR_SUB);
5409 # Check whether this test case did hit any line at all
5412 # Write back resulting hash
5413 $data_testdata->{$testname} = $data_count;
5414 $data_testfncdata->{$testname} =
5416 $data_testbrdata->{$testname} =
5421 # Delete test case which did not impact this
5423 delete($data_testdata->{$testname});
5424 delete($data_testfncdata->{$testname});
5425 delete($data_testbrdata->{$testname});
5428 # Add counts to sum of counts
5429 ($sumcount, $found, $hit) =
5430 add_counts($sumcount, $data_count);
5431 ($sumfnccount, $fn_found, $fn_hit) =
5432 add_fnccount($sumfnccount, $data_testfnccount);
5433 ($sumbrcount, $br_found, $br_hit) =
5434 combine_brcount($sumbrcount, $data_testbrcount,
5438 # Write back resulting entry
5439 set_info_entry($data, $data_testdata, $sumcount, $data_funcdata,
5440 $data_checkdata, $data_testfncdata, $sumfnccount,
5441 $data_testbrdata, $sumbrcount, $found, $hit,
5442 $fn_found, $fn_hit, $br_found, $br_hit);
5444 $data_hash{$filename} = $data;
5447 return (\%data_hash);
5452 # remove_unused_descriptions()
5454 # Removes all test descriptions from the global hash %test_description which
5455 # are not present in %info_data.
5458 sub remove_unused_descriptions()
5460 my $filename; # The current filename
5461 my %test_list; # Hash containing found test names
5462 my $test_data; # Reference to hash test_name -> count_data
5463 my $before; # Initial number of descriptions
5464 my $after; # Remaining number of descriptions
5466 $before = scalar(keys(%test_description));
5468 foreach $filename (keys(%info_data))
5470 ($test_data) = get_info_entry($info_data{$filename});
5471 foreach (keys(%{$test_data}))
5473 $test_list{$_} = "";
5477 # Remove descriptions for tests which are not in our list
5478 foreach (keys(%test_description))
5480 if (!defined($test_list{$_}))
5482 delete($test_description{$_});
5486 $after = scalar(keys(%test_description));
5487 if ($after < $before)
5489 info("Removed ".($before - $after).
5490 " unused descriptions, $after remaining.\n");
5496 # apply_prefix(filename, prefix)
5498 # If FILENAME begins with PREFIX, remove PREFIX from FILENAME and return
5499 # resulting string, otherwise return FILENAME.
5502 sub apply_prefix($$)
5504 my $filename = $_[0];
5507 if (defined($prefix) && ($prefix ne ""))
5509 if ($filename =~ /^\Q$prefix\E\/(.*)$/)
5511 return substr($filename, length($prefix) + 1);
5520 # system_no_output(mode, parameters)
5522 # Call an external program using PARAMETERS while suppressing depending on
5523 # the value of MODE:
5525 # MODE & 1: suppress STDOUT
5526 # MODE & 2: suppress STDERR
5528 # Return 0 on success, non-zero otherwise.
5531 sub system_no_output($@)
5538 # Save old stdout and stderr handles
5539 ($mode & 1) && open(OLD_STDOUT, ">>&", "STDOUT");
5540 ($mode & 2) && open(OLD_STDERR, ">>&", "STDERR");
5542 # Redirect to /dev/null
5543 ($mode & 1) && open(STDOUT, ">", "/dev/null");
5544 ($mode & 2) && open(STDERR, ">", "/dev/null");
5549 # Close redirected handles
5550 ($mode & 1) && close(STDOUT);
5551 ($mode & 2) && close(STDERR);
5553 # Restore old handles
5554 ($mode & 1) && open(STDOUT, ">>&", "OLD_STDOUT");
5555 ($mode & 2) && open(STDERR, ">>&", "OLD_STDERR");
5562 # read_config(filename)
5564 # Read configuration file FILENAME and return a reference to a hash containing
5565 # all valid key=value pairs found.
5570 my $filename = $_[0];
5576 if (!open(HANDLE, "<", $filename))
5578 warn("WARNING: cannot read configuration file $filename\n");
5586 # Remove leading blanks
5588 # Remove trailing blanks
5591 ($key, $value) = split(/\s*=\s*/, $_, 2);
5592 if (defined($key) && defined($value))
5594 $result{$key} = $value;
5598 warn("WARNING: malformed statement in line $. ".
5599 "of configuration file $filename\n");
5610 # REF is a reference to a hash containing the following mapping:
5612 # key_string => var_ref
5614 # where KEY_STRING is a keyword and VAR_REF is a reference to an associated
5615 # variable. If the global configuration hashes CONFIG or OPT_RC contain a value
5616 # for keyword KEY_STRING, VAR_REF will be assigned the value for that keyword.
5623 foreach (keys(%{$ref}))
5625 if (defined($opt_rc{$_})) {
5626 ${$ref->{$_}} = $opt_rc{$_};
5627 } elsif (defined($config->{$_})) {
5628 ${$ref->{$_}} = $config->{$_};
5635 # get_html_prolog(FILENAME)
5637 # If FILENAME is defined, return contents of file. Otherwise return default
5638 # HTML prolog. Die on error.
5641 sub get_html_prolog($)
5643 my $filename = $_[0];
5646 if (defined($filename))
5650 open(HANDLE, "<", $filename)
5651 or die("ERROR: cannot open html prolog $filename!\n");
5660 $result = <<END_OF_HTML
5661 <!DOCTYPE HTML PUBLIC
"-//W3C//DTD HTML 4.01 Transitional//EN">
5666 <meta http
-equiv
="Content-Type" content
="text/html; charset=$charset">
5667 <title
>\
@pagetitle\@
</title
>
5668 <link rel
="stylesheet" type
="text/css" href
="\@basedir\@gcov.css">
5682 # get_html_epilog(FILENAME)
5684 # If FILENAME is defined, return contents of file. Otherwise return default
5685 # HTML epilog. Die on error.
5687 sub get_html_epilog
($)
5689 my $filename = $_[0];
5692 if (defined($filename))
5696 open(HANDLE
, "<", $filename)
5697 or die("ERROR: cannot open html epilog $filename!\n");
5706 $result = <<END_OF_HTML
5722 warn("$tool_name: $msg");
5729 die("$tool_name: $msg");
5733 # parse_ignore_errors(@ignore_errors)
5735 # Parse user input about which errors to ignore.
5738 sub parse_ignore_errors(@)
5740 my (@ignore_errors) = @_;
5744 return if (!@ignore_errors);
5746 foreach $item (@ignore_errors) {
5749 # Split and add comma-separated parameters
5750 push(@items, split(/,/, $item));
5752 # Add single parameter
5753 push(@items, $item);
5756 foreach $item (@items) {
5757 my $item_id = $ERROR_ID{lc($item)};
5759 if (!defined($item_id)) {
5760 die("ERROR: unknown argument for --ignore-errors: ".
5763 $ignore[$item_id] = 1;
5768 # rate(hit, found[, suffix, precision, width])
5770 # Return the coverage rate [0..100] for HIT and FOUND values. 0 is only
5771 # returned when HIT is 0. 100 is only returned when HIT equals FOUND.
5772 # PRECISION specifies the precision of the result. SUFFIX defines a
5773 # string that is appended to the result if FOUND is non-zero. Spaces
5774 # are added to the start of the resulting string until it is at least WIDTH
5780 my ($hit, $found, $suffix, $precision, $width) = @_;
5783 # Assign defaults if necessary
5784 $precision = 1 if (!defined($precision));
5785 $suffix = "" if (!defined($suffix));
5786 $width = 0 if (!defined($width));
5788 return sprintf("%*s", $width, "-") if (!defined($found) || $found == 0);
5789 $rate = sprintf("%.*f", $precision, $hit * 100 / $found);
5791 # Adjust rates if necessary
5792 if ($rate == 0 && $hit > 0) {
5793 $rate = sprintf("%.*f", $precision, 1 / 10 ** $precision);
5794 } elsif ($rate == 100 && $hit != $found) {
5795 $rate = sprintf("%.*f", $precision, 100 - 1 / 10 ** $precision);
5798 return sprintf("%*s", $width, $rate.$suffix);