Roll src/third_party/WebKit 06cb9e9:a978ee5 (svn 202558:202559)
[chromium-blink-merge.git] / third_party / lcov / bin / genhtml
blob8979f8c813d4b8418cfae6ff28a561d6fe6cab17
1 #!/usr/bin/perl -w
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
20 # genhtml
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.
27 # History:
28 # 2002-08-23 created by Peter Oberparleiter <Peter.Oberparleiter@de.ibm.com>
29 # IBM Lab Boeblingen
30 # based on code by Manoj Iyer <manjo@mail.utexas.edu> and
31 # Megan Bock <mbock@us.ibm.com>
32 # IBM Austin
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
45 # a test name
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
58 # logic
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)
67 use strict;
68 use File::Basename;
69 use Getopt::Long;
70 use Digest::MD5 qw(md5_base64);
73 # Global constants
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
85 our $hi_limit = 90;
86 our $med_limit = 75;
88 # For function coverage
89 our $fn_hi_limit;
90 our $fn_med_limit;
92 # For branch coverage
93 our $br_hi_limit;
94 our $br_med_limit;
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;
122 # Internal Constants
124 # Header types
125 our $HDR_DIR = 0;
126 our $HDR_FILE = 1;
127 our $HDR_SOURCE = 2;
128 our $HDR_TESTDESC = 3;
129 our $HDR_FUNC = 4;
131 # Sort types
132 our $SORT_FILE = 0;
133 our $SORT_LINE = 1;
134 our $SORT_FUNC = 2;
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
143 our $BR_BLOCK = 0;
144 our $BR_BRANCH = 1;
145 our $BR_TAKEN = 2;
146 our $BR_VEC_ENTRIES = 3;
147 our $BR_VEC_WIDTH = 32;
149 # Additional offsets used when converting branch coverage data to HTML
150 our $BR_LEN = 3;
151 our $BR_OPEN = 4;
152 our $BR_CLOSE = 5;
154 # Branch data combination types
155 our $BR_SUB = 0;
156 our $BR_ADD = 1;
158 # Error classes which users may specify to ignore during processing
159 our $ERROR_SOURCE = 0;
160 our %ERROR_ID = (
161 "source" => $ERROR_SOURCE,
164 # Data related prototypes
165 sub print_usage(*);
166 sub gen_html();
167 sub html_create($$);
168 sub process_dir($);
169 sub process_file($$$);
170 sub info(@);
171 sub read_info_file($);
172 sub get_info_entry($);
173 sub set_info_entry($$$$$$$$$;$$$$$$);
174 sub get_prefix($@);
175 sub shorten_prefix($);
176 sub get_dir_list(@);
177 sub get_relative_base_path($);
178 sub read_testfile($);
179 sub get_date_string();
180 sub create_sub_dir($);
181 sub subtract_counts($$);
182 sub add_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($@);
192 sub read_config($);
193 sub apply_config($);
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($$);
200 sub br_ivec_len($);
201 sub br_ivec_get($$);
202 sub br_ivec_push($$$$);
203 sub combine_brcount($$$);
204 sub get_br_found_and_hit($);
205 sub warn_handler($);
206 sub die_handler($);
207 sub parse_ignore_errors(@);
208 sub rate($$;$$$);
211 # HTML related prototypes
212 sub escape_html($);
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(*$$$$$$$$$$);
221 sub write_html(*$);
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)
250 sub gen_png($$$@);
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
291 our @ignore;
292 our $opt_config_file; # User-specified configuration file location
293 our %opt_rc;
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
304 chomp($cwd);
305 our $tool_dir = dirname($0); # Directory where genhtml tool is installed
309 # Code entry point
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
345 apply_config({
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,
411 "sort" => \$sort,
412 "no-sort" => \$no_sort,
413 "demangle-cpp" => \$demangle_cpp,
414 "ignore-errors=s" => \@opt_ignore_errors,
415 "config-file=s" => \$opt_config_file,
416 "rc=s%" => \%opt_rc,
419 print(STDERR "Use $tool_name --help to get usage information\n");
420 exit(1);
421 } else {
422 # Merge options
423 if ($no_func_coverage) {
424 $func_coverage = 0;
426 if ($no_br_coverage) {
427 $br_coverage = 0;
430 # Merge sort options
431 if ($no_sort) {
432 $sort = 0;
436 @info_filenames = @ARGV;
438 # Check for help option
439 if ($help)
441 print_usage(*STDOUT);
442 exit(0);
445 # Check for version option
446 if ($version)
448 print("$tool_name: $lcov_version\n");
449 exit(0);
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
463 if (!$test_title)
465 if (scalar(@info_filenames) == 1)
467 # Only one filename specified, use it as title
468 $test_title = basename($info_filenames[0]);
470 else
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
478 # directories)
479 if ($css_filename)
481 if (!($css_filename =~ /^\/(.*)$/))
483 $css_filename = $cwd."/".$css_filename;
487 # Make sure tab_size is within valid range
488 if ($tab_size < 1)
490 print(STDERR "ERROR: invalid number of spaces specified: ".
491 "$tab_size!\n");
492 exit(1);
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 ".
503 "was specified!\n");
504 $frames = undef;
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 ".
511 "specified!\n");
512 $dir_prefix = undef;
515 @fileview_sortlist = ($SORT_FILE);
516 @funcview_sortlist = ($SORT_FILE);
518 if ($sort) {
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);
525 if ($frames)
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
532 if ($demangle_cpp)
534 if (system_no_output(3, "c++filt", "--version")) {
535 die("ERROR: could not find c++filt tool needed for ".
536 "--demangle-cpp\n");
540 # Make sure output_directory exists, create it if necessary
541 if ($output_directory)
543 stat($output_directory);
545 if (! -e _)
547 create_sub_dir($output_directory);
551 # Do something
552 gen_html();
554 exit(0);
559 # print_usage(handle)
561 # Print usage information.
564 sub print_usage(*)
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.
574 Misc:
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)
582 Operation:
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
593 HTML output:
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
609 END_OF_USAGE
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
619 # file list.
622 sub get_rate($$)
624 my ($found, $hit) = @_;
626 if ($found == 0) {
627 return 10000;
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
637 # found/hit data.
640 sub get_overall_line($$$$)
642 my ($found, $hit, $name_sn, $name_pl) = @_;
643 my $name;
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
653 # br_found, br_hit)
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"))
666 if ($ln_do);
667 info(" functions..: %s\n",
668 get_overall_line($fn_found, $fn_hit, "function", "functions"))
669 if ($fn_do);
670 info(" branches...: %s\n",
671 get_overall_line($br_found, $br_hit, "branch", "branches"))
672 if ($br_do);
677 # gen_html()
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
682 # in ouput.
684 # Die on error.
687 sub gen_html()
689 local *HTML_HANDLE;
690 my %overview;
691 my %base_data;
692 my $lines_found;
693 my $lines_hit;
694 my $fn_found;
695 my $fn_hit;
696 my $br_found;
697 my $br_hit;
698 my $overall_found = 0;
699 my $overall_hit = 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;
704 my $dir_name;
705 my $link_name;
706 my @dir_list;
707 my %new_info;
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
721 if ($base_filename)
723 # Read baseline file
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)));
728 # Apply baseline
729 info("Subtracting baseline data.\n");
730 %info_data = %{apply_baseline(\%info_data, \%base_data)};
733 @dir_list = get_dir_list(keys(%info_data));
735 if ($no_prefix)
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));
745 if ($dir_prefix)
747 info("Found common filename prefix \"$dir_prefix\"\n");
749 else
751 info("No common filename prefix found!\n");
752 $no_prefix=1;
755 else
757 info("Using user-specified filename prefix \"".
758 "$dir_prefix\"\n");
761 # Read in test description file if specified
762 if ($desc_filename)
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");
784 write_css_file();
785 write_png_files();
787 if ($html_gzip)
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,
799 $br_found, $br_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";
817 else
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);
860 chdir($cwd);
864 # html_create(handle, filename)
867 sub html_create($$)
869 my $handle = $_[0];
870 my $filename = $_[1];
872 if ($html_gzip)
874 open($handle, "|-", "gzip -c >'$filename'")
875 or die("ERROR: cannot open $filename for writing ".
876 "(gzip)!\n");
878 else
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)) {
895 $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);
905 close(*HTML_HANDLE);
910 # process_dir(dir_name)
913 sub process_dir($)
915 my $abs_dir = $_[0];
916 my $trunc_dir;
917 my $rel_dir = $abs_dir;
918 my $base_dir;
919 my $filename;
920 my %overview;
921 my $lines_found;
922 my $lines_hit;
923 my $fn_found;
924 my $fn_hit;
925 my $br_found;
926 my $br_hit;
927 my $overall_found=0;
928 my $overall_hit=0;
929 my $total_fn_found=0;
930 my $total_fn_hit=0;
931 my $total_br_found = 0;
932 my $total_br_hit = 0;
933 my $base_name;
934 my $extension;
935 my $testdata;
936 my %testhash;
937 my $testfncdata;
938 my %testfnchash;
939 my $testbrdata;
940 my %testbrhash;
941 my @sort_list;
942 local *HTML_HANDLE;
944 # Remove prefix if applicable
945 if (!$no_prefix)
947 # Match directory name beginning with $dir_prefix
948 $rel_dir = apply_prefix($rel_dir, $dir_prefix);
951 $trunc_dir = $rel_dir;
953 # Remove leading /
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
968 # sub-directories
969 foreach $filename (grep(/^\Q$abs_dir\E\/[^\/]*$/,keys(%info_data)))
971 my $page_link;
972 my $func_link;
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) {
981 $page_link = "";
982 } elsif ($frames) {
983 # Link to frameset page
984 $page_link = "$base_name.gcov.frameset.$html_ext";
985 } else {
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,
991 $page_link,
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, {},
1017 {}, {}, 1, $_);
1018 if (!$show_details) {
1019 next;
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];
1046 my $testcount;
1047 my %converted;
1048 my %nonconverted;
1049 my $hash;
1050 my $testcase;
1051 my $line;
1052 my %result;
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;
1064 else
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)
1075 $hash->{$line} = 1;
1080 # Combine both hashes to resulting list
1081 foreach $line (keys(%converted))
1083 if (!defined($nonconverted{$line}))
1085 $result{$line} = 1;
1089 return \%result;
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) = @_;
1099 my $pagetitle;
1100 my $filename;
1102 # Generate function table for this file
1103 if ($sort_type == 0) {
1104 $filename = "$rel_dir/$base_name.func.$html_ext";
1105 } else {
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);
1137 my $testdata;
1138 my $testcount;
1139 my $sumcount;
1140 my $funcdata;
1141 my $checkdata;
1142 my $testfncdata;
1143 my $sumfnccount;
1144 my $testbrdata;
1145 my $sumbrcount;
1146 my $lines_found;
1147 my $lines_hit;
1148 my $fn_found;
1149 my $fn_hit;
1150 my $br_found;
1151 my $br_hit;
1152 my $converted;
1153 my @source;
1154 my $pagetitle;
1155 local *HTML_HANDLE;
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
1163 # source code view
1164 if ($no_sourceview)
1166 return ($lines_found, $lines_hit, $fn_found, $fn_hit,
1167 $br_found, $br_hit, $testdata, $testfncdata,
1168 $testbrdata);
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,
1192 $br_hit, $sumcount,
1193 $funcdata, $sumfnccount,
1194 $testfncdata, $sumbrcount,
1195 $testbrdata, $_);
1199 # Additional files are needed in case of frame output
1200 if (!$frames)
1202 return ($lines_found, $lines_hit, $fn_found, $fn_hit,
1203 $br_found, $br_hit, $testdata, $testfncdata,
1204 $testbrdata);
1207 # Create overview png file
1208 gen_png("$rel_dir/$base_name.gcov.png", $overview_width, $tab_size,
1209 @source);
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,
1221 scalar(@source));
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
1267 # this file.
1269 # Die on error.
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
1277 my $testdata; # " "
1278 my $testcount; # " "
1279 my $sumcount; # " "
1280 my $funcdata; # " "
1281 my $checkdata; # " "
1282 my $testfncdata;
1283 my $testfnccount;
1284 my $sumfnccount;
1285 my $testbrdata;
1286 my $testbrcount;
1287 my $sumbrcount;
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
1296 my $br_found;
1297 my $br_hit;
1298 local *INFO_HANDLE; # Filehandle for .info file
1300 info("Reading data file $tracefile\n");
1302 # Check if file exists and is readable
1303 stat($_[0]);
1304 if (!(-r _))
1306 die("ERROR: cannot read file $_[0]!\n");
1309 # Check if this is really a plain file
1310 if (!(-f _))
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 ".
1330 "file $_[0]!\n");
1332 else
1334 # Open decompressed file
1335 open(INFO_HANDLE, "<", $_[0])
1336 or die("ERROR: cannot read file $_[0]!\n");
1339 $testname = "";
1340 while (<INFO_HANDLE>)
1342 chomp($_);
1343 $line = $_;
1345 # Switch statement
1346 foreach ($line)
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));
1357 last;
1360 /^[SK]F:(.*)/ && do
1362 # Filename information found
1363 # Retrieve data for new entry
1364 $filename = $1;
1366 $data = $result{$filename};
1367 ($testdata, $sumcount, $funcdata, $checkdata,
1368 $testfncdata, $sumfnccount, $testbrdata,
1369 $sumbrcount) =
1370 get_info_entry($data);
1372 if (defined($testname))
1374 $testcount = $testdata->{$testname};
1375 $testfnccount = $testfncdata->{$testname};
1376 $testbrcount = $testbrdata->{$testname};
1378 else
1380 $testcount = {};
1381 $testfnccount = {};
1382 $testbrcount = {};
1384 last;
1387 /^DA:(\d+),(-?\d+)(,[^,\s]+)?/ && do
1389 # Fix negative counts
1390 $count = $2 < 0 ? 0 : $2;
1391 if ($2 < 0)
1393 $negative = 1;
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
1406 if (defined($3))
1408 $line_checksum = substr($3, 1);
1410 # Does it match a previous definition
1411 if (defined($checkdata->{$1}) &&
1412 ($checkdata->{$1} ne
1413 $line_checksum))
1415 die("ERROR: checksum mismatch ".
1416 "at $filename:$1\n");
1419 $checkdata->{$1} = $line_checksum;
1421 last;
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;
1441 last;
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;
1456 last;
1459 /^BRDA:(\d+),(\d+),(\d+),(\d+|-)/ && do {
1460 # Branch coverage data found
1461 my ($line, $block, $branch, $taken) =
1462 ($1, $2, $3, $4);
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} =
1472 br_ivec_push(
1473 $testbrcount->{$line},
1474 $block, $branch,
1475 $taken);
1477 last;
1480 /^end_of_record/ && do
1482 # Found end of section marker
1483 if ($filename)
1485 # Store current section data
1486 if (defined($testname))
1488 $testdata->{$testname} =
1489 $testcount;
1490 $testfncdata->{$testname} =
1491 $testfnccount;
1492 $testbrdata->{$testname} =
1493 $testbrcount;
1496 set_info_entry($data, $testdata,
1497 $sumcount, $funcdata,
1498 $checkdata, $testfncdata,
1499 $sumfnccount,
1500 $testbrdata,
1501 $sumbrcount);
1502 $result{$filename} = $data;
1503 last;
1507 # default
1508 last;
1511 close(INFO_HANDLE);
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});
1526 next;
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}));
1540 $hitcount = 0;
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}));
1551 $hitcount = 0;
1553 foreach (keys(%{$sumfnccount})) {
1554 if ($sumfnccount->{$_} > 0) {
1555 $hitcount++;
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");
1571 if ($negative)
1573 warn("WARNING: negative counts found in tracefile ".
1574 "$tracefile\n");
1576 if ($changed_testname)
1578 warn("WARNING: invalid characters removed from testname in ".
1579 "tracefile $tracefile\n");
1582 return(\%result);
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.
1663 sub add_counts($$)
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;
1685 $found++;
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};
1698 $found++;
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($$$)
1720 my $ref1 = $_[0];
1721 my $ref2 = $_[1];
1722 my $filename = $_[2];
1723 my %result;
1724 my $line;
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};
1741 return \%result;
1746 # merge_func_data(funcdata1, funcdata2, filename)
1749 sub merge_func_data($$$)
1751 my ($funcdata1, $funcdata2, $filename) = @_;
1752 my %result;
1753 my $func;
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");
1766 next;
1768 $result{$func} = $line2;
1771 return \%result;
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) = @_;
1784 my %result;
1785 my $fn_found;
1786 my $fn_hit;
1787 my $function;
1789 if (defined($fnccount1)) {
1790 %result = %{$fnccount1};
1792 foreach $function (keys(%{$fnccount2})) {
1793 $result{$function} += $fnccount2->{$function};
1795 $fn_found = scalar(keys(%result));
1796 $fn_hit = 0;
1797 foreach $function (keys(%result)) {
1798 if ($result{$function} > 0) {
1799 $fn_hit++;
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) = @_;
1816 my %result;
1817 my $testname;
1819 foreach $testname (keys(%{$testfncdata1})) {
1820 if (defined($testfncdata2->{$testname})) {
1821 my $fnccount;
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;
1829 next;
1831 # Function call count data for this testname is unique to
1832 # data set 1: copy
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};
1842 return \%result;
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($)
1858 my ($brcount) = @_;
1859 my $line;
1860 my $db;
1862 # Add branches from first count to database
1863 foreach $line (keys(%{$brcount})) {
1864 my $brdata = $brcount->{$line};
1865 my $i;
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;
1875 return $db;
1880 # db_to_brcount(db)
1882 # Convert branch coverage data back to brcount format.
1885 sub db_to_brcount($)
1887 my ($db) = @_;
1888 my $line;
1889 my $brcount = {};
1890 my $br_found = 0;
1891 my $br_hit = 0;
1893 # Convert database back to brcount format
1894 foreach $line (sort({$a <=> $b} keys(%{$db}))) {
1895 my $ldata = $db->{$line};
1896 my $brdata;
1897 my $block;
1899 foreach $block (sort({$a <=> $b} keys(%{$ldata}))) {
1900 my $bdata = $ldata->{$block};
1901 my $branch;
1903 foreach $branch (sort({$a <=> $b} keys(%{$bdata}))) {
1904 my $taken = $bdata->{$branch};
1906 $br_found++;
1907 $br_hit++ if ($taken ne "-" && $taken > 0);
1908 $brdata = br_ivec_push($brdata, $block,
1909 $branch, $taken);
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) = @_;
1930 my $line;
1931 my $block;
1932 my $branch;
1933 my $taken;
1934 my $db;
1935 my $br_found = 0;
1936 my $br_hit = 0;
1937 my $result;
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);
1945 my $i;
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
1971 # added_testbrdata.
1974 sub add_testbrdata($$)
1976 my ($testbrdata1, $testbrdata2) = @_;
1977 my %result;
1978 my $testname;
1980 foreach $testname (keys(%{$testbrdata1})) {
1981 if (defined($testbrdata2->{$testname})) {
1982 my $brcount;
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;
1989 next;
1991 # Branch coverage data for this testname is unique to
1992 # data set 1: copy
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};
2002 return \%result;
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
2016 my $testdata1;
2017 my $sumcount1;
2018 my $funcdata1;
2019 my $checkdata1;
2020 my $testfncdata1;
2021 my $sumfnccount1;
2022 my $testbrdata1;
2023 my $sumbrcount1;
2025 my $entry2 = $_[1]; # Reference to hash containing second entry
2026 my $testdata2;
2027 my $sumcount2;
2028 my $funcdata2;
2029 my $checkdata2;
2030 my $testfncdata2;
2031 my $sumfnccount2;
2032 my $testbrdata2;
2033 my $sumbrcount2;
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;
2043 my $lines_found;
2044 my $lines_hit;
2045 my $fn_found;
2046 my $fn_hit;
2047 my $br_found;
2048 my $br_hit;
2050 my $testname;
2051 my $filename = $_[2];
2053 # Retrieve data
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);
2059 # Merge checksums
2060 $checkdata1 = merge_checksums($checkdata1, $checkdata2, $filename);
2062 # Combine funcdata
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);
2075 # Combine testdata
2076 foreach $testname (keys(%{$testdata1}))
2078 if (defined($testdata2->{$testname}))
2080 # testname is present in both entries, requires
2081 # combination
2082 ($result_testdata{$testname}) =
2083 add_counts($testdata1->{$testname},
2084 $testdata2->{$testname});
2086 else
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
2114 # Store result
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);
2121 return(\%result);
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]};
2136 my $filename;
2138 foreach $filename (keys(%hash2))
2140 if ($hash1{$filename})
2142 # Entry already exists in hash1, combine them
2143 $hash1{$filename} =
2144 combine_info_entries($hash1{$filename},
2145 $hash2{$filename},
2146 $filename);
2148 else
2150 # Entry is unique in both hashes, simply add to
2151 # resulting hash
2152 $hash1{$filename} = $hash2{$filename};
2156 return(\%hash1);
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.
2169 sub get_prefix($@)
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
2180 $current = $_;
2181 while ($current = shorten_prefix($current))
2183 $current .= "/";
2185 # Skip rest if the remaining prefix has already been
2186 # added to hash
2187 if (exists($prefix{$current})) { last; }
2189 # Initialize with 0
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})
2233 $current = $_;
2237 $current =~ s/\/$//;
2239 return($current);
2244 # shorten_prefix(prefix)
2246 # Return PREFIX shortened by last directory component.
2249 sub shorten_prefix($)
2251 my @list = split("/", $_[0]);
2253 pop(@list);
2254 return join("/", @list);
2260 # get_dir_list(filename_list)
2262 # Return sorted list of directories for each entry in given FILENAME_LIST.
2265 sub get_dir_list(@)
2267 my %result;
2269 foreach (@_)
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
2282 # in SUBDIRECTORY.
2284 # Example: get_relative_base_path("fs/mm") -> "../../"
2287 sub get_relative_base_path($)
2289 my $result = "";
2290 my $index;
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--)
2301 $result .= "../";
2304 return $result;
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.
2320 # Die on error.
2323 sub read_testfile($)
2325 my %result;
2326 my $test_name;
2327 my $changed_testname;
2328 local *TEST_HANDLE;
2330 open(TEST_HANDLE, "<", $_[0])
2331 or die("ERROR: cannot open $_[0]!\n");
2333 while (<TEST_HANDLE>)
2335 chomp($_);
2337 # Match lines beginning with TN:<whitespace(s)>
2338 if (/^TN:\s+(.*?)\s*$/)
2340 # Store name for later use
2341 $test_name = $1;
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
2352 if ($1)
2354 # Add description to hash
2355 $result{$test_name} .= " $1";
2357 else
2359 # Add empty line
2360 $result{$test_name} .= "\n\n";
2365 close(TEST_HANDLE);
2367 if ($changed_testname)
2369 warn("WARNING: invalid characters removed from testname in ".
2370 "descriptions file $_[0]\n");
2373 return \%result;
2378 # escape_html(STRING)
2380 # Return a copy of STRING in which all occurrences of HTML special characters
2381 # are escaped.
2384 sub escape_html($)
2386 my $string = $_[0];
2388 if (!$string) { return ""; }
2390 $string =~ s/&/&amp;/g; # & -> &amp;
2391 $string =~ s/</&lt;/g; # < -> &lt;
2392 $string =~ s/>/&gt;/g; # > -> &gt;
2393 $string =~ s/\"/&quot;/g; # " -> &quot;
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>
2403 return $string;
2408 # get_date_string()
2410 # Return the current date in the form: yyyy-mm-dd
2413 sub get_date_string()
2415 my $year;
2416 my $month;
2417 my $day;
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.
2431 # Die on error.
2434 sub create_sub_dir($)
2436 my ($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,
2446 # total_br_hit)
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
2453 # Die on error.
2456 sub write_description_file($$$$$$$)
2458 my %description = %{$_[0]};
2459 my $found = $_[1];
2460 my $hit = $_[2];
2461 my $fn_found = $_[3];
2462 my $fn_hit = $_[4];
2463 my $br_found = $_[5];
2464 my $br_hit = $_[6];
2465 my $test_name;
2466 local *HTML_HANDLE;
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);
2491 # write_png_files()
2493 # Create all necessary .png files for the HTML-output in the current
2494 # directory. .png-files are used as bar graphs.
2496 # Die on error.
2499 sub write_png_files()
2501 my %data;
2502 local *PNG_HANDLE;
2504 $data{"ruby.png"} =
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,
2519 0x82];
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,
2535 0x82];
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,
2551 0x82];
2552 $data{"snow.png"} =
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,
2567 0x82];
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{$_}}));
2605 close(PNG_HANDLE);
2611 # write_htaccess_file()
2614 sub write_htaccess_file()
2616 local *HTACCESS_HANDLE;
2617 my $htaccess_data;
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
2624 END_OF_HTACCESS
2627 print(HTACCESS_HANDLE $htaccess_data);
2628 close(*HTACCESS_HANDLE);
2633 # write_css_file()
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()
2641 local *CSS_HANDLE;
2643 # Check for a specified external style sheet file
2644 if ($css_filename)
2646 # Simply copy that file
2647 system("cp", $css_filename, "gcov.css")
2648 and die("ERROR: cannot copy file $css_filename!\n");
2649 return;
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 */
2660 body
2662 color: #000000;
2663 background-color: #FFFFFF;
2666 /* All views: standard link format*/
2667 a:link
2669 color: #284FA8;
2670 text-decoration: underline;
2673 /* All views: standard link - visited format */
2674 a:visited
2676 color: #00CB40;
2677 text-decoration: underline;
2680 /* All views: standard link - activated format */
2681 a:active
2683 color: #FF0040;
2684 text-decoration: underline;
2687 /* All views: main title format */
2688 td.title
2690 text-align: center;
2691 padding-bottom: 10px;
2692 font-family: sans-serif;
2693 font-size: 20pt;
2694 font-style: italic;
2695 font-weight: bold;
2698 /* All views: header item format */
2699 td.headerItem
2701 text-align: right;
2702 padding-right: 6px;
2703 font-family: sans-serif;
2704 font-weight: bold;
2705 vertical-align: top;
2706 white-space: nowrap;
2709 /* All views: header item value format */
2710 td.headerValue
2712 text-align: left;
2713 color: #284FA8;
2714 font-family: sans-serif;
2715 font-weight: bold;
2716 white-space: nowrap;
2719 /* All views: header item coverage table heading */
2720 td.headerCovTableHead
2722 text-align: center;
2723 padding-right: 6px;
2724 padding-left: 6px;
2725 padding-bottom: 0px;
2726 font-family: sans-serif;
2727 font-size: 80%;
2728 white-space: nowrap;
2731 /* All views: header item coverage table entry */
2732 td.headerCovTableEntry
2734 text-align: right;
2735 color: #284FA8;
2736 font-family: sans-serif;
2737 font-weight: bold;
2738 white-space: nowrap;
2739 padding-left: 12px;
2740 padding-right: 4px;
2741 background-color: #DAE7FE;
2744 /* All views: header item coverage table entry for high coverage rate */
2745 td.headerCovTableEntryHi
2747 text-align: right;
2748 color: #000000;
2749 font-family: sans-serif;
2750 font-weight: bold;
2751 white-space: nowrap;
2752 padding-left: 12px;
2753 padding-right: 4px;
2754 background-color: #A7FC9D;
2757 /* All views: header item coverage table entry for medium coverage rate */
2758 td.headerCovTableEntryMed
2760 text-align: right;
2761 color: #000000;
2762 font-family: sans-serif;
2763 font-weight: bold;
2764 white-space: nowrap;
2765 padding-left: 12px;
2766 padding-right: 4px;
2767 background-color: #FFEA20;
2770 /* All views: header item coverage table entry for ow coverage rate */
2771 td.headerCovTableEntryLo
2773 text-align: right;
2774 color: #000000;
2775 font-family: sans-serif;
2776 font-weight: bold;
2777 white-space: nowrap;
2778 padding-left: 12px;
2779 padding-right: 4px;
2780 background-color: #FF0000;
2783 /* All views: header legend value for legend entry */
2784 td.headerValueLeg
2786 text-align: left;
2787 color: #000000;
2788 font-family: sans-serif;
2789 font-size: 80%;
2790 white-space: nowrap;
2791 padding-top: 4px;
2794 /* All views: color of horizontal ruler */
2795 td.ruler
2797 background-color: #6688D4;
2800 /* All views: version string format */
2801 td.versionInfo
2803 text-align: center;
2804 padding-top: 2px;
2805 font-family: sans-serif;
2806 font-style: italic;
2809 /* Directory view/File view (all)/Test case descriptions:
2810 table headline format */
2811 td.tableHead
2813 text-align: center;
2814 color: #FFFFFF;
2815 background-color: #6688D4;
2816 font-family: sans-serif;
2817 font-size: 120%;
2818 font-weight: bold;
2819 white-space: nowrap;
2820 padding-left: 4px;
2821 padding-right: 4px;
2824 span.tableHeadSort
2826 padding-right: 4px;
2829 /* Directory view/File view (all): filename entry format */
2830 td.coverFile
2832 text-align: left;
2833 padding-left: 10px;
2834 padding-right: 20px;
2835 color: #284FA8;
2836 background-color: #DAE7FE;
2837 font-family: monospace;
2840 /* Directory view/File view (all): bar-graph entry format*/
2841 td.coverBar
2843 padding-left: 10px;
2844 padding-right: 10px;
2845 background-color: #DAE7FE;
2848 /* Directory view/File view (all): bar-graph outline color */
2849 td.coverBarOutline
2851 background-color: #000000;
2854 /* Directory view/File view (all): percentage entry for files with
2855 high coverage rate */
2856 td.coverPerHi
2858 text-align: right;
2859 padding-left: 10px;
2860 padding-right: 10px;
2861 background-color: #A7FC9D;
2862 font-weight: bold;
2863 font-family: sans-serif;
2866 /* Directory view/File view (all): line count entry for files with
2867 high coverage rate */
2868 td.coverNumHi
2870 text-align: right;
2871 padding-left: 10px;
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 */
2880 td.coverPerMed
2882 text-align: right;
2883 padding-left: 10px;
2884 padding-right: 10px;
2885 background-color: #FFEA20;
2886 font-weight: bold;
2887 font-family: sans-serif;
2890 /* Directory view/File view (all): line count entry for files with
2891 medium coverage rate */
2892 td.coverNumMed
2894 text-align: right;
2895 padding-left: 10px;
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 */
2904 td.coverPerLo
2906 text-align: right;
2907 padding-left: 10px;
2908 padding-right: 10px;
2909 background-color: #FF0000;
2910 font-weight: bold;
2911 font-family: sans-serif;
2914 /* Directory view/File view (all): line count entry for files with
2915 low coverage rate */
2916 td.coverNumLo
2918 text-align: right;
2919 padding-left: 10px;
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 */
2927 a.detail:link
2929 color: #B8D0FF;
2930 font-size:80%;
2933 /* File view (all): "show/hide details" link - visited format */
2934 a.detail:visited
2936 color: #B8D0FF;
2937 font-size:80%;
2940 /* File view (all): "show/hide details" link - activated format */
2941 a.detail:active
2943 color: #FFFFFF;
2944 font-size:80%;
2947 /* File view (detail): test name entry */
2948 td.testName
2950 text-align: right;
2951 padding-right: 10px;
2952 background-color: #DAE7FE;
2953 font-family: sans-serif;
2956 /* File view (detail): test percentage entry */
2957 td.testPer
2959 text-align: right;
2960 padding-left: 10px;
2961 padding-right: 10px;
2962 background-color: #DAE7FE;
2963 font-family: sans-serif;
2966 /* File view (detail): test lines count entry */
2967 td.testNum
2969 text-align: right;
2970 padding-left: 10px;
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;
2980 font-weight: bold;
2983 /* Test case descriptions: description table body */
2984 td.testDescription
2986 padding-top: 10px;
2987 padding-left: 30px;
2988 padding-bottom: 10px;
2989 padding-right: 30px;
2990 background-color: #DAE7FE;
2993 /* Source code view: function entry */
2994 td.coverFn
2996 text-align: left;
2997 padding-left: 10px;
2998 padding-right: 20px;
2999 color: #284FA8;
3000 background-color: #DAE7FE;
3001 font-family: monospace;
3004 /* Source code view: function entry zero count*/
3005 td.coverFnLo
3007 text-align: right;
3008 padding-left: 10px;
3009 padding-right: 10px;
3010 background-color: #FF0000;
3011 font-weight: bold;
3012 font-family: sans-serif;
3015 /* Source code view: function entry nonzero count*/
3016 td.coverFnHi
3018 text-align: right;
3019 padding-left: 10px;
3020 padding-right: 10px;
3021 background-color: #DAE7FE;
3022 font-weight: bold;
3023 font-family: sans-serif;
3026 /* Source code view: source code format */
3027 pre.source
3029 font-family: monospace;
3030 white-space: pre;
3031 margin-top: 2px;
3034 /* Source code view: line number format */
3035 span.lineNum
3037 background-color: #EFE383;
3040 /* Source code view: format for lines which were executed */
3041 td.lineCov,
3042 span.lineCov
3044 background-color: #CAD7FE;
3047 /* Source code view: format for Cov legend */
3048 span.coverLegendCov
3050 padding-left: 10px;
3051 padding-right: 10px;
3052 padding-bottom: 2px;
3053 background-color: #CAD7FE;
3056 /* Source code view: format for lines which were not executed */
3057 td.lineNoCov,
3058 span.lineNoCov
3060 background-color: #FF6230;
3063 /* Source code view: format for NoCov legend */
3064 span.coverLegendNoCov
3066 padding-left: 10px;
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
3076 color: black;
3077 text-decoration: underline;
3080 /* Source code view: format for lines which were executed only in a
3081 previous version */
3082 span.lineDiffCov
3084 background-color: #B5F7AF;
3087 /* Source code view: format for branches which were executed
3088 * and taken */
3089 span.branchCov
3091 background-color: #CAD7FE;
3094 /* Source code view: format for branches which were executed
3095 * but not taken */
3096 span.branchNoCov
3098 background-color: #FF6230;
3101 /* Source code view: format for branches which were not executed */
3102 span.branchNoExec
3104 background-color: #FF6230;
3107 /* Source code view: format for the source code heading line */
3108 pre.sourceHeading
3110 white-space: pre;
3111 font-family: monospace;
3112 font-weight: bold;
3113 margin: 0px;
3116 /* All views: header legend value for low rate */
3117 td.headerValueLegL
3119 font-family: sans-serif;
3120 text-align: center;
3121 white-space: nowrap;
3122 padding-left: 4px;
3123 padding-right: 2px;
3124 background-color: #FF0000;
3125 font-size: 80%;
3128 /* All views: header legend value for med rate */
3129 td.headerValueLegM
3131 font-family: sans-serif;
3132 text-align: center;
3133 white-space: nowrap;
3134 padding-left: 2px;
3135 padding-right: 2px;
3136 background-color: #FFEA20;
3137 font-size: 80%;
3140 /* All views: header legend value for hi rate */
3141 td.headerValueLegH
3143 font-family: sans-serif;
3144 text-align: center;
3145 white-space: nowrap;
3146 padding-left: 2px;
3147 padding-right: 4px;
3148 background-color: #A7FC9D;
3149 font-size: 80%;
3152 /* All views except source code view: legend format for low coverage */
3153 span.coverLegendCovLo
3155 padding-left: 10px;
3156 padding-right: 10px;
3157 padding-top: 2px;
3158 background-color: #FF0000;
3161 /* All views except source code view: legend format for med coverage */
3162 span.coverLegendCovMed
3164 padding-left: 10px;
3165 padding-right: 10px;
3166 padding-top: 2px;
3167 background-color: #FFEA20;
3170 /* All views except source code view: legend format for hi coverage */
3171 span.coverLegendCovHi
3173 padding-left: 10px;
3174 padding-right: 10px;
3175 padding-top: 2px;
3176 background-color: #A7FC9D;
3178 END_OF_CSS
3181 # *************************************************************
3184 # Remove leading tab from all lines
3185 $css_data =~ s/^\t//gm;
3187 print(CSS_HANDLE $css_data);
3189 close(CSS_HANDLE);
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) = @_;
3203 my $rate;
3204 my $alt;
3205 my $width;
3206 my $remainder;
3207 my $png_name;
3208 my $graph_code;
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,
3219 $hi_limit)];
3221 if ($width == 0)
3223 # Zero coverage
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>
3226 END_OF_HTML
3229 elsif ($width == 100)
3231 # Full coverage
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>
3234 END_OF_HTML
3237 else
3239 # Positive coverage
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>
3242 END_OF_HTML
3246 # Remove leading tabs from all lines
3247 $graph_code =~ s/^\t+//gm;
3248 chomp($graph_code);
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) = @_;
3262 my $rate;
3264 if ($found == 0) {
3265 return 2;
3267 $rate = rate($hit, $found);
3268 if ($rate < $med) {
3269 return 0;
3270 } elsif ($rate < $hi) {
3271 return 1;
3273 return 2;
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.
3284 sub write_html(*$)
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];
3309 my $prolog;
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>
3334 <tr>
3335 <td width="100%">
3336 <table cellpadding=1 border=0 width="100%">
3337 END_OF_HTML
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) = @_;
3353 my $entry;
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\"";
3361 } else {
3362 $width = "";
3364 if (defined($class)) {
3365 $class = " class=\"$class\"";
3366 } else {
3367 $class = "";
3369 if (defined($colspan)) {
3370 $colspan = " colspan=\"$colspan\"";
3371 } else {
3372 $colspan = "";
3374 $text = "" if (!defined($text));
3375 write_html($handle,
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>
3394 </table>
3395 </td>
3396 </tr>
3398 <tr><td class="ruler"><img src="$_[1]glass.png" width=3 height=3 alt=""></td></tr>
3399 </table>
3401 END_OF_HTML
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;
3418 my $file_width;
3419 my $col;
3420 my $width;
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;
3433 # Table definition
3434 write_html($handle, <<END_OF_HTML);
3435 <center>
3436 <table width="80%" cellpadding=1 cellspacing=1 border=0>
3438 <tr>
3439 <td width="$file_width%"><br></td>
3440 END_OF_HTML
3441 # Empty first row
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>
3448 END_OF_HTML
3451 # Next row
3452 write_html($handle, <<END_OF_HTML);
3453 </tr>
3455 <tr>
3456 <td class="tableHead">$file_heading</td>
3457 END_OF_HTML
3458 # Heading row
3459 foreach $col (@columns) {
3460 my ($heading, $cols) = @{$col};
3461 my $colspan = "";
3463 $colspan = " colspan=$cols" if ($cols > 1);
3464 write_html($handle, <<END_OF_HTML);
3465 <td class="tableHead"$colspan>$heading</td>
3466 END_OF_HTML
3468 write_html($handle, <<END_OF_HTML);
3469 </tr>
3470 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) = @_;
3483 my $file_code;
3484 my $entry;
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>";
3490 } else {
3491 $file_code = $esc_filename;
3494 # First column: filename
3495 write_html($handle, <<END_OF_HTML);
3496 <tr>
3497 <td class="coverFile">$file_code</td>
3498 END_OF_HTML
3499 # Columns as defined
3500 foreach $entry (@entries) {
3501 my ($found, $hit, $med, $hi, $graph) = @{$entry};
3502 my $bar_graph;
3503 my $class;
3504 my $rate;
3506 # Generate bar graph if requested
3507 if ($graph) {
3508 $bar_graph = get_bar_graph_code($base_dir, $found,
3509 $hit);
3510 write_html($handle, <<END_OF_HTML);
3511 <td class="coverBar" align="center">
3512 $bar_graph
3513 </td>
3514 END_OF_HTML
3516 # Get rate color and text
3517 if ($found == 0) {
3518 $rate = "-";
3519 $class = "Hi";
3520 } else {
3521 $rate = rate($hit, $found, "&nbsp;%");
3522 $class = $rate_name[classify_rate($found, $hit,
3523 $med, $hi)];
3525 write_html($handle, <<END_OF_HTML);
3526 <td class="coverPer$class">$rate</td>
3527 <td class="coverNum$class">$hit / $found</td>
3528 END_OF_HTML
3530 # End of row
3531 write_html($handle, <<END_OF_HTML);
3532 </tr>
3533 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) = @_;
3546 my $entry;
3548 if ($test eq "") {
3549 $test = "<span style=\"font-style:italic\">&lt;unnamed&gt;</span>";
3550 } elsif ($test =~ /^(.*),diff$/) {
3551 $test = $1." (converted)";
3553 # Testname
3554 write_html($handle, <<END_OF_HTML);
3555 <tr>
3556 <td class="testName" colspan=2>$test</td>
3557 END_OF_HTML
3558 # Test data
3559 foreach $entry (@entries) {
3560 my ($found, $hit) = @{$entry};
3561 my $rate = rate($hit, $found, "&nbsp;%");
3563 write_html($handle, <<END_OF_HTML);
3564 <td class="testPer">$rate</td>
3565 <td class="testNum">$hit&nbsp;/&nbsp;$found</td>
3566 END_OF_HTML
3569 write_html($handle, <<END_OF_HTML);
3570 </tr>
3572 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)
3589 </table>
3590 </center>
3591 <br>
3593 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)
3611 <center>
3612 <table width="80%" cellpadding=2 cellspacing=1 border=0>
3614 <tr>
3615 <td><br></td>
3616 </tr>
3618 <tr>
3619 <td class="tableHead">$_[1]</td>
3620 </tr>
3622 <tr>
3623 <td class="testDescription">
3624 <dl>
3625 END_OF_HTML
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]">&nbsp;</a></dt>
3644 <dd>$_[2]<br><br></dd>
3645 END_OF_HTML
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)
3663 </dl>
3664 </td>
3665 </tr>
3666 </table>
3667 </center>
3668 <br>
3670 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";
3701 if ($br_coverage) {
3702 $branch_heading = fmt_centered($br_field_width, "Branch data").
3703 " ";
3705 # *************************************************************
3707 write_html($_[0], <<END_OF_HTML)
3708 <table cellpadding=0 cellspacing=0 border=0>
3709 <tr>
3710 <td><br></td>
3711 </tr>
3712 <tr>
3713 <td>
3714 <pre class="sourceHeading">${lineno_heading}${branch_heading}${line_heading} ${source_heading}</pre>
3715 <pre class="source">
3716 END_OF_HTML
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($)
3735 my ($brdata) = @_;
3736 my $last_block_num;
3737 my $block = [];
3738 my @blocks;
3739 my $i;
3740 my $num = br_ivec_len($brdata);
3742 # Group branches
3743 for ($i = 0; $i < $num; $i++) {
3744 my ($block_num, $branch, $taken) = br_ivec_get($brdata, $i);
3745 my $br;
3747 if (defined($last_block_num) && $block_num != $last_block_num) {
3748 push(@blocks, $block);
3749 $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]++;
3765 return @blocks;
3769 # get_block_len(block)
3771 # Calculate total text length of all branches in a block of branches.
3774 sub get_block_len($)
3776 my ($block) = @_;
3777 my $len = 0;
3778 my $branch;
3780 foreach $branch (@{$block}) {
3781 $len += $branch->[$BR_LEN];
3784 return $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($)
3797 my ($brdata) = @_;
3798 my @blocks = get_branch_blocks($brdata);
3799 my $block;
3800 my $branch;
3801 my $line_len = 0;
3802 my $line = []; # [branch2|" ", branch|" ", ...]
3803 my @lines; # [line1, line2, ...]
3804 my @result;
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) {
3812 # Add it
3813 $line_len += $block_len;
3814 push(@{$line}, @{$block});
3815 next;
3816 } elsif ($block_len <= $br_field_width) {
3817 # It would fit if the line was empty - add it to new
3818 # line
3819 push(@lines, $line);
3820 $line_len = $block_len;
3821 $line = [ @{$block} ];
3822 next;
3824 # Split the block into several lines
3825 foreach $branch (@{$block}) {
3826 if ($line_len + $branch->[$BR_LEN] >= $br_field_width) {
3827 # Start a new line
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
3832 # one # row
3833 push(@{$line}, " ");
3835 push(@lines, $line);
3836 $line_len = 0;
3837 $line = [];
3839 push(@{$line}, $branch);
3840 $line_len += $branch->[$BR_LEN];
3843 push(@lines, $line);
3845 # Convert to HTML
3846 foreach $line (@lines) {
3847 my $current = "";
3848 my $current_len = 0;
3850 foreach $branch (@$line) {
3851 # Skip alignment space
3852 if ($branch eq " ") {
3853 $current .= " ";
3854 $current_len++;
3855 next;
3858 my ($block_num, $br_num, $taken, $len, $open, $close) =
3859 @{$branch};
3860 my $class;
3861 my $title;
3862 my $text;
3864 if ($taken eq '-') {
3865 $class = "branchNoExec";
3866 $text = " # ";
3867 $title = "Branch $br_num was not executed";
3868 } elsif ($taken == 0) {
3869 $class = "branchNoCov";
3870 $text = " - ";
3871 $title = "Branch $br_num was not taken";
3872 } else {
3873 $class = "branchCov";
3874 $text = " + ";
3875 $title = "Branch $br_num was taken $taken ".
3876 "time";
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)).
3889 $current;
3891 push(@result, $current);
3894 return @result;
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) = @_;
3907 my $result;
3908 my $exp;
3910 $result = sprintf("%*.0f", $width, $count);
3911 while (length($result) > $width) {
3912 last if ($count < 10);
3913 $exp++;
3914 $count = int($count/10);
3915 $result = sprintf("%*s", $width, ">$count*10^$exp");
3917 return $result;
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
3925 # by gen_png()
3928 sub write_source_line(*$$$$$$)
3930 my ($handle, $line, $source, $count, $converted, $brdata,
3931 $add_anchor) = @_;
3932 my $source_format;
3933 my $count_format;
3934 my $result;
3935 my $anchor_start = "";
3936 my $anchor_end = "";
3937 my $count_field_width = $line_field_width - 1;
3938 my @br_html;
3939 my $html;
3941 # Get branch HTML data for this line
3942 @br_html = get_branch_html($brdata) if ($br_coverage);
3944 if (!defined($count)) {
3945 $result = "";
3946 $source_format = "";
3947 $count_format = " "x$count_field_width;
3949 elsif ($count == 0) {
3950 $result = $count;
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);
3959 else {
3960 $result = $count;
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
3968 if ($add_anchor)
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);
3987 if ($br_coverage) {
3988 # Add lines for overlong branch information
3989 foreach (@br_html) {
3990 write_html($handle, "<span class=\"lineNum\">".
3991 " </span>$_\n");
3994 # *************************************************************
3996 return($result);
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)
4011 </pre>
4012 </td>
4013 </tr>
4014 </table>
4015 <br>
4017 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 = "";
4036 my $epilog;
4038 if (defined($_[2]))
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>
4049 </table>
4050 <br>
4051 END_OF_HTML
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">
4075 <html lang="en">
4077 <head>
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">
4081 </head>
4083 <frameset cols="$frame_width,*">
4084 <frame src="$_[2].gcov.overview.$html_ext" name="overview">
4085 <frame src="$_[2].gcov.$html_ext" name="source">
4086 <noframes>
4087 <center>Frames not supported by your browser!<br></center>
4088 </noframes>
4089 </frameset>
4091 </html>
4092 END_OF_HTML
4095 # *************************************************************
4100 # sub write_overview_line(filehandle, basename, line, link)
4104 sub write_overview_line(*$$$)
4106 my $y1 = $_[2] - 1;
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">
4114 END_OF_HTML
4117 # *************************************************************
4122 # write_overview(filehandle, basedir, basename, pagetitle, lines)
4126 sub write_overview(*$$$$)
4128 my $index;
4129 my $max_line = $_[4] - 1;
4130 my $offset;
4132 # *************************************************************
4134 write_html($_[0], <<END_OF_HTML)
4135 <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">
4137 <html lang="en">
4139 <head>
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">
4143 </head>
4145 <body>
4146 <map name="overview">
4147 END_OF_HTML
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);
4164 else
4166 write_overview_line($_[0], $_[2], $index, $index - $offset);
4170 # *************************************************************
4172 write_html($_[0], <<END_OF_HTML)
4173 </map>
4175 <center>
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">
4178 </center>
4179 </body>
4180 </html>
4181 END_OF_HTML
4184 # *************************************************************
4188 sub max($$)
4190 my ($a, $b) = @_;
4192 return $a if ($a > $b);
4193 return $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];
4209 my $type = $_[1];
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];
4215 my $fn_hit = $_[7];
4216 my $br_found = $_[8];
4217 my $br_hit = $_[9];
4218 my $sort_type = $_[10];
4219 my $base_dir;
4220 my $view;
4221 my $test;
4222 my $base_name;
4223 my $style;
4224 my $rate;
4225 my @row_left;
4226 my @row_right;
4227 my $num_rows;
4228 my $i;
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)
4236 # Main overview
4237 $base_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)
4249 # File view
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);
4255 if ($frames)
4257 # Need to break frameset when clicking any of these
4258 # links
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";
4264 else
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)";
4280 $view .= "</span>";
4283 elsif ($type == $HDR_TESTDESC)
4285 # Test description header
4286 $base_dir = "";
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> )";
4305 else
4307 $test .= " ( <span style=\"font-size:80%;\">".
4308 "<a href=\"$base_dir".
4309 "descriptions.$html_ext\">".
4310 "view descriptions</a></span> )";
4314 # Write header
4315 write_header_prolog(*HTML_HANDLE, $base_dir);
4317 # Left row
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]]);
4325 # Right row
4326 if ($legend && ($type == $HDR_SOURCE || $type == $HDR_FUNC)) {
4327 my $text = <<END_OF_HTML;
4328 Lines:
4329 <span class="coverLegendCov">hit</span>
4330 <span class="coverLegendNoCov">not hit</span>
4331 END_OF_HTML
4332 if ($br_coverage) {
4333 $text .= <<END_OF_HTML;
4334 | Branches:
4335 <span class="coverLegendCov">+</span> taken
4336 <span class="coverLegendNoCov">-</span> not taken
4337 <span class="coverLegendNoCov">#</span> not executed
4338 END_OF_HTML
4340 push(@row_left, [[undef, "headerItem", "Legend:"],
4341 [undef, "headerValueLeg", $text]]);
4342 } elsif ($legend && ($type != $HDR_TESTDESC)) {
4343 my $text = <<END_OF_HTML;
4344 Rating:
4345 <span class="coverLegendCovLo" title="Coverage rates below $med_limit % are classified as low">low: &lt; $med_limit %</span>
4346 <span class="coverLegendCovMed" title="Coverage rates between $med_limit % and $hi_limit % are classified as medium">medium: &gt;= $med_limit %</span>
4347 <span class="coverLegendCovHi" title="Coverage rates of $hi_limit % and more are classified as high">high: &gt;= $hi_limit %</span>
4348 END_OF_HTML
4349 push(@row_left, [[undef, "headerItem", "Legend:"],
4350 [undef, "headerValueLeg", $text]]);
4352 if ($type == $HDR_TESTDESC) {
4353 push(@row_right, [[ "55%" ]]);
4354 } else {
4355 push(@row_right, [["15%", undef, undef ],
4356 ["10%", "headerCovTableHead", "Hit" ],
4357 ["10%", "headerCovTableHead", "Total" ],
4358 ["15%", "headerCovTableHead", "Coverage"]]);
4360 # Line 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);
4369 # Function coverage
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);
4380 # Branch coverage
4381 if ($br_coverage) {
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);
4392 # Print rows
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)) {
4402 $right = [];
4404 write_header_line(*HTML_HANDLE, @{$left},
4405 [ $i == 0 ? "5%" : undef, undef, undef],
4406 @{$right});
4409 # Fourth line
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) {
4423 # Sort by name
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) = @_;
4440 my $png;
4441 my $link_start;
4442 my $link_end;
4444 if (!defined($link)) {
4445 $png = "glass.png";
4446 $link_start = "";
4447 $link_end = "";
4448 } else {
4449 $png = "updown.png";
4450 $link_start = '<a href="'.$link.'">';
4451 $link_end = "</a>";
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) = @_;
4462 my $result = $text;
4463 my $link;
4465 if ($sort_button) {
4466 if ($type == $HEAD_NO_DETAIL) {
4467 $link = "index.$html_ext";
4468 } else {
4469 $link = "index-detail.$html_ext";
4472 $result .= get_sort_code($link, "Sort by name", $base);
4474 return $result;
4477 sub get_line_code($$$$$)
4479 my ($type, $sort_type, $text, $sort_button, $base) = @_;
4480 my $result = $text;
4481 my $sort_link;
4483 if ($type == $HEAD_NO_DETAIL) {
4484 # Just text
4485 if ($sort_button) {
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> )';
4493 if ($sort_button) {
4494 $sort_link = "index-sort-l.$html_ext";
4496 } else {
4497 # Text + link to standard view
4498 $result .= ' ( <a class="detail" href="index'.
4499 $fileview_sortname[$sort_type].'.'.$html_ext.
4500 '">hide details</a> )';
4501 if ($sort_button) {
4502 $sort_link = "index-detail-sort-l.$html_ext";
4505 # Add sort button
4506 $result .= get_sort_code($sort_link, "Sort by line coverage", $base);
4508 return $result;
4511 sub get_func_code($$$$)
4513 my ($type, $text, $sort_button, $base) = @_;
4514 my $result = $text;
4515 my $link;
4517 if ($sort_button) {
4518 if ($type == $HEAD_NO_DETAIL) {
4519 $link = "index-sort-f.$html_ext";
4520 } else {
4521 $link = "index-detail-sort-f.$html_ext";
4524 $result .= get_sort_code($link, "Sort by function coverage", $base);
4525 return $result;
4528 sub get_br_code($$$$)
4530 my ($type, $text, $sort_button, $base) = @_;
4531 my $result = $text;
4532 my $link;
4534 if ($sort_button) {
4535 if ($type == $HEAD_NO_DETAIL) {
4536 $link = "index-sort-b.$html_ext";
4537 } else {
4538 $link = "index-detail-sort-b.$html_ext";
4541 $result .= get_sort_code($link, "Sort by branch coverage", $base);
4542 return $result;
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,
4553 # func_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"
4562 # otherwise.
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];
4575 my $filename;
4576 my $bar_graph;
4577 my $hit;
4578 my $found;
4579 my $fn_found;
4580 my $fn_hit;
4581 my $br_found;
4582 my $br_hit;
4583 my $page_link;
4584 my $testname;
4585 my $testdata;
4586 my $testfncdata;
4587 my $testbrdata;
4588 my %affecting_tests;
4589 my $line_code = "";
4590 my $func_code;
4591 my $br_code;
4592 my $file_code;
4593 my @head_columns;
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 :
4601 $HEAD_NO_DETAIL,
4602 $fileview ? "Filename" : "Directory",
4603 $sort && $sort_type != $SORT_FILE,
4604 $base_dir);
4605 $line_code = get_line_code($detailed ? $HEAD_DETAIL_SHOWN :
4606 $HEAD_DETAIL_HIDDEN,
4607 $sort_type,
4608 "Line Coverage",
4609 $sort && $sort_type != $SORT_LINE,
4610 $base_dir);
4611 $func_code = get_func_code($detailed ? $HEAD_DETAIL_HIDDEN :
4612 $HEAD_NO_DETAIL,
4613 "Functions",
4614 $sort && $sort_type != $SORT_FUNC,
4615 $base_dir);
4616 $br_code = get_br_code($detailed ? $HEAD_DETAIL_HIDDEN :
4617 $HEAD_NO_DETAIL,
4618 "Branches",
4619 $sort && $sort_type != $SORT_BRANCH,
4620 $base_dir);
4621 } else {
4622 $file_code = get_file_code($HEAD_NO_DETAIL,
4623 $fileview ? "Filename" : "Directory",
4624 $sort && $sort_type != $SORT_FILE,
4625 $base_dir);
4626 $line_code = get_line_code($HEAD_NO_DETAIL, $sort_type, "Line Coverage",
4627 $sort && $sort_type != $SORT_LINE,
4628 $base_dir);
4629 $func_code = get_func_code($HEAD_NO_DETAIL, "Functions",
4630 $sort && $sort_type != $SORT_FUNC,
4631 $base_dir);
4632 $br_code = get_br_code($HEAD_NO_DETAIL, "Branches",
4633 $sort && $sort_type != $SORT_BRANCH,
4634 $base_dir);
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))
4644 my @columns;
4645 ($found, $hit, $fn_found, $fn_hit, $br_found, $br_hit,
4646 $page_link) = @{$overview->{$filename}};
4648 # Line coverage
4649 push(@columns, [$found, $hit, $med_limit, $hi_limit, 1]);
4650 # Function coverage
4651 if ($func_coverage) {
4652 push(@columns, [$fn_found, $fn_hit, $fn_med_limit,
4653 $fn_hi_limit, 0]);
4655 # Branch coverage
4656 if ($br_coverage) {
4657 push(@columns, [$br_found, $br_hit, $br_med_limit,
4658 $br_hi_limit, 0]);
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
4668 # as well
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))
4680 my @results;
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\">".
4689 "$testname</a>";
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,
4696 @results);
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]};
4715 my $found = 0;
4716 my $hit = 0;
4718 # Calculate sum
4719 $found = 0;
4720 $hit = 0;
4722 foreach (keys(%hash))
4724 $found++;
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) = @_;
4741 my $function;
4742 my $fn_found;
4743 my $fn_hit;
4745 $fn_found = scalar(keys(%{$sumfnccount}));
4746 $fn_hit = 0;
4747 foreach $function (keys(%{$sumfnccount})) {
4748 if ($sumfnccount->{$function} > 0) {
4749 $fn_hit++;
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($)
4764 my ($taken) = @_;
4766 return 0 if ($taken eq '-');
4767 return $taken + 1;
4772 # br_num_to_taken(taken)
4774 # Convert a branch taken value in number format to .info format.
4777 sub br_num_to_taken($)
4779 my ($taken) = @_;
4781 return '-' if ($taken == 0);
4782 return $taken - 1;
4787 # br_taken_add(taken1, taken2)
4789 # Return the result of taken1 + taken2 for 'branch taken' values.
4792 sub br_taken_add($$)
4794 my ($t1, $t2) = @_;
4796 return $t1 if (!defined($t2));
4797 return $t2 if (!defined($t1));
4798 return $t1 if ($t2 eq '-');
4799 return $t2 if ($t1 eq '-');
4800 return $t1 + $t2;
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($$)
4813 my ($t1, $t2) = @_;
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;
4820 return $t1 - $t2;
4825 # br_ivec_len(vector)
4827 # Return the number of entries in the branch coverage vector.
4830 sub br_ivec_len($)
4832 my ($vec) = @_;
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.
4845 sub br_ivec_get($$)
4847 my ($vec, $num) = @_;
4848 my $block;
4849 my $branch;
4850 my $taken;
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) = @_;
4875 my $offset;
4876 my $num = br_ivec_len($vec);
4877 my $i;
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);
4887 # Add taken counts
4888 $taken = br_taken_add($taken, $v_taken);
4889 last;
4892 $offset = $i * $BR_VEC_ENTRIES;
4893 $taken = br_taken_to_num($taken);
4895 # Add to vector
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;
4900 return $vec;
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) = @_;
4913 my $line;
4914 my $br_found = 0;
4915 my $br_hit = 0;
4917 foreach $line (keys(%{$sumbrcount})) {
4918 my $brdata = $sumbrcount->{$line};
4919 my $i;
4920 my $num = br_ivec_len($brdata);
4922 for ($i = 0; $i < $num; $i++) {
4923 my $taken;
4925 (undef, undef, $taken) = br_ivec_get($brdata, $i);
4927 $br_found++;
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) = @_;
4947 my $testname;
4948 my $testcount;
4949 my $testfnccount;
4950 my $testbrcount;
4951 my %result;
4952 my $found;
4953 my $hit;
4954 my $fn_found;
4955 my $fn_hit;
4956 my $br_found;
4957 my $br_hit;
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};
4966 # Calculate sum
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);
4971 if ($hit>0)
4973 $result{$testname} = "$found,$hit,$fn_found,$fn_hit,".
4974 "$br_found,$br_hit";
4978 return(\%result);
4982 sub get_hash_reverse($)
4984 my ($hash) = @_;
4985 my %result;
4987 foreach (keys(%{$hash})) {
4988 $result{$hash->{$_}} = $_;
4991 return \%result;
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().
5001 # Die on error.
5004 sub write_source($$$$$$$)
5006 local *HTML_HANDLE = $_[0];
5007 local *SOURCE_HANDLE;
5008 my $source_filename = $_[1];
5009 my %count_data;
5010 my $line_number;
5011 my @result;
5012 my $checkdata = $_[3];
5013 my $converted = $_[4];
5014 my $funcdata = $_[5];
5015 my $sumbrcount = $_[6];
5016 my $datafunc = get_hash_reverse($funcdata);
5017 my $add_anchor;
5018 my @file;
5020 if ($_[2])
5022 %count_data = %{$_[2]};
5025 if (!open(SOURCE_HANDLE, "<", $source_filename)) {
5026 my @lines;
5027 my $last_line = 0;
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));
5037 if (@lines) {
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;
5044 $line_number++) {
5045 push(@file, "/* EOF */");
5047 } else {
5048 @file = <SOURCE_HANDLE>;
5051 write_source_prolog(*HTML_HANDLE);
5052 $line_number = 0;
5053 foreach (@file) {
5054 $line_number++;
5055 chomp($_);
5057 # Also remove CR from line-end
5058 s/\015$//;
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:".
5065 "$line_number\n");
5068 $add_anchor = 0;
5069 if ($frames) {
5070 if (($line_number - 1) % $nav_resolution == 0) {
5071 $add_anchor = 1;
5074 if ($func_coverage) {
5075 if ($line_number == 1) {
5076 $add_anchor = 1;
5077 } elsif (defined($datafunc->{$line_number +
5078 $func_offset})) {
5079 $add_anchor = 1;
5082 push (@result,
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);
5091 return(@result);
5095 sub funcview_get_func_code($$$)
5097 my ($name, $base, $type) = @_;
5098 my $result;
5099 my $link;
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);
5107 return $result;
5110 sub funcview_get_count_code($$$)
5112 my ($name, $base, $type) = @_;
5113 my $result;
5114 my $link;
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);
5122 return $result;
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) = @_;
5136 if ($type == 0) {
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.
5151 # Die on error.
5154 sub write_function_table(*$$$$$$$$$$)
5156 local *HTML_HANDLE = $_[0];
5157 my $source = $_[1];
5158 my $sumcount = $_[2];
5159 my $funcdata = $_[3];
5160 my $sumfncdata = $_[4];
5161 my $testfncdata = $_[5];
5162 my $sumbrcount = $_[6];
5163 my $testbrdata = $_[7];
5164 my $name = $_[8];
5165 my $base = $_[9];
5166 my $type = $_[10];
5167 my $func;
5168 my $func_code;
5169 my $count_code;
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)
5175 <center>
5176 <table width="60%" cellpadding=1 cellspacing=1 border=0>
5177 <tr><td><br></td></tr>
5178 <tr>
5179 <td width="80%" class="tableHead">$func_code</td>
5180 <td width="20%" class="tableHead">$count_code</td>
5181 </tr>
5182 END_OF_HTML
5185 # Get a sorted table
5186 foreach $func (funcview_get_sorted($funcdata, $sumfncdata, $type)) {
5187 if (!defined($funcdata->{$func}))
5189 next;
5192 my $startline = $funcdata->{$func} - $func_offset;
5193 my $name = $func;
5194 my $count = $sumfncdata->{$name};
5195 my $countstyle;
5197 # Demangle C++ function names if requested
5198 if ($demangle_cpp) {
5199 $name = `c++filt "$name"`;
5200 chomp($name);
5202 # Escape any remaining special characters
5203 $name = escape_html($name);
5204 if ($startline < 1) {
5205 $startline = 1;
5207 if ($count == 0) {
5208 $countstyle = "coverFnLo";
5209 } else {
5210 $countstyle = "coverFnHi";
5213 write_html(*HTML_HANDLE, <<END_OF_HTML)
5214 <tr>
5215 <td class="coverFn"><a href="$source#$startline">$name</a></td>
5216 <td class="$countstyle">$count</td>
5217 </tr>
5218 END_OF_HTML
5221 write_html(*HTML_HANDLE, <<END_OF_HTML)
5222 </table>
5223 <br>
5224 </center>
5225 END_OF_HTML
5231 # info(printf_parameter)
5233 # Use printf to write PRINTF_PARAMETER to stdout only when the $quiet flag
5234 # is not set.
5237 sub info(@)
5239 if (!$quiet)
5241 # Print info string
5242 printf(@_);
5248 # subtract_counts(data_ref, base_ref)
5251 sub subtract_counts($$)
5253 my %data = %{$_[0]};
5254 my %base = %{$_[1]};
5255 my $line;
5256 my $data_count;
5257 my $base_count;
5258 my $hit = 0;
5259 my $found = 0;
5261 foreach $line (keys(%data))
5263 $found++;
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($$)
5292 my %data;
5293 my %base;
5294 my $func;
5295 my $data_count;
5296 my $base_count;
5297 my $fn_hit = 0;
5298 my $fn_found = 0;
5300 %data = %{$_[0]} if (defined($_[0]));
5301 %base = %{$_[1]} if (defined($_[1]));
5302 foreach $func (keys(%data)) {
5303 $fn_found++;
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) {
5312 $data_count = 0;
5316 $data{$func} = $data_count;
5317 if ($data_count > 0) {
5318 $fn_hit++;
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]};
5337 my $filename;
5338 my $testname;
5339 my $data;
5340 my $data_testdata;
5341 my $data_funcdata;
5342 my $data_checkdata;
5343 my $data_testfncdata;
5344 my $data_testbrdata;
5345 my $data_count;
5346 my $data_testfnccount;
5347 my $data_testbrcount;
5348 my $base;
5349 my $base_checkdata;
5350 my $base_sumfnccount;
5351 my $base_sumbrcount;
5352 my $base_count;
5353 my $sumcount;
5354 my $sumfnccount;
5355 my $sumbrcount;
5356 my $found;
5357 my $hit;
5358 my $fn_found;
5359 my $fn_hit;
5360 my $br_found;
5361 my $br_hit;
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))
5372 next;
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
5387 $sumcount = {};
5388 $sumfnccount = {};
5389 $sumbrcount = {};
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,
5403 $base_sumfnccount);
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
5410 if ($hit > 0)
5412 # Write back resulting hash
5413 $data_testdata->{$testname} = $data_count;
5414 $data_testfncdata->{$testname} =
5415 $data_testfnccount;
5416 $data_testbrdata->{$testname} =
5417 $data_testbrcount;
5419 else
5421 # Delete test case which did not impact this
5422 # file
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,
5435 $BR_ADD);
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];
5505 my $prefix = $_[1];
5507 if (defined($prefix) && ($prefix ne ""))
5509 if ($filename =~ /^\Q$prefix\E\/(.*)$/)
5511 return substr($filename, length($prefix) + 1);
5515 return $filename;
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($@)
5533 my $mode = shift;
5534 my $result;
5535 local *OLD_STDERR;
5536 local *OLD_STDOUT;
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");
5546 system(@_);
5547 $result = $?;
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");
5557 return $result;
5562 # read_config(filename)
5564 # Read configuration file FILENAME and return a reference to a hash containing
5565 # all valid key=value pairs found.
5568 sub read_config($)
5570 my $filename = $_[0];
5571 my %result;
5572 my $key;
5573 my $value;
5574 local *HANDLE;
5576 if (!open(HANDLE, "<", $filename))
5578 warn("WARNING: cannot read configuration file $filename\n");
5579 return undef;
5581 while (<HANDLE>)
5583 chomp;
5584 # Skip comments
5585 s/#.*//;
5586 # Remove leading blanks
5587 s/^\s+//;
5588 # Remove trailing blanks
5589 s/\s+$//;
5590 next unless length;
5591 ($key, $value) = split(/\s*=\s*/, $_, 2);
5592 if (defined($key) && defined($value))
5594 $result{$key} = $value;
5596 else
5598 warn("WARNING: malformed statement in line $. ".
5599 "of configuration file $filename\n");
5602 close(HANDLE);
5603 return \%result;
5608 # apply_config(REF)
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.
5619 sub apply_config($)
5621 my $ref = $_[0];
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];
5644 my $result = "";
5646 if (defined($filename))
5648 local *HANDLE;
5650 open(HANDLE, "<", $filename)
5651 or die("ERROR: cannot open html prolog $filename!\n");
5652 while (<HANDLE>)
5654 $result .= $_;
5656 close(HANDLE);
5658 else
5660 $result = <<END_OF_HTML
5661 <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">
5663 <html lang="en">
5665 <head>
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">
5669 </head>
5671 <body>
5673 END_OF_HTML
5677 return $result;
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];
5690 my $result = "";
5692 if (defined($filename))
5694 local *HANDLE;
5696 open(HANDLE, "<", $filename)
5697 or die("ERROR: cannot open html epilog $filename!\n");
5698 while (<HANDLE>)
5700 $result .= $_;
5702 close(HANDLE);
5704 else
5706 $result = <<END_OF_HTML
5708 </body>
5709 </html>
5710 END_OF_HTML
5714 return $result;
5718 sub warn_handler($)
5720 my ($msg) = @_;
5722 warn("$tool_name: $msg");
5725 sub die_handler($)
5727 my ($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) = @_;
5741 my @items;
5742 my $item;
5744 return if (!@ignore_errors);
5746 foreach $item (@ignore_errors) {
5747 $item =~ s/\s//g;
5748 if ($item =~ /,/) {
5749 # Split and add comma-separated parameters
5750 push(@items, split(/,/, $item));
5751 } else {
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: ".
5761 "$item\n");
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
5775 # characters wide.
5778 sub rate($$;$$$)
5780 my ($hit, $found, $suffix, $precision, $width) = @_;
5781 my $rate;
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);