Separate projection mode from rest of touch HUD
[chromium-blink-merge.git] / third_party / lcov-1.9 / bin / genhtml
blobd74063a4fdb85e582a2af0fd67f90b88ea6fba23
1 #!/usr/bin/perl -w
3 # Copyright (c) International Business Machines Corp., 2002,2010
5 # This program is free software; you can redistribute it and/or modify
6 # it under the terms of the GNU General Public License as published by
7 # the Free Software Foundation; either version 2 of the License, or (at
8 # your option) any later version.
10 # This program is distributed in the hope that it will be useful, but
11 # WITHOUT ANY WARRANTY; without even the implied warranty of
12 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
13 # General Public License for more details.
15 # You should have received a copy of the GNU General Public License
16 # along with this program; if not, write to the Free Software
17 # Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
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.9';
76 our $lcov_url = "http://ltp.sourceforge.net/coverage/lcov.php";
77 our $tool_name = basename($0);
79 # Specify coverage rate limits (in %) for classifying file entries
80 # HI: $hi_limit <= rate <= 100 graph color: green
81 # MED: $med_limit <= rate < $hi_limit graph color: orange
82 # LO: 0 <= rate < $med_limit graph color: red
84 # For line coverage/all coverage types if not specified
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 # Data related prototypes
159 sub print_usage(*);
160 sub gen_html();
161 sub html_create($$);
162 sub process_dir($);
163 sub process_file($$$);
164 sub info(@);
165 sub read_info_file($);
166 sub get_info_entry($);
167 sub set_info_entry($$$$$$$$$;$$$$$$);
168 sub get_prefix(@);
169 sub shorten_prefix($);
170 sub get_dir_list(@);
171 sub get_relative_base_path($);
172 sub read_testfile($);
173 sub get_date_string();
174 sub create_sub_dir($);
175 sub subtract_counts($$);
176 sub add_counts($$);
177 sub apply_baseline($$);
178 sub remove_unused_descriptions();
179 sub get_found_and_hit($);
180 sub get_affecting_tests($$$);
181 sub combine_info_files($$);
182 sub merge_checksums($$$);
183 sub combine_info_entries($$$);
184 sub apply_prefix($$);
185 sub system_no_output($@);
186 sub read_config($);
187 sub apply_config($);
188 sub get_html_prolog($);
189 sub get_html_epilog($);
190 sub write_dir_page($$$$$$$$$$$$$$$$$);
191 sub classify_rate($$$$);
192 sub br_taken_add($$);
193 sub br_taken_sub($$);
194 sub br_ivec_len($);
195 sub br_ivec_get($$);
196 sub br_ivec_push($$$$);
197 sub combine_brcount($$$);
198 sub get_br_found_and_hit($);
199 sub warn_handler($);
200 sub die_handler($);
203 # HTML related prototypes
204 sub escape_html($);
205 sub get_bar_graph_code($$$);
207 sub write_png_files();
208 sub write_htaccess_file();
209 sub write_css_file();
210 sub write_description_file($$$$$$$);
211 sub write_function_table(*$$$$$$$$$$);
213 sub write_html(*$);
214 sub write_html_prolog(*$$);
215 sub write_html_epilog(*$;$);
217 sub write_header(*$$$$$$$$$$);
218 sub write_header_prolog(*$);
219 sub write_header_line(*@);
220 sub write_header_epilog(*$);
222 sub write_file_table(*$$$$$$$);
223 sub write_file_table_prolog(*$@);
224 sub write_file_table_entry(*$$$@);
225 sub write_file_table_detail_entry(*$@);
226 sub write_file_table_epilog(*);
228 sub write_test_table_prolog(*$);
229 sub write_test_table_entry(*$$);
230 sub write_test_table_epilog(*);
232 sub write_source($$$$$$$);
233 sub write_source_prolog(*);
234 sub write_source_line(*$$$$$$);
235 sub write_source_epilog(*);
237 sub write_frameset(*$$$);
238 sub write_overview_line(*$$$);
239 sub write_overview(*$$$$);
241 # External prototype (defined in genpng)
242 sub gen_png($$$@);
245 # Global variables & initialization
246 our %info_data; # Hash containing all data from .info file
247 our $dir_prefix; # Prefix to remove from all sub directories
248 our %test_description; # Hash containing test descriptions if available
249 our $date = get_date_string();
251 our @info_filenames; # List of .info files to use as data source
252 our $test_title; # Title for output as written to each page header
253 our $output_directory; # Name of directory in which to store output
254 our $base_filename; # Optional name of file containing baseline data
255 our $desc_filename; # Name of file containing test descriptions
256 our $css_filename; # Optional name of external stylesheet file to use
257 our $quiet; # If set, suppress information messages
258 our $help; # Help option flag
259 our $version; # Version option flag
260 our $show_details; # If set, generate detailed directory view
261 our $no_prefix; # If set, do not remove filename prefix
262 our $func_coverage = 1; # If set, generate function coverage statistics
263 our $no_func_coverage; # Disable func_coverage
264 our $br_coverage = 1; # If set, generate branch coverage statistics
265 our $no_br_coverage; # Disable br_coverage
266 our $sort = 1; # If set, provide directory listings with sorted entries
267 our $no_sort; # Disable sort
268 our $frames; # If set, use frames for source code view
269 our $keep_descriptions; # If set, do not remove unused test case descriptions
270 our $no_sourceview; # If set, do not create a source code view for each file
271 our $highlight; # If set, highlight lines covered by converted data only
272 our $legend; # If set, include legend in output
273 our $tab_size = 8; # Number of spaces to use in place of tab
274 our $config; # Configuration file contents
275 our $html_prolog_file; # Custom HTML prolog file (up to and including <body>)
276 our $html_epilog_file; # Custom HTML epilog file (from </body> onwards)
277 our $html_prolog; # Actual HTML prolog
278 our $html_epilog; # Actual HTML epilog
279 our $html_ext = "html"; # Extension for generated HTML files
280 our $html_gzip = 0; # Compress with gzip
281 our $demangle_cpp = 0; # Demangle C++ function names
282 our @fileview_sortlist;
283 our @fileview_sortname = ("", "-sort-l", "-sort-f", "-sort-b");
284 our @funcview_sortlist;
285 our @rate_name = ("Lo", "Med", "Hi");
286 our @rate_png = ("ruby.png", "amber.png", "emerald.png");
288 our $cwd = `pwd`; # Current working directory
289 chomp($cwd);
290 our $tool_dir = dirname($0); # Directory where genhtml tool is installed
294 # Code entry point
297 $SIG{__WARN__} = \&warn_handler;
298 $SIG{__DIE__} = \&die_handler;
300 # Prettify version string
301 $lcov_version =~ s/\$\s*Revision\s*:?\s*(\S+)\s*\$/$1/;
303 # Add current working directory if $tool_dir is not already an absolute path
304 if (! ($tool_dir =~ /^\/(.*)$/))
306 $tool_dir = "$cwd/$tool_dir";
309 # Read configuration file if available
310 if (defined($ENV{"HOME"}) && (-r $ENV{"HOME"}."/.lcovrc"))
312 $config = read_config($ENV{"HOME"}."/.lcovrc");
314 elsif (-r "/etc/lcovrc")
316 $config = read_config("/etc/lcovrc");
319 if ($config)
321 # Copy configuration file values to variables
322 apply_config({
323 "genhtml_css_file" => \$css_filename,
324 "genhtml_hi_limit" => \$hi_limit,
325 "genhtml_med_limit" => \$med_limit,
326 "genhtml_line_field_width" => \$line_field_width,
327 "genhtml_overview_width" => \$overview_width,
328 "genhtml_nav_resolution" => \$nav_resolution,
329 "genhtml_nav_offset" => \$nav_offset,
330 "genhtml_keep_descriptions" => \$keep_descriptions,
331 "genhtml_no_prefix" => \$no_prefix,
332 "genhtml_no_source" => \$no_sourceview,
333 "genhtml_num_spaces" => \$tab_size,
334 "genhtml_highlight" => \$highlight,
335 "genhtml_legend" => \$legend,
336 "genhtml_html_prolog" => \$html_prolog_file,
337 "genhtml_html_epilog" => \$html_epilog_file,
338 "genhtml_html_extension" => \$html_ext,
339 "genhtml_html_gzip" => \$html_gzip,
340 "genhtml_function_hi_limit" => \$fn_hi_limit,
341 "genhtml_function_med_limit" => \$fn_med_limit,
342 "genhtml_function_coverage" => \$func_coverage,
343 "genhtml_branch_hi_limit" => \$br_hi_limit,
344 "genhtml_branch_med_limit" => \$br_med_limit,
345 "genhtml_branch_coverage" => \$br_coverage,
346 "genhtml_branch_field_width" => \$br_field_width,
347 "genhtml_sort" => \$sort,
351 # Copy limit values if not specified
352 $fn_hi_limit = $hi_limit if (!defined($fn_hi_limit));
353 $fn_med_limit = $med_limit if (!defined($fn_med_limit));
354 $br_hi_limit = $hi_limit if (!defined($br_hi_limit));
355 $br_med_limit = $med_limit if (!defined($br_med_limit));
357 # Parse command line options
358 if (!GetOptions("output-directory|o=s" => \$output_directory,
359 "title|t=s" => \$test_title,
360 "description-file|d=s" => \$desc_filename,
361 "keep-descriptions|k" => \$keep_descriptions,
362 "css-file|c=s" => \$css_filename,
363 "baseline-file|b=s" => \$base_filename,
364 "prefix|p=s" => \$dir_prefix,
365 "num-spaces=i" => \$tab_size,
366 "no-prefix" => \$no_prefix,
367 "no-sourceview" => \$no_sourceview,
368 "show-details|s" => \$show_details,
369 "frames|f" => \$frames,
370 "highlight" => \$highlight,
371 "legend" => \$legend,
372 "quiet|q" => \$quiet,
373 "help|h|?" => \$help,
374 "version|v" => \$version,
375 "html-prolog=s" => \$html_prolog_file,
376 "html-epilog=s" => \$html_epilog_file,
377 "html-extension=s" => \$html_ext,
378 "html-gzip" => \$html_gzip,
379 "function-coverage" => \$func_coverage,
380 "no-function-coverage" => \$no_func_coverage,
381 "branch-coverage" => \$br_coverage,
382 "no-branch-coverage" => \$no_br_coverage,
383 "sort" => \$sort,
384 "no-sort" => \$no_sort,
385 "demangle-cpp" => \$demangle_cpp,
388 print(STDERR "Use $tool_name --help to get usage information\n");
389 exit(1);
390 } else {
391 # Merge options
392 if ($no_func_coverage) {
393 $func_coverage = 0;
395 if ($no_br_coverage) {
396 $br_coverage = 0;
399 # Merge sort options
400 if ($no_sort) {
401 $sort = 0;
405 @info_filenames = @ARGV;
407 # Check for help option
408 if ($help)
410 print_usage(*STDOUT);
411 exit(0);
414 # Check for version option
415 if ($version)
417 print("$tool_name: $lcov_version\n");
418 exit(0);
421 # Check for info filename
422 if (!@info_filenames)
424 die("No filename specified\n".
425 "Use $tool_name --help to get usage information\n");
428 # Generate a title if none is specified
429 if (!$test_title)
431 if (scalar(@info_filenames) == 1)
433 # Only one filename specified, use it as title
434 $test_title = basename($info_filenames[0]);
436 else
438 # More than one filename specified, used default title
439 $test_title = "unnamed";
443 # Make sure css_filename is an absolute path (in case we're changing
444 # directories)
445 if ($css_filename)
447 if (!($css_filename =~ /^\/(.*)$/))
449 $css_filename = $cwd."/".$css_filename;
453 # Make sure tab_size is within valid range
454 if ($tab_size < 1)
456 print(STDERR "ERROR: invalid number of spaces specified: ".
457 "$tab_size!\n");
458 exit(1);
461 # Get HTML prolog and epilog
462 $html_prolog = get_html_prolog($html_prolog_file);
463 $html_epilog = get_html_epilog($html_epilog_file);
465 # Issue a warning if --no-sourceview is enabled together with --frames
466 if ($no_sourceview && defined($frames))
468 warn("WARNING: option --frames disabled because --no-sourceview ".
469 "was specified!\n");
470 $frames = undef;
473 # Issue a warning if --no-prefix is enabled together with --prefix
474 if ($no_prefix && defined($dir_prefix))
476 warn("WARNING: option --prefix disabled because --no-prefix was ".
477 "specified!\n");
478 $dir_prefix = undef;
481 @fileview_sortlist = ($SORT_FILE);
482 @funcview_sortlist = ($SORT_FILE);
484 if ($sort) {
485 push(@fileview_sortlist, $SORT_LINE);
486 push(@fileview_sortlist, $SORT_FUNC) if ($func_coverage);
487 push(@fileview_sortlist, $SORT_BRANCH) if ($br_coverage);
488 push(@funcview_sortlist, $SORT_LINE);
491 if ($frames)
493 # Include genpng code needed for overview image generation
494 do("$tool_dir/genpng");
497 # Ensure that the c++filt tool is available when using --demangle-cpp
498 if ($demangle_cpp)
500 if (system_no_output(3, "c++filt", "--version")) {
501 die("ERROR: could not find c++filt tool needed for ".
502 "--demangle-cpp\n");
506 # Make sure output_directory exists, create it if necessary
507 if ($output_directory)
509 stat($output_directory);
511 if (! -e _)
513 create_sub_dir($output_directory);
517 # Do something
518 gen_html();
520 exit(0);
525 # print_usage(handle)
527 # Print usage information.
530 sub print_usage(*)
532 local *HANDLE = $_[0];
534 print(HANDLE <<END_OF_USAGE);
535 Usage: $tool_name [OPTIONS] INFOFILE(S)
537 Create HTML output for coverage data found in INFOFILE. Note that INFOFILE
538 may also be a list of filenames.
540 Misc:
541 -h, --help Print this help, then exit
542 -v, --version Print version number, then exit
543 -q, --quiet Do not print progress messages
545 Operation:
546 -o, --output-directory OUTDIR Write HTML output to OUTDIR
547 -s, --show-details Generate detailed directory view
548 -d, --description-file DESCFILE Read test case descriptions from DESCFILE
549 -k, --keep-descriptions Do not remove unused test descriptions
550 -b, --baseline-file BASEFILE Use BASEFILE as baseline file
551 -p, --prefix PREFIX Remove PREFIX from all directory names
552 --no-prefix Do not remove prefix from directory names
553 --(no-)function-coverage Enable (disable) function coverage display
554 --(no-)branch-coverage Enable (disable) branch coverage display
556 HTML output:
557 -f, --frames Use HTML frames for source code view
558 -t, --title TITLE Display TITLE in header of all pages
559 -c, --css-file CSSFILE Use external style sheet file CSSFILE
560 --no-source Do not create source code view
561 --num-spaces NUM Replace tabs with NUM spaces in source view
562 --highlight Highlight lines with converted-only data
563 --legend Include color legend in HTML output
564 --html-prolog FILE Use FILE as HTML prolog for generated pages
565 --html-epilog FILE Use FILE as HTML epilog for generated pages
566 --html-extension EXT Use EXT as filename extension for pages
567 --html-gzip Use gzip to compress HTML
568 --(no-)sort Enable (disable) sorted coverage views
569 --demangle-cpp Demangle C++ function names
571 For more information see: $lcov_url
572 END_OF_USAGE
578 # get_rate(found, hit)
580 # Return a relative value for the specified found&hit values
581 # which is used for sorting the corresponding entries in a
582 # file list.
585 sub get_rate($$)
587 my ($found, $hit) = @_;
589 if ($found == 0) {
590 return 10000;
592 return int($hit * 1000 / $found) * 10 + 2 - (1 / $found);
597 # get_overall_line(found, hit, name_singular, name_plural)
599 # Return a string containing overall information for the specified
600 # found/hit data.
603 sub get_overall_line($$$$)
605 my ($found, $hit, $name_sn, $name_pl) = @_;
606 my $name;
608 return "no data found" if (!defined($found) || $found == 0);
609 $name = ($found == 1) ? $name_sn : $name_pl;
610 return sprintf("%.1f%% (%d of %d %s)", $hit * 100 / $found, $hit,
611 $found, $name);
616 # print_overall_rate(ln_do, ln_found, ln_hit, fn_do, fn_found, fn_hit, br_do
617 # br_found, br_hit)
619 # Print overall coverage rates for the specified coverage types.
622 sub print_overall_rate($$$$$$$$$)
624 my ($ln_do, $ln_found, $ln_hit, $fn_do, $fn_found, $fn_hit,
625 $br_do, $br_found, $br_hit) = @_;
627 info("Overall coverage rate:\n");
628 info(" lines......: %s\n",
629 get_overall_line($ln_found, $ln_hit, "line", "lines"))
630 if ($ln_do);
631 info(" functions..: %s\n",
632 get_overall_line($fn_found, $fn_hit, "function", "functions"))
633 if ($fn_do);
634 info(" branches...: %s\n",
635 get_overall_line($br_found, $br_hit, "branch", "branches"))
636 if ($br_do);
641 # gen_html()
643 # Generate a set of HTML pages from contents of .info file INFO_FILENAME.
644 # Files will be written to the current directory. If provided, test case
645 # descriptions will be read from .tests file TEST_FILENAME and included
646 # in ouput.
648 # Die on error.
651 sub gen_html()
653 local *HTML_HANDLE;
654 my %overview;
655 my %base_data;
656 my $lines_found;
657 my $lines_hit;
658 my $fn_found;
659 my $fn_hit;
660 my $br_found;
661 my $br_hit;
662 my $overall_found = 0;
663 my $overall_hit = 0;
664 my $total_fn_found = 0;
665 my $total_fn_hit = 0;
666 my $total_br_found = 0;
667 my $total_br_hit = 0;
668 my $dir_name;
669 my $link_name;
670 my @dir_list;
671 my %new_info;
673 # Read in all specified .info files
674 foreach (@info_filenames)
676 %new_info = %{read_info_file($_)};
678 # Combine %new_info with %info_data
679 %info_data = %{combine_info_files(\%info_data, \%new_info)};
682 info("Found %d entries.\n", scalar(keys(%info_data)));
684 # Read and apply baseline data if specified
685 if ($base_filename)
687 # Read baseline file
688 info("Reading baseline file $base_filename\n");
689 %base_data = %{read_info_file($base_filename)};
690 info("Found %d entries.\n", scalar(keys(%base_data)));
692 # Apply baseline
693 info("Subtracting baseline data.\n");
694 %info_data = %{apply_baseline(\%info_data, \%base_data)};
697 @dir_list = get_dir_list(keys(%info_data));
699 if ($no_prefix)
701 # User requested that we leave filenames alone
702 info("User asked not to remove filename prefix\n");
704 elsif (!defined($dir_prefix))
706 # Get prefix common to most directories in list
707 $dir_prefix = get_prefix(@dir_list);
709 if ($dir_prefix)
711 info("Found common filename prefix \"$dir_prefix\"\n");
713 else
715 info("No common filename prefix found!\n");
716 $no_prefix=1;
719 else
721 info("Using user-specified filename prefix \"".
722 "$dir_prefix\"\n");
725 # Read in test description file if specified
726 if ($desc_filename)
728 info("Reading test description file $desc_filename\n");
729 %test_description = %{read_testfile($desc_filename)};
731 # Remove test descriptions which are not referenced
732 # from %info_data if user didn't tell us otherwise
733 if (!$keep_descriptions)
735 remove_unused_descriptions();
739 # Change to output directory if specified
740 if ($output_directory)
742 chdir($output_directory)
743 or die("ERROR: cannot change to directory ".
744 "$output_directory!\n");
747 info("Writing .css and .png files.\n");
748 write_css_file();
749 write_png_files();
751 if ($html_gzip)
753 info("Writing .htaccess file.\n");
754 write_htaccess_file();
757 info("Generating output.\n");
759 # Process each subdirectory and collect overview information
760 foreach $dir_name (@dir_list)
762 ($lines_found, $lines_hit, $fn_found, $fn_hit,
763 $br_found, $br_hit)
764 = process_dir($dir_name);
766 # Remove prefix if applicable
767 if (!$no_prefix && $dir_prefix)
769 # Match directory names beginning with $dir_prefix
770 $dir_name = apply_prefix($dir_name, $dir_prefix);
773 # Generate name for directory overview HTML page
774 if ($dir_name =~ /^\/(.*)$/)
776 $link_name = substr($dir_name, 1)."/index.$html_ext";
778 else
780 $link_name = $dir_name."/index.$html_ext";
783 $overview{$dir_name} = [$lines_found, $lines_hit, $fn_found,
784 $fn_hit, $br_found, $br_hit, $link_name,
785 get_rate($lines_found, $lines_hit),
786 get_rate($fn_found, $fn_hit),
787 get_rate($br_found, $br_hit)];
788 $overall_found += $lines_found;
789 $overall_hit += $lines_hit;
790 $total_fn_found += $fn_found;
791 $total_fn_hit += $fn_hit;
792 $total_br_found += $br_found;
793 $total_br_hit += $br_hit;
796 # Generate overview page
797 info("Writing directory view page.\n");
799 # Create sorted pages
800 foreach (@fileview_sortlist) {
801 write_dir_page($fileview_sortname[$_], ".", "", $test_title,
802 undef, $overall_found, $overall_hit,
803 $total_fn_found, $total_fn_hit, $total_br_found,
804 $total_br_hit, \%overview, {}, {}, {}, 0, $_);
807 # Check if there are any test case descriptions to write out
808 if (%test_description)
810 info("Writing test case description file.\n");
811 write_description_file( \%test_description,
812 $overall_found, $overall_hit,
813 $total_fn_found, $total_fn_hit,
814 $total_br_found, $total_br_hit);
817 print_overall_rate(1, $overall_found, $overall_hit,
818 $func_coverage, $total_fn_found, $total_fn_hit,
819 $br_coverage, $total_br_found, $total_br_hit);
821 chdir($cwd);
825 # html_create(handle, filename)
828 sub html_create($$)
830 my $handle = $_[0];
831 my $filename = $_[1];
833 if ($html_gzip)
835 open($handle, "|gzip -c >$filename")
836 or die("ERROR: cannot open $filename for writing ".
837 "(gzip)!\n");
839 else
841 open($handle, ">$filename")
842 or die("ERROR: cannot open $filename for writing!\n");
846 sub write_dir_page($$$$$$$$$$$$$$$$$)
848 my ($name, $rel_dir, $base_dir, $title, $trunc_dir, $overall_found,
849 $overall_hit, $total_fn_found, $total_fn_hit, $total_br_found,
850 $total_br_hit, $overview, $testhash, $testfnchash, $testbrhash,
851 $view_type, $sort_type) = @_;
853 # Generate directory overview page including details
854 html_create(*HTML_HANDLE, "$rel_dir/index$name.$html_ext");
855 if (!defined($trunc_dir)) {
856 $trunc_dir = "";
858 write_html_prolog(*HTML_HANDLE, $base_dir, "LCOV - $title$trunc_dir");
859 write_header(*HTML_HANDLE, $view_type, $trunc_dir, $rel_dir,
860 $overall_found, $overall_hit, $total_fn_found,
861 $total_fn_hit, $total_br_found, $total_br_hit, $sort_type);
862 write_file_table(*HTML_HANDLE, $base_dir, $overview, $testhash,
863 $testfnchash, $testbrhash, $view_type, $sort_type);
864 write_html_epilog(*HTML_HANDLE, $base_dir);
865 close(*HTML_HANDLE);
870 # process_dir(dir_name)
873 sub process_dir($)
875 my $abs_dir = $_[0];
876 my $trunc_dir;
877 my $rel_dir = $abs_dir;
878 my $base_dir;
879 my $filename;
880 my %overview;
881 my $lines_found;
882 my $lines_hit;
883 my $fn_found;
884 my $fn_hit;
885 my $br_found;
886 my $br_hit;
887 my $overall_found=0;
888 my $overall_hit=0;
889 my $total_fn_found=0;
890 my $total_fn_hit=0;
891 my $total_br_found = 0;
892 my $total_br_hit = 0;
893 my $base_name;
894 my $extension;
895 my $testdata;
896 my %testhash;
897 my $testfncdata;
898 my %testfnchash;
899 my $testbrdata;
900 my %testbrhash;
901 my @sort_list;
902 local *HTML_HANDLE;
904 # Remove prefix if applicable
905 if (!$no_prefix)
907 # Match directory name beginning with $dir_prefix
908 $rel_dir = apply_prefix($rel_dir, $dir_prefix);
911 $trunc_dir = $rel_dir;
913 # Remove leading /
914 if ($rel_dir =~ /^\/(.*)$/)
916 $rel_dir = substr($rel_dir, 1);
919 $base_dir = get_relative_base_path($rel_dir);
921 create_sub_dir($rel_dir);
923 # Match filenames which specify files in this directory, not including
924 # sub-directories
925 foreach $filename (grep(/^\Q$abs_dir\E\/[^\/]*$/,keys(%info_data)))
927 my $page_link;
928 my $func_link;
930 ($lines_found, $lines_hit, $fn_found, $fn_hit, $br_found,
931 $br_hit, $testdata, $testfncdata, $testbrdata) =
932 process_file($trunc_dir, $rel_dir, $filename);
934 $base_name = basename($filename);
936 if ($no_sourceview) {
937 $page_link = "";
938 } elsif ($frames) {
939 # Link to frameset page
940 $page_link = "$base_name.gcov.frameset.$html_ext";
941 } else {
942 # Link directory to source code view page
943 $page_link = "$base_name.gcov.$html_ext";
945 $overview{$base_name} = [$lines_found, $lines_hit, $fn_found,
946 $fn_hit, $br_found, $br_hit,
947 $page_link,
948 get_rate($lines_found, $lines_hit),
949 get_rate($fn_found, $fn_hit),
950 get_rate($br_found, $br_hit)];
952 $testhash{$base_name} = $testdata;
953 $testfnchash{$base_name} = $testfncdata;
954 $testbrhash{$base_name} = $testbrdata;
956 $overall_found += $lines_found;
957 $overall_hit += $lines_hit;
959 $total_fn_found += $fn_found;
960 $total_fn_hit += $fn_hit;
962 $total_br_found += $br_found;
963 $total_br_hit += $br_hit;
966 # Create sorted pages
967 foreach (@fileview_sortlist) {
968 # Generate directory overview page (without details)
969 write_dir_page($fileview_sortname[$_], $rel_dir, $base_dir,
970 $test_title, $trunc_dir, $overall_found,
971 $overall_hit, $total_fn_found, $total_fn_hit,
972 $total_br_found, $total_br_hit, \%overview, {},
973 {}, {}, 1, $_);
974 if (!$show_details) {
975 next;
977 # Generate directory overview page including details
978 write_dir_page("-detail".$fileview_sortname[$_], $rel_dir,
979 $base_dir, $test_title, $trunc_dir,
980 $overall_found, $overall_hit, $total_fn_found,
981 $total_fn_hit, $total_br_found, $total_br_hit,
982 \%overview, \%testhash, \%testfnchash,
983 \%testbrhash, 1, $_);
986 # Calculate resulting line counts
987 return ($overall_found, $overall_hit, $total_fn_found, $total_fn_hit,
988 $total_br_found, $total_br_hit);
993 # get_converted_lines(testdata)
995 # Return hash of line numbers of those lines which were only covered in
996 # converted data sets.
999 sub get_converted_lines($)
1001 my $testdata = $_[0];
1002 my $testcount;
1003 my %converted;
1004 my %nonconverted;
1005 my $hash;
1006 my $testcase;
1007 my $line;
1008 my %result;
1011 # Get a hash containing line numbers with positive counts both for
1012 # converted and original data sets
1013 foreach $testcase (keys(%{$testdata}))
1015 # Check to see if this is a converted data set
1016 if ($testcase =~ /,diff$/)
1018 $hash = \%converted;
1020 else
1022 $hash = \%nonconverted;
1025 $testcount = $testdata->{$testcase};
1026 # Add lines with a positive count to hash
1027 foreach $line (keys%{$testcount})
1029 if ($testcount->{$line} > 0)
1031 $hash->{$line} = 1;
1036 # Combine both hashes to resulting list
1037 foreach $line (keys(%converted))
1039 if (!defined($nonconverted{$line}))
1041 $result{$line} = 1;
1045 return \%result;
1049 sub write_function_page($$$$$$$$$$$$$$$$$$)
1051 my ($base_dir, $rel_dir, $trunc_dir, $base_name, $title,
1052 $lines_found, $lines_hit, $fn_found, $fn_hit, $br_found, $br_hit,
1053 $sumcount, $funcdata, $sumfnccount, $testfncdata, $sumbrcount,
1054 $testbrdata, $sort_type) = @_;
1055 my $pagetitle;
1056 my $filename;
1058 # Generate function table for this file
1059 if ($sort_type == 0) {
1060 $filename = "$rel_dir/$base_name.func.$html_ext";
1061 } else {
1062 $filename = "$rel_dir/$base_name.func-sort-c.$html_ext";
1064 html_create(*HTML_HANDLE, $filename);
1065 $pagetitle = "LCOV - $title - $trunc_dir/$base_name - functions";
1066 write_html_prolog(*HTML_HANDLE, $base_dir, $pagetitle);
1067 write_header(*HTML_HANDLE, 4, "$trunc_dir/$base_name",
1068 "$rel_dir/$base_name", $lines_found, $lines_hit,
1069 $fn_found, $fn_hit, $br_found, $br_hit, $sort_type);
1070 write_function_table(*HTML_HANDLE, "$base_name.gcov.$html_ext",
1071 $sumcount, $funcdata,
1072 $sumfnccount, $testfncdata, $sumbrcount,
1073 $testbrdata, $base_name,
1074 $base_dir, $sort_type);
1075 write_html_epilog(*HTML_HANDLE, $base_dir, 1);
1076 close(*HTML_HANDLE);
1081 # process_file(trunc_dir, rel_dir, filename)
1084 sub process_file($$$)
1086 info("Processing file ".apply_prefix($_[2], $dir_prefix)."\n");
1088 my $trunc_dir = $_[0];
1089 my $rel_dir = $_[1];
1090 my $filename = $_[2];
1091 my $base_name = basename($filename);
1092 my $base_dir = get_relative_base_path($rel_dir);
1093 my $testdata;
1094 my $testcount;
1095 my $sumcount;
1096 my $funcdata;
1097 my $checkdata;
1098 my $testfncdata;
1099 my $sumfnccount;
1100 my $testbrdata;
1101 my $sumbrcount;
1102 my $lines_found;
1103 my $lines_hit;
1104 my $fn_found;
1105 my $fn_hit;
1106 my $br_found;
1107 my $br_hit;
1108 my $converted;
1109 my @source;
1110 my $pagetitle;
1111 local *HTML_HANDLE;
1113 ($testdata, $sumcount, $funcdata, $checkdata, $testfncdata,
1114 $sumfnccount, $testbrdata, $sumbrcount, $lines_found, $lines_hit,
1115 $fn_found, $fn_hit, $br_found, $br_hit)
1116 = get_info_entry($info_data{$filename});
1118 # Return after this point in case user asked us not to generate
1119 # source code view
1120 if ($no_sourceview)
1122 return ($lines_found, $lines_hit, $fn_found, $fn_hit,
1123 $br_found, $br_hit, $testdata, $testfncdata,
1124 $testbrdata);
1127 $converted = get_converted_lines($testdata);
1128 # Generate source code view for this file
1129 html_create(*HTML_HANDLE, "$rel_dir/$base_name.gcov.$html_ext");
1130 $pagetitle = "LCOV - $test_title - $trunc_dir/$base_name";
1131 write_html_prolog(*HTML_HANDLE, $base_dir, $pagetitle);
1132 write_header(*HTML_HANDLE, 2, "$trunc_dir/$base_name",
1133 "$rel_dir/$base_name", $lines_found, $lines_hit,
1134 $fn_found, $fn_hit, $br_found, $br_hit, 0);
1135 @source = write_source(*HTML_HANDLE, $filename, $sumcount, $checkdata,
1136 $converted, $funcdata, $sumbrcount);
1138 write_html_epilog(*HTML_HANDLE, $base_dir, 1);
1139 close(*HTML_HANDLE);
1141 if ($func_coverage) {
1142 # Create function tables
1143 foreach (@funcview_sortlist) {
1144 write_function_page($base_dir, $rel_dir, $trunc_dir,
1145 $base_name, $test_title,
1146 $lines_found, $lines_hit,
1147 $fn_found, $fn_hit, $br_found,
1148 $br_hit, $sumcount,
1149 $funcdata, $sumfnccount,
1150 $testfncdata, $sumbrcount,
1151 $testbrdata, $_);
1155 # Additional files are needed in case of frame output
1156 if (!$frames)
1158 return ($lines_found, $lines_hit, $fn_found, $fn_hit,
1159 $br_found, $br_hit, $testdata, $testfncdata,
1160 $testbrdata);
1163 # Create overview png file
1164 gen_png("$rel_dir/$base_name.gcov.png", $overview_width, $tab_size,
1165 @source);
1167 # Create frameset page
1168 html_create(*HTML_HANDLE,
1169 "$rel_dir/$base_name.gcov.frameset.$html_ext");
1170 write_frameset(*HTML_HANDLE, $base_dir, $base_name, $pagetitle);
1171 close(*HTML_HANDLE);
1173 # Write overview frame
1174 html_create(*HTML_HANDLE,
1175 "$rel_dir/$base_name.gcov.overview.$html_ext");
1176 write_overview(*HTML_HANDLE, $base_dir, $base_name, $pagetitle,
1177 scalar(@source));
1178 close(*HTML_HANDLE);
1180 return ($lines_found, $lines_hit, $fn_found, $fn_hit, $br_found,
1181 $br_hit, $testdata, $testfncdata, $testbrdata);
1186 # read_info_file(info_filename)
1188 # Read in the contents of the .info file specified by INFO_FILENAME. Data will
1189 # be returned as a reference to a hash containing the following mappings:
1191 # %result: for each filename found in file -> \%data
1193 # %data: "test" -> \%testdata
1194 # "sum" -> \%sumcount
1195 # "func" -> \%funcdata
1196 # "found" -> $lines_found (number of instrumented lines found in file)
1197 # "hit" -> $lines_hit (number of executed lines in file)
1198 # "check" -> \%checkdata
1199 # "testfnc" -> \%testfncdata
1200 # "sumfnc" -> \%sumfnccount
1201 # "testbr" -> \%testbrdata
1202 # "sumbr" -> \%sumbrcount
1204 # %testdata : name of test affecting this file -> \%testcount
1205 # %testfncdata: name of test affecting this file -> \%testfnccount
1206 # %testbrdata: name of test affecting this file -> \%testbrcount
1208 # %testcount : line number -> execution count for a single test
1209 # %testfnccount: function name -> execution count for a single test
1210 # %testbrcount : line number -> branch coverage data for a single test
1211 # %sumcount : line number -> execution count for all tests
1212 # %sumfnccount : function name -> execution count for all tests
1213 # %sumbrcount : line number -> branch coverage data for all tests
1214 # %funcdata : function name -> line number
1215 # %checkdata : line number -> checksum of source code line
1216 # $brdata : vector of items: block, branch, taken
1218 # Note that .info file sections referring to the same file and test name
1219 # will automatically be combined by adding all execution counts.
1221 # Note that if INFO_FILENAME ends with ".gz", it is assumed that the file
1222 # is compressed using GZIP. If available, GUNZIP will be used to decompress
1223 # this file.
1225 # Die on error.
1228 sub read_info_file($)
1230 my $tracefile = $_[0]; # Name of tracefile
1231 my %result; # Resulting hash: file -> data
1232 my $data; # Data handle for current entry
1233 my $testdata; # " "
1234 my $testcount; # " "
1235 my $sumcount; # " "
1236 my $funcdata; # " "
1237 my $checkdata; # " "
1238 my $testfncdata;
1239 my $testfnccount;
1240 my $sumfnccount;
1241 my $testbrdata;
1242 my $testbrcount;
1243 my $sumbrcount;
1244 my $line; # Current line read from .info file
1245 my $testname; # Current test name
1246 my $filename; # Current filename
1247 my $hitcount; # Count for lines hit
1248 my $count; # Execution count of current line
1249 my $negative; # If set, warn about negative counts
1250 my $changed_testname; # If set, warn about changed testname
1251 my $line_checksum; # Checksum of current line
1252 my $br_found;
1253 my $br_hit;
1254 local *INFO_HANDLE; # Filehandle for .info file
1256 info("Reading data file $tracefile\n");
1258 # Check if file exists and is readable
1259 stat($_[0]);
1260 if (!(-r _))
1262 die("ERROR: cannot read file $_[0]!\n");
1265 # Check if this is really a plain file
1266 if (!(-f _))
1268 die("ERROR: not a plain file: $_[0]!\n");
1271 # Check for .gz extension
1272 if ($_[0] =~ /\.gz$/)
1274 # Check for availability of GZIP tool
1275 system_no_output(1, "gunzip" ,"-h")
1276 and die("ERROR: gunzip command not available!\n");
1278 # Check integrity of compressed file
1279 system_no_output(1, "gunzip", "-t", $_[0])
1280 and die("ERROR: integrity check failed for ".
1281 "compressed file $_[0]!\n");
1283 # Open compressed file
1284 open(INFO_HANDLE, "gunzip -c $_[0]|")
1285 or die("ERROR: cannot start gunzip to decompress ".
1286 "file $_[0]!\n");
1288 else
1290 # Open decompressed file
1291 open(INFO_HANDLE, $_[0])
1292 or die("ERROR: cannot read file $_[0]!\n");
1295 $testname = "";
1296 while (<INFO_HANDLE>)
1298 chomp($_);
1299 $line = $_;
1301 # Switch statement
1302 foreach ($line)
1304 /^TN:([^,]*)(,diff)?/ && do
1306 # Test name information found
1307 $testname = defined($1) ? $1 : "";
1308 if ($testname =~ s/\W/_/g)
1310 $changed_testname = 1;
1312 $testname .= $2 if (defined($2));
1313 last;
1316 /^[SK]F:(.*)/ && do
1318 # Filename information found
1319 # Retrieve data for new entry
1320 $filename = $1;
1322 $data = $result{$filename};
1323 ($testdata, $sumcount, $funcdata, $checkdata,
1324 $testfncdata, $sumfnccount, $testbrdata,
1325 $sumbrcount) =
1326 get_info_entry($data);
1328 if (defined($testname))
1330 $testcount = $testdata->{$testname};
1331 $testfnccount = $testfncdata->{$testname};
1332 $testbrcount = $testbrdata->{$testname};
1334 else
1336 $testcount = {};
1337 $testfnccount = {};
1338 $testbrcount = {};
1340 last;
1343 /^DA:(\d+),(-?\d+)(,[^,\s]+)?/ && do
1345 # Fix negative counts
1346 $count = $2 < 0 ? 0 : $2;
1347 if ($2 < 0)
1349 $negative = 1;
1351 # Execution count found, add to structure
1352 # Add summary counts
1353 $sumcount->{$1} += $count;
1355 # Add test-specific counts
1356 if (defined($testname))
1358 $testcount->{$1} += $count;
1361 # Store line checksum if available
1362 if (defined($3))
1364 $line_checksum = substr($3, 1);
1366 # Does it match a previous definition
1367 if (defined($checkdata->{$1}) &&
1368 ($checkdata->{$1} ne
1369 $line_checksum))
1371 die("ERROR: checksum mismatch ".
1372 "at $filename:$1\n");
1375 $checkdata->{$1} = $line_checksum;
1377 last;
1380 /^FN:(\d+),([^,]+)/ && do
1382 # Function data found, add to structure
1383 $funcdata->{$2} = $1;
1385 # Also initialize function call data
1386 if (!defined($sumfnccount->{$2})) {
1387 $sumfnccount->{$2} = 0;
1389 if (defined($testname))
1391 if (!defined($testfnccount->{$2})) {
1392 $testfnccount->{$2} = 0;
1395 last;
1398 /^FNDA:(\d+),([^,]+)/ && do
1400 # Function call count found, add to structure
1401 # Add summary counts
1402 $sumfnccount->{$2} += $1;
1404 # Add test-specific counts
1405 if (defined($testname))
1407 $testfnccount->{$2} += $1;
1409 last;
1412 /^BRDA:(\d+),(\d+),(\d+),(\d+|-)/ && do {
1413 # Branch coverage data found
1414 my ($line, $block, $branch, $taken) =
1415 ($1, $2, $3, $4);
1417 $sumbrcount->{$line} =
1418 br_ivec_push($sumbrcount->{$line},
1419 $block, $branch, $taken);
1421 # Add test-specific counts
1422 if (defined($testname)) {
1423 $testbrcount->{$line} =
1424 br_ivec_push(
1425 $testbrcount->{$line},
1426 $block, $branch,
1427 $taken);
1429 last;
1432 /^end_of_record/ && do
1434 # Found end of section marker
1435 if ($filename)
1437 # Store current section data
1438 if (defined($testname))
1440 $testdata->{$testname} =
1441 $testcount;
1442 $testfncdata->{$testname} =
1443 $testfnccount;
1444 $testbrdata->{$testname} =
1445 $testbrcount;
1448 set_info_entry($data, $testdata,
1449 $sumcount, $funcdata,
1450 $checkdata, $testfncdata,
1451 $sumfnccount,
1452 $testbrdata,
1453 $sumbrcount);
1454 $result{$filename} = $data;
1455 last;
1459 # default
1460 last;
1463 close(INFO_HANDLE);
1465 # Calculate lines_found and lines_hit for each file
1466 foreach $filename (keys(%result))
1468 $data = $result{$filename};
1470 ($testdata, $sumcount, undef, undef, $testfncdata,
1471 $sumfnccount, $testbrdata, $sumbrcount) =
1472 get_info_entry($data);
1474 # Filter out empty files
1475 if (scalar(keys(%{$sumcount})) == 0)
1477 delete($result{$filename});
1478 next;
1480 # Filter out empty test cases
1481 foreach $testname (keys(%{$testdata}))
1483 if (!defined($testdata->{$testname}) ||
1484 scalar(keys(%{$testdata->{$testname}})) == 0)
1486 delete($testdata->{$testname});
1487 delete($testfncdata->{$testname});
1491 $data->{"found"} = scalar(keys(%{$sumcount}));
1492 $hitcount = 0;
1494 foreach (keys(%{$sumcount}))
1496 if ($sumcount->{$_} > 0) { $hitcount++; }
1499 $data->{"hit"} = $hitcount;
1501 # Get found/hit values for function call data
1502 $data->{"f_found"} = scalar(keys(%{$sumfnccount}));
1503 $hitcount = 0;
1505 foreach (keys(%{$sumfnccount})) {
1506 if ($sumfnccount->{$_} > 0) {
1507 $hitcount++;
1510 $data->{"f_hit"} = $hitcount;
1512 # Get found/hit values for branch data
1513 ($br_found, $br_hit) = get_br_found_and_hit($sumbrcount);
1515 $data->{"b_found"} = $br_found;
1516 $data->{"b_hit"} = $br_hit;
1519 if (scalar(keys(%result)) == 0)
1521 die("ERROR: no valid records found in tracefile $tracefile\n");
1523 if ($negative)
1525 warn("WARNING: negative counts found in tracefile ".
1526 "$tracefile\n");
1528 if ($changed_testname)
1530 warn("WARNING: invalid characters removed from testname in ".
1531 "tracefile $tracefile\n");
1534 return(\%result);
1539 # get_info_entry(hash_ref)
1541 # Retrieve data from an entry of the structure generated by read_info_file().
1542 # Return a list of references to hashes:
1543 # (test data hash ref, sum count hash ref, funcdata hash ref, checkdata hash
1544 # ref, testfncdata hash ref, sumfnccount hash ref, lines found, lines hit,
1545 # functions found, functions hit)
1548 sub get_info_entry($)
1550 my $testdata_ref = $_[0]->{"test"};
1551 my $sumcount_ref = $_[0]->{"sum"};
1552 my $funcdata_ref = $_[0]->{"func"};
1553 my $checkdata_ref = $_[0]->{"check"};
1554 my $testfncdata = $_[0]->{"testfnc"};
1555 my $sumfnccount = $_[0]->{"sumfnc"};
1556 my $testbrdata = $_[0]->{"testbr"};
1557 my $sumbrcount = $_[0]->{"sumbr"};
1558 my $lines_found = $_[0]->{"found"};
1559 my $lines_hit = $_[0]->{"hit"};
1560 my $fn_found = $_[0]->{"f_found"};
1561 my $fn_hit = $_[0]->{"f_hit"};
1562 my $br_found = $_[0]->{"b_found"};
1563 my $br_hit = $_[0]->{"b_hit"};
1565 return ($testdata_ref, $sumcount_ref, $funcdata_ref, $checkdata_ref,
1566 $testfncdata, $sumfnccount, $testbrdata, $sumbrcount,
1567 $lines_found, $lines_hit, $fn_found, $fn_hit,
1568 $br_found, $br_hit);
1573 # set_info_entry(hash_ref, testdata_ref, sumcount_ref, funcdata_ref,
1574 # checkdata_ref, testfncdata_ref, sumfcncount_ref,
1575 # testbrdata_ref, sumbrcount_ref[,lines_found,
1576 # lines_hit, f_found, f_hit, $b_found, $b_hit])
1578 # Update the hash referenced by HASH_REF with the provided data references.
1581 sub set_info_entry($$$$$$$$$;$$$$$$)
1583 my $data_ref = $_[0];
1585 $data_ref->{"test"} = $_[1];
1586 $data_ref->{"sum"} = $_[2];
1587 $data_ref->{"func"} = $_[3];
1588 $data_ref->{"check"} = $_[4];
1589 $data_ref->{"testfnc"} = $_[5];
1590 $data_ref->{"sumfnc"} = $_[6];
1591 $data_ref->{"testbr"} = $_[7];
1592 $data_ref->{"sumbr"} = $_[8];
1594 if (defined($_[9])) { $data_ref->{"found"} = $_[9]; }
1595 if (defined($_[10])) { $data_ref->{"hit"} = $_[10]; }
1596 if (defined($_[11])) { $data_ref->{"f_found"} = $_[11]; }
1597 if (defined($_[12])) { $data_ref->{"f_hit"} = $_[12]; }
1598 if (defined($_[13])) { $data_ref->{"b_found"} = $_[13]; }
1599 if (defined($_[14])) { $data_ref->{"b_hit"} = $_[14]; }
1604 # add_counts(data1_ref, data2_ref)
1606 # DATA1_REF and DATA2_REF are references to hashes containing a mapping
1608 # line number -> execution count
1610 # Return a list (RESULT_REF, LINES_FOUND, LINES_HIT) where RESULT_REF
1611 # is a reference to a hash containing the combined mapping in which
1612 # execution counts are added.
1615 sub add_counts($$)
1617 my %data1 = %{$_[0]}; # Hash 1
1618 my %data2 = %{$_[1]}; # Hash 2
1619 my %result; # Resulting hash
1620 my $line; # Current line iteration scalar
1621 my $data1_count; # Count of line in hash1
1622 my $data2_count; # Count of line in hash2
1623 my $found = 0; # Total number of lines found
1624 my $hit = 0; # Number of lines with a count > 0
1626 foreach $line (keys(%data1))
1628 $data1_count = $data1{$line};
1629 $data2_count = $data2{$line};
1631 # Add counts if present in both hashes
1632 if (defined($data2_count)) { $data1_count += $data2_count; }
1634 # Store sum in %result
1635 $result{$line} = $data1_count;
1637 $found++;
1638 if ($data1_count > 0) { $hit++; }
1641 # Add lines unique to data2
1642 foreach $line (keys(%data2))
1644 # Skip lines already in data1
1645 if (defined($data1{$line})) { next; }
1647 # Copy count from data2
1648 $result{$line} = $data2{$line};
1650 $found++;
1651 if ($result{$line} > 0) { $hit++; }
1654 return (\%result, $found, $hit);
1659 # merge_checksums(ref1, ref2, filename)
1661 # REF1 and REF2 are references to hashes containing a mapping
1663 # line number -> checksum
1665 # Merge checksum lists defined in REF1 and REF2 and return reference to
1666 # resulting hash. Die if a checksum for a line is defined in both hashes
1667 # but does not match.
1670 sub merge_checksums($$$)
1672 my $ref1 = $_[0];
1673 my $ref2 = $_[1];
1674 my $filename = $_[2];
1675 my %result;
1676 my $line;
1678 foreach $line (keys(%{$ref1}))
1680 if (defined($ref2->{$line}) &&
1681 ($ref1->{$line} ne $ref2->{$line}))
1683 die("ERROR: checksum mismatch at $filename:$line\n");
1685 $result{$line} = $ref1->{$line};
1688 foreach $line (keys(%{$ref2}))
1690 $result{$line} = $ref2->{$line};
1693 return \%result;
1698 # merge_func_data(funcdata1, funcdata2, filename)
1701 sub merge_func_data($$$)
1703 my ($funcdata1, $funcdata2, $filename) = @_;
1704 my %result;
1705 my $func;
1707 if (defined($funcdata1)) {
1708 %result = %{$funcdata1};
1711 foreach $func (keys(%{$funcdata2})) {
1712 my $line1 = $result{$func};
1713 my $line2 = $funcdata2->{$func};
1715 if (defined($line1) && ($line1 != $line2)) {
1716 warn("WARNING: function data mismatch at ".
1717 "$filename:$line2\n");
1718 next;
1720 $result{$func} = $line2;
1723 return \%result;
1728 # add_fnccount(fnccount1, fnccount2)
1730 # Add function call count data. Return list (fnccount_added, f_found, f_hit)
1733 sub add_fnccount($$)
1735 my ($fnccount1, $fnccount2) = @_;
1736 my %result;
1737 my $fn_found;
1738 my $fn_hit;
1739 my $function;
1741 if (defined($fnccount1)) {
1742 %result = %{$fnccount1};
1744 foreach $function (keys(%{$fnccount2})) {
1745 $result{$function} += $fnccount2->{$function};
1747 $fn_found = scalar(keys(%result));
1748 $fn_hit = 0;
1749 foreach $function (keys(%result)) {
1750 if ($result{$function} > 0) {
1751 $fn_hit++;
1755 return (\%result, $fn_found, $fn_hit);
1759 # add_testfncdata(testfncdata1, testfncdata2)
1761 # Add function call count data for several tests. Return reference to
1762 # added_testfncdata.
1765 sub add_testfncdata($$)
1767 my ($testfncdata1, $testfncdata2) = @_;
1768 my %result;
1769 my $testname;
1771 foreach $testname (keys(%{$testfncdata1})) {
1772 if (defined($testfncdata2->{$testname})) {
1773 my $fnccount;
1775 # Function call count data for this testname exists
1776 # in both data sets: add
1777 ($fnccount) = add_fnccount(
1778 $testfncdata1->{$testname},
1779 $testfncdata2->{$testname});
1780 $result{$testname} = $fnccount;
1781 next;
1783 # Function call count data for this testname is unique to
1784 # data set 1: copy
1785 $result{$testname} = $testfncdata1->{$testname};
1788 # Add count data for testnames unique to data set 2
1789 foreach $testname (keys(%{$testfncdata2})) {
1790 if (!defined($result{$testname})) {
1791 $result{$testname} = $testfncdata2->{$testname};
1794 return \%result;
1799 # brcount_to_db(brcount)
1801 # Convert brcount data to the following format:
1803 # db: line number -> block hash
1804 # block hash: block number -> branch hash
1805 # branch hash: branch number -> taken value
1808 sub brcount_to_db($)
1810 my ($brcount) = @_;
1811 my $line;
1812 my $db;
1814 # Add branches from first count to database
1815 foreach $line (keys(%{$brcount})) {
1816 my $brdata = $brcount->{$line};
1817 my $i;
1818 my $num = br_ivec_len($brdata);
1820 for ($i = 0; $i < $num; $i++) {
1821 my ($block, $branch, $taken) = br_ivec_get($brdata, $i);
1823 $db->{$line}->{$block}->{$branch} = $taken;
1827 return $db;
1832 # db_to_brcount(db)
1834 # Convert branch coverage data back to brcount format.
1837 sub db_to_brcount($)
1839 my ($db) = @_;
1840 my $line;
1841 my $brcount = {};
1842 my $br_found = 0;
1843 my $br_hit = 0;
1845 # Convert database back to brcount format
1846 foreach $line (sort({$a <=> $b} keys(%{$db}))) {
1847 my $ldata = $db->{$line};
1848 my $brdata;
1849 my $block;
1851 foreach $block (sort({$a <=> $b} keys(%{$ldata}))) {
1852 my $bdata = $ldata->{$block};
1853 my $branch;
1855 foreach $branch (sort({$a <=> $b} keys(%{$bdata}))) {
1856 my $taken = $bdata->{$branch};
1858 $br_found++;
1859 $br_hit++ if ($taken ne "-" && $taken > 0);
1860 $brdata = br_ivec_push($brdata, $block,
1861 $branch, $taken);
1864 $brcount->{$line} = $brdata;
1867 return ($brcount, $br_found, $br_hit);
1872 # combine_brcount(brcount1, brcount2, type)
1874 # If add is BR_ADD, add branch coverage data and return list (brcount_added,
1875 # br_found, br_hit). If add is BR_SUB, subtract the taken values of brcount2
1876 # from brcount1 and return (brcount_sub, br_found, br_hit).
1879 sub combine_brcount($$$)
1881 my ($brcount1, $brcount2, $type) = @_;
1882 my $line;
1883 my $block;
1884 my $branch;
1885 my $taken;
1886 my $db;
1887 my $br_found = 0;
1888 my $br_hit = 0;
1889 my $result;
1891 # Convert branches from first count to database
1892 $db = brcount_to_db($brcount1);
1893 # Combine values from database and second count
1894 foreach $line (keys(%{$brcount2})) {
1895 my $brdata = $brcount2->{$line};
1896 my $num = br_ivec_len($brdata);
1897 my $i;
1899 for ($i = 0; $i < $num; $i++) {
1900 ($block, $branch, $taken) = br_ivec_get($brdata, $i);
1901 my $new_taken = $db->{$line}->{$block}->{$branch};
1903 if ($type == $BR_ADD) {
1904 $new_taken = br_taken_add($new_taken, $taken);
1905 } elsif ($type == $BR_SUB) {
1906 $new_taken = br_taken_sub($new_taken, $taken);
1908 $db->{$line}->{$block}->{$branch} = $new_taken
1909 if (defined($new_taken));
1912 # Convert database back to brcount format
1913 ($result, $br_found, $br_hit) = db_to_brcount($db);
1915 return ($result, $br_found, $br_hit);
1920 # add_testbrdata(testbrdata1, testbrdata2)
1922 # Add branch coverage data for several tests. Return reference to
1923 # added_testbrdata.
1926 sub add_testbrdata($$)
1928 my ($testbrdata1, $testbrdata2) = @_;
1929 my %result;
1930 my $testname;
1932 foreach $testname (keys(%{$testbrdata1})) {
1933 if (defined($testbrdata2->{$testname})) {
1934 my $brcount;
1936 # Branch coverage data for this testname exists
1937 # in both data sets: add
1938 ($brcount) = combine_brcount($testbrdata1->{$testname},
1939 $testbrdata2->{$testname}, $BR_ADD);
1940 $result{$testname} = $brcount;
1941 next;
1943 # Branch coverage data for this testname is unique to
1944 # data set 1: copy
1945 $result{$testname} = $testbrdata1->{$testname};
1948 # Add count data for testnames unique to data set 2
1949 foreach $testname (keys(%{$testbrdata2})) {
1950 if (!defined($result{$testname})) {
1951 $result{$testname} = $testbrdata2->{$testname};
1954 return \%result;
1959 # combine_info_entries(entry_ref1, entry_ref2, filename)
1961 # Combine .info data entry hashes referenced by ENTRY_REF1 and ENTRY_REF2.
1962 # Return reference to resulting hash.
1965 sub combine_info_entries($$$)
1967 my $entry1 = $_[0]; # Reference to hash containing first entry
1968 my $testdata1;
1969 my $sumcount1;
1970 my $funcdata1;
1971 my $checkdata1;
1972 my $testfncdata1;
1973 my $sumfnccount1;
1974 my $testbrdata1;
1975 my $sumbrcount1;
1977 my $entry2 = $_[1]; # Reference to hash containing second entry
1978 my $testdata2;
1979 my $sumcount2;
1980 my $funcdata2;
1981 my $checkdata2;
1982 my $testfncdata2;
1983 my $sumfnccount2;
1984 my $testbrdata2;
1985 my $sumbrcount2;
1987 my %result; # Hash containing combined entry
1988 my %result_testdata;
1989 my $result_sumcount = {};
1990 my $result_funcdata;
1991 my $result_testfncdata;
1992 my $result_sumfnccount;
1993 my $result_testbrdata;
1994 my $result_sumbrcount;
1995 my $lines_found;
1996 my $lines_hit;
1997 my $fn_found;
1998 my $fn_hit;
1999 my $br_found;
2000 my $br_hit;
2002 my $testname;
2003 my $filename = $_[2];
2005 # Retrieve data
2006 ($testdata1, $sumcount1, $funcdata1, $checkdata1, $testfncdata1,
2007 $sumfnccount1, $testbrdata1, $sumbrcount1) = get_info_entry($entry1);
2008 ($testdata2, $sumcount2, $funcdata2, $checkdata2, $testfncdata2,
2009 $sumfnccount2, $testbrdata2, $sumbrcount2) = get_info_entry($entry2);
2011 # Merge checksums
2012 $checkdata1 = merge_checksums($checkdata1, $checkdata2, $filename);
2014 # Combine funcdata
2015 $result_funcdata = merge_func_data($funcdata1, $funcdata2, $filename);
2017 # Combine function call count data
2018 $result_testfncdata = add_testfncdata($testfncdata1, $testfncdata2);
2019 ($result_sumfnccount, $fn_found, $fn_hit) =
2020 add_fnccount($sumfnccount1, $sumfnccount2);
2022 # Combine branch coverage data
2023 $result_testbrdata = add_testbrdata($testbrdata1, $testbrdata2);
2024 ($result_sumbrcount, $br_found, $br_hit) =
2025 combine_brcount($sumbrcount1, $sumbrcount2, $BR_ADD);
2027 # Combine testdata
2028 foreach $testname (keys(%{$testdata1}))
2030 if (defined($testdata2->{$testname}))
2032 # testname is present in both entries, requires
2033 # combination
2034 ($result_testdata{$testname}) =
2035 add_counts($testdata1->{$testname},
2036 $testdata2->{$testname});
2038 else
2040 # testname only present in entry1, add to result
2041 $result_testdata{$testname} = $testdata1->{$testname};
2044 # update sum count hash
2045 ($result_sumcount, $lines_found, $lines_hit) =
2046 add_counts($result_sumcount,
2047 $result_testdata{$testname});
2050 foreach $testname (keys(%{$testdata2}))
2052 # Skip testnames already covered by previous iteration
2053 if (defined($testdata1->{$testname})) { next; }
2055 # testname only present in entry2, add to result hash
2056 $result_testdata{$testname} = $testdata2->{$testname};
2058 # update sum count hash
2059 ($result_sumcount, $lines_found, $lines_hit) =
2060 add_counts($result_sumcount,
2061 $result_testdata{$testname});
2064 # Calculate resulting sumcount
2066 # Store result
2067 set_info_entry(\%result, \%result_testdata, $result_sumcount,
2068 $result_funcdata, $checkdata1, $result_testfncdata,
2069 $result_sumfnccount, $result_testbrdata,
2070 $result_sumbrcount, $lines_found, $lines_hit,
2071 $fn_found, $fn_hit, $br_found, $br_hit);
2073 return(\%result);
2078 # combine_info_files(info_ref1, info_ref2)
2080 # Combine .info data in hashes referenced by INFO_REF1 and INFO_REF2. Return
2081 # reference to resulting hash.
2084 sub combine_info_files($$)
2086 my %hash1 = %{$_[0]};
2087 my %hash2 = %{$_[1]};
2088 my $filename;
2090 foreach $filename (keys(%hash2))
2092 if ($hash1{$filename})
2094 # Entry already exists in hash1, combine them
2095 $hash1{$filename} =
2096 combine_info_entries($hash1{$filename},
2097 $hash2{$filename},
2098 $filename);
2100 else
2102 # Entry is unique in both hashes, simply add to
2103 # resulting hash
2104 $hash1{$filename} = $hash2{$filename};
2108 return(\%hash1);
2113 # get_prefix(filename_list)
2115 # Search FILENAME_LIST for a directory prefix which is common to as many
2116 # list entries as possible, so that removing this prefix will minimize the
2117 # sum of the lengths of all resulting shortened filenames.
2120 sub get_prefix(@)
2122 my @filename_list = @_; # provided list of filenames
2123 my %prefix; # mapping: prefix -> sum of lengths
2124 my $current; # Temporary iteration variable
2126 # Find list of prefixes
2127 foreach (@filename_list)
2129 # Need explicit assignment to get a copy of $_ so that
2130 # shortening the contained prefix does not affect the list
2131 $current = shorten_prefix($_);
2132 while ($current = shorten_prefix($current))
2134 # Skip rest if the remaining prefix has already been
2135 # added to hash
2136 if ($prefix{$current}) { last; }
2138 # Initialize with 0
2139 $prefix{$current}="0";
2144 # Calculate sum of lengths for all prefixes
2145 foreach $current (keys(%prefix))
2147 foreach (@filename_list)
2149 # Add original length
2150 $prefix{$current} += length($_);
2152 # Check whether prefix matches
2153 if (substr($_, 0, length($current)) eq $current)
2155 # Subtract prefix length for this filename
2156 $prefix{$current} -= length($current);
2161 # Find and return prefix with minimal sum
2162 $current = (keys(%prefix))[0];
2164 foreach (keys(%prefix))
2166 if ($prefix{$_} < $prefix{$current})
2168 $current = $_;
2172 return($current);
2177 # shorten_prefix(prefix)
2179 # Return PREFIX shortened by last directory component.
2182 sub shorten_prefix($)
2184 my @list = split("/", $_[0]);
2186 pop(@list);
2187 return join("/", @list);
2193 # get_dir_list(filename_list)
2195 # Return sorted list of directories for each entry in given FILENAME_LIST.
2198 sub get_dir_list(@)
2200 my %result;
2202 foreach (@_)
2204 $result{shorten_prefix($_)} = "";
2207 return(sort(keys(%result)));
2212 # get_relative_base_path(subdirectory)
2214 # Return a relative path string which references the base path when applied
2215 # in SUBDIRECTORY.
2217 # Example: get_relative_base_path("fs/mm") -> "../../"
2220 sub get_relative_base_path($)
2222 my $result = "";
2223 my $index;
2225 # Make an empty directory path a special case
2226 if (!$_[0]) { return(""); }
2228 # Count number of /s in path
2229 $index = ($_[0] =~ s/\//\//g);
2231 # Add a ../ to $result for each / in the directory path + 1
2232 for (; $index>=0; $index--)
2234 $result .= "../";
2237 return $result;
2242 # read_testfile(test_filename)
2244 # Read in file TEST_FILENAME which contains test descriptions in the format:
2246 # TN:<whitespace><test name>
2247 # TD:<whitespace><test description>
2249 # for each test case. Return a reference to a hash containing a mapping
2251 # test name -> test description.
2253 # Die on error.
2256 sub read_testfile($)
2258 my %result;
2259 my $test_name;
2260 my $changed_testname;
2261 local *TEST_HANDLE;
2263 open(TEST_HANDLE, "<".$_[0])
2264 or die("ERROR: cannot open $_[0]!\n");
2266 while (<TEST_HANDLE>)
2268 chomp($_);
2270 # Match lines beginning with TN:<whitespace(s)>
2271 if (/^TN:\s+(.*?)\s*$/)
2273 # Store name for later use
2274 $test_name = $1;
2275 if ($test_name =~ s/\W/_/g)
2277 $changed_testname = 1;
2281 # Match lines beginning with TD:<whitespace(s)>
2282 if (/^TD:\s+(.*?)\s*$/)
2284 # Check for empty line
2285 if ($1)
2287 # Add description to hash
2288 $result{$test_name} .= " $1";
2290 else
2292 # Add empty line
2293 $result{$test_name} .= "\n\n";
2298 close(TEST_HANDLE);
2300 if ($changed_testname)
2302 warn("WARNING: invalid characters removed from testname in ".
2303 "descriptions file $_[0]\n");
2306 return \%result;
2311 # escape_html(STRING)
2313 # Return a copy of STRING in which all occurrences of HTML special characters
2314 # are escaped.
2317 sub escape_html($)
2319 my $string = $_[0];
2321 if (!$string) { return ""; }
2323 $string =~ s/&/&amp;/g; # & -> &amp;
2324 $string =~ s/</&lt;/g; # < -> &lt;
2325 $string =~ s/>/&gt;/g; # > -> &gt;
2326 $string =~ s/\"/&quot;/g; # " -> &quot;
2328 while ($string =~ /^([^\t]*)(\t)/)
2330 my $replacement = " "x($tab_size - (length($1) % $tab_size));
2331 $string =~ s/^([^\t]*)(\t)/$1$replacement/;
2334 $string =~ s/\n/<br>/g; # \n -> <br>
2336 return $string;
2341 # get_date_string()
2343 # Return the current date in the form: yyyy-mm-dd
2346 sub get_date_string()
2348 my $year;
2349 my $month;
2350 my $day;
2352 ($year, $month, $day) = (localtime())[5, 4, 3];
2354 return sprintf("%d-%02d-%02d", $year+1900, $month+1, $day);
2359 # create_sub_dir(dir_name)
2361 # Create subdirectory DIR_NAME if it does not already exist, including all its
2362 # parent directories.
2364 # Die on error.
2367 sub create_sub_dir($)
2369 my ($dir) = @_;
2371 system("mkdir", "-p" ,$dir)
2372 and die("ERROR: cannot create directory $dir!\n");
2377 # write_description_file(descriptions, overall_found, overall_hit,
2378 # total_fn_found, total_fn_hit, total_br_found,
2379 # total_br_hit)
2381 # Write HTML file containing all test case descriptions. DESCRIPTIONS is a
2382 # reference to a hash containing a mapping
2384 # test case name -> test case description
2386 # Die on error.
2389 sub write_description_file($$$$$$$)
2391 my %description = %{$_[0]};
2392 my $found = $_[1];
2393 my $hit = $_[2];
2394 my $fn_found = $_[3];
2395 my $fn_hit = $_[4];
2396 my $br_found = $_[5];
2397 my $br_hit = $_[6];
2398 my $test_name;
2399 local *HTML_HANDLE;
2401 html_create(*HTML_HANDLE,"descriptions.$html_ext");
2402 write_html_prolog(*HTML_HANDLE, "", "LCOV - test case descriptions");
2403 write_header(*HTML_HANDLE, 3, "", "", $found, $hit, $fn_found,
2404 $fn_hit, $br_found, $br_hit, 0);
2406 write_test_table_prolog(*HTML_HANDLE,
2407 "Test case descriptions - alphabetical list");
2409 foreach $test_name (sort(keys(%description)))
2411 write_test_table_entry(*HTML_HANDLE, $test_name,
2412 escape_html($description{$test_name}));
2415 write_test_table_epilog(*HTML_HANDLE);
2416 write_html_epilog(*HTML_HANDLE, "");
2418 close(*HTML_HANDLE);
2424 # write_png_files()
2426 # Create all necessary .png files for the HTML-output in the current
2427 # directory. .png-files are used as bar graphs.
2429 # Die on error.
2432 sub write_png_files()
2434 my %data;
2435 local *PNG_HANDLE;
2437 $data{"ruby.png"} =
2438 [0x89, 0x50, 0x4e, 0x47, 0x0d, 0x0a, 0x1a, 0x0a, 0x00, 0x00,
2439 0x00, 0x0d, 0x49, 0x48, 0x44, 0x52, 0x00, 0x00, 0x00, 0x01,
2440 0x00, 0x00, 0x00, 0x01, 0x01, 0x03, 0x00, 0x00, 0x00, 0x25,
2441 0xdb, 0x56, 0xca, 0x00, 0x00, 0x00, 0x07, 0x74, 0x49, 0x4d,
2442 0x45, 0x07, 0xd2, 0x07, 0x11, 0x0f, 0x18, 0x10, 0x5d, 0x57,
2443 0x34, 0x6e, 0x00, 0x00, 0x00, 0x09, 0x70, 0x48, 0x59, 0x73,
2444 0x00, 0x00, 0x0b, 0x12, 0x00, 0x00, 0x0b, 0x12, 0x01, 0xd2,
2445 0xdd, 0x7e, 0xfc, 0x00, 0x00, 0x00, 0x04, 0x67, 0x41, 0x4d,
2446 0x41, 0x00, 0x00, 0xb1, 0x8f, 0x0b, 0xfc, 0x61, 0x05, 0x00,
2447 0x00, 0x00, 0x06, 0x50, 0x4c, 0x54, 0x45, 0xff, 0x35, 0x2f,
2448 0x00, 0x00, 0x00, 0xd0, 0x33, 0x9a, 0x9d, 0x00, 0x00, 0x00,
2449 0x0a, 0x49, 0x44, 0x41, 0x54, 0x78, 0xda, 0x63, 0x60, 0x00,
2450 0x00, 0x00, 0x02, 0x00, 0x01, 0xe5, 0x27, 0xde, 0xfc, 0x00,
2451 0x00, 0x00, 0x00, 0x49, 0x45, 0x4e, 0x44, 0xae, 0x42, 0x60,
2452 0x82];
2453 $data{"amber.png"} =
2454 [0x89, 0x50, 0x4e, 0x47, 0x0d, 0x0a, 0x1a, 0x0a, 0x00, 0x00,
2455 0x00, 0x0d, 0x49, 0x48, 0x44, 0x52, 0x00, 0x00, 0x00, 0x01,
2456 0x00, 0x00, 0x00, 0x01, 0x01, 0x03, 0x00, 0x00, 0x00, 0x25,
2457 0xdb, 0x56, 0xca, 0x00, 0x00, 0x00, 0x07, 0x74, 0x49, 0x4d,
2458 0x45, 0x07, 0xd2, 0x07, 0x11, 0x0f, 0x28, 0x04, 0x98, 0xcb,
2459 0xd6, 0xe0, 0x00, 0x00, 0x00, 0x09, 0x70, 0x48, 0x59, 0x73,
2460 0x00, 0x00, 0x0b, 0x12, 0x00, 0x00, 0x0b, 0x12, 0x01, 0xd2,
2461 0xdd, 0x7e, 0xfc, 0x00, 0x00, 0x00, 0x04, 0x67, 0x41, 0x4d,
2462 0x41, 0x00, 0x00, 0xb1, 0x8f, 0x0b, 0xfc, 0x61, 0x05, 0x00,
2463 0x00, 0x00, 0x06, 0x50, 0x4c, 0x54, 0x45, 0xff, 0xe0, 0x50,
2464 0x00, 0x00, 0x00, 0xa2, 0x7a, 0xda, 0x7e, 0x00, 0x00, 0x00,
2465 0x0a, 0x49, 0x44, 0x41, 0x54, 0x78, 0xda, 0x63, 0x60, 0x00,
2466 0x00, 0x00, 0x02, 0x00, 0x01, 0xe5, 0x27, 0xde, 0xfc, 0x00,
2467 0x00, 0x00, 0x00, 0x49, 0x45, 0x4e, 0x44, 0xae, 0x42, 0x60,
2468 0x82];
2469 $data{"emerald.png"} =
2470 [0x89, 0x50, 0x4e, 0x47, 0x0d, 0x0a, 0x1a, 0x0a, 0x00, 0x00,
2471 0x00, 0x0d, 0x49, 0x48, 0x44, 0x52, 0x00, 0x00, 0x00, 0x01,
2472 0x00, 0x00, 0x00, 0x01, 0x01, 0x03, 0x00, 0x00, 0x00, 0x25,
2473 0xdb, 0x56, 0xca, 0x00, 0x00, 0x00, 0x07, 0x74, 0x49, 0x4d,
2474 0x45, 0x07, 0xd2, 0x07, 0x11, 0x0f, 0x22, 0x2b, 0xc9, 0xf5,
2475 0x03, 0x33, 0x00, 0x00, 0x00, 0x09, 0x70, 0x48, 0x59, 0x73,
2476 0x00, 0x00, 0x0b, 0x12, 0x00, 0x00, 0x0b, 0x12, 0x01, 0xd2,
2477 0xdd, 0x7e, 0xfc, 0x00, 0x00, 0x00, 0x04, 0x67, 0x41, 0x4d,
2478 0x41, 0x00, 0x00, 0xb1, 0x8f, 0x0b, 0xfc, 0x61, 0x05, 0x00,
2479 0x00, 0x00, 0x06, 0x50, 0x4c, 0x54, 0x45, 0x1b, 0xea, 0x59,
2480 0x0a, 0x0a, 0x0a, 0x0f, 0xba, 0x50, 0x83, 0x00, 0x00, 0x00,
2481 0x0a, 0x49, 0x44, 0x41, 0x54, 0x78, 0xda, 0x63, 0x60, 0x00,
2482 0x00, 0x00, 0x02, 0x00, 0x01, 0xe5, 0x27, 0xde, 0xfc, 0x00,
2483 0x00, 0x00, 0x00, 0x49, 0x45, 0x4e, 0x44, 0xae, 0x42, 0x60,
2484 0x82];
2485 $data{"snow.png"} =
2486 [0x89, 0x50, 0x4e, 0x47, 0x0d, 0x0a, 0x1a, 0x0a, 0x00, 0x00,
2487 0x00, 0x0d, 0x49, 0x48, 0x44, 0x52, 0x00, 0x00, 0x00, 0x01,
2488 0x00, 0x00, 0x00, 0x01, 0x01, 0x03, 0x00, 0x00, 0x00, 0x25,
2489 0xdb, 0x56, 0xca, 0x00, 0x00, 0x00, 0x07, 0x74, 0x49, 0x4d,
2490 0x45, 0x07, 0xd2, 0x07, 0x11, 0x0f, 0x1e, 0x1d, 0x75, 0xbc,
2491 0xef, 0x55, 0x00, 0x00, 0x00, 0x09, 0x70, 0x48, 0x59, 0x73,
2492 0x00, 0x00, 0x0b, 0x12, 0x00, 0x00, 0x0b, 0x12, 0x01, 0xd2,
2493 0xdd, 0x7e, 0xfc, 0x00, 0x00, 0x00, 0x04, 0x67, 0x41, 0x4d,
2494 0x41, 0x00, 0x00, 0xb1, 0x8f, 0x0b, 0xfc, 0x61, 0x05, 0x00,
2495 0x00, 0x00, 0x06, 0x50, 0x4c, 0x54, 0x45, 0xff, 0xff, 0xff,
2496 0x00, 0x00, 0x00, 0x55, 0xc2, 0xd3, 0x7e, 0x00, 0x00, 0x00,
2497 0x0a, 0x49, 0x44, 0x41, 0x54, 0x78, 0xda, 0x63, 0x60, 0x00,
2498 0x00, 0x00, 0x02, 0x00, 0x01, 0xe5, 0x27, 0xde, 0xfc, 0x00,
2499 0x00, 0x00, 0x00, 0x49, 0x45, 0x4e, 0x44, 0xae, 0x42, 0x60,
2500 0x82];
2501 $data{"glass.png"} =
2502 [0x89, 0x50, 0x4e, 0x47, 0x0d, 0x0a, 0x1a, 0x0a, 0x00, 0x00,
2503 0x00, 0x0d, 0x49, 0x48, 0x44, 0x52, 0x00, 0x00, 0x00, 0x01,
2504 0x00, 0x00, 0x00, 0x01, 0x01, 0x03, 0x00, 0x00, 0x00, 0x25,
2505 0xdb, 0x56, 0xca, 0x00, 0x00, 0x00, 0x04, 0x67, 0x41, 0x4d,
2506 0x41, 0x00, 0x00, 0xb1, 0x8f, 0x0b, 0xfc, 0x61, 0x05, 0x00,
2507 0x00, 0x00, 0x06, 0x50, 0x4c, 0x54, 0x45, 0xff, 0xff, 0xff,
2508 0x00, 0x00, 0x00, 0x55, 0xc2, 0xd3, 0x7e, 0x00, 0x00, 0x00,
2509 0x01, 0x74, 0x52, 0x4e, 0x53, 0x00, 0x40, 0xe6, 0xd8, 0x66,
2510 0x00, 0x00, 0x00, 0x01, 0x62, 0x4b, 0x47, 0x44, 0x00, 0x88,
2511 0x05, 0x1d, 0x48, 0x00, 0x00, 0x00, 0x09, 0x70, 0x48, 0x59,
2512 0x73, 0x00, 0x00, 0x0b, 0x12, 0x00, 0x00, 0x0b, 0x12, 0x01,
2513 0xd2, 0xdd, 0x7e, 0xfc, 0x00, 0x00, 0x00, 0x07, 0x74, 0x49,
2514 0x4d, 0x45, 0x07, 0xd2, 0x07, 0x13, 0x0f, 0x08, 0x19, 0xc4,
2515 0x40, 0x56, 0x10, 0x00, 0x00, 0x00, 0x0a, 0x49, 0x44, 0x41,
2516 0x54, 0x78, 0x9c, 0x63, 0x60, 0x00, 0x00, 0x00, 0x02, 0x00,
2517 0x01, 0x48, 0xaf, 0xa4, 0x71, 0x00, 0x00, 0x00, 0x00, 0x49,
2518 0x45, 0x4e, 0x44, 0xae, 0x42, 0x60, 0x82];
2519 $data{"updown.png"} =
2520 [0x89, 0x50, 0x4e, 0x47, 0x0d, 0x0a, 0x1a, 0x0a, 0x00, 0x00,
2521 0x00, 0x0d, 0x49, 0x48, 0x44, 0x52, 0x00, 0x00, 0x00, 0x0a,
2522 0x00, 0x00, 0x00, 0x0e, 0x08, 0x06, 0x00, 0x00, 0x00, 0x16,
2523 0xa3, 0x8d, 0xab, 0x00, 0x00, 0x00, 0x3c, 0x49, 0x44, 0x41,
2524 0x54, 0x28, 0xcf, 0x63, 0x60, 0x40, 0x03, 0xff, 0xa1, 0x00,
2525 0x5d, 0x9c, 0x11, 0x5d, 0x11, 0x8a, 0x24, 0x23, 0x23, 0x23,
2526 0x86, 0x42, 0x6c, 0xa6, 0x20, 0x2b, 0x66, 0xc4, 0xa7, 0x08,
2527 0x59, 0x31, 0x23, 0x21, 0x45, 0x30, 0xc0, 0xc4, 0x30, 0x60,
2528 0x80, 0xfa, 0x6e, 0x24, 0x3e, 0x78, 0x48, 0x0a, 0x70, 0x62,
2529 0xa2, 0x90, 0x81, 0xd8, 0x44, 0x01, 0x00, 0xe9, 0x5c, 0x2f,
2530 0xf5, 0xe2, 0x9d, 0x0f, 0xf9, 0x00, 0x00, 0x00, 0x00, 0x49,
2531 0x45, 0x4e, 0x44, 0xae, 0x42, 0x60, 0x82] if ($sort);
2532 foreach (keys(%data))
2534 open(PNG_HANDLE, ">".$_)
2535 or die("ERROR: cannot create $_!\n");
2536 binmode(PNG_HANDLE);
2537 print(PNG_HANDLE map(chr,@{$data{$_}}));
2538 close(PNG_HANDLE);
2544 # write_htaccess_file()
2547 sub write_htaccess_file()
2549 local *HTACCESS_HANDLE;
2550 my $htaccess_data;
2552 open(*HTACCESS_HANDLE, ">.htaccess")
2553 or die("ERROR: cannot open .htaccess for writing!\n");
2555 $htaccess_data = (<<"END_OF_HTACCESS")
2556 AddEncoding x-gzip .html
2557 END_OF_HTACCESS
2560 print(HTACCESS_HANDLE $htaccess_data);
2561 close(*HTACCESS_HANDLE);
2566 # write_css_file()
2568 # Write the cascading style sheet file gcov.css to the current directory.
2569 # This file defines basic layout attributes of all generated HTML pages.
2572 sub write_css_file()
2574 local *CSS_HANDLE;
2576 # Check for a specified external style sheet file
2577 if ($css_filename)
2579 # Simply copy that file
2580 system("cp", $css_filename, "gcov.css")
2581 and die("ERROR: cannot copy file $css_filename!\n");
2582 return;
2585 open(CSS_HANDLE, ">gcov.css")
2586 or die ("ERROR: cannot open gcov.css for writing!\n");
2589 # *************************************************************
2591 my $css_data = ($_=<<"END_OF_CSS")
2592 /* All views: initial background and text color */
2593 body
2595 color: #000000;
2596 background-color: #FFFFFF;
2599 /* All views: standard link format*/
2600 a:link
2602 color: #284FA8;
2603 text-decoration: underline;
2606 /* All views: standard link - visited format */
2607 a:visited
2609 color: #00CB40;
2610 text-decoration: underline;
2613 /* All views: standard link - activated format */
2614 a:active
2616 color: #FF0040;
2617 text-decoration: underline;
2620 /* All views: main title format */
2621 td.title
2623 text-align: center;
2624 padding-bottom: 10px;
2625 font-family: sans-serif;
2626 font-size: 20pt;
2627 font-style: italic;
2628 font-weight: bold;
2631 /* All views: header item format */
2632 td.headerItem
2634 text-align: right;
2635 padding-right: 6px;
2636 font-family: sans-serif;
2637 font-weight: bold;
2638 vertical-align: top;
2639 white-space: nowrap;
2642 /* All views: header item value format */
2643 td.headerValue
2645 text-align: left;
2646 color: #284FA8;
2647 font-family: sans-serif;
2648 font-weight: bold;
2649 white-space: nowrap;
2652 /* All views: header item coverage table heading */
2653 td.headerCovTableHead
2655 text-align: center;
2656 padding-right: 6px;
2657 padding-left: 6px;
2658 padding-bottom: 0px;
2659 font-family: sans-serif;
2660 font-size: 80%;
2661 white-space: nowrap;
2664 /* All views: header item coverage table entry */
2665 td.headerCovTableEntry
2667 text-align: right;
2668 color: #284FA8;
2669 font-family: sans-serif;
2670 font-weight: bold;
2671 white-space: nowrap;
2672 padding-left: 12px;
2673 padding-right: 4px;
2674 background-color: #DAE7FE;
2677 /* All views: header item coverage table entry for high coverage rate */
2678 td.headerCovTableEntryHi
2680 text-align: right;
2681 color: #000000;
2682 font-family: sans-serif;
2683 font-weight: bold;
2684 white-space: nowrap;
2685 padding-left: 12px;
2686 padding-right: 4px;
2687 background-color: #A7FC9D;
2690 /* All views: header item coverage table entry for medium coverage rate */
2691 td.headerCovTableEntryMed
2693 text-align: right;
2694 color: #000000;
2695 font-family: sans-serif;
2696 font-weight: bold;
2697 white-space: nowrap;
2698 padding-left: 12px;
2699 padding-right: 4px;
2700 background-color: #FFEA20;
2703 /* All views: header item coverage table entry for ow coverage rate */
2704 td.headerCovTableEntryLo
2706 text-align: right;
2707 color: #000000;
2708 font-family: sans-serif;
2709 font-weight: bold;
2710 white-space: nowrap;
2711 padding-left: 12px;
2712 padding-right: 4px;
2713 background-color: #FF0000;
2716 /* All views: header legend value for legend entry */
2717 td.headerValueLeg
2719 text-align: left;
2720 color: #000000;
2721 font-family: sans-serif;
2722 font-size: 80%;
2723 white-space: nowrap;
2724 padding-top: 4px;
2727 /* All views: color of horizontal ruler */
2728 td.ruler
2730 background-color: #6688D4;
2733 /* All views: version string format */
2734 td.versionInfo
2736 text-align: center;
2737 padding-top: 2px;
2738 font-family: sans-serif;
2739 font-style: italic;
2742 /* Directory view/File view (all)/Test case descriptions:
2743 table headline format */
2744 td.tableHead
2746 text-align: center;
2747 color: #FFFFFF;
2748 background-color: #6688D4;
2749 font-family: sans-serif;
2750 font-size: 120%;
2751 font-weight: bold;
2752 white-space: nowrap;
2753 padding-left: 4px;
2754 padding-right: 4px;
2757 span.tableHeadSort
2759 padding-right: 4px;
2762 /* Directory view/File view (all): filename entry format */
2763 td.coverFile
2765 text-align: left;
2766 padding-left: 10px;
2767 padding-right: 20px;
2768 color: #284FA8;
2769 background-color: #DAE7FE;
2770 font-family: monospace;
2773 /* Directory view/File view (all): bar-graph entry format*/
2774 td.coverBar
2776 padding-left: 10px;
2777 padding-right: 10px;
2778 background-color: #DAE7FE;
2781 /* Directory view/File view (all): bar-graph outline color */
2782 td.coverBarOutline
2784 background-color: #000000;
2787 /* Directory view/File view (all): percentage entry for files with
2788 high coverage rate */
2789 td.coverPerHi
2791 text-align: right;
2792 padding-left: 10px;
2793 padding-right: 10px;
2794 background-color: #A7FC9D;
2795 font-weight: bold;
2796 font-family: sans-serif;
2799 /* Directory view/File view (all): line count entry for files with
2800 high coverage rate */
2801 td.coverNumHi
2803 text-align: right;
2804 padding-left: 10px;
2805 padding-right: 10px;
2806 background-color: #A7FC9D;
2807 white-space: nowrap;
2808 font-family: sans-serif;
2811 /* Directory view/File view (all): percentage entry for files with
2812 medium coverage rate */
2813 td.coverPerMed
2815 text-align: right;
2816 padding-left: 10px;
2817 padding-right: 10px;
2818 background-color: #FFEA20;
2819 font-weight: bold;
2820 font-family: sans-serif;
2823 /* Directory view/File view (all): line count entry for files with
2824 medium coverage rate */
2825 td.coverNumMed
2827 text-align: right;
2828 padding-left: 10px;
2829 padding-right: 10px;
2830 background-color: #FFEA20;
2831 white-space: nowrap;
2832 font-family: sans-serif;
2835 /* Directory view/File view (all): percentage entry for files with
2836 low coverage rate */
2837 td.coverPerLo
2839 text-align: right;
2840 padding-left: 10px;
2841 padding-right: 10px;
2842 background-color: #FF0000;
2843 font-weight: bold;
2844 font-family: sans-serif;
2847 /* Directory view/File view (all): line count entry for files with
2848 low coverage rate */
2849 td.coverNumLo
2851 text-align: right;
2852 padding-left: 10px;
2853 padding-right: 10px;
2854 background-color: #FF0000;
2855 white-space: nowrap;
2856 font-family: sans-serif;
2859 /* File view (all): "show/hide details" link format */
2860 a.detail:link
2862 color: #B8D0FF;
2863 font-size:80%;
2866 /* File view (all): "show/hide details" link - visited format */
2867 a.detail:visited
2869 color: #B8D0FF;
2870 font-size:80%;
2873 /* File view (all): "show/hide details" link - activated format */
2874 a.detail:active
2876 color: #FFFFFF;
2877 font-size:80%;
2880 /* File view (detail): test name entry */
2881 td.testName
2883 text-align: right;
2884 padding-right: 10px;
2885 background-color: #DAE7FE;
2886 font-family: sans-serif;
2889 /* File view (detail): test percentage entry */
2890 td.testPer
2892 text-align: right;
2893 padding-left: 10px;
2894 padding-right: 10px;
2895 background-color: #DAE7FE;
2896 font-family: sans-serif;
2899 /* File view (detail): test lines count entry */
2900 td.testNum
2902 text-align: right;
2903 padding-left: 10px;
2904 padding-right: 10px;
2905 background-color: #DAE7FE;
2906 font-family: sans-serif;
2909 /* Test case descriptions: test name format*/
2912 font-family: sans-serif;
2913 font-weight: bold;
2916 /* Test case descriptions: description table body */
2917 td.testDescription
2919 padding-top: 10px;
2920 padding-left: 30px;
2921 padding-bottom: 10px;
2922 padding-right: 30px;
2923 background-color: #DAE7FE;
2926 /* Source code view: function entry */
2927 td.coverFn
2929 text-align: left;
2930 padding-left: 10px;
2931 padding-right: 20px;
2932 color: #284FA8;
2933 background-color: #DAE7FE;
2934 font-family: monospace;
2937 /* Source code view: function entry zero count*/
2938 td.coverFnLo
2940 text-align: right;
2941 padding-left: 10px;
2942 padding-right: 10px;
2943 background-color: #FF0000;
2944 font-weight: bold;
2945 font-family: sans-serif;
2948 /* Source code view: function entry nonzero count*/
2949 td.coverFnHi
2951 text-align: right;
2952 padding-left: 10px;
2953 padding-right: 10px;
2954 background-color: #DAE7FE;
2955 font-weight: bold;
2956 font-family: sans-serif;
2959 /* Source code view: source code format */
2960 pre.source
2962 font-family: monospace;
2963 white-space: pre;
2964 margin-top: 2px;
2967 /* Source code view: line number format */
2968 span.lineNum
2970 background-color: #EFE383;
2973 /* Source code view: format for lines which were executed */
2974 td.lineCov,
2975 span.lineCov
2977 background-color: #CAD7FE;
2980 /* Source code view: format for Cov legend */
2981 span.coverLegendCov
2983 padding-left: 10px;
2984 padding-right: 10px;
2985 padding-bottom: 2px;
2986 background-color: #CAD7FE;
2989 /* Source code view: format for lines which were not executed */
2990 td.lineNoCov,
2991 span.lineNoCov
2993 background-color: #FF6230;
2996 /* Source code view: format for NoCov legend */
2997 span.coverLegendNoCov
2999 padding-left: 10px;
3000 padding-right: 10px;
3001 padding-bottom: 2px;
3002 background-color: #FF6230;
3005 /* Source code view (function table): standard link - visited format */
3006 td.lineNoCov > a:visited,
3007 td.lineCov > a:visited
3009 color: black;
3010 text-decoration: underline;
3013 /* Source code view: format for lines which were executed only in a
3014 previous version */
3015 span.lineDiffCov
3017 background-color: #B5F7AF;
3020 /* Source code view: format for branches which were executed
3021 * and taken */
3022 span.branchCov
3024 background-color: #CAD7FE;
3027 /* Source code view: format for branches which were executed
3028 * but not taken */
3029 span.branchNoCov
3031 background-color: #FF6230;
3034 /* Source code view: format for branches which were not executed */
3035 span.branchNoExec
3037 background-color: #FF6230;
3040 /* Source code view: format for the source code heading line */
3041 pre.sourceHeading
3043 white-space: pre;
3044 font-family: monospace;
3045 font-weight: bold;
3046 margin: 0px;
3049 /* All views: header legend value for low rate */
3050 td.headerValueLegL
3052 font-family: sans-serif;
3053 text-align: center;
3054 white-space: nowrap;
3055 padding-left: 4px;
3056 padding-right: 2px;
3057 background-color: #FF0000;
3058 font-size: 80%;
3061 /* All views: header legend value for med rate */
3062 td.headerValueLegM
3064 font-family: sans-serif;
3065 text-align: center;
3066 white-space: nowrap;
3067 padding-left: 2px;
3068 padding-right: 2px;
3069 background-color: #FFEA20;
3070 font-size: 80%;
3073 /* All views: header legend value for hi rate */
3074 td.headerValueLegH
3076 font-family: sans-serif;
3077 text-align: center;
3078 white-space: nowrap;
3079 padding-left: 2px;
3080 padding-right: 4px;
3081 background-color: #A7FC9D;
3082 font-size: 80%;
3085 /* All views except source code view: legend format for low coverage */
3086 span.coverLegendCovLo
3088 padding-left: 10px;
3089 padding-right: 10px;
3090 padding-top: 2px;
3091 background-color: #FF0000;
3094 /* All views except source code view: legend format for med coverage */
3095 span.coverLegendCovMed
3097 padding-left: 10px;
3098 padding-right: 10px;
3099 padding-top: 2px;
3100 background-color: #FFEA20;
3103 /* All views except source code view: legend format for hi coverage */
3104 span.coverLegendCovHi
3106 padding-left: 10px;
3107 padding-right: 10px;
3108 padding-top: 2px;
3109 background-color: #A7FC9D;
3111 END_OF_CSS
3114 # *************************************************************
3117 # Remove leading tab from all lines
3118 $css_data =~ s/^\t//gm;
3120 print(CSS_HANDLE $css_data);
3122 close(CSS_HANDLE);
3127 # get_bar_graph_code(base_dir, cover_found, cover_hit)
3129 # Return a string containing HTML code which implements a bar graph display
3130 # for a coverage rate of cover_hit * 100 / cover_found.
3133 sub get_bar_graph_code($$$)
3135 my $rate;
3136 my $alt;
3137 my $width;
3138 my $remainder;
3139 my $png_name;
3140 my $graph_code;
3142 # Check number of instrumented lines
3143 if ($_[1] == 0) { return ""; }
3145 $rate = $_[2] * 100 / $_[1];
3146 $alt = sprintf("%.1f", $rate)."%";
3147 $width = sprintf("%.0f", $rate);
3148 $remainder = sprintf("%d", 100-$width);
3150 # Decide which .png file to use
3151 $png_name = $rate_png[classify_rate($_[1], $_[2], $med_limit,
3152 $hi_limit)];
3154 if ($width == 0)
3156 # Zero coverage
3157 $graph_code = (<<END_OF_HTML)
3158 <table border=0 cellspacing=0 cellpadding=1><tr><td class="coverBarOutline"><img src="$_[0]snow.png" width=100 height=10 alt="$alt"></td></tr></table>
3159 END_OF_HTML
3162 elsif ($width == 100)
3164 # Full coverage
3165 $graph_code = (<<END_OF_HTML)
3166 <table border=0 cellspacing=0 cellpadding=1><tr><td class="coverBarOutline"><img src="$_[0]$png_name" width=100 height=10 alt="$alt"></td></tr></table>
3167 END_OF_HTML
3170 else
3172 # Positive coverage
3173 $graph_code = (<<END_OF_HTML)
3174 <table border=0 cellspacing=0 cellpadding=1><tr><td class="coverBarOutline"><img src="$_[0]$png_name" width=$width height=10 alt="$alt"><img src="$_[0]snow.png" width=$remainder height=10 alt="$alt"></td></tr></table>
3175 END_OF_HTML
3179 # Remove leading tabs from all lines
3180 $graph_code =~ s/^\t+//gm;
3181 chomp($graph_code);
3183 return($graph_code);
3187 # sub classify_rate(found, hit, med_limit, high_limit)
3189 # Return 0 for low rate, 1 for medium rate and 2 for hi rate.
3192 sub classify_rate($$$$)
3194 my ($found, $hit, $med, $hi) = @_;
3195 my $rate;
3197 if ($found == 0) {
3198 return 2;
3200 $rate = $hit * 100 / $found;
3201 if ($rate < $med) {
3202 return 0;
3203 } elsif ($rate < $hi) {
3204 return 1;
3206 return 2;
3211 # write_html(filehandle, html_code)
3213 # Write out HTML_CODE to FILEHANDLE while removing a leading tabulator mark
3214 # in each line of HTML_CODE.
3217 sub write_html(*$)
3219 local *HTML_HANDLE = $_[0];
3220 my $html_code = $_[1];
3222 # Remove leading tab from all lines
3223 $html_code =~ s/^\t//gm;
3225 print(HTML_HANDLE $html_code)
3226 or die("ERROR: cannot write HTML data ($!)\n");
3231 # write_html_prolog(filehandle, base_dir, pagetitle)
3233 # Write an HTML prolog common to all HTML files to FILEHANDLE. PAGETITLE will
3234 # be used as HTML page title. BASE_DIR contains a relative path which points
3235 # to the base directory.
3238 sub write_html_prolog(*$$)
3240 my $basedir = $_[1];
3241 my $pagetitle = $_[2];
3242 my $prolog;
3244 $prolog = $html_prolog;
3245 $prolog =~ s/\@pagetitle\@/$pagetitle/g;
3246 $prolog =~ s/\@basedir\@/$basedir/g;
3248 write_html($_[0], $prolog);
3253 # write_header_prolog(filehandle, base_dir)
3255 # Write beginning of page header HTML code.
3258 sub write_header_prolog(*$)
3260 # *************************************************************
3262 write_html($_[0], <<END_OF_HTML)
3263 <table width="100%" border=0 cellspacing=0 cellpadding=0>
3264 <tr><td class="title">$title</td></tr>
3265 <tr><td class="ruler"><img src="$_[1]glass.png" width=3 height=3 alt=""></td></tr>
3267 <tr>
3268 <td width="100%">
3269 <table cellpadding=1 border=0 width="100%">
3270 END_OF_HTML
3273 # *************************************************************
3278 # write_header_line(handle, content)
3280 # Write a header line with the specified table contents.
3283 sub write_header_line(*@)
3285 my ($handle, @content) = @_;
3286 my $entry;
3288 write_html($handle, " <tr>\n");
3289 foreach $entry (@content) {
3290 my ($width, $class, $text, $colspan) = @{$entry};
3292 if (defined($width)) {
3293 $width = " width=\"$width\"";
3294 } else {
3295 $width = "";
3297 if (defined($class)) {
3298 $class = " class=\"$class\"";
3299 } else {
3300 $class = "";
3302 if (defined($colspan)) {
3303 $colspan = " colspan=\"$colspan\"";
3304 } else {
3305 $colspan = "";
3307 $text = "" if (!defined($text));
3308 write_html($handle,
3309 " <td$width$class$colspan>$text</td>\n");
3311 write_html($handle, " </tr>\n");
3316 # write_header_epilog(filehandle, base_dir)
3318 # Write end of page header HTML code.
3321 sub write_header_epilog(*$)
3323 # *************************************************************
3325 write_html($_[0], <<END_OF_HTML)
3326 <tr><td><img src="$_[1]glass.png" width=3 height=3 alt=""></td></tr>
3327 </table>
3328 </td>
3329 </tr>
3331 <tr><td class="ruler"><img src="$_[1]glass.png" width=3 height=3 alt=""></td></tr>
3332 </table>
3334 END_OF_HTML
3337 # *************************************************************
3342 # write_file_table_prolog(handle, file_heading, ([heading, num_cols], ...))
3344 # Write heading for file table.
3347 sub write_file_table_prolog(*$@)
3349 my ($handle, $file_heading, @columns) = @_;
3350 my $num_columns = 0;
3351 my $file_width;
3352 my $col;
3353 my $width;
3355 $width = 20 if (scalar(@columns) == 1);
3356 $width = 10 if (scalar(@columns) == 2);
3357 $width = 8 if (scalar(@columns) > 2);
3359 foreach $col (@columns) {
3360 my ($heading, $cols) = @{$col};
3362 $num_columns += $cols;
3364 $file_width = 100 - $num_columns * $width;
3366 # Table definition
3367 write_html($handle, <<END_OF_HTML);
3368 <center>
3369 <table width="80%" cellpadding=1 cellspacing=1 border=0>
3371 <tr>
3372 <td width="$file_width%"><br></td>
3373 END_OF_HTML
3374 # Empty first row
3375 foreach $col (@columns) {
3376 my ($heading, $cols) = @{$col};
3378 while ($cols-- > 0) {
3379 write_html($handle, <<END_OF_HTML);
3380 <td width="$width%"></td>
3381 END_OF_HTML
3384 # Next row
3385 write_html($handle, <<END_OF_HTML);
3386 </tr>
3388 <tr>
3389 <td class="tableHead">$file_heading</td>
3390 END_OF_HTML
3391 # Heading row
3392 foreach $col (@columns) {
3393 my ($heading, $cols) = @{$col};
3394 my $colspan = "";
3396 $colspan = " colspan=$cols" if ($cols > 1);
3397 write_html($handle, <<END_OF_HTML);
3398 <td class="tableHead"$colspan>$heading</td>
3399 END_OF_HTML
3401 write_html($handle, <<END_OF_HTML);
3402 </tr>
3403 END_OF_HTML
3407 # write_file_table_entry(handle, base_dir, filename, page_link,
3408 # ([ found, hit, med_limit, hi_limit, graph ], ..)
3410 # Write an entry of the file table.
3413 sub write_file_table_entry(*$$$@)
3415 my ($handle, $base_dir, $filename, $page_link, @entries) = @_;
3416 my $file_code;
3417 my $entry;
3419 # Add link to source if provided
3420 if (defined($page_link) && $page_link ne "") {
3421 $file_code = "<a href=\"$page_link\">$filename</a>";
3422 } else {
3423 $file_code = $filename;
3426 # First column: filename
3427 write_html($handle, <<END_OF_HTML);
3428 <tr>
3429 <td class="coverFile">$file_code</td>
3430 END_OF_HTML
3431 # Columns as defined
3432 foreach $entry (@entries) {
3433 my ($found, $hit, $med, $hi, $graph) = @{$entry};
3434 my $bar_graph;
3435 my $class;
3436 my $rate;
3438 # Generate bar graph if requested
3439 if ($graph) {
3440 $bar_graph = get_bar_graph_code($base_dir, $found,
3441 $hit);
3442 write_html($handle, <<END_OF_HTML);
3443 <td class="coverBar" align="center">
3444 $bar_graph
3445 </td>
3446 END_OF_HTML
3448 # Get rate color and text
3449 if ($found == 0) {
3450 $rate = "-";
3451 $class = "Hi";
3452 } else {
3453 $rate = sprintf("%.1f&nbsp;%%", $hit * 100 / $found);
3454 $class = $rate_name[classify_rate($found, $hit,
3455 $med, $hi)];
3457 write_html($handle, <<END_OF_HTML);
3458 <td class="coverPer$class">$rate</td>
3459 <td class="coverNum$class">$hit / $found</td>
3460 END_OF_HTML
3462 # End of row
3463 write_html($handle, <<END_OF_HTML);
3464 </tr>
3465 END_OF_HTML
3470 # write_file_table_detail_entry(filehandle, test_name, ([found, hit], ...))
3472 # Write entry for detail section in file table.
3475 sub write_file_table_detail_entry(*$@)
3477 my ($handle, $test, @entries) = @_;
3478 my $entry;
3480 if ($test eq "") {
3481 $test = "<span style=\"font-style:italic\">&lt;unnamed&gt;</span>";
3482 } elsif ($test =~ /^(.*),diff$/) {
3483 $test = $1." (converted)";
3485 # Testname
3486 write_html($handle, <<END_OF_HTML);
3487 <tr>
3488 <td class="testName" colspan=2>$test</td>
3489 END_OF_HTML
3490 # Test data
3491 foreach $entry (@entries) {
3492 my ($found, $hit) = @{$entry};
3493 my $rate = "-";
3495 if ($found > 0) {
3496 $rate = sprintf("%.1f&nbsp;%%", $hit * 100 / $found);
3498 write_html($handle, <<END_OF_HTML);
3499 <td class="testPer">$rate</td>
3500 <td class="testNum">$hit&nbsp;/&nbsp;$found</td>
3501 END_OF_HTML
3504 write_html($handle, <<END_OF_HTML);
3505 </tr>
3507 END_OF_HTML
3509 # *************************************************************
3514 # write_file_table_epilog(filehandle)
3516 # Write end of file table HTML code.
3519 sub write_file_table_epilog(*)
3521 # *************************************************************
3523 write_html($_[0], <<END_OF_HTML)
3524 </table>
3525 </center>
3526 <br>
3528 END_OF_HTML
3531 # *************************************************************
3536 # write_test_table_prolog(filehandle, table_heading)
3538 # Write heading for test case description table.
3541 sub write_test_table_prolog(*$)
3543 # *************************************************************
3545 write_html($_[0], <<END_OF_HTML)
3546 <center>
3547 <table width="80%" cellpadding=2 cellspacing=1 border=0>
3549 <tr>
3550 <td><br></td>
3551 </tr>
3553 <tr>
3554 <td class="tableHead">$_[1]</td>
3555 </tr>
3557 <tr>
3558 <td class="testDescription">
3559 <dl>
3560 END_OF_HTML
3563 # *************************************************************
3568 # write_test_table_entry(filehandle, test_name, test_description)
3570 # Write entry for the test table.
3573 sub write_test_table_entry(*$$)
3575 # *************************************************************
3577 write_html($_[0], <<END_OF_HTML)
3578 <dt>$_[1]<a name="$_[1]">&nbsp;</a></dt>
3579 <dd>$_[2]<br><br></dd>
3580 END_OF_HTML
3583 # *************************************************************
3588 # write_test_table_epilog(filehandle)
3590 # Write end of test description table HTML code.
3593 sub write_test_table_epilog(*)
3595 # *************************************************************
3597 write_html($_[0], <<END_OF_HTML)
3598 </dl>
3599 </td>
3600 </tr>
3601 </table>
3602 </center>
3603 <br>
3605 END_OF_HTML
3608 # *************************************************************
3612 sub fmt_centered($$)
3614 my ($width, $text) = @_;
3615 my $w0 = length($text);
3616 my $w1 = int(($width - $w0) / 2);
3617 my $w2 = $width - $w0 - $w1;
3619 return (" "x$w1).$text.(" "x$w2);
3624 # write_source_prolog(filehandle)
3626 # Write start of source code table.
3629 sub write_source_prolog(*)
3631 my $lineno_heading = " ";
3632 my $branch_heading = "";
3633 my $line_heading = fmt_centered($line_field_width, "Line data");
3634 my $source_heading = " Source code";
3636 if ($br_coverage) {
3637 $branch_heading = fmt_centered($br_field_width, "Branch data").
3638 " ";
3640 # *************************************************************
3642 write_html($_[0], <<END_OF_HTML)
3643 <table cellpadding=0 cellspacing=0 border=0>
3644 <tr>
3645 <td><br></td>
3646 </tr>
3647 <tr>
3648 <td>
3649 <pre class="sourceHeading">${lineno_heading}${branch_heading}${line_heading} ${source_heading}</pre>
3650 <pre class="source">
3651 END_OF_HTML
3654 # *************************************************************
3659 # get_branch_blocks(brdata)
3661 # Group branches that belong to the same basic block.
3663 # Returns: [block1, block2, ...]
3664 # block: [branch1, branch2, ...]
3665 # branch: [block_num, branch_num, taken_count, text_length, open, close]
3668 sub get_branch_blocks($)
3670 my ($brdata) = @_;
3671 my $last_block_num;
3672 my $block = [];
3673 my @blocks;
3674 my $i;
3675 my $num = br_ivec_len($brdata);
3677 # Group branches
3678 for ($i = 0; $i < $num; $i++) {
3679 my ($block_num, $branch, $taken) = br_ivec_get($brdata, $i);
3680 my $br;
3682 if (defined($last_block_num) && $block_num != $last_block_num) {
3683 push(@blocks, $block);
3684 $block = [];
3686 $br = [$block_num, $branch, $taken, 3, 0, 0];
3687 push(@{$block}, $br);
3688 $last_block_num = $block_num;
3690 push(@blocks, $block) if (scalar(@{$block}) > 0);
3692 # Add braces to first and last branch in group
3693 foreach $block (@blocks) {
3694 $block->[0]->[$BR_OPEN] = 1;
3695 $block->[0]->[$BR_LEN]++;
3696 $block->[scalar(@{$block}) - 1]->[$BR_CLOSE] = 1;
3697 $block->[scalar(@{$block}) - 1]->[$BR_LEN]++;
3700 return @blocks;
3704 # get_block_len(block)
3706 # Calculate total text length of all branches in a block of branches.
3709 sub get_block_len($)
3711 my ($block) = @_;
3712 my $len = 0;
3713 my $branch;
3715 foreach $branch (@{$block}) {
3716 $len += $branch->[$BR_LEN];
3719 return $len;
3724 # get_branch_html(brdata)
3726 # Return a list of HTML lines which represent the specified branch coverage
3727 # data in source code view.
3730 sub get_branch_html($)
3732 my ($brdata) = @_;
3733 my @blocks = get_branch_blocks($brdata);
3734 my $block;
3735 my $branch;
3736 my $line_len = 0;
3737 my $line = []; # [branch2|" ", branch|" ", ...]
3738 my @lines; # [line1, line2, ...]
3739 my @result;
3741 # Distribute blocks to lines
3742 foreach $block (@blocks) {
3743 my $block_len = get_block_len($block);
3745 # Does this block fit into the current line?
3746 if ($line_len + $block_len <= $br_field_width) {
3747 # Add it
3748 $line_len += $block_len;
3749 push(@{$line}, @{$block});
3750 next;
3751 } elsif ($block_len <= $br_field_width) {
3752 # It would fit if the line was empty - add it to new
3753 # line
3754 push(@lines, $line);
3755 $line_len = $block_len;
3756 $line = [ @{$block} ];
3757 next;
3759 # Split the block into several lines
3760 foreach $branch (@{$block}) {
3761 if ($line_len + $branch->[$BR_LEN] >= $br_field_width) {
3762 # Start a new line
3763 if (($line_len + 1 <= $br_field_width) &&
3764 scalar(@{$line}) > 0 &&
3765 !$line->[scalar(@$line) - 1]->[$BR_CLOSE]) {
3766 # Try to align branch symbols to be in
3767 # one # row
3768 push(@{$line}, " ");
3770 push(@lines, $line);
3771 $line_len = 0;
3772 $line = [];
3774 push(@{$line}, $branch);
3775 $line_len += $branch->[$BR_LEN];
3778 push(@lines, $line);
3780 # Convert to HTML
3781 foreach $line (@lines) {
3782 my $current = "";
3783 my $current_len = 0;
3785 foreach $branch (@$line) {
3786 # Skip alignment space
3787 if ($branch eq " ") {
3788 $current .= " ";
3789 $current_len++;
3790 next;
3793 my ($block_num, $br_num, $taken, $len, $open, $close) =
3794 @{$branch};
3795 my $class;
3796 my $title;
3797 my $text;
3799 if ($taken eq '-') {
3800 $class = "branchNoExec";
3801 $text = " # ";
3802 $title = "Branch $br_num was not executed";
3803 } elsif ($taken == 0) {
3804 $class = "branchNoCov";
3805 $text = " - ";
3806 $title = "Branch $br_num was not taken";
3807 } else {
3808 $class = "branchCov";
3809 $text = " + ";
3810 $title = "Branch $br_num was taken $taken ".
3811 "time";
3812 $title .= "s" if ($taken > 1);
3814 $current .= "[" if ($open);
3815 $current .= "<span class=\"$class\" title=\"$title\">";
3816 $current .= $text."</span>";
3817 $current .= "]" if ($close);
3818 $current_len += $len;
3821 # Right-align result text
3822 if ($current_len < $br_field_width) {
3823 $current = (" "x($br_field_width - $current_len)).
3824 $current;
3826 push(@result, $current);
3829 return @result;
3834 # format_count(count, width)
3836 # Return a right-aligned representation of count that fits in width characters.
3839 sub format_count($$)
3841 my ($count, $width) = @_;
3842 my $result;
3843 my $exp;
3845 $result = sprintf("%*.0f", $width, $count);
3846 while (length($result) > $width) {
3847 last if ($count < 10);
3848 $exp++;
3849 $count = int($count/10);
3850 $result = sprintf("%*s", $width, ">$count*10^$exp");
3852 return $result;
3856 # write_source_line(filehandle, line_num, source, hit_count, converted,
3857 # brdata, add_anchor)
3859 # Write formatted source code line. Return a line in a format as needed
3860 # by gen_png()
3863 sub write_source_line(*$$$$$$)
3865 my ($handle, $line, $source, $count, $converted, $brdata,
3866 $add_anchor) = @_;
3867 my $source_format;
3868 my $count_format;
3869 my $result;
3870 my $anchor_start = "";
3871 my $anchor_end = "";
3872 my $count_field_width = $line_field_width - 1;
3873 my @br_html;
3874 my $html;
3876 # Get branch HTML data for this line
3877 @br_html = get_branch_html($brdata) if ($br_coverage);
3879 if (!defined($count)) {
3880 $result = "";
3881 $source_format = "";
3882 $count_format = " "x$count_field_width;
3884 elsif ($count == 0) {
3885 $result = $count;
3886 $source_format = '<span class="lineNoCov">';
3887 $count_format = format_count($count, $count_field_width);
3889 elsif ($converted && defined($highlight)) {
3890 $result = "*".$count;
3891 $source_format = '<span class="lineDiffCov">';
3892 $count_format = format_count($count, $count_field_width);
3894 else {
3895 $result = $count;
3896 $source_format = '<span class="lineCov">';
3897 $count_format = format_count($count, $count_field_width);
3899 $result .= ":".$source;
3901 # Write out a line number navigation anchor every $nav_resolution
3902 # lines if necessary
3903 if ($add_anchor)
3905 $anchor_start = "<a name=\"$_[1]\">";
3906 $anchor_end = "</a>";
3910 # *************************************************************
3912 $html = $anchor_start;
3913 $html .= "<span class=\"lineNum\">".sprintf("%8d", $line)." </span>";
3914 $html .= shift(@br_html).":" if ($br_coverage);
3915 $html .= "$source_format$count_format : ";
3916 $html .= escape_html($source);
3917 $html .= "</span>" if ($source_format);
3918 $html .= $anchor_end."\n";
3920 write_html($handle, $html);
3922 if ($br_coverage) {
3923 # Add lines for overlong branch information
3924 foreach (@br_html) {
3925 write_html($handle, "<span class=\"lineNum\">".
3926 " </span>$_\n");
3929 # *************************************************************
3931 return($result);
3936 # write_source_epilog(filehandle)
3938 # Write end of source code table.
3941 sub write_source_epilog(*)
3943 # *************************************************************
3945 write_html($_[0], <<END_OF_HTML)
3946 </pre>
3947 </td>
3948 </tr>
3949 </table>
3950 <br>
3952 END_OF_HTML
3955 # *************************************************************
3960 # write_html_epilog(filehandle, base_dir[, break_frames])
3962 # Write HTML page footer to FILEHANDLE. BREAK_FRAMES should be set when
3963 # this page is embedded in a frameset, clicking the URL link will then
3964 # break this frameset.
3967 sub write_html_epilog(*$;$)
3969 my $basedir = $_[1];
3970 my $break_code = "";
3971 my $epilog;
3973 if (defined($_[2]))
3975 $break_code = " target=\"_parent\"";
3978 # *************************************************************
3980 write_html($_[0], <<END_OF_HTML)
3981 <table width="100%" border=0 cellspacing=0 cellpadding=0>
3982 <tr><td class="ruler"><img src="$_[1]glass.png" width=3 height=3 alt=""></td></tr>
3983 <tr><td class="versionInfo">Generated by: <a href="$lcov_url"$break_code>$lcov_version</a></td></tr>
3984 </table>
3985 <br>
3986 END_OF_HTML
3989 $epilog = $html_epilog;
3990 $epilog =~ s/\@basedir\@/$basedir/g;
3992 write_html($_[0], $epilog);
3997 # write_frameset(filehandle, basedir, basename, pagetitle)
4001 sub write_frameset(*$$$)
4003 my $frame_width = $overview_width + 40;
4005 # *************************************************************
4007 write_html($_[0], <<END_OF_HTML)
4008 <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Frameset//EN">
4010 <html lang="en">
4012 <head>
4013 <meta http-equiv="Content-Type" content="text/html; charset=ISO-8859-1">
4014 <title>$_[3]</title>
4015 <link rel="stylesheet" type="text/css" href="$_[1]gcov.css">
4016 </head>
4018 <frameset cols="$frame_width,*">
4019 <frame src="$_[2].gcov.overview.$html_ext" name="overview">
4020 <frame src="$_[2].gcov.$html_ext" name="source">
4021 <noframes>
4022 <center>Frames not supported by your browser!<br></center>
4023 </noframes>
4024 </frameset>
4026 </html>
4027 END_OF_HTML
4030 # *************************************************************
4035 # sub write_overview_line(filehandle, basename, line, link)
4039 sub write_overview_line(*$$$)
4041 my $y1 = $_[2] - 1;
4042 my $y2 = $y1 + $nav_resolution - 1;
4043 my $x2 = $overview_width - 1;
4045 # *************************************************************
4047 write_html($_[0], <<END_OF_HTML)
4048 <area shape="rect" coords="0,$y1,$x2,$y2" href="$_[1].gcov.$html_ext#$_[3]" target="source" alt="overview">
4049 END_OF_HTML
4052 # *************************************************************
4057 # write_overview(filehandle, basedir, basename, pagetitle, lines)
4061 sub write_overview(*$$$$)
4063 my $index;
4064 my $max_line = $_[4] - 1;
4065 my $offset;
4067 # *************************************************************
4069 write_html($_[0], <<END_OF_HTML)
4070 <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">
4072 <html lang="en">
4074 <head>
4075 <title>$_[3]</title>
4076 <meta http-equiv="Content-Type" content="text/html; charset=ISO-8859-1">
4077 <link rel="stylesheet" type="text/css" href="$_[1]gcov.css">
4078 </head>
4080 <body>
4081 <map name="overview">
4082 END_OF_HTML
4085 # *************************************************************
4087 # Make $offset the next higher multiple of $nav_resolution
4088 $offset = ($nav_offset + $nav_resolution - 1) / $nav_resolution;
4089 $offset = sprintf("%d", $offset ) * $nav_resolution;
4091 # Create image map for overview image
4092 for ($index = 1; $index <= $_[4]; $index += $nav_resolution)
4094 # Enforce nav_offset
4095 if ($index < $offset + 1)
4097 write_overview_line($_[0], $_[2], $index, 1);
4099 else
4101 write_overview_line($_[0], $_[2], $index, $index - $offset);
4105 # *************************************************************
4107 write_html($_[0], <<END_OF_HTML)
4108 </map>
4110 <center>
4111 <a href="$_[2].gcov.$html_ext#top" target="source">Top</a><br><br>
4112 <img src="$_[2].gcov.png" width=$overview_width height=$max_line alt="Overview" border=0 usemap="#overview">
4113 </center>
4114 </body>
4115 </html>
4116 END_OF_HTML
4119 # *************************************************************
4123 # format_rate(found, hit)
4125 # Return formatted percent string for coverage rate.
4128 sub format_rate($$)
4130 return $_[0] == 0 ? "-" : sprintf("%.1f", $_[1] * 100 / $_[0])." %";
4134 sub max($$)
4136 my ($a, $b) = @_;
4138 return $a if ($a > $b);
4139 return $b;
4144 # write_header(filehandle, type, trunc_file_name, rel_file_name, lines_found,
4145 # lines_hit, funcs_found, funcs_hit, sort_type)
4147 # Write a complete standard page header. TYPE may be (0, 1, 2, 3, 4)
4148 # corresponding to (directory view header, file view header, source view
4149 # header, test case description header, function view header)
4152 sub write_header(*$$$$$$$$$$)
4154 local *HTML_HANDLE = $_[0];
4155 my $type = $_[1];
4156 my $trunc_name = $_[2];
4157 my $rel_filename = $_[3];
4158 my $lines_found = $_[4];
4159 my $lines_hit = $_[5];
4160 my $fn_found = $_[6];
4161 my $fn_hit = $_[7];
4162 my $br_found = $_[8];
4163 my $br_hit = $_[9];
4164 my $sort_type = $_[10];
4165 my $base_dir;
4166 my $view;
4167 my $test;
4168 my $base_name;
4169 my $style;
4170 my $rate;
4171 my @row_left;
4172 my @row_right;
4173 my $num_rows;
4174 my $i;
4176 $base_name = basename($rel_filename);
4178 # Prepare text for "current view" field
4179 if ($type == $HDR_DIR)
4181 # Main overview
4182 $base_dir = "";
4183 $view = $overview_title;
4185 elsif ($type == $HDR_FILE)
4187 # Directory overview
4188 $base_dir = get_relative_base_path($rel_filename);
4189 $view = "<a href=\"$base_dir"."index.$html_ext\">".
4190 "$overview_title</a> - $trunc_name";
4192 elsif ($type == $HDR_SOURCE || $type == $HDR_FUNC)
4194 # File view
4195 my $dir_name = dirname($rel_filename);
4197 $base_dir = get_relative_base_path($dir_name);
4198 if ($frames)
4200 # Need to break frameset when clicking any of these
4201 # links
4202 $view = "<a href=\"$base_dir"."index.$html_ext\" ".
4203 "target=\"_parent\">$overview_title</a> - ".
4204 "<a href=\"index.$html_ext\" target=\"_parent\">".
4205 "$dir_name</a> - $base_name";
4207 else
4209 $view = "<a href=\"$base_dir"."index.$html_ext\">".
4210 "$overview_title</a> - ".
4211 "<a href=\"index.$html_ext\">".
4212 "$dir_name</a> - $base_name";
4215 # Add function suffix
4216 if ($func_coverage) {
4217 $view .= "<span style=\"font-size: 80%;\">";
4218 if ($type == $HDR_SOURCE) {
4219 $view .= " (source / <a href=\"$base_name.func.$html_ext\">functions</a>)";
4220 } elsif ($type == $HDR_FUNC) {
4221 $view .= " (<a href=\"$base_name.gcov.$html_ext\">source</a> / functions)";
4223 $view .= "</span>";
4226 elsif ($type == $HDR_TESTDESC)
4228 # Test description header
4229 $base_dir = "";
4230 $view = "<a href=\"$base_dir"."index.$html_ext\">".
4231 "$overview_title</a> - test case descriptions";
4234 # Prepare text for "test" field
4235 $test = escape_html($test_title);
4237 # Append link to test description page if available
4238 if (%test_description && ($type != $HDR_TESTDESC))
4240 if ($frames && ($type == $HDR_SOURCE || $type == $HDR_FUNC))
4242 # Need to break frameset when clicking this link
4243 $test .= " ( <span style=\"font-size:80%;\">".
4244 "<a href=\"$base_dir".
4245 "descriptions.$html_ext\" target=\"_parent\">".
4246 "view descriptions</a></span> )";
4248 else
4250 $test .= " ( <span style=\"font-size:80%;\">".
4251 "<a href=\"$base_dir".
4252 "descriptions.$html_ext\">".
4253 "view descriptions</a></span> )";
4257 # Write header
4258 write_header_prolog(*HTML_HANDLE, $base_dir);
4260 # Left row
4261 push(@row_left, [[ "10%", "headerItem", "Current view:" ],
4262 [ "35%", "headerValue", $view ]]);
4263 push(@row_left, [[undef, "headerItem", "Test:"],
4264 [undef, "headerValue", $test]]);
4265 push(@row_left, [[undef, "headerItem", "Date:"],
4266 [undef, "headerValue", $date]]);
4268 # Right row
4269 if ($legend && ($type == $HDR_SOURCE || $type == $HDR_FUNC)) {
4270 my $text = <<END_OF_HTML;
4271 Lines:
4272 <span class="coverLegendCov">hit</span>
4273 <span class="coverLegendNoCov">not hit</span>
4274 END_OF_HTML
4275 if ($br_coverage) {
4276 $text .= <<END_OF_HTML;
4277 | Branches:
4278 <span class="coverLegendCov">+</span> taken
4279 <span class="coverLegendNoCov">-</span> not taken
4280 <span class="coverLegendNoCov">#</span> not executed
4281 END_OF_HTML
4283 push(@row_left, [[undef, "headerItem", "Legend:"],
4284 [undef, "headerValueLeg", $text]]);
4285 } elsif ($legend && ($type != $HDR_TESTDESC)) {
4286 my $text = <<END_OF_HTML;
4287 Rating:
4288 <span class="coverLegendCovLo" title="Coverage rates below $med_limit % are classified as low">low: &lt; $med_limit %</span>
4289 <span class="coverLegendCovMed" title="Coverage rates between $med_limit % and $hi_limit % are classified as medium">medium: &gt;= $med_limit %</span>
4290 <span class="coverLegendCovHi" title="Coverage rates of $hi_limit % and more are classified as high">high: &gt;= $hi_limit %</span>
4291 END_OF_HTML
4292 push(@row_left, [[undef, "headerItem", "Legend:"],
4293 [undef, "headerValueLeg", $text]]);
4295 if ($type == $HDR_TESTDESC) {
4296 push(@row_right, [[ "55%" ]]);
4297 } else {
4298 push(@row_right, [["15%", undef, undef ],
4299 ["10%", "headerCovTableHead", "Hit" ],
4300 ["10%", "headerCovTableHead", "Total" ],
4301 ["15%", "headerCovTableHead", "Coverage"]]);
4303 # Line coverage
4304 $style = $rate_name[classify_rate($lines_found, $lines_hit,
4305 $med_limit, $hi_limit)];
4306 $rate = format_rate($lines_found, $lines_hit);
4307 push(@row_right, [[undef, "headerItem", "Lines:"],
4308 [undef, "headerCovTableEntry", $lines_hit],
4309 [undef, "headerCovTableEntry", $lines_found],
4310 [undef, "headerCovTableEntry$style", $rate]])
4311 if ($type != $HDR_TESTDESC);
4312 # Function coverage
4313 if ($func_coverage) {
4314 $style = $rate_name[classify_rate($fn_found, $fn_hit,
4315 $fn_med_limit, $fn_hi_limit)];
4316 $rate = format_rate($fn_found, $fn_hit);
4317 push(@row_right, [[undef, "headerItem", "Functions:"],
4318 [undef, "headerCovTableEntry", $fn_hit],
4319 [undef, "headerCovTableEntry", $fn_found],
4320 [undef, "headerCovTableEntry$style", $rate]])
4321 if ($type != $HDR_TESTDESC);
4323 # Branch coverage
4324 if ($br_coverage) {
4325 $style = $rate_name[classify_rate($br_found, $br_hit,
4326 $br_med_limit, $br_hi_limit)];
4327 $rate = format_rate($br_found, $br_hit);
4328 push(@row_right, [[undef, "headerItem", "Branches:"],
4329 [undef, "headerCovTableEntry", $br_hit],
4330 [undef, "headerCovTableEntry", $br_found],
4331 [undef, "headerCovTableEntry$style", $rate]])
4332 if ($type != $HDR_TESTDESC);
4335 # Print rows
4336 $num_rows = max(scalar(@row_left), scalar(@row_right));
4337 for ($i = 0; $i < $num_rows; $i++) {
4338 my $left = $row_left[$i];
4339 my $right = $row_right[$i];
4341 if (!defined($left)) {
4342 $left = [[undef, undef, undef], [undef, undef, undef]];
4344 if (!defined($right)) {
4345 $right = [];
4347 write_header_line(*HTML_HANDLE, @{$left},
4348 [ $i == 0 ? "5%" : undef, undef, undef],
4349 @{$right});
4352 # Fourth line
4353 write_header_epilog(*HTML_HANDLE, $base_dir);
4358 # get_sorted_keys(hash_ref, sort_type)
4361 sub get_sorted_keys($$)
4363 my ($hash, $type) = @_;
4365 if ($type == $SORT_FILE) {
4366 # Sort by name
4367 return sort(keys(%{$hash}));
4368 } elsif ($type == $SORT_LINE) {
4369 # Sort by line coverage
4370 return sort({$hash->{$a}[7] <=> $hash->{$b}[7]} keys(%{$hash}));
4371 } elsif ($type == $SORT_FUNC) {
4372 # Sort by function coverage;
4373 return sort({$hash->{$a}[8] <=> $hash->{$b}[8]} keys(%{$hash}));
4374 } elsif ($type == $SORT_BRANCH) {
4375 # Sort by br coverage;
4376 return sort({$hash->{$a}[9] <=> $hash->{$b}[9]} keys(%{$hash}));
4380 sub get_sort_code($$$)
4382 my ($link, $alt, $base) = @_;
4383 my $png;
4384 my $link_start;
4385 my $link_end;
4387 if (!defined($link)) {
4388 $png = "glass.png";
4389 $link_start = "";
4390 $link_end = "";
4391 } else {
4392 $png = "updown.png";
4393 $link_start = '<a href="'.$link.'">';
4394 $link_end = "</a>";
4397 return ' <span class="tableHeadSort">'.$link_start.
4398 '<img src="'.$base.$png.'" width=10 height=14 '.
4399 'alt="'.$alt.'" title="'.$alt.'" border=0>'.$link_end.'</span>';
4402 sub get_file_code($$$$)
4404 my ($type, $text, $sort_button, $base) = @_;
4405 my $result = $text;
4406 my $link;
4408 if ($sort_button) {
4409 if ($type == $HEAD_NO_DETAIL) {
4410 $link = "index.$html_ext";
4411 } else {
4412 $link = "index-detail.$html_ext";
4415 $result .= get_sort_code($link, "Sort by name", $base);
4417 return $result;
4420 sub get_line_code($$$$$)
4422 my ($type, $sort_type, $text, $sort_button, $base) = @_;
4423 my $result = $text;
4424 my $sort_link;
4426 if ($type == $HEAD_NO_DETAIL) {
4427 # Just text
4428 if ($sort_button) {
4429 $sort_link = "index-sort-l.$html_ext";
4431 } elsif ($type == $HEAD_DETAIL_HIDDEN) {
4432 # Text + link to detail view
4433 $result .= ' ( <a class="detail" href="index-detail'.
4434 $fileview_sortname[$sort_type].'.'.$html_ext.
4435 '">show details</a> )';
4436 if ($sort_button) {
4437 $sort_link = "index-sort-l.$html_ext";
4439 } else {
4440 # Text + link to standard view
4441 $result .= ' ( <a class="detail" href="index'.
4442 $fileview_sortname[$sort_type].'.'.$html_ext.
4443 '">hide details</a> )';
4444 if ($sort_button) {
4445 $sort_link = "index-detail-sort-l.$html_ext";
4448 # Add sort button
4449 $result .= get_sort_code($sort_link, "Sort by line coverage", $base);
4451 return $result;
4454 sub get_func_code($$$$)
4456 my ($type, $text, $sort_button, $base) = @_;
4457 my $result = $text;
4458 my $link;
4460 if ($sort_button) {
4461 if ($type == $HEAD_NO_DETAIL) {
4462 $link = "index-sort-f.$html_ext";
4463 } else {
4464 $link = "index-detail-sort-f.$html_ext";
4467 $result .= get_sort_code($link, "Sort by function coverage", $base);
4468 return $result;
4471 sub get_br_code($$$$)
4473 my ($type, $text, $sort_button, $base) = @_;
4474 my $result = $text;
4475 my $link;
4477 if ($sort_button) {
4478 if ($type == $HEAD_NO_DETAIL) {
4479 $link = "index-sort-b.$html_ext";
4480 } else {
4481 $link = "index-detail-sort-b.$html_ext";
4484 $result .= get_sort_code($link, "Sort by branch coverage", $base);
4485 return $result;
4489 # write_file_table(filehandle, base_dir, overview, testhash, testfnchash,
4490 # testbrhash, fileview, sort_type)
4492 # Write a complete file table. OVERVIEW is a reference to a hash containing
4493 # the following mapping:
4495 # filename -> "lines_found,lines_hit,funcs_found,funcs_hit,page_link,
4496 # func_link"
4498 # TESTHASH is a reference to the following hash:
4500 # filename -> \%testdata
4501 # %testdata: name of test affecting this file -> \%testcount
4502 # %testcount: line number -> execution count for a single test
4504 # Heading of first column is "Filename" if FILEVIEW is true, "Directory name"
4505 # otherwise.
4508 sub write_file_table(*$$$$$$$)
4510 local *HTML_HANDLE = $_[0];
4511 my $base_dir = $_[1];
4512 my $overview = $_[2];
4513 my $testhash = $_[3];
4514 my $testfnchash = $_[4];
4515 my $testbrhash = $_[5];
4516 my $fileview = $_[6];
4517 my $sort_type = $_[7];
4518 my $filename;
4519 my $bar_graph;
4520 my $hit;
4521 my $found;
4522 my $fn_found;
4523 my $fn_hit;
4524 my $br_found;
4525 my $br_hit;
4526 my $page_link;
4527 my $testname;
4528 my $testdata;
4529 my $testfncdata;
4530 my $testbrdata;
4531 my %affecting_tests;
4532 my $line_code = "";
4533 my $func_code;
4534 my $br_code;
4535 my $file_code;
4536 my @head_columns;
4538 # Determine HTML code for column headings
4539 if (($base_dir ne "") && $show_details)
4541 my $detailed = keys(%{$testhash});
4543 $file_code = get_file_code($detailed ? $HEAD_DETAIL_HIDDEN :
4544 $HEAD_NO_DETAIL,
4545 $fileview ? "Filename" : "Directory",
4546 $sort && $sort_type != $SORT_FILE,
4547 $base_dir);
4548 $line_code = get_line_code($detailed ? $HEAD_DETAIL_SHOWN :
4549 $HEAD_DETAIL_HIDDEN,
4550 $sort_type,
4551 "Line Coverage",
4552 $sort && $sort_type != $SORT_LINE,
4553 $base_dir);
4554 $func_code = get_func_code($detailed ? $HEAD_DETAIL_HIDDEN :
4555 $HEAD_NO_DETAIL,
4556 "Functions",
4557 $sort && $sort_type != $SORT_FUNC,
4558 $base_dir);
4559 $br_code = get_br_code($detailed ? $HEAD_DETAIL_HIDDEN :
4560 $HEAD_NO_DETAIL,
4561 "Branches",
4562 $sort && $sort_type != $SORT_BRANCH,
4563 $base_dir);
4564 } else {
4565 $file_code = get_file_code($HEAD_NO_DETAIL,
4566 $fileview ? "Filename" : "Directory",
4567 $sort && $sort_type != $SORT_FILE,
4568 $base_dir);
4569 $line_code = get_line_code($HEAD_NO_DETAIL, $sort_type, "Line Coverage",
4570 $sort && $sort_type != $SORT_LINE,
4571 $base_dir);
4572 $func_code = get_func_code($HEAD_NO_DETAIL, "Functions",
4573 $sort && $sort_type != $SORT_FUNC,
4574 $base_dir);
4575 $br_code = get_br_code($HEAD_NO_DETAIL, "Branches",
4576 $sort && $sort_type != $SORT_BRANCH,
4577 $base_dir);
4579 push(@head_columns, [ $line_code, 3 ]);
4580 push(@head_columns, [ $func_code, 2]) if ($func_coverage);
4581 push(@head_columns, [ $br_code, 2]) if ($br_coverage);
4583 write_file_table_prolog(*HTML_HANDLE, $file_code, @head_columns);
4585 foreach $filename (get_sorted_keys($overview, $sort_type))
4587 my @columns;
4588 ($found, $hit, $fn_found, $fn_hit, $br_found, $br_hit,
4589 $page_link) = @{$overview->{$filename}};
4591 # Line coverage
4592 push(@columns, [$found, $hit, $med_limit, $hi_limit, 1]);
4593 # Function coverage
4594 if ($func_coverage) {
4595 push(@columns, [$fn_found, $fn_hit, $fn_med_limit,
4596 $fn_hi_limit, 0]);
4598 # Branch coverage
4599 if ($br_coverage) {
4600 push(@columns, [$br_found, $br_hit, $br_med_limit,
4601 $br_hi_limit, 0]);
4603 write_file_table_entry(*HTML_HANDLE, $base_dir, $filename,
4604 $page_link, @columns);
4606 $testdata = $testhash->{$filename};
4607 $testfncdata = $testfnchash->{$filename};
4608 $testbrdata = $testbrhash->{$filename};
4610 # Check whether we should write test specific coverage
4611 # as well
4612 if (!($show_details && $testdata)) { next; }
4614 # Filter out those tests that actually affect this file
4615 %affecting_tests = %{ get_affecting_tests($testdata,
4616 $testfncdata, $testbrdata) };
4618 # Does any of the tests affect this file at all?
4619 if (!%affecting_tests) { next; }
4621 foreach $testname (keys(%affecting_tests))
4623 my @results;
4624 ($found, $hit, $fn_found, $fn_hit, $br_found, $br_hit) =
4625 split(",", $affecting_tests{$testname});
4627 # Insert link to description of available
4628 if ($test_description{$testname})
4630 $testname = "<a href=\"$base_dir".
4631 "descriptions.$html_ext#$testname\">".
4632 "$testname</a>";
4635 push(@results, [$found, $hit]);
4636 push(@results, [$fn_found, $fn_hit]) if ($func_coverage);
4637 push(@results, [$br_found, $br_hit]) if ($br_coverage);
4638 write_file_table_detail_entry(*HTML_HANDLE, $testname,
4639 @results);
4643 write_file_table_epilog(*HTML_HANDLE);
4648 # get_found_and_hit(hash)
4650 # Return the count for entries (found) and entries with an execution count
4651 # greater than zero (hit) in a hash (linenumber -> execution count) as
4652 # a list (found, hit)
4655 sub get_found_and_hit($)
4657 my %hash = %{$_[0]};
4658 my $found = 0;
4659 my $hit = 0;
4661 # Calculate sum
4662 $found = 0;
4663 $hit = 0;
4665 foreach (keys(%hash))
4667 $found++;
4668 if ($hash{$_}>0) { $hit++; }
4671 return ($found, $hit);
4676 # get_func_found_and_hit(sumfnccount)
4678 # Return (f_found, f_hit) for sumfnccount
4681 sub get_func_found_and_hit($)
4683 my ($sumfnccount) = @_;
4684 my $function;
4685 my $fn_found;
4686 my $fn_hit;
4688 $fn_found = scalar(keys(%{$sumfnccount}));
4689 $fn_hit = 0;
4690 foreach $function (keys(%{$sumfnccount})) {
4691 if ($sumfnccount->{$function} > 0) {
4692 $fn_hit++;
4695 return ($fn_found, $fn_hit);
4700 # br_taken_to_num(taken)
4702 # Convert a branch taken value .info format to number format.
4705 sub br_taken_to_num($)
4707 my ($taken) = @_;
4709 return 0 if ($taken eq '-');
4710 return $taken + 1;
4715 # br_num_to_taken(taken)
4717 # Convert a branch taken value in number format to .info format.
4720 sub br_num_to_taken($)
4722 my ($taken) = @_;
4724 return '-' if ($taken == 0);
4725 return $taken - 1;
4730 # br_taken_add(taken1, taken2)
4732 # Return the result of taken1 + taken2 for 'branch taken' values.
4735 sub br_taken_add($$)
4737 my ($t1, $t2) = @_;
4739 return $t1 if (!defined($t2));
4740 return $t2 if (!defined($t1));
4741 return $t1 if ($t2 eq '-');
4742 return $t2 if ($t1 eq '-');
4743 return $t1 + $t2;
4748 # br_taken_sub(taken1, taken2)
4750 # Return the result of taken1 - taken2 for 'branch taken' values. Return 0
4751 # if the result would become negative.
4754 sub br_taken_sub($$)
4756 my ($t1, $t2) = @_;
4758 return $t1 if (!defined($t2));
4759 return undef if (!defined($t1));
4760 return $t1 if ($t1 eq '-');
4761 return $t1 if ($t2 eq '-');
4762 return 0 if $t2 > $t1;
4763 return $t1 - $t2;
4768 # br_ivec_len(vector)
4770 # Return the number of entries in the branch coverage vector.
4773 sub br_ivec_len($)
4775 my ($vec) = @_;
4777 return 0 if (!defined($vec));
4778 return (length($vec) * 8 / $BR_VEC_WIDTH) / $BR_VEC_ENTRIES;
4783 # br_ivec_get(vector, number)
4785 # Return an entry from the branch coverage vector.
4788 sub br_ivec_get($$)
4790 my ($vec, $num) = @_;
4791 my $block;
4792 my $branch;
4793 my $taken;
4794 my $offset = $num * $BR_VEC_ENTRIES;
4796 # Retrieve data from vector
4797 $block = vec($vec, $offset + $BR_BLOCK, $BR_VEC_WIDTH);
4798 $branch = vec($vec, $offset + $BR_BRANCH, $BR_VEC_WIDTH);
4799 $taken = vec($vec, $offset + $BR_TAKEN, $BR_VEC_WIDTH);
4801 # Decode taken value from an integer
4802 $taken = br_num_to_taken($taken);
4804 return ($block, $branch, $taken);
4809 # br_ivec_push(vector, block, branch, taken)
4811 # Add an entry to the branch coverage vector. If an entry with the same
4812 # branch ID already exists, add the corresponding taken values.
4815 sub br_ivec_push($$$$)
4817 my ($vec, $block, $branch, $taken) = @_;
4818 my $offset;
4819 my $num = br_ivec_len($vec);
4820 my $i;
4822 $vec = "" if (!defined($vec));
4824 # Check if branch already exists in vector
4825 for ($i = 0; $i < $num; $i++) {
4826 my ($v_block, $v_branch, $v_taken) = br_ivec_get($vec, $i);
4828 next if ($v_block != $block || $v_branch != $branch);
4830 # Add taken counts
4831 $taken = br_taken_add($taken, $v_taken);
4832 last;
4835 $offset = $i * $BR_VEC_ENTRIES;
4836 $taken = br_taken_to_num($taken);
4838 # Add to vector
4839 vec($vec, $offset + $BR_BLOCK, $BR_VEC_WIDTH) = $block;
4840 vec($vec, $offset + $BR_BRANCH, $BR_VEC_WIDTH) = $branch;
4841 vec($vec, $offset + $BR_TAKEN, $BR_VEC_WIDTH) = $taken;
4843 return $vec;
4848 # get_br_found_and_hit(sumbrcount)
4850 # Return (br_found, br_hit) for sumbrcount
4853 sub get_br_found_and_hit($)
4855 my ($sumbrcount) = @_;
4856 my $line;
4857 my $br_found = 0;
4858 my $br_hit = 0;
4860 foreach $line (keys(%{$sumbrcount})) {
4861 my $brdata = $sumbrcount->{$line};
4862 my $i;
4863 my $num = br_ivec_len($brdata);
4865 for ($i = 0; $i < $num; $i++) {
4866 my $taken;
4868 (undef, undef, $taken) = br_ivec_get($brdata, $i);
4870 $br_found++;
4871 $br_hit++ if ($taken ne "-" && $taken > 0);
4875 return ($br_found, $br_hit);
4880 # get_affecting_tests(testdata, testfncdata, testbrdata)
4882 # HASHREF contains a mapping filename -> (linenumber -> exec count). Return
4883 # a hash containing mapping filename -> "lines found, lines hit" for each
4884 # filename which has a nonzero hit count.
4887 sub get_affecting_tests($$$)
4889 my ($testdata, $testfncdata, $testbrdata) = @_;
4890 my $testname;
4891 my $testcount;
4892 my $testfnccount;
4893 my $testbrcount;
4894 my %result;
4895 my $found;
4896 my $hit;
4897 my $fn_found;
4898 my $fn_hit;
4899 my $br_found;
4900 my $br_hit;
4902 foreach $testname (keys(%{$testdata}))
4904 # Get (line number -> count) hash for this test case
4905 $testcount = $testdata->{$testname};
4906 $testfnccount = $testfncdata->{$testname};
4907 $testbrcount = $testbrdata->{$testname};
4909 # Calculate sum
4910 ($found, $hit) = get_found_and_hit($testcount);
4911 ($fn_found, $fn_hit) = get_func_found_and_hit($testfnccount);
4912 ($br_found, $br_hit) = get_br_found_and_hit($testbrcount);
4914 if ($hit>0)
4916 $result{$testname} = "$found,$hit,$fn_found,$fn_hit,".
4917 "$br_found,$br_hit";
4921 return(\%result);
4925 sub get_hash_reverse($)
4927 my ($hash) = @_;
4928 my %result;
4930 foreach (keys(%{$hash})) {
4931 $result{$hash->{$_}} = $_;
4934 return \%result;
4938 # write_source(filehandle, source_filename, count_data, checksum_data,
4939 # converted_data, func_data, sumbrcount)
4941 # Write an HTML view of a source code file. Returns a list containing
4942 # data as needed by gen_png().
4944 # Die on error.
4947 sub write_source($$$$$$$)
4949 local *HTML_HANDLE = $_[0];
4950 local *SOURCE_HANDLE;
4951 my $source_filename = $_[1];
4952 my %count_data;
4953 my $line_number;
4954 my @result;
4955 my $checkdata = $_[3];
4956 my $converted = $_[4];
4957 my $funcdata = $_[5];
4958 my $sumbrcount = $_[6];
4959 my $datafunc = get_hash_reverse($funcdata);
4960 my $add_anchor;
4962 if ($_[2])
4964 %count_data = %{$_[2]};
4967 open(SOURCE_HANDLE, "<".$source_filename)
4968 or die("ERROR: cannot open $source_filename for reading!\n");
4970 write_source_prolog(*HTML_HANDLE);
4972 for ($line_number = 1; <SOURCE_HANDLE> ; $line_number++)
4974 chomp($_);
4976 # Also remove CR from line-end
4977 s/\015$//;
4979 # Source code matches coverage data?
4980 if (defined($checkdata->{$line_number}) &&
4981 ($checkdata->{$line_number} ne md5_base64($_)))
4983 die("ERROR: checksum mismatch at $source_filename:".
4984 "$line_number\n");
4987 $add_anchor = 0;
4988 if ($frames) {
4989 if (($line_number - 1) % $nav_resolution == 0) {
4990 $add_anchor = 1;
4993 if ($func_coverage) {
4994 if ($line_number == 1) {
4995 $add_anchor = 1;
4996 } elsif (defined($datafunc->{$line_number +
4997 $func_offset})) {
4998 $add_anchor = 1;
5001 push (@result,
5002 write_source_line(HTML_HANDLE, $line_number,
5003 $_, $count_data{$line_number},
5004 $converted->{$line_number},
5005 $sumbrcount->{$line_number}, $add_anchor));
5008 close(SOURCE_HANDLE);
5009 write_source_epilog(*HTML_HANDLE);
5010 return(@result);
5014 sub funcview_get_func_code($$$)
5016 my ($name, $base, $type) = @_;
5017 my $result;
5018 my $link;
5020 if ($sort && $type == 1) {
5021 $link = "$name.func.$html_ext";
5023 $result = "Function Name";
5024 $result .= get_sort_code($link, "Sort by function name", $base);
5026 return $result;
5029 sub funcview_get_count_code($$$)
5031 my ($name, $base, $type) = @_;
5032 my $result;
5033 my $link;
5035 if ($sort && $type == 0) {
5036 $link = "$name.func-sort-c.$html_ext";
5038 $result = "Hit count";
5039 $result .= get_sort_code($link, "Sort by hit count", $base);
5041 return $result;
5045 # funcview_get_sorted(funcdata, sumfncdata, sort_type)
5047 # Depending on the value of sort_type, return a list of functions sorted
5048 # by name (type 0) or by the associated call count (type 1).
5051 sub funcview_get_sorted($$$)
5053 my ($funcdata, $sumfncdata, $type) = @_;
5055 if ($type == 0) {
5056 return sort(keys(%{$funcdata}));
5058 return sort({$sumfncdata->{$b} <=> $sumfncdata->{$a}}
5059 keys(%{$sumfncdata}));
5063 # write_function_table(filehandle, source_file, sumcount, funcdata,
5064 # sumfnccount, testfncdata, sumbrcount, testbrdata,
5065 # base_name, base_dir, sort_type)
5067 # Write an HTML table listing all functions in a source file, including
5068 # also function call counts and line coverages inside of each function.
5070 # Die on error.
5073 sub write_function_table(*$$$$$$$$$$)
5075 local *HTML_HANDLE = $_[0];
5076 my $source = $_[1];
5077 my $sumcount = $_[2];
5078 my $funcdata = $_[3];
5079 my $sumfncdata = $_[4];
5080 my $testfncdata = $_[5];
5081 my $sumbrcount = $_[6];
5082 my $testbrdata = $_[7];
5083 my $name = $_[8];
5084 my $base = $_[9];
5085 my $type = $_[10];
5086 my $func;
5087 my $func_code;
5088 my $count_code;
5090 # Get HTML code for headings
5091 $func_code = funcview_get_func_code($name, $base, $type);
5092 $count_code = funcview_get_count_code($name, $base, $type);
5093 write_html(*HTML_HANDLE, <<END_OF_HTML)
5094 <center>
5095 <table width="60%" cellpadding=1 cellspacing=1 border=0>
5096 <tr><td><br></td></tr>
5097 <tr>
5098 <td width="80%" class="tableHead">$func_code</td>
5099 <td width="20%" class="tableHead">$count_code</td>
5100 </tr>
5101 END_OF_HTML
5104 # Get a sorted table
5105 foreach $func (funcview_get_sorted($funcdata, $sumfncdata, $type)) {
5106 if (!defined($funcdata->{$func}))
5108 next;
5111 my $startline = $funcdata->{$func} - $func_offset;
5112 my $name = $func;
5113 my $count = $sumfncdata->{$name};
5114 my $countstyle;
5116 # Demangle C++ function names if requested
5117 if ($demangle_cpp) {
5118 $name = `c++filt "$name"`;
5119 chomp($name);
5121 # Escape any remaining special characters
5122 $name = escape_html($name);
5123 if ($startline < 1) {
5124 $startline = 1;
5126 if ($count == 0) {
5127 $countstyle = "coverFnLo";
5128 } else {
5129 $countstyle = "coverFnHi";
5132 write_html(*HTML_HANDLE, <<END_OF_HTML)
5133 <tr>
5134 <td class="coverFn"><a href="$source#$startline">$name</a></td>
5135 <td class="$countstyle">$count</td>
5136 </tr>
5137 END_OF_HTML
5140 write_html(*HTML_HANDLE, <<END_OF_HTML)
5141 </table>
5142 <br>
5143 </center>
5144 END_OF_HTML
5150 # info(printf_parameter)
5152 # Use printf to write PRINTF_PARAMETER to stdout only when the $quiet flag
5153 # is not set.
5156 sub info(@)
5158 if (!$quiet)
5160 # Print info string
5161 printf(@_);
5167 # subtract_counts(data_ref, base_ref)
5170 sub subtract_counts($$)
5172 my %data = %{$_[0]};
5173 my %base = %{$_[1]};
5174 my $line;
5175 my $data_count;
5176 my $base_count;
5177 my $hit = 0;
5178 my $found = 0;
5180 foreach $line (keys(%data))
5182 $found++;
5183 $data_count = $data{$line};
5184 $base_count = $base{$line};
5186 if (defined($base_count))
5188 $data_count -= $base_count;
5190 # Make sure we don't get negative numbers
5191 if ($data_count<0) { $data_count = 0; }
5194 $data{$line} = $data_count;
5195 if ($data_count > 0) { $hit++; }
5198 return (\%data, $found, $hit);
5203 # subtract_fnccounts(data, base)
5205 # Subtract function call counts found in base from those in data.
5206 # Return (data, f_found, f_hit).
5209 sub subtract_fnccounts($$)
5211 my %data;
5212 my %base;
5213 my $func;
5214 my $data_count;
5215 my $base_count;
5216 my $fn_hit = 0;
5217 my $fn_found = 0;
5219 %data = %{$_[0]} if (defined($_[0]));
5220 %base = %{$_[1]} if (defined($_[1]));
5221 foreach $func (keys(%data)) {
5222 $fn_found++;
5223 $data_count = $data{$func};
5224 $base_count = $base{$func};
5226 if (defined($base_count)) {
5227 $data_count -= $base_count;
5229 # Make sure we don't get negative numbers
5230 if ($data_count < 0) {
5231 $data_count = 0;
5235 $data{$func} = $data_count;
5236 if ($data_count > 0) {
5237 $fn_hit++;
5241 return (\%data, $fn_found, $fn_hit);
5246 # apply_baseline(data_ref, baseline_ref)
5248 # Subtract the execution counts found in the baseline hash referenced by
5249 # BASELINE_REF from actual data in DATA_REF.
5252 sub apply_baseline($$)
5254 my %data_hash = %{$_[0]};
5255 my %base_hash = %{$_[1]};
5256 my $filename;
5257 my $testname;
5258 my $data;
5259 my $data_testdata;
5260 my $data_funcdata;
5261 my $data_checkdata;
5262 my $data_testfncdata;
5263 my $data_testbrdata;
5264 my $data_count;
5265 my $data_testfnccount;
5266 my $data_testbrcount;
5267 my $base;
5268 my $base_checkdata;
5269 my $base_sumfnccount;
5270 my $base_sumbrcount;
5271 my $base_count;
5272 my $sumcount;
5273 my $sumfnccount;
5274 my $sumbrcount;
5275 my $found;
5276 my $hit;
5277 my $fn_found;
5278 my $fn_hit;
5279 my $br_found;
5280 my $br_hit;
5282 foreach $filename (keys(%data_hash))
5284 # Get data set for data and baseline
5285 $data = $data_hash{$filename};
5286 $base = $base_hash{$filename};
5288 # Skip data entries for which no base entry exists
5289 if (!defined($base))
5291 next;
5294 # Get set entries for data and baseline
5295 ($data_testdata, undef, $data_funcdata, $data_checkdata,
5296 $data_testfncdata, undef, $data_testbrdata) =
5297 get_info_entry($data);
5298 (undef, $base_count, undef, $base_checkdata, undef,
5299 $base_sumfnccount, undef, $base_sumbrcount) =
5300 get_info_entry($base);
5302 # Check for compatible checksums
5303 merge_checksums($data_checkdata, $base_checkdata, $filename);
5305 # sumcount has to be calculated anew
5306 $sumcount = {};
5307 $sumfnccount = {};
5308 $sumbrcount = {};
5310 # For each test case, subtract test specific counts
5311 foreach $testname (keys(%{$data_testdata}))
5313 # Get counts of both data and baseline
5314 $data_count = $data_testdata->{$testname};
5315 $data_testfnccount = $data_testfncdata->{$testname};
5316 $data_testbrcount = $data_testbrdata->{$testname};
5318 ($data_count, undef, $hit) =
5319 subtract_counts($data_count, $base_count);
5320 ($data_testfnccount) =
5321 subtract_fnccounts($data_testfnccount,
5322 $base_sumfnccount);
5323 ($data_testbrcount) =
5324 combine_brcount($data_testbrcount,
5325 $base_sumbrcount, $BR_SUB);
5328 # Check whether this test case did hit any line at all
5329 if ($hit > 0)
5331 # Write back resulting hash
5332 $data_testdata->{$testname} = $data_count;
5333 $data_testfncdata->{$testname} =
5334 $data_testfnccount;
5335 $data_testbrdata->{$testname} =
5336 $data_testbrcount;
5338 else
5340 # Delete test case which did not impact this
5341 # file
5342 delete($data_testdata->{$testname});
5343 delete($data_testfncdata->{$testname});
5344 delete($data_testbrdata->{$testname});
5347 # Add counts to sum of counts
5348 ($sumcount, $found, $hit) =
5349 add_counts($sumcount, $data_count);
5350 ($sumfnccount, $fn_found, $fn_hit) =
5351 add_fnccount($sumfnccount, $data_testfnccount);
5352 ($sumbrcount, $br_found, $br_hit) =
5353 combine_brcount($sumbrcount, $data_testbrcount,
5354 $BR_ADD);
5357 # Write back resulting entry
5358 set_info_entry($data, $data_testdata, $sumcount, $data_funcdata,
5359 $data_checkdata, $data_testfncdata, $sumfnccount,
5360 $data_testbrdata, $sumbrcount, $found, $hit,
5361 $fn_found, $fn_hit, $br_found, $br_hit);
5363 $data_hash{$filename} = $data;
5366 return (\%data_hash);
5371 # remove_unused_descriptions()
5373 # Removes all test descriptions from the global hash %test_description which
5374 # are not present in %info_data.
5377 sub remove_unused_descriptions()
5379 my $filename; # The current filename
5380 my %test_list; # Hash containing found test names
5381 my $test_data; # Reference to hash test_name -> count_data
5382 my $before; # Initial number of descriptions
5383 my $after; # Remaining number of descriptions
5385 $before = scalar(keys(%test_description));
5387 foreach $filename (keys(%info_data))
5389 ($test_data) = get_info_entry($info_data{$filename});
5390 foreach (keys(%{$test_data}))
5392 $test_list{$_} = "";
5396 # Remove descriptions for tests which are not in our list
5397 foreach (keys(%test_description))
5399 if (!defined($test_list{$_}))
5401 delete($test_description{$_});
5405 $after = scalar(keys(%test_description));
5406 if ($after < $before)
5408 info("Removed ".($before - $after).
5409 " unused descriptions, $after remaining.\n");
5415 # apply_prefix(filename, prefix)
5417 # If FILENAME begins with PREFIX, remove PREFIX from FILENAME and return
5418 # resulting string, otherwise return FILENAME.
5421 sub apply_prefix($$)
5423 my $filename = $_[0];
5424 my $prefix = $_[1];
5426 if (defined($prefix) && ($prefix ne ""))
5428 if ($filename =~ /^\Q$prefix\E\/(.*)$/)
5430 return substr($filename, length($prefix) + 1);
5434 return $filename;
5439 # system_no_output(mode, parameters)
5441 # Call an external program using PARAMETERS while suppressing depending on
5442 # the value of MODE:
5444 # MODE & 1: suppress STDOUT
5445 # MODE & 2: suppress STDERR
5447 # Return 0 on success, non-zero otherwise.
5450 sub system_no_output($@)
5452 my $mode = shift;
5453 my $result;
5454 local *OLD_STDERR;
5455 local *OLD_STDOUT;
5457 # Save old stdout and stderr handles
5458 ($mode & 1) && open(OLD_STDOUT, ">>&STDOUT");
5459 ($mode & 2) && open(OLD_STDERR, ">>&STDERR");
5461 # Redirect to /dev/null
5462 ($mode & 1) && open(STDOUT, ">/dev/null");
5463 ($mode & 2) && open(STDERR, ">/dev/null");
5465 system(@_);
5466 $result = $?;
5468 # Close redirected handles
5469 ($mode & 1) && close(STDOUT);
5470 ($mode & 2) && close(STDERR);
5472 # Restore old handles
5473 ($mode & 1) && open(STDOUT, ">>&OLD_STDOUT");
5474 ($mode & 2) && open(STDERR, ">>&OLD_STDERR");
5476 return $result;
5481 # read_config(filename)
5483 # Read configuration file FILENAME and return a reference to a hash containing
5484 # all valid key=value pairs found.
5487 sub read_config($)
5489 my $filename = $_[0];
5490 my %result;
5491 my $key;
5492 my $value;
5493 local *HANDLE;
5495 if (!open(HANDLE, "<$filename"))
5497 warn("WARNING: cannot read configuration file $filename\n");
5498 return undef;
5500 while (<HANDLE>)
5502 chomp;
5503 # Skip comments
5504 s/#.*//;
5505 # Remove leading blanks
5506 s/^\s+//;
5507 # Remove trailing blanks
5508 s/\s+$//;
5509 next unless length;
5510 ($key, $value) = split(/\s*=\s*/, $_, 2);
5511 if (defined($key) && defined($value))
5513 $result{$key} = $value;
5515 else
5517 warn("WARNING: malformed statement in line $. ".
5518 "of configuration file $filename\n");
5521 close(HANDLE);
5522 return \%result;
5527 # apply_config(REF)
5529 # REF is a reference to a hash containing the following mapping:
5531 # key_string => var_ref
5533 # where KEY_STRING is a keyword and VAR_REF is a reference to an associated
5534 # variable. If the global configuration hash CONFIG contains a value for
5535 # keyword KEY_STRING, VAR_REF will be assigned the value for that keyword.
5538 sub apply_config($)
5540 my $ref = $_[0];
5542 foreach (keys(%{$ref}))
5544 if (defined($config->{$_}))
5546 ${$ref->{$_}} = $config->{$_};
5553 # get_html_prolog(FILENAME)
5555 # If FILENAME is defined, return contents of file. Otherwise return default
5556 # HTML prolog. Die on error.
5559 sub get_html_prolog($)
5561 my $filename = $_[0];
5562 my $result = "";
5564 if (defined($filename))
5566 local *HANDLE;
5568 open(HANDLE, "<".$filename)
5569 or die("ERROR: cannot open html prolog $filename!\n");
5570 while (<HANDLE>)
5572 $result .= $_;
5574 close(HANDLE);
5576 else
5578 $result = <<END_OF_HTML
5579 <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">
5581 <html lang="en">
5583 <head>
5584 <meta http-equiv="Content-Type" content="text/html; charset=ISO-8859-1">
5585 <title>\@pagetitle\@</title>
5586 <link rel="stylesheet" type="text/css" href="\@basedir\@gcov.css">
5587 </head>
5589 <body>
5591 END_OF_HTML
5595 return $result;
5600 # get_html_epilog(FILENAME)
5602 # If FILENAME is defined, return contents of file. Otherwise return default
5603 # HTML epilog. Die on error.
5605 sub get_html_epilog($)
5607 my $filename = $_[0];
5608 my $result = "";
5610 if (defined($filename))
5612 local *HANDLE;
5614 open(HANDLE, "<".$filename)
5615 or die("ERROR: cannot open html epilog $filename!\n");
5616 while (<HANDLE>)
5618 $result .= $_;
5620 close(HANDLE);
5622 else
5624 $result = <<END_OF_HTML
5626 </body>
5627 </html>
5628 END_OF_HTML
5632 return $result;
5636 sub warn_handler($)
5638 my ($msg) = @_;
5640 warn("$tool_name: $msg");
5643 sub die_handler($)
5645 my ($msg) = @_;
5647 die("$tool_name: $msg");