Home | History | Annotate | Download | only in bin
      1 #!/usr/bin/perl -w
      2 #
      3 #   Copyright (c) International Business Machines  Corp., 2002
      4 #
      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.
      9 #
     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. 
     14 #
     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
     18 #
     19 #
     20 # genhtml
     21 #
     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.
     25 #
     26 #
     27 # History:
     28 #   2002-08-23 created by Peter Oberparleiter <Peter.Oberparleiter (at] de.ibm.com>
     29 #                         IBM Lab Boeblingen
     30 #        based on code by Manoj Iyer <manjo (at] mail.utexas.edu> and
     31 #                         Megan Bock <mbock (at] 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)
     65 #
     66 
     67 use strict;
     68 use File::Basename; 
     69 use Getopt::Long;
     70 use Digest::MD5 qw(md5_base64);
     71 
     72 
     73 # Global constants
     74 our $title		= "LCOV - code coverage report";
     75 our $lcov_version	= "LCOV version 1.7";
     76 our $lcov_url		= "http://ltp.sourceforge.net/coverage/lcov.php";
     77 our $tool_name		= basename($0);
     78 
     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
     83 
     84 # For line coverage
     85 our $hi_limit	= 50;
     86 our $med_limit	= 15;
     87 
     88 # For function coverage
     89 our $fn_hi_limit	= 90;
     90 our $fn_med_limit	= 75;
     91 
     92 # Width of overview image
     93 our $overview_width = 80;
     94 
     95 # Resolution of overview navigation: this number specifies the maximum
     96 # difference in lines between the position a user selected from the overview
     97 # and the position the source code window is scrolled to.
     98 our $nav_resolution = 4;
     99 
    100 # Clicking a line in the overview image should show the source code view at
    101 # a position a bit further up so that the requested line is not the first
    102 # line in the window. This number specifies that offset in lines.
    103 our $nav_offset = 10;
    104 
    105 # Clicking on a function name should show the source code at a position a
    106 # few lines before the first line of code of that function. This number
    107 # specifies that offset in lines.
    108 our $func_offset = 2;
    109 
    110 our $overview_title = "directory";
    111 
    112 # Data related prototypes
    113 sub print_usage(*);
    114 sub gen_html();
    115 sub html_create($$);
    116 sub process_dir($);
    117 sub process_file($$$);
    118 sub info(@);
    119 sub read_info_file($);
    120 sub get_info_entry($);
    121 sub set_info_entry($$$$$$$;$$$$);
    122 sub get_prefix(@);
    123 sub shorten_prefix($);
    124 sub get_dir_list(@);
    125 sub get_relative_base_path($);
    126 sub read_testfile($);
    127 sub get_date_string();
    128 sub split_filename($);
    129 sub create_sub_dir($);
    130 sub subtract_counts($$);
    131 sub add_counts($$);
    132 sub apply_baseline($$);
    133 sub remove_unused_descriptions();
    134 sub get_found_and_hit($);
    135 sub get_affecting_tests($$);
    136 sub combine_info_files($$);
    137 sub merge_checksums($$$);
    138 sub combine_info_entries($$$);
    139 sub apply_prefix($$);
    140 sub system_no_output($@);
    141 sub read_config($);
    142 sub apply_config($);
    143 sub get_html_prolog($);
    144 sub get_html_epilog($);
    145 
    146 
    147 # HTML related prototypes
    148 sub escape_html($);
    149 sub get_bar_graph_code($$$);
    150 
    151 sub write_png_files();
    152 sub write_htaccess_file();
    153 sub write_css_file();
    154 sub write_description_file($$$$$);
    155 sub write_function_rable(*$$$);
    156 
    157 sub write_html(*$);
    158 sub write_html_prolog(*$$);
    159 sub write_html_epilog(*$;$);
    160 
    161 sub write_header(*$$$$$$$$);
    162 sub write_header_prolog(*$);
    163 sub write_header_line(*$@);
    164 sub write_header_epilog(*$);
    165 
    166 sub write_file_table(*$$$$$$);
    167 sub write_file_table_prolog(*$$$);
    168 sub write_file_table_entry(*$$$$$$$);
    169 sub write_file_table_detail_heading(*$$$);
    170 sub write_file_table_detail_entry(*$$$$$);
    171 sub write_file_table_epilog(*);
    172 
    173 sub write_test_table_prolog(*$);
    174 sub write_test_table_entry(*$$);
    175 sub write_test_table_epilog(*);
    176 
    177 sub write_source($$$$$$);
    178 sub write_source_prolog(*);
    179 sub write_source_line(*$$$$$);
    180 sub write_source_epilog(*);
    181 
    182 sub write_frameset(*$$$);
    183 sub write_overview_line(*$$$);
    184 sub write_overview(*$$$$);
    185 
    186 # External prototype (defined in genpng)
    187 sub gen_png($$$@);
    188 
    189 
    190 # Global variables & initialization
    191 our %info_data;		# Hash containing all data from .info file
    192 our $dir_prefix;	# Prefix to remove from all sub directories
    193 our %test_description;	# Hash containing test descriptions if available
    194 our $date = get_date_string();
    195 
    196 our @info_filenames;	# List of .info files to use as data source
    197 our $test_title;	# Title for output as written to each page header
    198 our $output_directory;	# Name of directory in which to store output
    199 our $base_filename;	# Optional name of file containing baseline data
    200 our $desc_filename;	# Name of file containing test descriptions
    201 our $css_filename;	# Optional name of external stylesheet file to use
    202 our $quiet;		# If set, suppress information messages
    203 our $help;		# Help option flag
    204 our $version;		# Version option flag
    205 our $show_details;	# If set, generate detailed directory view
    206 our $no_prefix;		# If set, do not remove filename prefix
    207 our $func_coverage = 1;	# If set, generate function coverage statistics
    208 our $no_func_coverage;	# Disable func_coverage
    209 our $sort = 1;		# If set, provide directory listings with sorted entries
    210 our $no_sort;		# Disable sort
    211 our $frames;		# If set, use frames for source code view
    212 our $keep_descriptions;	# If set, do not remove unused test case descriptions
    213 our $no_sourceview;	# If set, do not create a source code view for each file
    214 our $highlight;		# If set, highlight lines covered by converted data only
    215 our $legend;		# If set, include legend in output
    216 our $tab_size = 8;	# Number of spaces to use in place of tab
    217 our $config;		# Configuration file contents
    218 our $html_prolog_file;	# Custom HTML prolog file (up to and including <body>)
    219 our $html_epilog_file;	# Custom HTML epilog file (from </body> onwards)
    220 our $html_prolog;	# Actual HTML prolog
    221 our $html_epilog;	# Actual HTML epilog
    222 our $html_ext = "html";	# Extension for generated HTML files
    223 our $html_gzip = 0;	# Compress with gzip
    224 our @fileview_sortlist;
    225 our @fileview_sortname = ("", "-sort-l", "-sort-f");
    226 our @funcview_sortlist;
    227 our @rate_name = ("Lo", "Med", "Hi");
    228 our @rate_png = ("ruby.png", "amber.png", "emerald.png");
    229 
    230 our $cwd = `pwd`;	# Current working directory
    231 chomp($cwd);
    232 our $tool_dir = dirname($0);	# Directory where genhtml tool is installed
    233 
    234 
    235 #
    236 # Code entry point
    237 #
    238 
    239 $SIG{__WARN__} = \&warn_handler;
    240 $SIG{__DIE__} = \&die_handler;
    241 
    242 # Add current working directory if $tool_dir is not already an absolute path
    243 if (! ($tool_dir =~ /^\/(.*)$/))
    244 {
    245 	$tool_dir = "$cwd/$tool_dir";
    246 }
    247 
    248 # Read configuration file if available
    249 if (-r $ENV{"HOME"}."/.lcovrc")
    250 {
    251 	$config = read_config($ENV{"HOME"}."/.lcovrc");
    252 }
    253 elsif (-r "/etc/lcovrc")
    254 {
    255 	$config = read_config("/etc/lcovrc");
    256 }
    257 
    258 if ($config)
    259 {
    260 	# Copy configuration file values to variables
    261 	apply_config({
    262 		"genhtml_css_file"		=> \$css_filename,
    263 		"genhtml_hi_limit"		=> \$hi_limit,
    264 		"genhtml_med_limit"		=> \$med_limit,
    265 		"genhtml_overview_width"	=> \$overview_width,
    266 		"genhtml_nav_resolution"	=> \$nav_resolution,
    267 		"genhtml_nav_offset"		=> \$nav_offset,
    268 		"genhtml_keep_descriptions"	=> \$keep_descriptions,
    269 		"genhtml_no_prefix"		=> \$no_prefix,
    270 		"genhtml_no_source"		=> \$no_sourceview,
    271 		"genhtml_num_spaces"		=> \$tab_size,
    272 		"genhtml_highlight"		=> \$highlight,
    273 		"genhtml_legend"		=> \$legend,
    274 		"genhtml_html_prolog"		=> \$html_prolog_file,
    275 		"genhtml_html_epilog"		=> \$html_epilog_file,
    276 		"genhtml_html_extension"	=> \$html_ext,
    277 		"genhtml_html_gzip"		=> \$html_gzip,
    278 		"genhtml_function_hi_limit"	=> \$fn_hi_limit,
    279 		"genhtml_function_med_limit"	=> \$fn_med_limit,
    280 		"genhtml_function_coverage"	=> \$func_coverage,
    281 		"genhtml_sort"			=> \$sort,
    282 		});
    283 }
    284 
    285 # Parse command line options
    286 if (!GetOptions("output-directory=s"	=> \$output_directory,
    287 		"title=s"		=> \$test_title,
    288 		"description-file=s"	=> \$desc_filename,
    289 		"keep-descriptions"	=> \$keep_descriptions,
    290 		"css-file=s"		=> \$css_filename,
    291 		"baseline-file=s"	=> \$base_filename,
    292 		"prefix=s"		=> \$dir_prefix,
    293 		"num-spaces=i"		=> \$tab_size,
    294 		"no-prefix"		=> \$no_prefix,
    295 		"no-sourceview"		=> \$no_sourceview,
    296 		"show-details"		=> \$show_details,
    297 		"frames"		=> \$frames,
    298 		"highlight"		=> \$highlight,
    299 		"legend"		=> \$legend,
    300 		"quiet"			=> \$quiet,
    301 		"help|h|?"		=> \$help,
    302 		"version"		=> \$version,
    303 		"html-prolog=s"		=> \$html_prolog_file,
    304 		"html-epilog=s"		=> \$html_epilog_file,
    305 		"html-extension=s"	=> \$html_ext,
    306 		"html-gzip"		=> \$html_gzip,
    307 		"function-coverage"	=> \$func_coverage,
    308 		"no-function-coverage"	=> \$no_func_coverage,
    309 		"sort"			=> \$sort,
    310 		"no-sort"		=> \$no_sort,
    311 		))
    312 {
    313 	print(STDERR "Use $tool_name --help to get usage information\n");
    314 	exit(1);
    315 } else {
    316 	# Merge options
    317 	if ($no_func_coverage) {
    318 		$func_coverage = 0;
    319 	}
    320 
    321 	# Merge sort options
    322 	if ($no_sort) {
    323 		$sort = 0;
    324 	}
    325 }
    326 
    327 @info_filenames = @ARGV;
    328 
    329 # Check for help option
    330 if ($help)
    331 {
    332 	print_usage(*STDOUT);
    333 	exit(0);
    334 }
    335 
    336 # Check for version option
    337 if ($version)
    338 {
    339 	print("$tool_name: $lcov_version\n");
    340 	exit(0);
    341 }
    342 
    343 # Check for info filename
    344 if (!@info_filenames)
    345 {
    346 	die("No filename specified\n".
    347 	    "Use $tool_name --help to get usage information\n");
    348 }
    349 
    350 # Generate a title if none is specified
    351 if (!$test_title)
    352 {
    353 	if (scalar(@info_filenames) == 1)
    354 	{
    355 		# Only one filename specified, use it as title
    356 		$test_title = basename($info_filenames[0]);
    357 	}
    358 	else
    359 	{
    360 		# More than one filename specified, used default title
    361 		$test_title = "unnamed";
    362 	}
    363 }
    364 
    365 # Make sure css_filename is an absolute path (in case we're changing
    366 # directories)
    367 if ($css_filename)
    368 {
    369 	if (!($css_filename =~ /^\/(.*)$/))
    370 	{
    371 		$css_filename = $cwd."/".$css_filename;
    372 	}
    373 }
    374 
    375 # Make sure tab_size is within valid range
    376 if ($tab_size < 1)
    377 {
    378 	print(STDERR "ERROR: invalid number of spaces specified: ".
    379 		     "$tab_size!\n");
    380 	exit(1);
    381 }
    382 
    383 # Get HTML prolog and epilog
    384 $html_prolog = get_html_prolog($html_prolog_file);
    385 $html_epilog = get_html_epilog($html_epilog_file);
    386 
    387 # Issue a warning if --no-sourceview is enabled together with --frames
    388 if ($no_sourceview && defined($frames))
    389 {
    390 	warn("WARNING: option --frames disabled because --no-sourceview ".
    391 	     "was specified!\n");
    392 	$frames = undef;
    393 }
    394 
    395 # Issue a warning if --no-prefix is enabled together with --prefix
    396 if ($no_prefix && defined($dir_prefix))
    397 {
    398 	warn("WARNING: option --prefix disabled because --no-prefix was ".
    399 	     "specified!\n");
    400 	$dir_prefix = undef;
    401 }
    402 
    403 if ($sort) {
    404 	@funcview_sortlist = (0, 1);
    405 	if ($func_coverage) {
    406 		@fileview_sortlist = (0, 1, 2);
    407 	} else {
    408 		@fileview_sortlist = (0, 1);
    409 	}
    410 } else {
    411 	@fileview_sortlist = (0);
    412 	@funcview_sortlist = (0);
    413 }
    414 
    415 if ($frames)
    416 {
    417 	# Include genpng code needed for overview image generation
    418 	do("$tool_dir/genpng");
    419 }
    420 
    421 # Make sure output_directory exists, create it if necessary
    422 if ($output_directory)
    423 {
    424 	stat($output_directory);
    425 
    426 	if (! -e _)
    427 	{
    428 		system("mkdir", "-p", $output_directory)
    429 			and die("ERROR: cannot create directory $_!\n");
    430 	}
    431 }
    432 
    433 # Do something
    434 gen_html();
    435 
    436 exit(0);
    437 
    438 
    439 
    440 #
    441 # print_usage(handle)
    442 #
    443 # Print usage information.
    444 #
    445 
    446 sub print_usage(*)
    447 {
    448 	local *HANDLE = $_[0];
    449 
    450 	print(HANDLE <<END_OF_USAGE);
    451 Usage: $tool_name [OPTIONS] INFOFILE(S)
    452 
    453 Create HTML output for coverage data found in INFOFILE. Note that INFOFILE
    454 may also be a list of filenames.
    455 
    456 Misc:
    457   -h, --help                        Print this help, then exit
    458   -v, --version                     Print version number, then exit
    459   -q, --quiet                       Do not print progress messages
    460 
    461 Operation:
    462   -o, --output-directory OUTDIR     Write HTML output to OUTDIR
    463   -s, --show-details                Generate detailed directory view
    464   -d, --description-file DESCFILE   Read test case descriptions from DESCFILE
    465   -k, --keep-descriptions           Do not remove unused test descriptions
    466   -b, --baseline-file BASEFILE      Use BASEFILE as baseline file
    467   -p, --prefix PREFIX               Remove PREFIX from all directory names
    468       --no-prefix                   Do not remove prefix from directory names
    469       --(no-)function-coverage      Enable (disable) function coverage display
    470 
    471 HTML output:
    472   -f, --frames                      Use HTML frames for source code view
    473   -t, --title TITLE                 Display TITLE in header of all pages
    474   -c, --css-file CSSFILE            Use external style sheet file CSSFILE
    475       --no-source                   Do not create source code view
    476       --num-spaces NUM              Replace tabs with NUM spaces in source view
    477       --highlight                   Highlight lines with converted-only data
    478       --legend                      Include color legend in HTML output
    479       --html-prolog FILE            Use FILE as HTML prolog for generated pages
    480       --html-epilog FILE            Use FILE as HTML epilog for generated pages
    481       --html-extension EXT          Use EXT as filename extension for pages
    482       --html-gzip                   Use gzip to compress HTML
    483       --(no-)sort                   Enable (disable) sorted coverage views
    484 
    485 For more information see: $lcov_url
    486 END_OF_USAGE
    487 	;
    488 }
    489 
    490 
    491 #
    492 # get_rate(found, hit)
    493 #
    494 # Return a relative value for the specified found&hit values
    495 # which is used for sorting the corresponding entries in a
    496 # file list.
    497 #
    498 
    499 sub get_rate($$)
    500 {
    501 	my ($found, $hit) = @_;
    502 
    503 	if ($found == 0) {
    504 		return 10000;
    505 	}
    506 	return int($hit * 1000 / $found) * 10 + 2 - (1 / $found);
    507 }
    508 
    509 
    510 #
    511 # gen_html()
    512 #
    513 # Generate a set of HTML pages from contents of .info file INFO_FILENAME.
    514 # Files will be written to the current directory. If provided, test case
    515 # descriptions will be read from .tests file TEST_FILENAME and included
    516 # in ouput.
    517 #
    518 # Die on error.
    519 #
    520 
    521 sub gen_html()
    522 {
    523 	local *HTML_HANDLE;
    524 	my %overview;
    525 	my %base_data;
    526 	my $lines_found;
    527 	my $lines_hit;
    528 	my $fn_found;
    529 	my $fn_hit;
    530 	my $overall_found = 0;
    531 	my $overall_hit = 0;
    532 	my $total_fn_found = 0;
    533 	my $total_fn_hit = 0;
    534 	my $dir_name;
    535 	my $link_name;
    536 	my @dir_list;
    537 	my %new_info;
    538 
    539 	# Read in all specified .info files
    540 	foreach (@info_filenames)
    541 	{
    542 		%new_info = %{read_info_file($_)};
    543 
    544 		# Combine %new_info with %info_data
    545 		%info_data = %{combine_info_files(\%info_data, \%new_info)};
    546 	}
    547 
    548 	info("Found %d entries.\n", scalar(keys(%info_data)));
    549 
    550 	# Read and apply baseline data if specified
    551 	if ($base_filename)
    552 	{
    553 		# Read baseline file
    554 		info("Reading baseline file $base_filename\n");
    555 		%base_data = %{read_info_file($base_filename)};
    556 		info("Found %d entries.\n", scalar(keys(%base_data)));
    557 
    558 		# Apply baseline
    559 		info("Subtracting baseline data.\n");
    560 		%info_data = %{apply_baseline(\%info_data, \%base_data)};
    561 	}
    562 
    563 	@dir_list = get_dir_list(keys(%info_data));
    564 
    565 	if ($no_prefix)
    566 	{
    567 		# User requested that we leave filenames alone
    568 		info("User asked not to remove filename prefix\n");
    569 	}
    570 	elsif (!defined($dir_prefix))
    571 	{
    572 		# Get prefix common to most directories in list
    573 		$dir_prefix = get_prefix(@dir_list);
    574 
    575 		if ($dir_prefix)
    576 		{
    577 			info("Found common filename prefix \"$dir_prefix\"\n");
    578 		}
    579 		else
    580 		{
    581 			info("No common filename prefix found!\n");
    582 			$no_prefix=1;
    583 		}
    584 	}
    585 	else
    586 	{
    587 		info("Using user-specified filename prefix \"".
    588 		     "$dir_prefix\"\n");
    589 	}
    590 
    591 	# Read in test description file if specified
    592 	if ($desc_filename)
    593 	{
    594 		info("Reading test description file $desc_filename\n");
    595 		%test_description = %{read_testfile($desc_filename)};
    596 
    597 		# Remove test descriptions which are not referenced
    598 		# from %info_data if user didn't tell us otherwise
    599 		if (!$keep_descriptions)
    600 		{
    601 			remove_unused_descriptions();
    602 		}
    603 	}
    604 
    605 	# Change to output directory if specified
    606 	if ($output_directory)
    607 	{
    608 		chdir($output_directory)
    609 			or die("ERROR: cannot change to directory ".
    610 			"$output_directory!\n");
    611 	}
    612 
    613 	info("Writing .css and .png files.\n");
    614 	write_css_file();
    615 	write_png_files();
    616 
    617 	if ($html_gzip)
    618 	{
    619 		info("Writing .htaccess file.\n");
    620 		write_htaccess_file();
    621 	}
    622 
    623 	info("Generating output.\n");
    624 
    625 	# Process each subdirectory and collect overview information
    626 	foreach $dir_name (@dir_list)
    627 	{
    628 		($lines_found, $lines_hit, $fn_found, $fn_hit)
    629 			= process_dir($dir_name);
    630 
    631 		# Remove prefix if applicable
    632 		if (!$no_prefix && $dir_prefix)
    633 		{
    634 			# Match directory names beginning with $dir_prefix
    635 			$dir_name = apply_prefix($dir_name, $dir_prefix);
    636 		}
    637 
    638 		# Generate name for directory overview HTML page
    639 		if ($dir_name =~ /^\/(.*)$/)
    640 		{
    641 			$link_name = substr($dir_name, 1)."/index.$html_ext";
    642 		}
    643 		else
    644 		{
    645 			$link_name = $dir_name."/index.$html_ext";
    646 		}
    647 
    648 		$overview{$dir_name} = [$lines_found, $lines_hit, $fn_found,
    649 					$fn_hit, $link_name,
    650 					get_rate($lines_found, $lines_hit),
    651 					get_rate($fn_found, $fn_hit)];
    652 		$overall_found	+= $lines_found;
    653 		$overall_hit	+= $lines_hit;
    654 		$total_fn_found	+= $fn_found;
    655 		$total_fn_hit	+= $fn_hit;
    656 	}
    657 
    658 	# Generate overview page
    659 	info("Writing directory view page.\n");
    660 
    661 	# Create sorted pages
    662 	foreach (@fileview_sortlist) {
    663 		write_dir_page($fileview_sortname[$_], ".", "", $test_title,
    664 			       undef, $overall_found, $overall_hit,
    665 			       $total_fn_found, $total_fn_hit, \%overview,
    666 			       {}, {}, 0, $_);
    667 	}
    668 
    669 	# Check if there are any test case descriptions to write out
    670 	if (%test_description)
    671 	{
    672 		info("Writing test case description file.\n");
    673 		write_description_file( \%test_description,
    674 					$overall_found, $overall_hit,
    675 					$total_fn_found, $total_fn_hit);
    676 	}
    677 
    678 	chdir($cwd);
    679 
    680 	info("Overall coverage rate:\n");
    681 
    682 	if ($overall_found == 0)
    683 	{
    684 		info("  lines......: no data found\n");
    685 		return;
    686 	}
    687 	info("  lines......: %.1f%% (%d of %d lines)\n",
    688 	     $overall_hit * 100 / $overall_found, $overall_hit,
    689 	     $overall_found,);
    690 
    691 	if ($func_coverage)
    692 	{
    693 		if ($total_fn_found == 0)
    694 		{
    695 			info("  functions..: no data found\n");
    696 		}
    697 		else
    698 		{
    699 			info("  functions..: %.1f%% (%d of %d functions)\n",
    700 			     $total_fn_hit * 100 / $total_fn_found,
    701 			     $total_fn_hit, $total_fn_found);
    702 
    703 		}
    704 	}
    705 
    706 }
    707 
    708 #
    709 # html_create(handle, filename)
    710 #
    711 
    712 sub html_create($$)
    713 {
    714 	my $handle = $_[0];
    715 	my $filename = $_[1];
    716 
    717 	if ($html_gzip)
    718 	{
    719 		open($handle, "|gzip -c >$filename")
    720 			or die("ERROR: cannot open $filename for writing ".
    721 			       "(gzip)!\n");
    722 	}
    723 	else
    724 	{
    725 		open($handle, ">$filename")
    726 			or die("ERROR: cannot open $filename for writing!\n");
    727 	}
    728 }
    729 
    730 sub write_dir_page($$$$$$$$$$$$$$)
    731 {
    732 	my ($name, $rel_dir, $base_dir, $title, $trunc_dir, $overall_found,
    733 	    $overall_hit, $total_fn_found, $total_fn_hit, $overview,
    734 	    $testhash, $testfnchash, $view_type, $sort_type) = @_;
    735 
    736 	# Generate directory overview page including details
    737 	html_create(*HTML_HANDLE, "$rel_dir/index$name.$html_ext");
    738 	if (!defined($trunc_dir)) {
    739 		$trunc_dir = "";
    740 	}
    741 	write_html_prolog(*HTML_HANDLE, $base_dir, "LCOV - $title$trunc_dir");
    742 	write_header(*HTML_HANDLE, $view_type, $trunc_dir, $rel_dir,
    743 		     $overall_found, $overall_hit, $total_fn_found,
    744 		     $total_fn_hit, $sort_type);
    745 	write_file_table(*HTML_HANDLE, $base_dir, $overview, $testhash,
    746 			 $testfnchash, $view_type, $sort_type);
    747 	write_html_epilog(*HTML_HANDLE, $base_dir);
    748 	close(*HTML_HANDLE);
    749 }
    750 
    751 
    752 #
    753 # process_dir(dir_name)
    754 #
    755 
    756 sub process_dir($)
    757 {
    758 	my $abs_dir = $_[0];
    759 	my $trunc_dir;
    760 	my $rel_dir = $abs_dir;
    761 	my $base_dir;
    762 	my $filename;
    763 	my %overview;
    764 	my $lines_found;
    765 	my $lines_hit;
    766 	my $fn_found;
    767 	my $fn_hit;
    768 	my $overall_found=0;
    769 	my $overall_hit=0;
    770 	my $total_fn_found=0;
    771 	my $total_fn_hit=0;
    772 	my $base_name;
    773 	my $extension;
    774 	my $testdata;
    775 	my %testhash;
    776 	my $testfncdata;
    777 	my %testfnchash;
    778 	my @sort_list;
    779 	local *HTML_HANDLE;
    780 
    781 	# Remove prefix if applicable
    782 	if (!$no_prefix)
    783 	{
    784 		# Match directory name beginning with $dir_prefix
    785 		$rel_dir = apply_prefix($rel_dir, $dir_prefix);
    786 	}
    787 
    788 	$trunc_dir = $rel_dir;
    789 
    790 	# Remove leading /
    791 	if ($rel_dir =~ /^\/(.*)$/)
    792 	{
    793 		$rel_dir = substr($rel_dir, 1);
    794 	}
    795 
    796 	$base_dir = get_relative_base_path($rel_dir);
    797 
    798 	create_sub_dir($rel_dir);
    799 
    800 	# Match filenames which specify files in this directory, not including
    801 	# sub-directories
    802 	foreach $filename (grep(/^\Q$abs_dir\E\/[^\/]*$/,keys(%info_data)))
    803 	{
    804 		my $page_link;
    805 		my $func_link;
    806 
    807 		($lines_found, $lines_hit, $fn_found, $fn_hit, $testdata,
    808 		 $testfncdata) = process_file($trunc_dir, $rel_dir, $filename);
    809 
    810 		$base_name = basename($filename);
    811 
    812 		if ($no_sourceview) {
    813 			$page_link = "";
    814 		} elsif ($frames) {
    815 			# Link to frameset page
    816 			$page_link = "$base_name.gcov.frameset.$html_ext";
    817 		} else {
    818 			# Link directory to source code view page
    819 			$page_link = "$base_name.gcov.$html_ext";
    820 		}
    821 		$overview{$base_name} = [$lines_found, $lines_hit, $fn_found,
    822 					 $fn_hit, $page_link,
    823 					 get_rate($lines_found, $lines_hit),
    824 					 get_rate($fn_found, $fn_hit)];
    825 
    826 		$testhash{$base_name} = $testdata;
    827 		$testfnchash{$base_name} = $testfncdata;
    828 
    829 		$overall_found	+= $lines_found;
    830 		$overall_hit	+= $lines_hit;
    831 
    832 		$total_fn_found += $fn_found;
    833 		$total_fn_hit   += $fn_hit;
    834 	}
    835 
    836 	# Create sorted pages
    837 	foreach (@fileview_sortlist) {
    838 		# Generate directory overview page (without details)	
    839 		write_dir_page($fileview_sortname[$_], $rel_dir, $base_dir,
    840 			       $test_title, $trunc_dir, $overall_found,
    841 			       $overall_hit, $total_fn_found, $total_fn_hit,
    842 			       \%overview, {}, {}, 1, $_);
    843 		if (!$show_details) {
    844 			next;
    845 		}
    846 		# Generate directory overview page including details
    847 		write_dir_page("-detail".$fileview_sortname[$_], $rel_dir,
    848 			       $base_dir, $test_title, $trunc_dir,
    849 			       $overall_found, $overall_hit, $total_fn_found,
    850 			       $total_fn_hit, \%overview, \%testhash,
    851 			       \%testfnchash, 1, $_);
    852 	}
    853 
    854 	# Calculate resulting line counts
    855 	return ($overall_found, $overall_hit, $total_fn_found, $total_fn_hit);
    856 }
    857 
    858 
    859 #
    860 # get_converted_lines(testdata)
    861 #
    862 # Return hash of line numbers of those lines which were only covered in
    863 # converted data sets.
    864 #
    865 
    866 sub get_converted_lines($)
    867 {
    868 	my $testdata = $_[0];
    869 	my $testcount;
    870 	my %converted;
    871 	my %nonconverted;
    872 	my $hash;
    873 	my $testcase;
    874 	my $line;
    875 	my %result;
    876 
    877 
    878 	# Get a hash containing line numbers with positive counts both for
    879 	# converted and original data sets
    880 	foreach $testcase (keys(%{$testdata}))
    881 	{
    882 		# Check to see if this is a converted data set
    883 		if ($testcase =~ /,diff$/)
    884 		{
    885 			$hash = \%converted;
    886 		}
    887 		else
    888 		{
    889 			$hash = \%nonconverted;
    890 		}
    891 
    892 		$testcount = $testdata->{$testcase};
    893 		# Add lines with a positive count to hash
    894 		foreach $line (keys%{$testcount})
    895 		{
    896 			if ($testcount->{$line} > 0)
    897 			{
    898 				$hash->{$line} = 1;
    899 			}
    900 		}
    901 	}
    902 
    903 	# Combine both hashes to resulting list
    904 	foreach $line (keys(%converted))
    905 	{
    906 		if (!defined($nonconverted{$line}))
    907 		{
    908 			$result{$line} = 1;
    909 		}
    910 	}
    911 
    912 	return \%result;
    913 }
    914 
    915 
    916 sub write_function_page($$$$$$$$$$$$$$)
    917 {
    918 	my ($base_dir, $rel_dir, $trunc_dir, $base_name, $title,
    919 	    $lines_found, $lines_hit, $fn_found, $fn_hit,
    920 	    $sumcount, $funcdata, $sumfnccount, $testfncdata, $sort_type) = @_;
    921 	my $pagetitle;
    922 	my $filename;
    923 
    924 	# Generate function table for this file
    925 	if ($sort_type == 0) {
    926 		$filename = "$rel_dir/$base_name.func.$html_ext";
    927 	} else {
    928 		$filename = "$rel_dir/$base_name.func-sort-c.$html_ext";
    929 	}
    930 	html_create(*HTML_HANDLE, $filename);
    931 	$pagetitle = "LCOV - $title - $trunc_dir/$base_name - functions";
    932 	write_html_prolog(*HTML_HANDLE, $base_dir, $pagetitle);
    933 	write_header(*HTML_HANDLE, 4, "$trunc_dir/$base_name",
    934 		     "$rel_dir/$base_name", $lines_found, $lines_hit,
    935 		     $fn_found, $fn_hit, $sort_type);
    936 	write_function_table(*HTML_HANDLE, "$base_name.gcov.$html_ext",
    937 			     $sumcount, $funcdata,
    938 			     $sumfnccount, $testfncdata, $base_name,
    939 			     $base_dir, $sort_type);
    940 	write_html_epilog(*HTML_HANDLE, $base_dir, 1);
    941 	close(*HTML_HANDLE);
    942 }
    943 
    944 
    945 #
    946 # process_file(trunc_dir, rel_dir, filename)
    947 #
    948 
    949 sub process_file($$$)
    950 {
    951 	info("Processing file ".apply_prefix($_[2], $dir_prefix)."\n");
    952 
    953 	my $trunc_dir = $_[0];
    954 	my $rel_dir = $_[1];
    955 	my $filename = $_[2];
    956 	my $base_name = basename($filename);
    957 	my $base_dir = get_relative_base_path($rel_dir);
    958 	my $testdata;
    959 	my $testcount;
    960 	my $sumcount;
    961 	my $funcdata;
    962 	my $checkdata;
    963 	my $testfncdata;
    964 	my $sumfnccount;
    965 	my $lines_found;
    966 	my $lines_hit;
    967 	my $fn_found;
    968 	my $fn_hit;
    969 	my $converted;
    970 	my @source;
    971 	my $pagetitle;
    972 	local *HTML_HANDLE;
    973 
    974 	($testdata, $sumcount, $funcdata, $checkdata, $testfncdata,
    975 	 $sumfnccount, $lines_found, $lines_hit, $fn_found, $fn_hit)
    976 		= get_info_entry($info_data{$filename});
    977 
    978 	# Return after this point in case user asked us not to generate
    979 	# source code view
    980 	if ($no_sourceview)
    981 	{
    982 		return ($lines_found, $lines_hit,
    983 			$fn_found, $fn_hit, $testdata);
    984 	}
    985 
    986 	$converted = get_converted_lines($testdata);
    987 	# Generate source code view for this file
    988 	html_create(*HTML_HANDLE, "$rel_dir/$base_name.gcov.$html_ext");
    989 	$pagetitle = "LCOV - $test_title - $trunc_dir/$base_name";
    990 	write_html_prolog(*HTML_HANDLE, $base_dir, $pagetitle);
    991 	write_header(*HTML_HANDLE, 2, "$trunc_dir/$base_name",
    992 		     "$rel_dir/$base_name", $lines_found, $lines_hit,
    993 		     $fn_found, $fn_hit, 0);
    994 	@source = write_source(*HTML_HANDLE, $filename, $sumcount, $checkdata,
    995 			       $converted, $funcdata);
    996 
    997 	write_html_epilog(*HTML_HANDLE, $base_dir, 1);
    998 	close(*HTML_HANDLE);
    999 
   1000 	if ($func_coverage) {
   1001 		# Create function tables
   1002 		foreach (@funcview_sortlist) {
   1003 			write_function_page($base_dir, $rel_dir, $trunc_dir,
   1004 					    $base_name, $test_title,
   1005 					    $lines_found, $lines_hit,
   1006 					    $fn_found, $fn_hit, $sumcount,
   1007 					    $funcdata, $sumfnccount,
   1008 					    $testfncdata, $_);
   1009 		}
   1010 	}
   1011 
   1012 	# Additional files are needed in case of frame output
   1013 	if (!$frames)
   1014 	{
   1015 		return ($lines_found, $lines_hit,
   1016 			$fn_found, $fn_hit, $testdata);
   1017 	}
   1018 
   1019 	# Create overview png file
   1020 	gen_png("$rel_dir/$base_name.gcov.png", $overview_width, $tab_size,
   1021 		@source);
   1022 
   1023 	# Create frameset page
   1024 	html_create(*HTML_HANDLE,
   1025 		    "$rel_dir/$base_name.gcov.frameset.$html_ext");
   1026 	write_frameset(*HTML_HANDLE, $base_dir, $base_name, $pagetitle);
   1027 	close(*HTML_HANDLE);
   1028 
   1029 	# Write overview frame
   1030 	html_create(*HTML_HANDLE,
   1031 		    "$rel_dir/$base_name.gcov.overview.$html_ext");
   1032 	write_overview(*HTML_HANDLE, $base_dir, $base_name, $pagetitle,
   1033 		       scalar(@source));
   1034 	close(*HTML_HANDLE);
   1035 
   1036 	return ($lines_found, $lines_hit, $fn_found, $fn_hit, $testdata,
   1037 		$testfncdata);
   1038 }
   1039 
   1040 
   1041 #
   1042 # read_info_file(info_filename)
   1043 #
   1044 # Read in the contents of the .info file specified by INFO_FILENAME. Data will
   1045 # be returned as a reference to a hash containing the following mappings:
   1046 #
   1047 # %result: for each filename found in file -> \%data
   1048 #
   1049 # %data: "test"  -> \%testdata
   1050 #        "sum"   -> \%sumcount
   1051 #        "func"  -> \%funcdata
   1052 #        "found" -> $lines_found (number of instrumented lines found in file)
   1053 #	 "hit"   -> $lines_hit (number of executed lines in file)
   1054 #        "check" -> \%checkdata
   1055 #        "testfnc" -> \%testfncdata
   1056 #        "sumfnc"  -> \%sumfnccount
   1057 #
   1058 # %testdata   : name of test affecting this file -> \%testcount
   1059 # %testfncdata: name of test affecting this file -> \%testfnccount
   1060 #
   1061 # %testcount   : line number   -> execution count for a single test
   1062 # %testfnccount: function name -> execution count for a single test
   1063 # %sumcount    : line number   -> execution count for all tests
   1064 # %sumfnccount : function name -> execution count for all tests
   1065 # %funcdata    : function name -> line number
   1066 # %checkdata   : line number   -> checksum of source code line
   1067 # 
   1068 # Note that .info file sections referring to the same file and test name
   1069 # will automatically be combined by adding all execution counts.
   1070 #
   1071 # Note that if INFO_FILENAME ends with ".gz", it is assumed that the file
   1072 # is compressed using GZIP. If available, GUNZIP will be used to decompress
   1073 # this file.
   1074 #
   1075 # Die on error.
   1076 #
   1077 
   1078 sub read_info_file($)
   1079 {
   1080 	my $tracefile = $_[0];		# Name of tracefile
   1081 	my %result;			# Resulting hash: file -> data
   1082 	my $data;			# Data handle for current entry
   1083 	my $testdata;			#       "             "
   1084 	my $testcount;			#       "             "
   1085 	my $sumcount;			#       "             "
   1086 	my $funcdata;			#       "             "
   1087 	my $checkdata;			#       "             "
   1088 	my $testfncdata;
   1089 	my $testfnccount;
   1090 	my $sumfnccount;
   1091 	my $line;			# Current line read from .info file
   1092 	my $testname;			# Current test name
   1093 	my $filename;			# Current filename
   1094 	my $hitcount;			# Count for lines hit
   1095 	my $count;			# Execution count of current line
   1096 	my $negative;			# If set, warn about negative counts
   1097 	my $changed_testname;		# If set, warn about changed testname
   1098 	my $line_checksum;		# Checksum of current line
   1099 	local *INFO_HANDLE;		# Filehandle for .info file
   1100 
   1101 	info("Reading data file $tracefile\n");
   1102 
   1103 	# Check if file exists and is readable
   1104 	stat($_[0]);
   1105 	if (!(-r _))
   1106 	{
   1107 		die("ERROR: cannot read file $_[0]!\n");
   1108 	}
   1109 
   1110 	# Check if this is really a plain file
   1111 	if (!(-f _))
   1112 	{
   1113 		die("ERROR: not a plain file: $_[0]!\n");
   1114 	}
   1115 
   1116 	# Check for .gz extension
   1117 	if ($_[0] =~ /\.gz$/)
   1118 	{
   1119 		# Check for availability of GZIP tool
   1120 		system_no_output(1, "gunzip" ,"-h")
   1121 			and die("ERROR: gunzip command not available!\n");
   1122 
   1123 		# Check integrity of compressed file
   1124 		system_no_output(1, "gunzip", "-t", $_[0])
   1125 			and die("ERROR: integrity check failed for ".
   1126 				"compressed file $_[0]!\n");
   1127 
   1128 		# Open compressed file
   1129 		open(INFO_HANDLE, "gunzip -c $_[0]|")
   1130 			or die("ERROR: cannot start gunzip to decompress ".
   1131 			       "file $_[0]!\n");
   1132 	}
   1133 	else
   1134 	{
   1135 		# Open decompressed file
   1136 		open(INFO_HANDLE, $_[0])
   1137 			or die("ERROR: cannot read file $_[0]!\n");
   1138 	}
   1139 
   1140 	$testname = "";
   1141 	while (<INFO_HANDLE>)
   1142 	{
   1143 		chomp($_);
   1144 		$line = $_;
   1145 
   1146 		# Switch statement
   1147 		foreach ($line)
   1148 		{
   1149 			/^TN:([^,]*)/ && do
   1150 			{
   1151 				# Test name information found
   1152 				$testname = defined($1) ? $1 : "";
   1153 				if ($testname =~ s/\W/_/g)
   1154 				{
   1155 					$changed_testname = 1;
   1156 				}
   1157 				last;
   1158 			};
   1159 
   1160 			/^[SK]F:(.*)/ && do
   1161 			{
   1162 				# Filename information found
   1163 				# Retrieve data for new entry
   1164 				$filename = $1;
   1165 
   1166 				$data = $result{$filename};
   1167 				($testdata, $sumcount, $funcdata, $checkdata,
   1168 				 $testfncdata, $sumfnccount) =
   1169 					get_info_entry($data);
   1170 
   1171 				if (defined($testname))
   1172 				{
   1173 					$testcount = $testdata->{$testname};
   1174 					$testfnccount = $testfncdata->{$testname};
   1175 				}
   1176 				else
   1177 				{
   1178 					$testcount = {};
   1179 					$testfnccount = {};
   1180 				}
   1181 				last;
   1182 			};
   1183 
   1184 			/^DA:(\d+),(-?\d+)(,[^,\s]+)?/ && do
   1185 			{
   1186 				# Fix negative counts
   1187 				$count = $2 < 0 ? 0 : $2;
   1188 				if ($2 < 0)
   1189 				{
   1190 					$negative = 1;
   1191 				}
   1192 				# Execution count found, add to structure
   1193 				# Add summary counts
   1194 				$sumcount->{$1} += $count;
   1195 
   1196 				# Add test-specific counts
   1197 				if (defined($testname))
   1198 				{
   1199 					$testcount->{$1} += $count;
   1200 				}
   1201 
   1202 				# Store line checksum if available
   1203 				if (defined($3))
   1204 				{
   1205 					$line_checksum = substr($3, 1);
   1206 
   1207 					# Does it match a previous definition
   1208 					if (defined($checkdata->{$1}) &&
   1209 					    ($checkdata->{$1} ne
   1210 					     $line_checksum))
   1211 					{
   1212 						die("ERROR: checksum mismatch ".
   1213 						    "at $filename:$1\n");
   1214 					}
   1215 
   1216 					$checkdata->{$1} = $line_checksum;
   1217 				}
   1218 				last;
   1219 			};
   1220 
   1221 			/^FN:(\d+),([^,]+)/ && do
   1222 			{
   1223 				# Function data found, add to structure
   1224 				$funcdata->{$2} = $1;
   1225 
   1226 				# Also initialize function call data
   1227 				if (!defined($sumfnccount->{$2})) {
   1228 					$sumfnccount->{$2} = 0;
   1229 				}
   1230 				if (defined($testname))
   1231 				{
   1232 					if (!defined($testfnccount->{$2})) {
   1233 						$testfnccount->{$2} = 0;
   1234 					}
   1235 				}
   1236 				last;
   1237 			};
   1238 
   1239 			/^FNDA:(\d+),([^,]+)/ && do
   1240 			{
   1241 				# Function call count found, add to structure
   1242 				# Add summary counts
   1243 				$sumfnccount->{$2} += $1;
   1244 
   1245 				# Add test-specific counts
   1246 				if (defined($testname))
   1247 				{
   1248 					$testfnccount->{$2} += $1;
   1249 				}
   1250 				last;
   1251 			};
   1252 			/^end_of_record/ && do
   1253 			{
   1254 				# Found end of section marker
   1255 				if ($filename)
   1256 				{
   1257 					# Store current section data
   1258 					if (defined($testname))
   1259 					{
   1260 						$testdata->{$testname} =
   1261 							$testcount;
   1262 						$testfncdata->{$testname} =
   1263 							$testfnccount;
   1264 					}	
   1265 
   1266 					set_info_entry($data, $testdata,
   1267 						       $sumcount, $funcdata,
   1268 						       $checkdata, $testfncdata,
   1269 						       $sumfnccount);
   1270 					$result{$filename} = $data;
   1271 					last;
   1272 				}
   1273 			};
   1274 
   1275 			# default
   1276 			last;
   1277 		}
   1278 	}
   1279 	close(INFO_HANDLE);
   1280 
   1281 	# Calculate lines_found and lines_hit for each file
   1282 	foreach $filename (keys(%result))
   1283 	{
   1284 		$data = $result{$filename};
   1285 
   1286 		($testdata, $sumcount, undef, undef, $testfncdata,
   1287 		 $sumfnccount) = get_info_entry($data);
   1288 
   1289 		# Filter out empty files
   1290 		if (scalar(keys(%{$sumcount})) == 0)
   1291 		{
   1292 			delete($result{$filename});
   1293 			next;
   1294 		}
   1295 		# Filter out empty test cases
   1296 		foreach $testname (keys(%{$testdata}))
   1297 		{
   1298 			if (!defined($testdata->{$testname}) ||
   1299 			    scalar(keys(%{$testdata->{$testname}})) == 0)
   1300 			{
   1301 				delete($testdata->{$testname});
   1302 				delete($testfncdata->{$testname});
   1303 			}
   1304 		}
   1305 
   1306 		$data->{"found"} = scalar(keys(%{$sumcount}));
   1307 		$hitcount = 0;
   1308 
   1309 		foreach (keys(%{$sumcount}))
   1310 		{
   1311 			if ($sumcount->{$_} > 0) { $hitcount++; }
   1312 		}
   1313 
   1314 		$data->{"hit"} = $hitcount;
   1315 
   1316 		# Get found/hit values for function call data
   1317 		$data->{"f_found"} = scalar(keys(%{$sumfnccount}));
   1318 		$hitcount = 0;
   1319 
   1320 		foreach (keys(%{$sumfnccount})) {
   1321 			if ($sumfnccount->{$_} > 0) {
   1322 				$hitcount++;
   1323 			}
   1324 		}
   1325 		$data->{"f_hit"} = $hitcount;
   1326 	}
   1327 
   1328 	if (scalar(keys(%result)) == 0)
   1329 	{
   1330 		die("ERROR: no valid records found in tracefile $tracefile\n");
   1331 	}
   1332 	if ($negative)
   1333 	{
   1334 		warn("WARNING: negative counts found in tracefile ".
   1335 		     "$tracefile\n");
   1336 	}
   1337 	if ($changed_testname)
   1338 	{
   1339 		warn("WARNING: invalid characters removed from testname in ".
   1340 		     "tracefile $tracefile\n");
   1341 	}
   1342 
   1343 	return(\%result);
   1344 }
   1345 
   1346 
   1347 #
   1348 # get_info_entry(hash_ref)
   1349 #
   1350 # Retrieve data from an entry of the structure generated by read_info_file().
   1351 # Return a list of references to hashes:
   1352 # (test data hash ref, sum count hash ref, funcdata hash ref, checkdata hash
   1353 #  ref, testfncdata hash ref, sumfnccount hash ref, lines found, lines hit,
   1354 #  functions found, functions hit)
   1355 #
   1356 
   1357 sub get_info_entry($)
   1358 {
   1359 	my $testdata_ref = $_[0]->{"test"};
   1360 	my $sumcount_ref = $_[0]->{"sum"};
   1361 	my $funcdata_ref = $_[0]->{"func"};
   1362 	my $checkdata_ref = $_[0]->{"check"};
   1363 	my $testfncdata = $_[0]->{"testfnc"};
   1364 	my $sumfnccount = $_[0]->{"sumfnc"};
   1365 	my $lines_found = $_[0]->{"found"};
   1366 	my $lines_hit = $_[0]->{"hit"};
   1367 	my $fn_found = $_[0]->{"f_found"};
   1368 	my $fn_hit = $_[0]->{"f_hit"};
   1369 
   1370 	return ($testdata_ref, $sumcount_ref, $funcdata_ref, $checkdata_ref,
   1371 		$testfncdata, $sumfnccount, $lines_found, $lines_hit,
   1372 		$fn_found, $fn_hit);
   1373 }
   1374 
   1375 
   1376 #
   1377 # set_info_entry(hash_ref, testdata_ref, sumcount_ref, funcdata_ref,
   1378 #                checkdata_ref, testfncdata_ref, sumfcncount_ref[,lines_found,
   1379 #                lines_hit, f_found, f_hit])
   1380 #
   1381 # Update the hash referenced by HASH_REF with the provided data references.
   1382 #
   1383 
   1384 sub set_info_entry($$$$$$$;$$$$)
   1385 {
   1386 	my $data_ref = $_[0];
   1387 
   1388 	$data_ref->{"test"} = $_[1];
   1389 	$data_ref->{"sum"} = $_[2];
   1390 	$data_ref->{"func"} = $_[3];
   1391 	$data_ref->{"check"} = $_[4];
   1392 	$data_ref->{"testfnc"} = $_[5];
   1393 	$data_ref->{"sumfnc"} = $_[6];
   1394 
   1395 	if (defined($_[7])) { $data_ref->{"found"} = $_[7]; }
   1396 	if (defined($_[8])) { $data_ref->{"hit"} = $_[8]; }
   1397 	if (defined($_[9])) { $data_ref->{"f_found"} = $_[9]; }
   1398 	if (defined($_[10])) { $data_ref->{"f_hit"} = $_[10]; }
   1399 }
   1400 
   1401 
   1402 #
   1403 # add_counts(data1_ref, data2_ref)
   1404 #
   1405 # DATA1_REF and DATA2_REF are references to hashes containing a mapping
   1406 #
   1407 #   line number -> execution count
   1408 #
   1409 # Return a list (RESULT_REF, LINES_FOUND, LINES_HIT) where RESULT_REF
   1410 # is a reference to a hash containing the combined mapping in which
   1411 # execution counts are added.
   1412 #
   1413 
   1414 sub add_counts($$)
   1415 {
   1416 	my %data1 = %{$_[0]};	# Hash 1
   1417 	my %data2 = %{$_[1]};	# Hash 2
   1418 	my %result;		# Resulting hash
   1419 	my $line;		# Current line iteration scalar
   1420 	my $data1_count;	# Count of line in hash1
   1421 	my $data2_count;	# Count of line in hash2
   1422 	my $found = 0;		# Total number of lines found
   1423 	my $hit = 0;		# Number of lines with a count > 0
   1424 
   1425 	foreach $line (keys(%data1))
   1426 	{
   1427 		$data1_count = $data1{$line};
   1428 		$data2_count = $data2{$line};
   1429 
   1430 		# Add counts if present in both hashes
   1431 		if (defined($data2_count)) { $data1_count += $data2_count; }
   1432 
   1433 		# Store sum in %result
   1434 		$result{$line} = $data1_count;
   1435 
   1436 		$found++;
   1437 		if ($data1_count > 0) { $hit++; }
   1438 	}
   1439 
   1440 	# Add lines unique to data2
   1441 	foreach $line (keys(%data2))
   1442 	{
   1443 		# Skip lines already in data1
   1444 		if (defined($data1{$line})) { next; }
   1445 
   1446 		# Copy count from data2
   1447 		$result{$line} = $data2{$line};
   1448 
   1449 		$found++;
   1450 		if ($result{$line} > 0) { $hit++; }
   1451 	}
   1452 
   1453 	return (\%result, $found, $hit);
   1454 }
   1455 
   1456 
   1457 #
   1458 # merge_checksums(ref1, ref2, filename)
   1459 #
   1460 # REF1 and REF2 are references to hashes containing a mapping
   1461 #
   1462 #   line number -> checksum
   1463 #
   1464 # Merge checksum lists defined in REF1 and REF2 and return reference to
   1465 # resulting hash. Die if a checksum for a line is defined in both hashes
   1466 # but does not match.
   1467 #
   1468 
   1469 sub merge_checksums($$$)
   1470 {
   1471 	my $ref1 = $_[0];
   1472 	my $ref2 = $_[1];
   1473 	my $filename = $_[2];
   1474 	my %result;
   1475 	my $line;
   1476 
   1477 	foreach $line (keys(%{$ref1}))
   1478 	{
   1479 		if (defined($ref2->{$line}) &&
   1480 		    ($ref1->{$line} ne $ref2->{$line}))
   1481 		{
   1482 			die("ERROR: checksum mismatch at $filename:$line\n");
   1483 		}
   1484 		$result{$line} = $ref1->{$line};
   1485 	}
   1486 
   1487 	foreach $line (keys(%{$ref2}))
   1488 	{
   1489 		$result{$line} = $ref2->{$line};
   1490 	}
   1491 
   1492 	return \%result;
   1493 }
   1494 
   1495 
   1496 #
   1497 # merge_func_data(funcdata1, funcdata2, filename)
   1498 #
   1499 
   1500 sub merge_func_data($$$)
   1501 {
   1502 	my ($funcdata1, $funcdata2, $filename) = @_;
   1503 	my %result;
   1504 	my $func;
   1505 
   1506 	%result = %{$funcdata1};
   1507 
   1508 	foreach $func (keys(%{$funcdata2})) {
   1509 		my $line1 = $result{$func};
   1510 		my $line2 = $funcdata2->{$func};
   1511 
   1512 		if (defined($line1) && ($line1 != $line2)) {
   1513 			warn("WARNING: function data mismatch at ".
   1514 			     "$filename:$line2\n");
   1515 			next;
   1516 		}
   1517 		$result{$func} = $line2;
   1518 	}
   1519 
   1520 	return \%result;
   1521 }
   1522 
   1523 
   1524 #
   1525 # add_fnccount(fnccount1, fnccount2)
   1526 #
   1527 # Add function call count data. Return list (fnccount_added, f_found, f_hit)
   1528 #
   1529 
   1530 sub add_fnccount($$)
   1531 {
   1532 	my ($fnccount1, $fnccount2) = @_;
   1533 	my %result;
   1534 	my $fn_found;
   1535 	my $fn_hit;
   1536 	my $function;
   1537 
   1538 	%result = %{$fnccount1};
   1539 	foreach $function (keys(%{$fnccount2})) {
   1540 		$result{$function} += $fnccount2->{$function};
   1541 	}
   1542 	$fn_found = scalar(keys(%result));
   1543 	$fn_hit = 0;
   1544 	foreach $function (keys(%result)) {
   1545 		if ($result{$function} > 0) {
   1546 			$fn_hit++;
   1547 		}
   1548 	}
   1549 
   1550 	return (\%result, $fn_found, $fn_hit);
   1551 }
   1552 
   1553 #
   1554 # add_testfncdata(testfncdata1, testfncdata2)
   1555 #
   1556 # Add function call count data for several tests. Return reference to
   1557 # added_testfncdata.
   1558 #
   1559 
   1560 sub add_testfncdata($$)
   1561 {
   1562 	my ($testfncdata1, $testfncdata2) = @_;
   1563 	my %result;
   1564 	my $testname;
   1565 
   1566 	foreach $testname (keys(%{$testfncdata1})) {
   1567 		if (defined($testfncdata2->{$testname})) {
   1568 			my $fnccount;
   1569 
   1570 			# Function call count data for this testname exists
   1571 			# in both data sets: add
   1572 			($fnccount) = add_fnccount(
   1573 				$testfncdata1->{$testname},
   1574 				$testfncdata2->{$testname});
   1575 			$result{$testname} = $fnccount;
   1576 			next;
   1577 		}
   1578 		# Function call count data for this testname is unique to
   1579 		# data set 1: copy
   1580 		$result{$testname} = $testfncdata1->{$testname};
   1581 	}
   1582 
   1583 	# Add count data for testnames unique to data set 2
   1584 	foreach $testname (keys(%{$testfncdata2})) {
   1585 		if (!defined($result{$testname})) {
   1586 			$result{$testname} = $testfncdata2->{$testname};
   1587 		}
   1588 	}
   1589 	return \%result;
   1590 }
   1591 
   1592 #
   1593 # combine_info_entries(entry_ref1, entry_ref2, filename)
   1594 #
   1595 # Combine .info data entry hashes referenced by ENTRY_REF1 and ENTRY_REF2.
   1596 # Return reference to resulting hash.
   1597 #
   1598 
   1599 sub combine_info_entries($$$)
   1600 {
   1601 	my $entry1 = $_[0];	# Reference to hash containing first entry
   1602 	my $testdata1;
   1603 	my $sumcount1;
   1604 	my $funcdata1;
   1605 	my $checkdata1;
   1606 	my $testfncdata1;
   1607 	my $sumfnccount1;
   1608 
   1609 	my $entry2 = $_[1];	# Reference to hash containing second entry
   1610 	my $testdata2;
   1611 	my $sumcount2;
   1612 	my $funcdata2;
   1613 	my $checkdata2;
   1614 	my $testfncdata2;
   1615 	my $sumfnccount2;
   1616 
   1617 	my %result;		# Hash containing combined entry
   1618 	my %result_testdata;
   1619 	my $result_sumcount = {};
   1620 	my $result_funcdata;
   1621 	my $result_testfncdata;
   1622 	my $result_sumfnccount;
   1623 	my $lines_found;
   1624 	my $lines_hit;
   1625 	my $fn_found;
   1626 	my $fn_hit;
   1627 
   1628 	my $testname;
   1629 	my $filename = $_[2];
   1630 
   1631 	# Retrieve data
   1632 	($testdata1, $sumcount1, $funcdata1, $checkdata1, $testfncdata1,
   1633 	 $sumfnccount1) = get_info_entry($entry1);
   1634 	($testdata2, $sumcount2, $funcdata2, $checkdata2, $testfncdata2,
   1635 	 $sumfnccount2) = get_info_entry($entry2);
   1636 
   1637 	# Merge checksums
   1638 	$checkdata1 = merge_checksums($checkdata1, $checkdata2, $filename);
   1639 
   1640 	# Combine funcdata
   1641 	$result_funcdata = merge_func_data($funcdata1, $funcdata2, $filename);
   1642 
   1643 	# Combine function call count data
   1644 	$result_testfncdata = add_testfncdata($testfncdata1, $testfncdata2);
   1645 	($result_sumfnccount, $fn_found, $fn_hit) =
   1646 		add_fnccount($sumfnccount1, $sumfnccount2);
   1647 	
   1648 	# Combine testdata
   1649 	foreach $testname (keys(%{$testdata1}))
   1650 	{
   1651 		if (defined($testdata2->{$testname}))
   1652 		{
   1653 			# testname is present in both entries, requires
   1654 			# combination
   1655 			($result_testdata{$testname}) =
   1656 				add_counts($testdata1->{$testname},
   1657 					   $testdata2->{$testname});
   1658 		}
   1659 		else
   1660 		{
   1661 			# testname only present in entry1, add to result
   1662 			$result_testdata{$testname} = $testdata1->{$testname};
   1663 		}
   1664 
   1665 		# update sum count hash
   1666 		($result_sumcount, $lines_found, $lines_hit) =
   1667 			add_counts($result_sumcount,
   1668 				   $result_testdata{$testname});
   1669 	}
   1670 
   1671 	foreach $testname (keys(%{$testdata2}))
   1672 	{
   1673 		# Skip testnames already covered by previous iteration
   1674 		if (defined($testdata1->{$testname})) { next; }
   1675 
   1676 		# testname only present in entry2, add to result hash
   1677 		$result_testdata{$testname} = $testdata2->{$testname};
   1678 
   1679 		# update sum count hash
   1680 		($result_sumcount, $lines_found, $lines_hit) =
   1681 			add_counts($result_sumcount,
   1682 				   $result_testdata{$testname});
   1683 	}
   1684 	
   1685 	# Calculate resulting sumcount
   1686 
   1687 	# Store result
   1688 	set_info_entry(\%result, \%result_testdata, $result_sumcount,
   1689 		       $result_funcdata, $checkdata1, $result_testfncdata,
   1690 		       $result_sumfnccount, $lines_found, $lines_hit,
   1691 		       $fn_found, $fn_hit);
   1692 
   1693 	return(\%result);
   1694 }
   1695 
   1696 
   1697 #
   1698 # combine_info_files(info_ref1, info_ref2)
   1699 #
   1700 # Combine .info data in hashes referenced by INFO_REF1 and INFO_REF2. Return
   1701 # reference to resulting hash.
   1702 #
   1703 
   1704 sub combine_info_files($$)
   1705 {
   1706 	my %hash1 = %{$_[0]};
   1707 	my %hash2 = %{$_[1]};
   1708 	my $filename;
   1709 
   1710 	foreach $filename (keys(%hash2))
   1711 	{
   1712 		if ($hash1{$filename})
   1713 		{
   1714 			# Entry already exists in hash1, combine them
   1715 			$hash1{$filename} =
   1716 				combine_info_entries($hash1{$filename},
   1717 						     $hash2{$filename},
   1718 						     $filename);
   1719 		}
   1720 		else
   1721 		{
   1722 			# Entry is unique in both hashes, simply add to
   1723 			# resulting hash
   1724 			$hash1{$filename} = $hash2{$filename};
   1725 		}
   1726 	}
   1727 
   1728 	return(\%hash1);
   1729 }
   1730 
   1731 
   1732 #
   1733 # get_prefix(filename_list)
   1734 #
   1735 # Search FILENAME_LIST for a directory prefix which is common to as many
   1736 # list entries as possible, so that removing this prefix will minimize the
   1737 # sum of the lengths of all resulting shortened filenames.
   1738 #
   1739 
   1740 sub get_prefix(@)
   1741 {
   1742 	my @filename_list = @_;		# provided list of filenames
   1743 	my %prefix;			# mapping: prefix -> sum of lengths
   1744 	my $current;			# Temporary iteration variable
   1745 
   1746 	# Find list of prefixes
   1747 	foreach (@filename_list)
   1748 	{
   1749 		# Need explicit assignment to get a copy of $_ so that
   1750 		# shortening the contained prefix does not affect the list
   1751 		$current = shorten_prefix($_);
   1752 		while ($current = shorten_prefix($current))
   1753 		{
   1754 			# Skip rest if the remaining prefix has already been
   1755 			# added to hash
   1756 			if ($prefix{$current}) { last; }
   1757 
   1758 			# Initialize with 0
   1759 			$prefix{$current}="0";
   1760 		}
   1761 
   1762 	}
   1763 
   1764 	# Calculate sum of lengths for all prefixes
   1765 	foreach $current (keys(%prefix))
   1766 	{
   1767 		foreach (@filename_list)
   1768 		{
   1769 			# Add original length
   1770 			$prefix{$current} += length($_);
   1771 
   1772 			# Check whether prefix matches
   1773 			if (substr($_, 0, length($current)) eq $current)
   1774 			{
   1775 				# Subtract prefix length for this filename
   1776 				$prefix{$current} -= length($current);
   1777 			}
   1778 		}
   1779 	}
   1780 
   1781 	# Find and return prefix with minimal sum
   1782 	$current = (keys(%prefix))[0];
   1783 
   1784 	foreach (keys(%prefix))
   1785 	{
   1786 		if ($prefix{$_} < $prefix{$current})
   1787 		{
   1788 			$current = $_;
   1789 		}
   1790 	}
   1791 
   1792 	return($current);
   1793 }
   1794 
   1795 
   1796 #
   1797 # shorten_prefix(prefix)
   1798 #
   1799 # Return PREFIX shortened by last directory component.
   1800 #
   1801 
   1802 sub shorten_prefix($)
   1803 {
   1804 	my @list = split("/", $_[0]);
   1805 
   1806 	pop(@list);
   1807 	return join("/", @list);
   1808 }
   1809 
   1810 
   1811 
   1812 #
   1813 # get_dir_list(filename_list)
   1814 #
   1815 # Return sorted list of directories for each entry in given FILENAME_LIST.
   1816 #
   1817 
   1818 sub get_dir_list(@)
   1819 {
   1820 	my %result;
   1821 
   1822 	foreach (@_)
   1823 	{
   1824 		$result{shorten_prefix($_)} = "";
   1825 	}
   1826 
   1827 	return(sort(keys(%result)));
   1828 }
   1829 
   1830 
   1831 #
   1832 # get_relative_base_path(subdirectory)
   1833 #
   1834 # Return a relative path string which references the base path when applied
   1835 # in SUBDIRECTORY.
   1836 #
   1837 # Example: get_relative_base_path("fs/mm") -> "../../"
   1838 #
   1839 
   1840 sub get_relative_base_path($)
   1841 {
   1842 	my $result = "";
   1843 	my $index;
   1844 
   1845 	# Make an empty directory path a special case
   1846 	if (!$_[0]) { return(""); }
   1847 
   1848 	# Count number of /s in path
   1849 	$index = ($_[0] =~ s/\//\//g);
   1850 
   1851 	# Add a ../ to $result for each / in the directory path + 1
   1852 	for (; $index>=0; $index--)
   1853 	{
   1854 		$result .= "../";
   1855 	}
   1856 
   1857 	return $result;
   1858 }
   1859 
   1860 
   1861 #
   1862 # read_testfile(test_filename)
   1863 #
   1864 # Read in file TEST_FILENAME which contains test descriptions in the format:
   1865 #
   1866 #   TN:<whitespace><test name>
   1867 #   TD:<whitespace><test description>
   1868 #
   1869 # for each test case. Return a reference to a hash containing a mapping
   1870 #
   1871 #   test name -> test description.
   1872 #
   1873 # Die on error.
   1874 #
   1875 
   1876 sub read_testfile($)
   1877 {
   1878 	my %result;
   1879 	my $test_name;
   1880 	my $changed_testname;
   1881 	local *TEST_HANDLE;
   1882 
   1883 	open(TEST_HANDLE, "<".$_[0])
   1884 		or die("ERROR: cannot open $_[0]!\n");
   1885 
   1886 	while (<TEST_HANDLE>)
   1887 	{
   1888 		chomp($_);
   1889 
   1890 		# Match lines beginning with TN:<whitespace(s)>
   1891 		if (/^TN:\s+(.*?)\s*$/)
   1892 		{
   1893 			# Store name for later use
   1894 			$test_name = $1;
   1895 			if ($test_name =~ s/\W/_/g)
   1896 			{
   1897 				$changed_testname = 1;
   1898 			}
   1899 		}
   1900 
   1901 		# Match lines beginning with TD:<whitespace(s)>
   1902 		if (/^TD:\s+(.*?)\s*$/)
   1903 		{
   1904 			# Check for empty line
   1905 			if ($1)
   1906 			{
   1907 				# Add description to hash
   1908 				$result{$test_name} .= " $1";
   1909 			}
   1910 			else
   1911 			{
   1912 				# Add empty line
   1913 				$result{$test_name} .= "\n\n";
   1914 			}
   1915 		}
   1916 	}
   1917 
   1918 	close(TEST_HANDLE);
   1919 
   1920 	if ($changed_testname)
   1921 	{
   1922 		warn("WARNING: invalid characters removed from testname in ".
   1923 		     "descriptions file $_[0]\n");
   1924 	}
   1925 
   1926 	return \%result;
   1927 }
   1928 
   1929 
   1930 #
   1931 # escape_html(STRING)
   1932 #
   1933 # Return a copy of STRING in which all occurrences of HTML special characters
   1934 # are escaped.
   1935 #
   1936 
   1937 sub escape_html($)
   1938 {
   1939 	my $string = $_[0];
   1940 
   1941 	if (!$string) { return ""; }
   1942 
   1943 	$string =~ s/&/&amp;/g;		# & -> &amp;
   1944 	$string =~ s/</&lt;/g;		# < -> &lt;
   1945 	$string =~ s/>/&gt;/g;		# > -> &gt;
   1946 	$string =~ s/\"/&quot;/g;	# " -> &quot;
   1947 
   1948 	while ($string =~ /^([^\t]*)(\t)/)
   1949 	{
   1950 		my $replacement = " "x($tab_size - (length($1) % $tab_size));
   1951 		$string =~ s/^([^\t]*)(\t)/$1$replacement/;
   1952 	}
   1953 
   1954 	$string =~ s/\n/<br>/g;		# \n -> <br>
   1955 
   1956 	return $string;
   1957 }
   1958 
   1959 
   1960 #
   1961 # get_date_string()
   1962 #
   1963 # Return the current date in the form: yyyy-mm-dd
   1964 #
   1965 
   1966 sub get_date_string()
   1967 {
   1968 	my $year;
   1969 	my $month;
   1970 	my $day;
   1971 
   1972 	($year, $month, $day) = (localtime())[5, 4, 3];
   1973 
   1974 	return sprintf("%d-%02d-%02d", $year+1900, $month+1, $day);
   1975 }
   1976 
   1977 
   1978 #
   1979 # create_sub_dir(dir_name)
   1980 #
   1981 # Create subdirectory DIR_NAME if it does not already exist, including all its
   1982 # parent directories.
   1983 #
   1984 # Die on error.
   1985 #
   1986 
   1987 sub create_sub_dir($)
   1988 {
   1989 	system("mkdir", "-p" ,$_[0])
   1990 		and die("ERROR: cannot create directory $_!\n");
   1991 }
   1992 
   1993 
   1994 #
   1995 # write_description_file(descriptions, overall_found, overall_hit,
   1996 #                        total_fn_found, total_fn_hit)
   1997 #
   1998 # Write HTML file containing all test case descriptions. DESCRIPTIONS is a
   1999 # reference to a hash containing a mapping
   2000 #
   2001 #   test case name -> test case description
   2002 #
   2003 # Die on error.
   2004 #
   2005 
   2006 sub write_description_file($$$$$)
   2007 {
   2008 	my %description = %{$_[0]};
   2009 	my $found = $_[1];
   2010 	my $hit = $_[2];
   2011 	my $fn_found = $_[3];
   2012 	my $fn_hit = $_[4];
   2013 	my $test_name;
   2014 	local *HTML_HANDLE;
   2015 
   2016 	html_create(*HTML_HANDLE,"descriptions.$html_ext");
   2017 	write_html_prolog(*HTML_HANDLE, "", "LCOV - test case descriptions");
   2018 	write_header(*HTML_HANDLE, 3, "", "", $found, $hit, $fn_found,
   2019 		     $fn_hit, 0);
   2020 
   2021 	write_test_table_prolog(*HTML_HANDLE,
   2022 			 "Test case descriptions - alphabetical list");
   2023 
   2024 	foreach $test_name (sort(keys(%description)))
   2025 	{
   2026 		write_test_table_entry(*HTML_HANDLE, $test_name,
   2027 				       escape_html($description{$test_name}));
   2028 	}
   2029 
   2030 	write_test_table_epilog(*HTML_HANDLE);
   2031 	write_html_epilog(*HTML_HANDLE, "");
   2032 
   2033 	close(*HTML_HANDLE);
   2034 }
   2035 
   2036 
   2037 
   2038 #
   2039 # write_png_files()
   2040 #
   2041 # Create all necessary .png files for the HTML-output in the current
   2042 # directory. .png-files are used as bar graphs.
   2043 #
   2044 # Die on error.
   2045 #
   2046 
   2047 sub write_png_files()
   2048 {
   2049 	my %data;
   2050 	local *PNG_HANDLE;
   2051 
   2052 	$data{"ruby.png"} =
   2053 		[0x89, 0x50, 0x4e, 0x47, 0x0d, 0x0a, 0x1a, 0x0a, 0x00, 0x00, 
   2054 		 0x00, 0x0d, 0x49, 0x48, 0x44, 0x52, 0x00, 0x00, 0x00, 0x01, 
   2055 		 0x00, 0x00, 0x00, 0x01, 0x01, 0x03, 0x00, 0x00, 0x00, 0x25, 
   2056 		 0xdb, 0x56, 0xca, 0x00, 0x00, 0x00, 0x07, 0x74, 0x49, 0x4d, 
   2057 		 0x45, 0x07, 0xd2, 0x07, 0x11, 0x0f, 0x18, 0x10, 0x5d, 0x57, 
   2058 		 0x34, 0x6e, 0x00, 0x00, 0x00, 0x09, 0x70, 0x48, 0x59, 0x73, 
   2059 		 0x00, 0x00, 0x0b, 0x12, 0x00, 0x00, 0x0b, 0x12, 0x01, 0xd2, 
   2060 		 0xdd, 0x7e, 0xfc, 0x00, 0x00, 0x00, 0x04, 0x67, 0x41, 0x4d, 
   2061 		 0x41, 0x00, 0x00, 0xb1, 0x8f, 0x0b, 0xfc, 0x61, 0x05, 0x00, 
   2062 		 0x00, 0x00, 0x06, 0x50, 0x4c, 0x54, 0x45, 0xff, 0x35, 0x2f, 
   2063 		 0x00, 0x00, 0x00, 0xd0, 0x33, 0x9a, 0x9d, 0x00, 0x00, 0x00, 
   2064 		 0x0a, 0x49, 0x44, 0x41, 0x54, 0x78, 0xda, 0x63, 0x60, 0x00, 
   2065 		 0x00, 0x00, 0x02, 0x00, 0x01, 0xe5, 0x27, 0xde, 0xfc, 0x00, 
   2066 		 0x00, 0x00, 0x00, 0x49, 0x45, 0x4e, 0x44, 0xae, 0x42, 0x60, 
   2067 		 0x82];
   2068 	$data{"amber.png"} =
   2069 		[0x89, 0x50, 0x4e, 0x47, 0x0d, 0x0a, 0x1a, 0x0a, 0x00, 0x00, 
   2070 		 0x00, 0x0d, 0x49, 0x48, 0x44, 0x52, 0x00, 0x00, 0x00, 0x01, 
   2071 		 0x00, 0x00, 0x00, 0x01, 0x01, 0x03, 0x00, 0x00, 0x00, 0x25, 
   2072 		 0xdb, 0x56, 0xca, 0x00, 0x00, 0x00, 0x07, 0x74, 0x49, 0x4d, 
   2073 		 0x45, 0x07, 0xd2, 0x07, 0x11, 0x0f, 0x28, 0x04, 0x98, 0xcb, 
   2074 		 0xd6, 0xe0, 0x00, 0x00, 0x00, 0x09, 0x70, 0x48, 0x59, 0x73, 
   2075 		 0x00, 0x00, 0x0b, 0x12, 0x00, 0x00, 0x0b, 0x12, 0x01, 0xd2, 
   2076 		 0xdd, 0x7e, 0xfc, 0x00, 0x00, 0x00, 0x04, 0x67, 0x41, 0x4d, 
   2077 		 0x41, 0x00, 0x00, 0xb1, 0x8f, 0x0b, 0xfc, 0x61, 0x05, 0x00, 
   2078 		 0x00, 0x00, 0x06, 0x50, 0x4c, 0x54, 0x45, 0xff, 0xe0, 0x50, 
   2079 		 0x00, 0x00, 0x00, 0xa2, 0x7a, 0xda, 0x7e, 0x00, 0x00, 0x00, 
   2080 		 0x0a, 0x49, 0x44, 0x41, 0x54, 0x78, 0xda, 0x63, 0x60, 0x00, 
   2081 	  	 0x00, 0x00, 0x02, 0x00, 0x01, 0xe5, 0x27, 0xde, 0xfc, 0x00, 
   2082 		 0x00, 0x00, 0x00, 0x49, 0x45, 0x4e, 0x44, 0xae, 0x42, 0x60, 
   2083 		 0x82];
   2084 	$data{"emerald.png"} =
   2085 		[0x89, 0x50, 0x4e, 0x47, 0x0d, 0x0a, 0x1a, 0x0a, 0x00, 0x00, 
   2086 		 0x00, 0x0d, 0x49, 0x48, 0x44, 0x52, 0x00, 0x00, 0x00, 0x01, 
   2087 		 0x00, 0x00, 0x00, 0x01, 0x01, 0x03, 0x00, 0x00, 0x00, 0x25, 
   2088 		 0xdb, 0x56, 0xca, 0x00, 0x00, 0x00, 0x07, 0x74, 0x49, 0x4d, 
   2089 		 0x45, 0x07, 0xd2, 0x07, 0x11, 0x0f, 0x22, 0x2b, 0xc9, 0xf5, 
   2090 		 0x03, 0x33, 0x00, 0x00, 0x00, 0x09, 0x70, 0x48, 0x59, 0x73, 
   2091 		 0x00, 0x00, 0x0b, 0x12, 0x00, 0x00, 0x0b, 0x12, 0x01, 0xd2, 
   2092 		 0xdd, 0x7e, 0xfc, 0x00, 0x00, 0x00, 0x04, 0x67, 0x41, 0x4d, 
   2093 		 0x41, 0x00, 0x00, 0xb1, 0x8f, 0x0b, 0xfc, 0x61, 0x05, 0x00, 
   2094 		 0x00, 0x00, 0x06, 0x50, 0x4c, 0x54, 0x45, 0x1b, 0xea, 0x59, 
   2095 		 0x0a, 0x0a, 0x0a, 0x0f, 0xba, 0x50, 0x83, 0x00, 0x00, 0x00, 
   2096 		 0x0a, 0x49, 0x44, 0x41, 0x54, 0x78, 0xda, 0x63, 0x60, 0x00, 
   2097 		 0x00, 0x00, 0x02, 0x00, 0x01, 0xe5, 0x27, 0xde, 0xfc, 0x00, 
   2098 		 0x00, 0x00, 0x00, 0x49, 0x45, 0x4e, 0x44, 0xae, 0x42, 0x60, 
   2099 		 0x82];
   2100 	$data{"snow.png"} =
   2101 		[0x89, 0x50, 0x4e, 0x47, 0x0d, 0x0a, 0x1a, 0x0a, 0x00, 0x00, 
   2102 		 0x00, 0x0d, 0x49, 0x48, 0x44, 0x52, 0x00, 0x00, 0x00, 0x01, 
   2103 		 0x00, 0x00, 0x00, 0x01, 0x01, 0x03, 0x00, 0x00, 0x00, 0x25, 
   2104 		 0xdb, 0x56, 0xca, 0x00, 0x00, 0x00, 0x07, 0x74, 0x49, 0x4d, 
   2105 		 0x45, 0x07, 0xd2, 0x07, 0x11, 0x0f, 0x1e, 0x1d, 0x75, 0xbc, 
   2106 		 0xef, 0x55, 0x00, 0x00, 0x00, 0x09, 0x70, 0x48, 0x59, 0x73, 
   2107 		 0x00, 0x00, 0x0b, 0x12, 0x00, 0x00, 0x0b, 0x12, 0x01, 0xd2, 
   2108 		 0xdd, 0x7e, 0xfc, 0x00, 0x00, 0x00, 0x04, 0x67, 0x41, 0x4d, 
   2109 		 0x41, 0x00, 0x00, 0xb1, 0x8f, 0x0b, 0xfc, 0x61, 0x05, 0x00, 
   2110 		 0x00, 0x00, 0x06, 0x50, 0x4c, 0x54, 0x45, 0xff, 0xff, 0xff, 
   2111 		 0x00, 0x00, 0x00, 0x55, 0xc2, 0xd3, 0x7e, 0x00, 0x00, 0x00, 
   2112 		 0x0a, 0x49, 0x44, 0x41, 0x54, 0x78, 0xda, 0x63, 0x60, 0x00, 
   2113 		 0x00, 0x00, 0x02, 0x00, 0x01, 0xe5, 0x27, 0xde, 0xfc, 0x00, 
   2114 		 0x00, 0x00, 0x00, 0x49, 0x45, 0x4e, 0x44, 0xae, 0x42, 0x60, 
   2115 		 0x82];
   2116 	$data{"glass.png"} =
   2117 		[0x89, 0x50, 0x4e, 0x47, 0x0d, 0x0a, 0x1a, 0x0a, 0x00, 0x00, 
   2118 		 0x00, 0x0d, 0x49, 0x48, 0x44, 0x52, 0x00, 0x00, 0x00, 0x01, 
   2119 		 0x00, 0x00, 0x00, 0x01, 0x01, 0x03, 0x00, 0x00, 0x00, 0x25, 
   2120 		 0xdb, 0x56, 0xca, 0x00, 0x00, 0x00, 0x04, 0x67, 0x41, 0x4d, 
   2121 		 0x41, 0x00, 0x00, 0xb1, 0x8f, 0x0b, 0xfc, 0x61, 0x05, 0x00, 
   2122 		 0x00, 0x00, 0x06, 0x50, 0x4c, 0x54, 0x45, 0xff, 0xff, 0xff, 
   2123 		 0x00, 0x00, 0x00, 0x55, 0xc2, 0xd3, 0x7e, 0x00, 0x00, 0x00, 
   2124 		 0x01, 0x74, 0x52, 0x4e, 0x53, 0x00, 0x40, 0xe6, 0xd8, 0x66, 
   2125 		 0x00, 0x00, 0x00, 0x01, 0x62, 0x4b, 0x47, 0x44, 0x00, 0x88, 
   2126 		 0x05, 0x1d, 0x48, 0x00, 0x00, 0x00, 0x09, 0x70, 0x48, 0x59, 
   2127 		 0x73, 0x00, 0x00, 0x0b, 0x12, 0x00, 0x00, 0x0b, 0x12, 0x01, 
   2128 		 0xd2, 0xdd, 0x7e, 0xfc, 0x00, 0x00, 0x00, 0x07, 0x74, 0x49, 
   2129 		 0x4d, 0x45, 0x07, 0xd2, 0x07, 0x13, 0x0f, 0x08, 0x19, 0xc4, 
   2130 		 0x40, 0x56, 0x10, 0x00, 0x00, 0x00, 0x0a, 0x49, 0x44, 0x41, 
   2131 		 0x54, 0x78, 0x9c, 0x63, 0x60, 0x00, 0x00, 0x00, 0x02, 0x00, 
   2132 		 0x01, 0x48, 0xaf, 0xa4, 0x71, 0x00, 0x00, 0x00, 0x00, 0x49, 
   2133 		 0x45, 0x4e, 0x44, 0xae, 0x42, 0x60, 0x82];
   2134 	$data{"updown.png"} =
   2135 		[0x89, 0x50, 0x4e, 0x47, 0x0d, 0x0a, 0x1a, 0x0a, 0x00, 0x00, 
   2136 		 0x00, 0x0d, 0x49, 0x48, 0x44, 0x52, 0x00, 0x00, 0x00, 0x0a, 
   2137 		 0x00, 0x00, 0x00, 0x0e, 0x08, 0x06, 0x00, 0x00, 0x00, 0x16, 
   2138 		 0xa3, 0x8d, 0xab, 0x00, 0x00, 0x00, 0x3c, 0x49, 0x44, 0x41, 
   2139 		 0x54, 0x28, 0xcf, 0x63, 0x60, 0x40, 0x03, 0xff, 0xa1, 0x00, 
   2140 		 0x5d, 0x9c, 0x11, 0x5d, 0x11, 0x8a, 0x24, 0x23, 0x23, 0x23, 
   2141 		 0x86, 0x42, 0x6c, 0xa6, 0x20, 0x2b, 0x66, 0xc4, 0xa7, 0x08, 
   2142 		 0x59, 0x31, 0x23, 0x21, 0x45, 0x30, 0xc0, 0xc4, 0x30, 0x60, 
   2143 		 0x80, 0xfa, 0x6e, 0x24, 0x3e, 0x78, 0x48, 0x0a, 0x70, 0x62, 
   2144 		 0xa2, 0x90, 0x81, 0xd8, 0x44, 0x01, 0x00, 0xe9, 0x5c, 0x2f, 
   2145 		 0xf5, 0xe2, 0x9d, 0x0f, 0xf9, 0x00, 0x00, 0x00, 0x00, 0x49, 
   2146 		 0x45, 0x4e, 0x44, 0xae, 0x42, 0x60, 0x82] if ($sort);
   2147 	foreach (keys(%data))
   2148 	{
   2149 		open(PNG_HANDLE, ">".$_)
   2150 			or die("ERROR: cannot create $_!\n");
   2151 		binmode(PNG_HANDLE);
   2152 		print(PNG_HANDLE map(chr,@{$data{$_}}));
   2153 		close(PNG_HANDLE);
   2154 	}
   2155 }
   2156 
   2157 
   2158 #
   2159 # write_htaccess_file()
   2160 #
   2161 
   2162 sub write_htaccess_file()
   2163 {
   2164 	local *HTACCESS_HANDLE;
   2165 	my $htaccess_data;
   2166 
   2167 	open(*HTACCESS_HANDLE, ">.htaccess")
   2168 		or die("ERROR: cannot open .htaccess for writing!\n");
   2169 
   2170 	$htaccess_data = (<<"END_OF_HTACCESS")
   2171 AddEncoding x-gzip .html
   2172 END_OF_HTACCESS
   2173 	;
   2174 
   2175 	print(HTACCESS_HANDLE $htaccess_data);
   2176 	close(*HTACCESS_HANDLE);
   2177 }
   2178 
   2179 
   2180 #
   2181 # write_css_file()
   2182 #
   2183 # Write the cascading style sheet file gcov.css to the current directory.
   2184 # This file defines basic layout attributes of all generated HTML pages.
   2185 #
   2186 
   2187 sub write_css_file()
   2188 {
   2189 	local *CSS_HANDLE;
   2190 
   2191 	# Check for a specified external style sheet file
   2192 	if ($css_filename)
   2193 	{
   2194 		# Simply copy that file
   2195 		system("cp", $css_filename, "gcov.css")
   2196 			and die("ERROR: cannot copy file $css_filename!\n");
   2197 		return;
   2198 	}
   2199 
   2200 	open(CSS_HANDLE, ">gcov.css")
   2201 		or die ("ERROR: cannot open gcov.css for writing!\n");
   2202 
   2203 
   2204 	# *************************************************************
   2205 
   2206 	my $css_data = ($_=<<"END_OF_CSS")
   2207 	/* All views: initial background and text color */
   2208 	body
   2209 	{
   2210 	  color: #000000;
   2211 	  background-color: #FFFFFF;
   2212 	}
   2213 	
   2214 	/* All views: standard link format*/
   2215 	a:link
   2216 	{
   2217 	  color: #284FA8;
   2218 	  text-decoration: underline;
   2219 	}
   2220 	
   2221 	/* All views: standard link - visited format */
   2222 	a:visited
   2223 	{
   2224 	  color: #00CB40;
   2225 	  text-decoration: underline;
   2226 	}
   2227 	
   2228 	/* All views: standard link - activated format */
   2229 	a:active
   2230 	{
   2231 	  color: #FF0040;
   2232 	  text-decoration: underline;
   2233 	}
   2234 	
   2235 	/* All views: main title format */
   2236 	td.title
   2237 	{
   2238 	  text-align: center;
   2239 	  padding-bottom: 10px;
   2240 	  font-family: sans-serif;
   2241 	  font-size: 20pt;
   2242 	  font-style: italic;
   2243 	  font-weight: bold;
   2244 	}
   2245 	
   2246 	/* All views: header item format */
   2247 	td.headerItem
   2248 	{
   2249 	  text-align: right;
   2250 	  padding-right: 6px;
   2251 	  font-family: sans-serif;
   2252 	  font-weight: bold;
   2253 	  vertical-align: top;
   2254 	  white-space: nowrap;
   2255 	}
   2256 	
   2257 	/* All views: header item value format */
   2258 	td.headerValue
   2259 	{
   2260 	  text-align: left;
   2261 	  color: #284FA8;
   2262 	  font-family: sans-serif;
   2263 	  font-weight: bold;
   2264 	  white-space: nowrap;
   2265 	}
   2266 
   2267 	/* All views: header item coverage table heading */
   2268 	td.headerCovTableHead
   2269 	{
   2270 	  text-align: center;
   2271 	  padding-right: 6px;
   2272 	  padding-left: 6px;
   2273 	  padding-bottom: 0px;
   2274 	  font-family: sans-serif;
   2275 	  font-size: 80%;
   2276 	  white-space: nowrap;
   2277 	}
   2278 	
   2279 	/* All views: header item coverage table entry */
   2280 	td.headerCovTableEntry
   2281 	{
   2282 	  text-align: right;
   2283 	  color: #284FA8;
   2284 	  font-family: sans-serif;
   2285 	  font-weight: bold;
   2286 	  white-space: nowrap;
   2287 	  padding-left: 12px;
   2288 	  padding-right: 4px;
   2289 	  background-color: #DAE7FE;
   2290 	}
   2291 	
   2292 	/* All views: header item coverage table entry for high coverage rate */
   2293 	td.headerCovTableEntryHi
   2294 	{
   2295 	  text-align: right;
   2296 	  color: #000000;
   2297 	  font-family: sans-serif;
   2298 	  font-weight: bold;
   2299 	  white-space: nowrap;
   2300 	  padding-left: 12px;
   2301 	  padding-right: 4px;
   2302 	  background-color: #A7FC9D;
   2303 	}
   2304 	
   2305 	/* All views: header item coverage table entry for medium coverage rate */
   2306 	td.headerCovTableEntryMed
   2307 	{
   2308 	  text-align: right;
   2309 	  color: #000000;
   2310 	  font-family: sans-serif;
   2311 	  font-weight: bold;
   2312 	  white-space: nowrap;
   2313 	  padding-left: 12px;
   2314 	  padding-right: 4px;
   2315 	  background-color: #FFEA20;
   2316 	}
   2317 	
   2318 	/* All views: header item coverage table entry for ow coverage rate */
   2319 	td.headerCovTableEntryLo
   2320 	{
   2321 	  text-align: right;
   2322 	  color: #000000;
   2323 	  font-family: sans-serif;
   2324 	  font-weight: bold;
   2325 	  white-space: nowrap;
   2326 	  padding-left: 12px;
   2327 	  padding-right: 4px;
   2328 	  background-color: #FF0000;
   2329 	}
   2330 	
   2331 	/* All views: header legend item for legend entry */
   2332 	td.headerItemLeg
   2333 	{
   2334 	  text-align: right;
   2335 	  padding-right: 6px;
   2336 	  font-family: sans-serif;
   2337 	  font-weight: bold;
   2338 	  vertical-align: bottom;
   2339 	  white-space: nowrap;
   2340 	}
   2341 
   2342 	/* All views: header legend value for legend entry */
   2343 	td.headerValueLeg
   2344 	{
   2345 	  text-align: left;
   2346 	  color: #000000;
   2347 	  font-family: sans-serif;
   2348 	  font-size: 80%;
   2349 	  white-space: nowrap;
   2350 	  padding-top: 4px;
   2351 	}
   2352 	
   2353 	/* All views: color of horizontal ruler */
   2354 	td.ruler
   2355 	{
   2356 	  background-color: #6688D4;
   2357 	}
   2358 	
   2359 	/* All views: version string format */
   2360 	td.versionInfo
   2361 	{
   2362 	  text-align: center;
   2363 	  padding-top: 2px;
   2364 	  font-family: sans-serif;
   2365 	  font-style: italic;
   2366 	}
   2367 	
   2368 	/* Directory view/File view (all)/Test case descriptions:
   2369 	   table headline format */
   2370 	td.tableHead
   2371 	{
   2372 	  text-align: center;
   2373 	  color: #FFFFFF;
   2374 	  background-color: #6688D4;
   2375 	  font-family: sans-serif;
   2376 	  font-size: 120%;
   2377 	  font-weight: bold;
   2378 	  white-space: nowrap;
   2379 	  padding-left: 4px;
   2380 	  padding-right: 4px;
   2381 	}
   2382 
   2383 	span.tableHeadSort
   2384 	{
   2385 	  padding-right: 4px;
   2386 	}
   2387 	
   2388 	/* Directory view/File view (all): filename entry format */
   2389 	td.coverFile
   2390 	{
   2391 	  text-align: left;
   2392 	  padding-left: 10px;
   2393 	  padding-right: 20px; 
   2394 	  color: #284FA8;
   2395 	  background-color: #DAE7FE;
   2396 	  font-family: monospace;
   2397 	}
   2398 	
   2399 	/* Directory view/File view (all): bar-graph entry format*/
   2400 	td.coverBar
   2401 	{
   2402 	  padding-left: 10px;
   2403 	  padding-right: 10px;
   2404 	  background-color: #DAE7FE;
   2405 	}
   2406 	
   2407 	/* Directory view/File view (all): bar-graph outline color */
   2408 	td.coverBarOutline
   2409 	{
   2410 	  background-color: #000000;
   2411 	}
   2412 	
   2413 	/* Directory view/File view (all): percentage entry for files with
   2414 	   high coverage rate */
   2415 	td.coverPerHi
   2416 	{
   2417 	  text-align: right;
   2418 	  padding-left: 10px;
   2419 	  padding-right: 10px;
   2420 	  background-color: #A7FC9D;
   2421 	  font-weight: bold;
   2422 	}
   2423 	
   2424 	/* Directory view/File view (all): line count entry for files with
   2425 	   high coverage rate */
   2426 	td.coverNumHi
   2427 	{
   2428 	  text-align: right;
   2429 	  padding-left: 10px;
   2430 	  padding-right: 10px;
   2431 	  background-color: #A7FC9D;
   2432 	  white-space: nowrap;
   2433 	}
   2434 	
   2435 	/* Directory view/File view (all): legend entry for high coverage
   2436 	   rate */
   2437 	span.coverLegendHi
   2438 	{
   2439 	  padding-left: 10px;
   2440 	  padding-right: 10px;
   2441 	  padding-bottom: 2px;
   2442 	  background-color: #A7FC9D;
   2443 	}
   2444 	
   2445 	/* Directory view/File view (all): percentage entry for files with
   2446 	   medium coverage rate */
   2447 	td.coverPerMed
   2448 	{
   2449 	  text-align: right;
   2450 	  padding-left: 10px;
   2451 	  padding-right: 10px;
   2452 	  background-color: #FFEA20;
   2453 	  font-weight: bold;
   2454 	}
   2455 	
   2456 	/* Directory view/File view (all): line count entry for files with
   2457 	   medium coverage rate */
   2458 	td.coverNumMed
   2459 	{
   2460 	  text-align: right;
   2461 	  padding-left: 10px;
   2462 	  padding-right: 10px;
   2463 	  background-color: #FFEA20;
   2464 	  white-space: nowrap;
   2465 	}
   2466 	
   2467 	/* Directory view/File view (all): legend entry for medium coverage
   2468 	   rate */
   2469 	span.coverLegendMed
   2470 	{
   2471 	  padding-left: 10px;
   2472 	  padding-right: 10px;
   2473 	  padding-bottom: 2px;
   2474 	  background-color: #FFEA20;
   2475 	}
   2476 	
   2477 	/* Directory view/File view (all): percentage entry for files with
   2478 	   low coverage rate */
   2479 	td.coverPerLo
   2480 	{
   2481 	  text-align: right;
   2482 	  padding-left: 10px;
   2483 	  padding-right: 10px;
   2484 	  background-color: #FF0000;
   2485 	  font-weight: bold;
   2486 	}
   2487 	
   2488 	/* Directory view/File view (all): line count entry for files with
   2489 	   low coverage rate */
   2490 	td.coverNumLo
   2491 	{
   2492 	  text-align: right;
   2493 	  padding-left: 10px;
   2494 	  padding-right: 10px;
   2495 	  background-color: #FF0000;
   2496 	  white-space: nowrap;
   2497 	}
   2498 	
   2499 	/* Directory view/File view (all): legend entry for low coverage
   2500 	   rate */
   2501 	span.coverLegendLo
   2502 	{
   2503 	  padding-left: 10px;
   2504 	  padding-right: 10px;
   2505 	  padding-bottom: 2px;
   2506 	  background-color: #FF0000;
   2507 	}
   2508 	
   2509 	/* File view (all): "show/hide details" link format */
   2510 	a.detail:link
   2511 	{
   2512 	  color: #B8D0FF;
   2513 	}
   2514 	
   2515 	/* File view (all): "show/hide details" link - visited format */
   2516 	a.detail:visited
   2517 	{
   2518 	  color: #B8D0FF;
   2519 	}
   2520 	
   2521 	/* File view (all): "show/hide details" link - activated format */
   2522 	a.detail:active
   2523 	{
   2524 	  color: #FFFFFF;
   2525 	}
   2526 	
   2527 	/* File view (detail): test name table headline format */
   2528 	td.testNameHead
   2529 	{
   2530 	  text-align: right;
   2531 	  padding-right: 10px;
   2532 	  background-color: #DAE7FE;
   2533 	  font-family: sans-serif;
   2534 	  font-weight: bold;
   2535 	}
   2536 	
   2537 	/* File view (detail): test lines table headline format */
   2538 	td.testLinesHead
   2539 	{
   2540 	  text-align: center;
   2541 	  background-color: #DAE7FE;
   2542 	  font-family: sans-serif;
   2543 	  font-weight: bold;
   2544 	}
   2545 	
   2546 	/* File view (detail): test name entry */
   2547 	td.testName
   2548 	{
   2549 	  text-align: right;
   2550 	  padding-right: 10px;
   2551 	  background-color: #DAE7FE;
   2552 	}
   2553 	
   2554 	/* File view (detail): test percentage entry */
   2555 	td.testPer
   2556 	{
   2557 	  text-align: right;
   2558 	  padding-left: 10px;
   2559 	  padding-right: 10px; 
   2560 	  background-color: #DAE7FE;
   2561 	}
   2562 	
   2563 	/* File view (detail): test lines count entry */
   2564 	td.testNum
   2565 	{
   2566 	  text-align: right;
   2567 	  padding-left: 10px;
   2568 	  padding-right: 10px; 
   2569 	  background-color: #DAE7FE;
   2570 	}
   2571 	
   2572 	/* Test case descriptions: test name format*/
   2573 	dt
   2574 	{
   2575 	  font-family: sans-serif;
   2576 	  font-weight: bold;
   2577 	}
   2578 	
   2579 	/* Test case descriptions: description table body */
   2580 	td.testDescription
   2581 	{
   2582 	  padding-top: 10px;
   2583 	  padding-left: 30px;
   2584 	  padding-bottom: 10px;
   2585 	  padding-right: 30px;
   2586 	  background-color: #DAE7FE;
   2587 	}
   2588 	
   2589 	/* Source code view: function entry */
   2590 	td.coverFn
   2591 	{
   2592 	  text-align: left;
   2593 	  padding-left: 10px;
   2594 	  padding-right: 20px; 
   2595 	  color: #284FA8;
   2596 	  background-color: #DAE7FE;
   2597 	  font-family: monospace;
   2598 	}
   2599 
   2600 	/* Source code view: function entry zero count*/
   2601 	td.coverFnLo
   2602 	{
   2603 	  text-align: right;
   2604 	  padding-left: 10px;
   2605 	  padding-right: 10px;
   2606 	  background-color: #FF0000;
   2607 	  font-weight: bold;
   2608 	}
   2609 
   2610 	/* Source code view: function entry nonzero count*/
   2611 	td.coverFnHi
   2612 	{
   2613 	  text-align: right;
   2614 	  padding-left: 10px;
   2615 	  padding-right: 10px;
   2616 	  background-color: #DAE7FE;
   2617 	  font-weight: bold;
   2618 	}
   2619 
   2620 	/* Source code view: source code format */
   2621 	/* Source code view: source code format */
   2622 	pre.source
   2623 	{
   2624 	  font-family: monospace;
   2625 	  white-space: pre;
   2626 	}
   2627 	
   2628 	/* Source code view: line number format */
   2629 	span.lineNum
   2630 	{
   2631 	  background-color: #EFE383;
   2632 	}
   2633 	
   2634 	/* Source code view: format for lines which were executed */
   2635 	td.lineCov,
   2636 	span.lineCov
   2637 	{
   2638 	  background-color: #CAD7FE;
   2639 	}
   2640 	
   2641 	/* Source code view: format for Cov legend */
   2642 	span.coverLegendCov
   2643 	{
   2644 	  padding-left: 10px;
   2645 	  padding-right: 10px;
   2646 	  padding-bottom: 2px;
   2647 	  background-color: #CAD7FE;
   2648 	}
   2649 	
   2650 	/* Source code view: format for lines which were not executed */
   2651 	td.lineNoCov,
   2652 	span.lineNoCov
   2653 	{
   2654 	  background-color: #FF6230;
   2655 	}
   2656 	
   2657 	/* Source code view: format for NoCov legend */
   2658 	span.coverLegendNoCov
   2659 	{
   2660 	  padding-left: 10px;
   2661 	  padding-right: 10px;
   2662 	  padding-bottom: 2px;
   2663 	  background-color: #FF0000;
   2664 	}
   2665 	
   2666 	/* Source code view (function table): standard link - visited format */
   2667 	td.lineNoCov > a:visited,
   2668 	td.lineCov > a:visited
   2669 	{  
   2670 	  color: black;
   2671 	  text-decoration: underline;
   2672 	}  
   2673 	
   2674 	/* Source code view: format for lines which were executed only in a
   2675 	   previous version */
   2676 	span.lineDiffCov
   2677 	{
   2678 	  background-color: #B5F7AF;
   2679 	}
   2680 	
   2681 	/* Source code view: format for DiffCov legend */
   2682 	span.LegendDiffCov
   2683 	{
   2684 	  text-align: center;
   2685 	  padding-left: 10px;
   2686 	  padding-right: 10px;
   2687 	  background-color: #B5F7AF;
   2688 	}
   2689 END_OF_CSS
   2690 	;
   2691 
   2692 	# *************************************************************
   2693 
   2694 
   2695 	# Remove leading tab from all lines
   2696 	$css_data =~ s/^\t//gm;
   2697 
   2698 	print(CSS_HANDLE $css_data);
   2699 
   2700 	close(CSS_HANDLE);
   2701 }
   2702 
   2703 
   2704 #
   2705 # get_bar_graph_code(base_dir, cover_found, cover_hit)
   2706 #
   2707 # Return a string containing HTML code which implements a bar graph display
   2708 # for a coverage rate of cover_hit * 100 / cover_found.
   2709 #
   2710 
   2711 sub get_bar_graph_code($$$)
   2712 {
   2713 	my $rate;
   2714 	my $alt;
   2715 	my $width;
   2716 	my $remainder;
   2717 	my $png_name;
   2718 	my $graph_code;
   2719 
   2720 	# Check number of instrumented lines
   2721 	if ($_[1] == 0) { return ""; }
   2722 
   2723 	$rate		= $_[2] * 100 / $_[1];
   2724 	$alt		= sprintf("%.1f", $rate)."%";
   2725 	$width		= sprintf("%.0f", $rate);
   2726 	$remainder	= sprintf("%d", 100-$width);
   2727 
   2728 	# Decide which .png file to use
   2729 	$png_name = $rate_png[classify_rate($_[1], $_[2], $med_limit,
   2730 					    $hi_limit)];
   2731 
   2732 	if ($width == 0)
   2733 	{
   2734 		# Zero coverage
   2735 		$graph_code = (<<END_OF_HTML)
   2736 	        <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>
   2737 END_OF_HTML
   2738 		;
   2739 	}
   2740 	elsif ($width == 100)
   2741 	{
   2742 		# Full coverage
   2743 		$graph_code = (<<END_OF_HTML)
   2744 		<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>
   2745 END_OF_HTML
   2746 		;
   2747 	}
   2748 	else
   2749 	{
   2750 		# Positive coverage
   2751 		$graph_code = (<<END_OF_HTML)
   2752 		<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>
   2753 END_OF_HTML
   2754 		;
   2755 	}
   2756 
   2757 	# Remove leading tabs from all lines
   2758 	$graph_code =~ s/^\t+//gm;
   2759 	chomp($graph_code);
   2760 
   2761 	return($graph_code);
   2762 }
   2763 
   2764 #
   2765 # sub classify_rate(found, hit, med_limit, high_limit)
   2766 #
   2767 # Return 0 for low rate, 1 for medium rate and 2 for hi rate.
   2768 #
   2769 
   2770 sub classify_rate($$$$)
   2771 {
   2772 	my ($found, $hit, $med, $hi) = @_;
   2773 	my $rate;
   2774 
   2775 	if ($found == 0) {
   2776 		return 2;
   2777 	}
   2778 	$rate = $hit * 100 / $found;
   2779 	if ($rate < $med) {
   2780 		return 0;
   2781 	} elsif ($rate < $hi) {
   2782 		return 1;
   2783 	}
   2784 	return 2;
   2785 }
   2786 
   2787 
   2788 #
   2789 # write_html(filehandle, html_code)
   2790 #
   2791 # Write out HTML_CODE to FILEHANDLE while removing a leading tabulator mark
   2792 # in each line of HTML_CODE.
   2793 #
   2794 
   2795 sub write_html(*$)
   2796 {
   2797 	local *HTML_HANDLE = $_[0];
   2798 	my $html_code = $_[1];
   2799 
   2800 	# Remove leading tab from all lines
   2801 	$html_code =~ s/^\t//gm;
   2802 
   2803 	print(HTML_HANDLE $html_code)
   2804 		or die("ERROR: cannot write HTML data ($!)\n");
   2805 }
   2806 
   2807 
   2808 #
   2809 # write_html_prolog(filehandle, base_dir, pagetitle)
   2810 #
   2811 # Write an HTML prolog common to all HTML files to FILEHANDLE. PAGETITLE will
   2812 # be used as HTML page title. BASE_DIR contains a relative path which points
   2813 # to the base directory.
   2814 #
   2815 
   2816 sub write_html_prolog(*$$)
   2817 {
   2818 	my $basedir = $_[1];
   2819 	my $pagetitle = $_[2];
   2820 	my $prolog;
   2821 
   2822 	$prolog = $html_prolog;
   2823 	$prolog =~ s/\@pagetitle\@/$pagetitle/g;
   2824 	$prolog =~ s/\@basedir\@/$basedir/g;
   2825 
   2826 	write_html($_[0], $prolog);
   2827 }
   2828 
   2829 
   2830 #
   2831 # write_header_prolog(filehandle, base_dir)
   2832 #
   2833 # Write beginning of page header HTML code.
   2834 #
   2835 
   2836 sub write_header_prolog(*$)
   2837 {
   2838 	# *************************************************************
   2839 
   2840 	write_html($_[0], <<END_OF_HTML)
   2841 	  <table width="100%" border=0 cellspacing=0 cellpadding=0>
   2842 	    <tr><td class="title">$title</td></tr>
   2843 	    <tr><td class="ruler"><img src="$_[1]glass.png" width=3 height=3 alt=""></td></tr>
   2844 
   2845 	    <tr>
   2846 	      <td width="100%">
   2847 	        <table cellpadding=1 border=0 width="100%">
   2848 END_OF_HTML
   2849 	;
   2850 
   2851 	# *************************************************************
   2852 }
   2853 
   2854 
   2855 #
   2856 # write_header_line(filehandle, type, additional params..)
   2857 #
   2858 # Write a header line.
   2859 #
   2860 
   2861 sub write_header_line(*$@)
   2862 {
   2863 	my $HANDLE = shift;
   2864 	my $type = shift;
   2865 	my @args = @_;
   2866 
   2867 	# Reduce indentation by using gotos
   2868 	if ($type eq 0) {
   2869 		goto header;
   2870 	} elsif ($type eq 1) {
   2871 		goto body;
   2872 	} elsif ($type eq 2) {
   2873 		goto legend_dir;
   2874 	} elsif ($type eq 3) {
   2875 		goto legend_source;
   2876 	} elsif ($type eq 4) {
   2877 		goto half_body;
   2878 	}
   2879 
   2880 header:
   2881 	# *************************************************************
   2882 	write_html($HANDLE, <<END_OF_HTML);
   2883         <tr>
   2884           <td width="5%"></td>
   2885           <td width="10%" class="headerItem">$args[0]</td>
   2886           <td width="35%" class="headerValue">$args[1]</td>
   2887           <td width="10%"></td>
   2888           <td width="10%" class="headerCovTableHead">$args[2]</td>
   2889           <td width="10%" class="headerCovTableHead">$args[3]</td>
   2890           <td width="15%" class="headerCovTableHead">$args[4]</td>
   2891           <td width="5%"></td>
   2892         </tr>
   2893 END_OF_HTML
   2894 	# *************************************************************
   2895 	return;
   2896 
   2897 body:
   2898 	# *************************************************************
   2899 	write_html($HANDLE, <<END_OF_HTML);
   2900         <tr>
   2901           <td></td>
   2902           <td class="headerItem">$args[0]</td>
   2903           <td class="headerValue">$args[1]</td>
   2904           <td class="headerItem">$args[2]</td>
   2905           <td class="headerCovTableEntry">$args[3]</td>
   2906           <td class="headerCovTableEntry">$args[4]</td>
   2907           <td class="headerCovTableEntry$args[5]">$args[6]</td>
   2908         </tr>
   2909 END_OF_HTML
   2910 	# *************************************************************
   2911 	return;
   2912 
   2913 half_body:
   2914 	# *************************************************************
   2915 	write_html($HANDLE, <<END_OF_HTML);
   2916         <tr>
   2917           <td></td>
   2918           <td class="headerItem">$args[0]</td>
   2919           <td class="headerValue">$args[1]</td>
   2920         </tr>
   2921 END_OF_HTML
   2922 	# *************************************************************
   2923 	return;
   2924 
   2925 legend_dir:
   2926 	# *************************************************************
   2927 	write_html($HANDLE, <<END_OF_HTML);
   2928         <tr>
   2929           <td></td>
   2930           <td class="headerItemLeg">$args[0]</td>
   2931           <td class="headerValueLeg">
   2932 $args[1]          </td>
   2933           <td></td>
   2934           <td class="headerValueLeg" colspan=3>
   2935 $args[2]          </td>
   2936         </tr>
   2937 END_OF_HTML
   2938 	# *************************************************************
   2939 	return;
   2940 
   2941 legend_source:
   2942 	# *************************************************************
   2943 	write_html($HANDLE, <<END_OF_HTML);
   2944         <tr>
   2945           <td></td>
   2946           <td class="headerItem">$args[0]</td>
   2947           <td class="headerValueLeg" colspan=5>
   2948             <span class="coverLegendNoCov">$args[1]</span>
   2949             <span class="coverLegendCov">$args[2]</span>
   2950           </td>
   2951         </tr>
   2952 END_OF_HTML
   2953 	# *************************************************************
   2954 }
   2955 
   2956 
   2957 #
   2958 # write_header_epilog(filehandle, base_dir)
   2959 #
   2960 # Write end of page header HTML code.
   2961 #
   2962 
   2963 sub write_header_epilog(*$)
   2964 {
   2965 	# *************************************************************
   2966 
   2967 	write_html($_[0], <<END_OF_HTML)
   2968                 <tr><td><img src="$_[1]glass.png" width=3 height=3 alt=""></td></tr>
   2969 	        </table>
   2970 	      </td>
   2971 	    </tr>
   2972 	    <tr><td class="ruler"><img src="$_[1]glass.png" width=3 height=3 alt=""></td></tr>
   2973 	  </table>
   2974 
   2975 END_OF_HTML
   2976 	;
   2977 
   2978 	# *************************************************************
   2979 }
   2980 
   2981 
   2982 #
   2983 # write_file_table_prolog(filehandle, file_heading, lines_heading, func_heading)
   2984 #
   2985 # Write heading for file table.
   2986 #
   2987 
   2988 sub write_file_table_prolog(*$$$)
   2989 {
   2990 	# *************************************************************
   2991 
   2992         if ($func_coverage)
   2993         {
   2994                 write_html($_[0], <<END_OF_HTML)
   2995 	  <center>
   2996 	  <table width="80%" cellpadding=1 cellspacing=1 border=0>
   2997 
   2998 	    <tr>
   2999 	      <td width="45%"><br></td>
   3000 	      <td width="15%"></td>
   3001 	      <td width="10%"></td>
   3002 	      <td width="10%"></td>
   3003 	      <td width="10%"></td>
   3004 	      <td width="10%"></td>
   3005 	    </tr>
   3006 
   3007 	    <tr>
   3008 	      <td class="tableHead">$_[1]</td>
   3009 	      <td class="tableHead" colspan=3>$_[2]</td>
   3010 	      <td class="tableHead" colspan=2>$_[3]</td>
   3011 	    </tr>
   3012 
   3013 END_OF_HTML
   3014                 ;
   3015         }
   3016         else
   3017         {
   3018                 write_html($_[0], <<END_OF_HTML)
   3019 	  <center>
   3020 	  <table width="80%" cellpadding=1 cellspacing=1 border=0>
   3021 
   3022 	    <tr>
   3023 	      <td width="50%"><br></td>
   3024 	      <td width="15%"></td>
   3025 	      <td width="15%"></td>
   3026 	      <td width="20%"></td>
   3027 	    </tr>
   3028 
   3029 	    <tr>
   3030 	      <td class="tableHead">$_[1]</td>
   3031 	      <td class="tableHead" colspan=3>$_[2]</td>
   3032 	    </tr>
   3033 
   3034 END_OF_HTML
   3035                 ;
   3036         }
   3037 
   3038 	# *************************************************************
   3039 }
   3040 
   3041 
   3042 #
   3043 # write_file_table_entry(filehandle, cover_filename, cover_bar_graph,
   3044 #                        cover_found, cover_hit, fn_found, fn_hit,
   3045 #			 page_link, func_link)
   3046 #
   3047 # Write an entry of the file table.
   3048 #
   3049 
   3050 sub write_file_table_entry(*$$$$$$$)
   3051 {
   3052 	local *HANDLE = shift;
   3053 	my ($filename, $bar_graph, $found, $hit, $fn_found, $fn_hit,
   3054 	    $page_link) = @_;
   3055 	my $rate;
   3056 	my $rate_string;
   3057 	my $funcs_string;
   3058 	my $class_lines = "Lo";
   3059 	my $class_funcs = "Hi";
   3060 	my $file_code;
   3061 
   3062 	# Add link to source if provided
   3063 	if (defined($page_link) && $page_link ne "") {
   3064 		$file_code = "<a href=\"$page_link\">$filename</a>";
   3065 	} else {
   3066 		$file_code = $filename;
   3067 	}
   3068 
   3069 	# Get line coverage rate
   3070 	if ($found > 0)
   3071 	{
   3072 		$rate = $hit * 100 / $found;
   3073 		$rate_string = sprintf("%.1f", $rate)."&nbsp;%";
   3074 		
   3075 		$class_lines = $rate_name[classify_rate($found, $hit,
   3076 					  $med_limit, $hi_limit)];
   3077 	}
   3078 	else
   3079 	{
   3080 		$rate_string = "-";
   3081 	}
   3082 
   3083 	# Get function coverage rate
   3084 	if ($fn_found > 0)
   3085 	{
   3086 		$rate = $fn_hit * 100 / $fn_found;
   3087 		$class_funcs = $rate_name[classify_rate($fn_found, $fn_hit,
   3088 					  $fn_med_limit, $fn_hi_limit)];
   3089 		$funcs_string = sprintf("%.1f", $rate)."&nbsp;%";		
   3090 	}
   3091 	else
   3092 	{
   3093 		# Define 0 of 0 functions as 100%
   3094 		$rate = 100;
   3095 		$funcs_string = "-";
   3096 	}
   3097 
   3098 	# *************************************************************
   3099 
   3100 	write_html(*HANDLE, <<END_OF_HTML)
   3101 	    <tr>
   3102 	      <td class="coverFile">$file_code</td>
   3103 	      <td class="coverBar" align="center">
   3104 	        $bar_graph
   3105 	      </td>
   3106 	      <td class="coverPer$class_lines">$rate_string</td>
   3107 	      <td class="coverNum$class_lines">$hit / $found</td>
   3108 END_OF_HTML
   3109 	;
   3110 
   3111         if ($func_coverage)
   3112         {
   3113                 write_html(*HANDLE, <<END_OF_HTML)
   3114 	      <td class="coverPer$class_funcs">$funcs_string</td>
   3115 	      <td class="coverNum$class_funcs">$fn_hit / $fn_found</td>
   3116 END_OF_HTML
   3117 	        ;
   3118         }
   3119         write_html(*HANDLE, <<END_OF_HTML)
   3120 	    </tr>
   3121 END_OF_HTML
   3122         ;
   3123 
   3124 	# *************************************************************
   3125 }
   3126 
   3127 
   3128 #
   3129 # write_file_table_detail_heading(filehandle, left_heading, right_heading)
   3130 #
   3131 # Write heading for detail section in file table.
   3132 #
   3133 
   3134 sub write_file_table_detail_heading(*$$$)
   3135 {
   3136         my $func_rows = "";
   3137 
   3138         if ($func_coverage)
   3139         {
   3140                 $func_rows = "<td class=\"testLinesHead\" colspan=2>$_[3]</td>";
   3141         }
   3142 
   3143 	# *************************************************************
   3144 	write_html($_[0], <<END_OF_HTML)
   3145 	    <tr>
   3146 	      <td class="testNameHead" colspan=2>$_[1]</td>
   3147 	      <td class="testLinesHead" colspan=2>$_[2]</td>
   3148               $func_rows
   3149 	    </tr>
   3150 
   3151 END_OF_HTML
   3152 	;
   3153 
   3154 	# *************************************************************
   3155 }
   3156 
   3157 
   3158 #
   3159 # write_file_table_detail_entry(filehandle, test_name,
   3160 #               cover_found, cover_hit, func_found, func_hit)
   3161 #
   3162 # Write entry for detail section in file table.
   3163 #
   3164 
   3165 sub write_file_table_detail_entry(*$$$$$)
   3166 {
   3167 	my $rate;
   3168 	my $func_rate;
   3169 	my $name = $_[1];
   3170 	
   3171 	if ($_[2]>0)
   3172 	{
   3173 		$rate = sprintf("%.1f", $_[3]*100/$_[2])."&nbsp;%";
   3174 	}
   3175 	else
   3176 	{
   3177 		$rate = "-";
   3178 	}
   3179 
   3180 	if ($_[4]>0)
   3181 	{
   3182 		$func_rate = sprintf("%.1f", $_[5]*100/$_[4])."&nbsp;%";
   3183 	}
   3184 	else
   3185 	{
   3186 		$func_rate = "-";
   3187 	}
   3188 
   3189 	if ($name =~ /^(.*),diff$/)
   3190 	{
   3191 		$name = $1." (converted)";
   3192 	}
   3193 
   3194 	if ($name eq "")
   3195 	{
   3196 		$name = "<span style=\"font-style:italic\">&lt;unnamed&gt;</span>";
   3197 	}
   3198 
   3199 	# *************************************************************
   3200 
   3201 	write_html($_[0], <<END_OF_HTML)
   3202 	    <tr>
   3203 	      <td class="testName" colspan=2>$name</td>
   3204 	      <td class="testPer">$rate</td>
   3205 	      <td class="testNum">$_[3]&nbsp;/&nbsp;$_[2]&nbsp;lines</td>
   3206 END_OF_HTML
   3207 	;
   3208         if ($func_coverage)
   3209         {
   3210                 write_html($_[0], <<END_OF_HTML)
   3211 	      <td class="testPer">$func_rate</td>
   3212 	      <td class="testNum">$_[5]&nbsp;/&nbsp;$_[4]</td>
   3213 END_OF_HTML
   3214 	        ;
   3215         }
   3216         write_html($_[0], <<END_OF_HTML)
   3217 	    </tr>
   3218 
   3219 END_OF_HTML
   3220         ;
   3221 
   3222 	# *************************************************************
   3223 }
   3224 
   3225 
   3226 #
   3227 # write_file_table_epilog(filehandle)
   3228 #
   3229 # Write end of file table HTML code.
   3230 #
   3231 
   3232 sub write_file_table_epilog(*)
   3233 {
   3234 	# *************************************************************
   3235 
   3236 	write_html($_[0], <<END_OF_HTML)
   3237 	  </table>
   3238 	  </center>
   3239 	  <br>
   3240 
   3241 END_OF_HTML
   3242 	;
   3243 
   3244 	# *************************************************************
   3245 }
   3246 
   3247 
   3248 #
   3249 # write_test_table_prolog(filehandle, table_heading)
   3250 #
   3251 # Write heading for test case description table.
   3252 #
   3253 
   3254 sub write_test_table_prolog(*$)
   3255 {
   3256 	# *************************************************************
   3257 
   3258 	write_html($_[0], <<END_OF_HTML)
   3259 	  <center>
   3260 	  <table width="80%" cellpadding=2 cellspacing=1 border=0>
   3261 
   3262 	    <tr>
   3263 	      <td><br></td>
   3264 	    </tr>
   3265 
   3266 	    <tr>
   3267 	      <td class="tableHead">$_[1]</td>
   3268 	    </tr>
   3269 
   3270 	    <tr>
   3271 	      <td class="testDescription">
   3272 	        <dl>
   3273 END_OF_HTML
   3274 	;
   3275 
   3276 	# *************************************************************
   3277 }
   3278 
   3279 
   3280 #
   3281 # write_test_table_entry(filehandle, test_name, test_description)
   3282 #
   3283 # Write entry for the test table.
   3284 #
   3285 
   3286 sub write_test_table_entry(*$$)
   3287 {
   3288 	# *************************************************************
   3289 
   3290 	write_html($_[0], <<END_OF_HTML)
   3291           <dt>$_[1]<a name="$_[1]">&nbsp;</a></dt>
   3292           <dd>$_[2]<br><br></dd>
   3293 END_OF_HTML
   3294 	;
   3295 
   3296 	# *************************************************************
   3297 }
   3298 
   3299 
   3300 #
   3301 # write_test_table_epilog(filehandle)
   3302 #
   3303 # Write end of test description table HTML code.
   3304 #
   3305 
   3306 sub write_test_table_epilog(*)
   3307 {
   3308 	# *************************************************************
   3309 
   3310 	write_html($_[0], <<END_OF_HTML)
   3311 	        </dl>
   3312 	      </td>
   3313 	    </tr>
   3314 	  </table>
   3315 	  </center>
   3316 	  <br>
   3317 
   3318 END_OF_HTML
   3319 	;
   3320 
   3321 	# *************************************************************
   3322 }
   3323 
   3324 
   3325 #
   3326 # write_source_prolog(filehandle)
   3327 #
   3328 # Write start of source code table.
   3329 #
   3330 
   3331 sub write_source_prolog(*)
   3332 {
   3333 	# *************************************************************
   3334 
   3335 	write_html($_[0], <<END_OF_HTML)
   3336 	  <table cellpadding=0 cellspacing=0 border=0>
   3337 	    <tr>
   3338 	      <td><br></td>
   3339 	    </tr>
   3340 	    <tr>
   3341 	      <td><pre class="source">
   3342 END_OF_HTML
   3343 	;
   3344 
   3345 	# *************************************************************
   3346 }
   3347 
   3348 
   3349 #
   3350 # write_source_line(filehandle, line_num, source, hit_count, converted,
   3351 #                   add_anchor)
   3352 #
   3353 # Write formatted source code line. Return a line in a format as needed
   3354 # by gen_png()
   3355 #
   3356 
   3357 sub write_source_line(*$$$$$)
   3358 {
   3359 	my $source_format;
   3360 	my $count;
   3361 	my $result;
   3362 	my $anchor_start = "";
   3363 	my $anchor_end = "";
   3364 
   3365 	if (!(defined$_[3]))
   3366 	{
   3367 		$result		= "";
   3368 		$source_format	= "";
   3369 		$count		= " "x15;
   3370 	}
   3371 	elsif ($_[3] == 0)
   3372 	{
   3373 		$result		= $_[3];
   3374 		$source_format	= '<span class="lineNoCov">';
   3375 		$count		= sprintf("%15d", $_[3]);
   3376 	}
   3377 	elsif ($_[4] && defined($highlight))
   3378 	{
   3379 		$result		= "*".$_[3];
   3380 		$source_format	= '<span class="lineDiffCov">';
   3381 		$count		= sprintf("%15d", $_[3]);
   3382 	}
   3383 	else
   3384 	{
   3385 		$result		= $_[3];
   3386 		$source_format	= '<span class="lineCov">';
   3387 		$count		= sprintf("%15d", $_[3]);
   3388 	}
   3389 
   3390 	$result .= ":".$_[2];
   3391 
   3392 	# Write out a line number navigation anchor every $nav_resolution
   3393 	# lines if necessary
   3394 	if ($_[5])
   3395 	{
   3396 		$anchor_start	= "<a name=\"$_[1]\">";
   3397 		$anchor_end	= "</a>";
   3398 	}
   3399 
   3400 
   3401 	# *************************************************************
   3402 
   3403 	write_html($_[0],
   3404 		   $anchor_start.
   3405 		   '<span class="lineNum">'.sprintf("%8d", $_[1]).
   3406 		   " </span>$source_format$count : ".
   3407 		   escape_html($_[2]).($source_format?"</span>":"").
   3408 		   $anchor_end."\n");
   3409 
   3410 	# *************************************************************
   3411 
   3412 	return($result);
   3413 }
   3414 
   3415 
   3416 #
   3417 # write_source_epilog(filehandle)
   3418 #
   3419 # Write end of source code table.
   3420 #
   3421 
   3422 sub write_source_epilog(*)
   3423 {
   3424 	# *************************************************************
   3425 
   3426 	write_html($_[0], <<END_OF_HTML)
   3427 	</pre>
   3428 	      </td>
   3429 	    </tr>
   3430 	  </table>
   3431 	  <br>
   3432 
   3433 END_OF_HTML
   3434 	;
   3435 
   3436 	# *************************************************************
   3437 }
   3438 
   3439 
   3440 #
   3441 # write_html_epilog(filehandle, base_dir[, break_frames])
   3442 #
   3443 # Write HTML page footer to FILEHANDLE. BREAK_FRAMES should be set when
   3444 # this page is embedded in a frameset, clicking the URL link will then
   3445 # break this frameset.
   3446 #
   3447 
   3448 sub write_html_epilog(*$;$)
   3449 {
   3450 	my $basedir = $_[1];
   3451 	my $break_code = "";
   3452 	my $epilog;
   3453 
   3454 	if (defined($_[2]))
   3455 	{
   3456 		$break_code = " target=\"_parent\"";
   3457 	}
   3458 
   3459 	# *************************************************************
   3460 
   3461 	write_html($_[0], <<END_OF_HTML)
   3462 	  <table width="100%" border=0 cellspacing=0 cellpadding=0>
   3463 	  <tr><td class="ruler"><img src="$_[1]glass.png" width=3 height=3 alt=""></td></tr>
   3464 	  <tr><td class="versionInfo">Generated by: <a href="$lcov_url"$break_code>$lcov_version</a></td></tr>
   3465 	  </table>
   3466 	  <br>
   3467 END_OF_HTML
   3468 	;
   3469 
   3470 	$epilog = $html_epilog;
   3471 	$epilog =~ s/\@basedir\@/$basedir/g;
   3472 
   3473 	write_html($_[0], $epilog);
   3474 }
   3475 
   3476 
   3477 #
   3478 # write_frameset(filehandle, basedir, basename, pagetitle)
   3479 #
   3480 #
   3481 
   3482 sub write_frameset(*$$$)
   3483 {
   3484 	my $frame_width = $overview_width + 40;
   3485 
   3486 	# *************************************************************
   3487 
   3488 	write_html($_[0], <<END_OF_HTML)
   3489 	<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Frameset//EN">
   3490 
   3491 	<html lang="en">
   3492 
   3493 	<head>
   3494 	  <meta http-equiv="Content-Type" content="text/html; charset=ISO-8859-1">
   3495 	  <title>$_[3]</title>
   3496 	  <link rel="stylesheet" type="text/css" href="$_[1]gcov.css">
   3497 	</head>
   3498 
   3499 	<frameset cols="$frame_width,*">
   3500 	  <frame src="$_[2].gcov.overview.$html_ext" name="overview">
   3501 	  <frame src="$_[2].gcov.$html_ext" name="source">
   3502 	  <noframes>
   3503 	    <center>Frames not supported by your browser!<br></center>
   3504 	  </noframes>
   3505 	</frameset>
   3506 
   3507 	</html>
   3508 END_OF_HTML
   3509 	;
   3510 
   3511 	# *************************************************************
   3512 }
   3513 
   3514 
   3515 #
   3516 # sub write_overview_line(filehandle, basename, line, link)
   3517 #
   3518 #
   3519 
   3520 sub write_overview_line(*$$$)
   3521 {
   3522 	my $y1 = $_[2] - 1;
   3523 	my $y2 = $y1 + $nav_resolution - 1;
   3524 	my $x2 = $overview_width - 1;
   3525 
   3526 	# *************************************************************
   3527 
   3528 	write_html($_[0], <<END_OF_HTML)
   3529 	    <area shape="rect" coords="0,$y1,$x2,$y2" href="$_[1].gcov.$html_ext#$_[3]" target="source" alt="overview">
   3530 END_OF_HTML
   3531 	;
   3532 
   3533 	# *************************************************************
   3534 }
   3535 
   3536 
   3537 #
   3538 # write_overview(filehandle, basedir, basename, pagetitle, lines)
   3539 #
   3540 #
   3541 
   3542 sub write_overview(*$$$$)
   3543 {
   3544 	my $index;
   3545 	my $max_line = $_[4] - 1;
   3546 	my $offset;
   3547 
   3548 	# *************************************************************
   3549 
   3550 	write_html($_[0], <<END_OF_HTML)
   3551 	<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">
   3552 
   3553 	<html lang="en">
   3554 
   3555 	<head>
   3556 	  <title>$_[3]</title>
   3557 	  <meta http-equiv="Content-Type" content="text/html; charset=ISO-8859-1">
   3558 	  <link rel="stylesheet" type="text/css" href="$_[1]gcov.css">
   3559 	</head>
   3560 
   3561 	<body>
   3562 	  <map name="overview">
   3563 END_OF_HTML
   3564 	;
   3565 
   3566 	# *************************************************************
   3567 
   3568 	# Make $offset the next higher multiple of $nav_resolution
   3569 	$offset = ($nav_offset + $nav_resolution - 1) / $nav_resolution;
   3570 	$offset = sprintf("%d", $offset ) * $nav_resolution;
   3571 
   3572 	# Create image map for overview image
   3573 	for ($index = 1; $index <= $_[4]; $index += $nav_resolution)
   3574 	{
   3575 		# Enforce nav_offset
   3576 		if ($index < $offset + 1)
   3577 		{
   3578 			write_overview_line($_[0], $_[2], $index, 1);
   3579 		}
   3580 		else
   3581 		{
   3582 			write_overview_line($_[0], $_[2], $index, $index - $offset);
   3583 		}
   3584 	}
   3585 
   3586 	# *************************************************************
   3587 
   3588 	write_html($_[0], <<END_OF_HTML)
   3589 	  </map>
   3590 
   3591 	  <center>
   3592 	  <a href="$_[2].gcov.$html_ext#top" target="source">Top</a><br><br>
   3593 	  <img src="$_[2].gcov.png" width=$overview_width height=$max_line alt="Overview" border=0 usemap="#overview">
   3594 	  </center>
   3595 	</body>
   3596 	</html>
   3597 END_OF_HTML
   3598 	;
   3599 
   3600 	# *************************************************************
   3601 }
   3602 
   3603 
   3604 # rate_to_col(found, hit)
   3605 #
   3606 # Return Lo, Med or Hi, depending on the coverage rate.
   3607 #
   3608 
   3609 sub rate_to_col($$)
   3610 {
   3611 	my ($found, $hit) = @_;
   3612 	my $rate;
   3613 
   3614 	if ($found == 0) {
   3615 		return "Hi";
   3616 	}
   3617 	$rate = 100 * $hit / $found;
   3618 	if ($rate < $med_limit) {
   3619 		return "Lo";
   3620 	} elsif ($rate < $hi_limit) {
   3621 		return "Med";
   3622 	}
   3623 	return "Hi";
   3624 }
   3625 
   3626 # format_rate(found, hit)
   3627 #
   3628 # Return formatted percent string for coverage rate.
   3629 #
   3630 
   3631 sub format_rate($$)
   3632 {	
   3633 	return $_[0] == 0 ? "-" : sprintf("%.1f", $_[1] * 100 / $_[0])." %";
   3634 }
   3635 
   3636 sub get_legend_code($$$)
   3637 {
   3638 	my ($text, $med, $hi) = @_;
   3639 	my $result;
   3640 
   3641 	$result = <<EOF;
   3642 	            $text<br>
   3643 	            <span class="coverLegendLo">0% to $med%</span>
   3644 	            <span class="coverLegendMed">$med% to $hi%</span>
   3645 	            <span class="coverLegendHi">$hi% to 100%</span>
   3646 EOF
   3647 	return $result;
   3648 }
   3649 
   3650 #
   3651 # write_header(filehandle, type, trunc_file_name, rel_file_name, lines_found,
   3652 # lines_hit, funcs_found, funcs_hit, sort_type)
   3653 #
   3654 # Write a complete standard page header. TYPE may be (0, 1, 2, 3, 4)
   3655 # corresponding to (directory view header, file view header, source view
   3656 # header, test case description header, function view header)
   3657 #
   3658 
   3659 sub write_header(*$$$$$$$$)
   3660 {
   3661 	local *HTML_HANDLE = $_[0];
   3662 	my $type = $_[1];
   3663 	my $trunc_name = $_[2];
   3664 	my $rel_filename = $_[3];
   3665 	my $lines_found = $_[4];
   3666 	my $lines_hit = $_[5];
   3667 	my $fn_found = $_[6];
   3668 	my $fn_hit = $_[7];
   3669 	my $sort_type = $_[8];
   3670 	my $base_dir;
   3671 	my $view;
   3672 	my $test;
   3673 	my $base_name;
   3674 
   3675 	$base_name = basename($rel_filename);
   3676 
   3677 	# Prepare text for "current view" field
   3678 	if ($type == 0)
   3679 	{
   3680 		# Main overview
   3681 		$base_dir = "";
   3682 		$view = $overview_title;
   3683 	}
   3684 	elsif ($type == 1)
   3685 	{
   3686 		# Directory overview
   3687 		$base_dir = get_relative_base_path($rel_filename);
   3688 		$view = "<a href=\"$base_dir"."index.$html_ext\">".
   3689 			"$overview_title</a> - $trunc_name";
   3690 	}
   3691 	elsif ($type == 2 || $type == 4)
   3692 	{
   3693 		# File view
   3694 		my $dir_name = dirname($rel_filename);
   3695 
   3696 		$base_dir = get_relative_base_path($dir_name);
   3697 		if ($frames)
   3698 		{
   3699 			# Need to break frameset when clicking any of these
   3700 			# links
   3701 			$view = "<a href=\"$base_dir"."index.$html_ext\" ".
   3702 				"target=\"_parent\">$overview_title</a> - ".
   3703 				"<a href=\"index.$html_ext\" target=\"_parent\">".
   3704 				"$dir_name</a> - $base_name";
   3705 		}
   3706 		else
   3707 		{
   3708 			$view = "<a href=\"$base_dir"."index.$html_ext\">".
   3709 				"$overview_title</a> - ".
   3710 				"<a href=\"index.$html_ext\">".
   3711 				"$dir_name</a> - $base_name";
   3712 		}
   3713 
   3714 		# Add function suffix
   3715 		if ($func_coverage) {
   3716 			if ($type == 2) {
   3717 				$view .= " (source / <a href=\"$base_name.func.$html_ext\">functions</a>)";
   3718 			} elsif ($type == 4) {
   3719 				$view .= " (<a href=\"$base_name.gcov.$html_ext\">source</a> / functions)";
   3720 			}
   3721 		}
   3722 	}
   3723 	elsif ($type == 3)
   3724 	{
   3725 		# Test description header
   3726 		$base_dir = "";
   3727 		$view = "<a href=\"$base_dir"."index.$html_ext\">".
   3728 			"$overview_title</a> - test case descriptions";
   3729 	}
   3730 
   3731 	# Prepare text for "test" field
   3732 	$test = escape_html($test_title);
   3733 
   3734 	# Append link to test description page if available
   3735 	if (%test_description && ($type != 3))
   3736 	{
   3737 		if ($frames && ($type == 2 || $type == 4))
   3738 		{
   3739 			# Need to break frameset when clicking this link
   3740 			$test .= " ( <a href=\"$base_dir".
   3741 				 "descriptions.$html_ext\" target=\"_parent\">".
   3742 				 "view descriptions</a> )";
   3743 		}
   3744 		else
   3745 		{
   3746 			$test .= " ( <a href=\"$base_dir".
   3747 				 "descriptions.$html_ext\">".
   3748 				 "view descriptions</a> )";
   3749 		}
   3750 	}
   3751 
   3752 	# Write header
   3753 	write_header_prolog(*HTML_HANDLE, $base_dir);
   3754 	write_header_line(*HTML_HANDLE, 0, "Current view:", $view,
   3755 			  "Found", "Hit", "Coverage");
   3756 	write_header_line(*HTML_HANDLE, 1, "Test:", $test, "Lines:",
   3757 			  $lines_found, $lines_hit,
   3758 			  $rate_name[classify_rate($lines_found, $lines_hit,
   3759 						   $med_limit, $hi_limit)],
   3760 			  format_rate($lines_found, $lines_hit));
   3761 	if ($func_coverage) {
   3762 		write_header_line(*HTML_HANDLE, 1, "Date:", $date, "Functions:",
   3763 				  $fn_found, $fn_hit,
   3764 				  $rate_name[classify_rate($fn_found,
   3765 							   $fn_hit,
   3766 							   $fn_med_limit,
   3767 							   $fn_hi_limit)],
   3768 				  format_rate($fn_found, $fn_hit));
   3769 	} else {
   3770 		write_header_line(*HTML_HANDLE, 4, "Date:", $date);
   3771 	}
   3772 	if ($legend) {
   3773 		if ($type == 0 || $type == 1) {
   3774 			my $line_code = get_legend_code("Line coverage:",
   3775 						$med_limit, $hi_limit);
   3776 			my $func_code = "";
   3777 
   3778 			if ($func_coverage) {
   3779 				$func_code = get_legend_code(
   3780 						"Function coverage:",
   3781 						$fn_med_limit,
   3782 						$fn_hi_limit);
   3783 			}
   3784 			write_header_line(*HTML_HANDLE, 2, "Colors:",
   3785 				$line_code, $func_code);
   3786 		} elsif ($type == 2 || $type == 4) {
   3787 			write_header_line(*HTML_HANDLE, 3, "Colors:",
   3788 					  "not hit", "hit");
   3789 		}
   3790 	}
   3791 	write_header_epilog(*HTML_HANDLE, $base_dir);
   3792 }
   3793 
   3794 
   3795 #
   3796 # split_filename(filename)
   3797 #
   3798 # Return (path, filename, extension) for a given FILENAME.
   3799 #
   3800 
   3801 sub split_filename($)
   3802 {
   3803 	if (!$_[0]) { return(); }
   3804 	my @path_components = split('/', $_[0]);
   3805 	my @file_components = split('\.', pop(@path_components));
   3806 	my $extension = pop(@file_components);
   3807 
   3808 	return (join("/",@path_components), join(".",@file_components),
   3809 		$extension);
   3810 }
   3811 
   3812 #
   3813 # get_sorted_keys(hash_ref, sort_type)
   3814 #
   3815 
   3816 sub get_sorted_keys($$)
   3817 {
   3818 	my ($hash, $type) = @_;
   3819 
   3820 	if ($type == 0) {
   3821 		# Sort by name
   3822 		return sort(keys(%{$hash}));
   3823 	} elsif ($type == 1) {
   3824 		# Sort by line coverage
   3825 		return sort({$hash->{$a}[5] <=> $hash->{$b}[5]} keys(%{$hash}));
   3826 	} elsif ($type == 2) {
   3827 		# Sort by function coverage;
   3828 		return sort({$hash->{$a}[6] <=> $hash->{$b}[6]}	keys(%{$hash}));
   3829 	}
   3830 }
   3831 
   3832 sub get_sort_code($$$)
   3833 {
   3834 	my ($link, $alt, $base) = @_;
   3835 	my $png;
   3836 	my $link_start;
   3837 	my $link_end;
   3838 
   3839 	if (!defined($link)) {
   3840 		$png = "glass.png";
   3841 		$link_start = "";
   3842 		$link_end = "";
   3843 	} else {
   3844 		$png = "updown.png";
   3845 		$link_start = '<a href="'.$link.'">';
   3846 		$link_end = "</a>";
   3847 	}
   3848 
   3849 	return ' <span class="tableHeadSort">'.$link_start.
   3850 	       '<img src="'.$base.$png.'" width=10 height=14 '.
   3851 	       'alt="'.$alt.'" title="'.$alt.'" border=0>'.$link_end.'</span>';
   3852 }
   3853 
   3854 sub get_file_code($$$$)
   3855 {
   3856 	my ($type, $text, $sort_button, $base) = @_;
   3857 	my $result = $text;
   3858 	my $link;
   3859 
   3860 	if ($sort_button) {
   3861 		if ($type == 1) {
   3862 			$link = "index.$html_ext";
   3863 		} else {
   3864 			$link = "index-detail.$html_ext";
   3865 		}
   3866 	}
   3867 	$result .= get_sort_code($link, "Sort by name", $base);
   3868 
   3869 	return $result;
   3870 }
   3871 
   3872 sub get_line_code($$$$$)
   3873 {
   3874 	my ($type, $sort_type, $text, $sort_button, $base) = @_;
   3875 	my $result = $text;
   3876 	my $sort_link;
   3877 
   3878 	if ($type == 1) {
   3879 		# Just text
   3880 		if ($sort_button) {
   3881 			$sort_link = "index-sort-l.$html_ext";
   3882 		}
   3883 	} elsif ($type == 2) {
   3884 		# Text + link to detail view
   3885 		$result .= ' ( <a class="detail" href="index-detail'.
   3886 			   $fileview_sortname[$sort_type].'.'.$html_ext.
   3887 			   '">show details</a> )';
   3888 		if ($sort_button) {
   3889 			$sort_link = "index-sort-l.$html_ext";
   3890 		}
   3891 	} else {
   3892 		# Text + link to standard view
   3893 		$result .= ' ( <a class="detail" href="index'.
   3894 			   $fileview_sortname[$sort_type].'.'.$html_ext.
   3895 			   '">hide details</a> )';
   3896 		if ($sort_button) {
   3897 			$sort_link = "index-detail-sort-l.$html_ext";
   3898 		}
   3899 	}
   3900 	# Add sort button
   3901 	$result .= get_sort_code($sort_link, "Sort by line coverage", $base);
   3902 
   3903 	return $result;
   3904 }
   3905 
   3906 sub get_func_code($$$$)
   3907 {
   3908 	my ($type, $text, $sort_button, $base) = @_;
   3909 	my $result = $text;
   3910 	my $link;
   3911 
   3912 	if ($sort_button) {
   3913 		if ($type == 1) {
   3914 			$link = "index-sort-f.$html_ext";
   3915 		} else {
   3916 			$link = "index-detail-sort-f.$html_ext";
   3917 		}
   3918 	}
   3919 	$result .= get_sort_code($link, "Sort by function coverage", $base);
   3920 	return $result;
   3921 }
   3922 
   3923 #
   3924 # write_file_table(filehandle, base_dir, overview, testhash, testfnchash,
   3925 #                  fileview, sort_type)
   3926 #
   3927 # Write a complete file table. OVERVIEW is a reference to a hash containing
   3928 # the following mapping:
   3929 #
   3930 #   filename -> "lines_found,lines_hit,funcs_found,funcs_hit,page_link,
   3931 #		 func_link"
   3932 #
   3933 # TESTHASH is a reference to the following hash:
   3934 #
   3935 #   filename -> \%testdata
   3936 #   %testdata: name of test affecting this file -> \%testcount
   3937 #   %testcount: line number -> execution count for a single test
   3938 #
   3939 # Heading of first column is "Filename" if FILEVIEW is true, "Directory name"
   3940 # otherwise.
   3941 #
   3942 
   3943 sub write_file_table(*$$$$$$)
   3944 {
   3945 	local *HTML_HANDLE = $_[0];
   3946 	my $base_dir = $_[1];
   3947 	my $overview = $_[2];
   3948 	my $testhash = $_[3];
   3949 	my $testfnchash = $_[4];
   3950 	my $fileview = $_[5];
   3951 	my $sort_type = $_[6];
   3952 	my $filename;
   3953 	my $bar_graph;
   3954 	my $hit;
   3955 	my $found;
   3956 	my $fn_found;
   3957 	my $fn_hit;
   3958 	my $page_link;
   3959 	my $testname;
   3960 	my $testdata;
   3961 	my $testfncdata;
   3962 	my $testcount;
   3963 	my $testfnccount;
   3964 	my %affecting_tests;
   3965 	my $line_code = "";
   3966 	my $func_code;
   3967 	my $file_code;
   3968 
   3969 	# Determine HTML code for column headings
   3970 	if (($base_dir ne "") && $show_details)
   3971 	{
   3972 		my $detailed = keys(%{$testhash});
   3973 
   3974 		$file_code = get_file_code($detailed ? 2 : 1,
   3975 					$fileview ? "Filename" : "Directory",
   3976 					$sort && $sort_type != 0, $base_dir);
   3977 		$line_code = get_line_code($detailed ? 3 : 2, $sort_type,
   3978 					"Line Coverage",
   3979 					$sort && $sort_type != 1, $base_dir);
   3980 		$func_code = get_func_code($detailed ? 2 : 1, "Functions",
   3981 					$sort && $sort_type != 2, $base_dir);
   3982 	} else {
   3983 		$file_code = get_file_code(1,
   3984 					$fileview ? "Filename" : "Directory",
   3985 					$sort && $sort_type != 0, $base_dir);
   3986 		$line_code = get_line_code(1, $sort_type, "Line Coverage",
   3987 					$sort && $sort_type != 1, $base_dir);
   3988 		$func_code = get_func_code(1, "Functions",
   3989 					$sort && $sort_type != 2, $base_dir);
   3990 	}
   3991 
   3992 	write_file_table_prolog(*HTML_HANDLE, $file_code, $line_code,
   3993 				$func_code);
   3994 
   3995 	foreach $filename (get_sorted_keys($overview, $sort_type))
   3996 	{
   3997 		($found, $hit, $fn_found, $fn_hit, $page_link)
   3998 			= @{$overview->{$filename}};
   3999 		$bar_graph = get_bar_graph_code($base_dir, $found, $hit);
   4000 
   4001 		$testdata = $testhash->{$filename};
   4002 		$testfncdata = $testfnchash->{$filename};
   4003 
   4004 		write_file_table_entry(*HTML_HANDLE, $filename, $bar_graph,
   4005 				       $found, $hit, $fn_found, $fn_hit,
   4006 				       $page_link);
   4007 
   4008 		# Check whether we should write test specific coverage
   4009 		# as well
   4010 		if (!($show_details && $testdata)) { next; }
   4011 
   4012 		# Filter out those tests that actually affect this file
   4013 		%affecting_tests = %{ get_affecting_tests($testdata,
   4014 					$testfncdata) };
   4015 
   4016 		# Does any of the tests affect this file at all?
   4017 		if (!%affecting_tests) { next; }
   4018 
   4019 		# Write test details for this entry
   4020 		write_file_table_detail_heading(*HTML_HANDLE, "Test name",
   4021 						"Lines hit", "Functions hit");
   4022 
   4023 		foreach $testname (keys(%affecting_tests))
   4024 		{
   4025 			($found, $hit, $fn_found, $fn_hit) =
   4026 				split(",", $affecting_tests{$testname});
   4027 
   4028 			# Insert link to description of available
   4029 			if ($test_description{$testname})
   4030 			{
   4031 				$testname = "<a href=\"$base_dir".
   4032 					    "descriptions.$html_ext#$testname\">".
   4033 					    "$testname</a>";
   4034 			}
   4035 
   4036 			write_file_table_detail_entry(*HTML_HANDLE, $testname,
   4037 				$found, $hit, $fn_found, $fn_hit);
   4038 		}
   4039 	}
   4040 
   4041 	write_file_table_epilog(*HTML_HANDLE);
   4042 }
   4043 
   4044 
   4045 #
   4046 # get_found_and_hit(hash)
   4047 #
   4048 # Return the count for entries (found) and entries with an execution count
   4049 # greater than zero (hit) in a hash (linenumber -> execution count) as
   4050 # a list (found, hit)
   4051 #
   4052 
   4053 sub get_found_and_hit($)
   4054 {
   4055 	my %hash = %{$_[0]};
   4056 	my $found = 0;
   4057 	my $hit = 0;
   4058 
   4059 	# Calculate sum
   4060 	$found = 0;
   4061 	$hit = 0;
   4062 			
   4063 	foreach (keys(%hash))
   4064 	{
   4065 		$found++;
   4066 		if ($hash{$_}>0) { $hit++; }
   4067 	}
   4068 
   4069 	return ($found, $hit);
   4070 }
   4071 
   4072 
   4073 #
   4074 # get_func_found_and_hit(sumfnccount)
   4075 #
   4076 # Return (f_found, f_hit) for sumfnccount
   4077 #
   4078 
   4079 sub get_func_found_and_hit($)
   4080 {
   4081 	my ($sumfnccount) = @_;
   4082 	my $function;
   4083 	my $fn_found;
   4084 	my $fn_hit;
   4085 
   4086 	$fn_found = scalar(keys(%{$sumfnccount}));
   4087 	$fn_hit = 0;
   4088 	foreach $function (keys(%{$sumfnccount})) {
   4089 		if ($sumfnccount->{$function} > 0) {
   4090 			$fn_hit++;
   4091 		}
   4092 	}
   4093 	return ($fn_found, $fn_hit);
   4094 }
   4095 
   4096 
   4097 #
   4098 # get_affecting_tests(testdata, testfncdata)
   4099 #
   4100 # HASHREF contains a mapping filename -> (linenumber -> exec count). Return
   4101 # a hash containing mapping filename -> "lines found, lines hit" for each
   4102 # filename which has a nonzero hit count.
   4103 #
   4104 
   4105 sub get_affecting_tests($$)
   4106 {
   4107 	my $testdata = $_[0];
   4108 	my $testfncdata = $_[1];
   4109 	my $testname;
   4110 	my $testcount;
   4111 	my $testfnccount;
   4112 	my %result;
   4113 	my $found;
   4114 	my $hit;
   4115 	my $fn_found;
   4116 	my $fn_hit;
   4117 
   4118 	foreach $testname (keys(%{$testdata}))
   4119 	{
   4120 		# Get (line number -> count) hash for this test case
   4121 		$testcount = $testdata->{$testname};
   4122 		$testfnccount = $testfncdata->{$testname};
   4123 
   4124 		# Calculate sum
   4125 		($found, $hit) = get_found_and_hit($testcount);
   4126 		($fn_found, $fn_hit) = get_func_found_and_hit($testfnccount);
   4127 
   4128 		if ($hit>0)
   4129 		{
   4130 			$result{$testname} = "$found,$hit,$fn_found,$fn_hit";
   4131 		}
   4132 	}
   4133 
   4134 	return(\%result);
   4135 }
   4136 
   4137 
   4138 sub get_hash_reverse($)
   4139 {
   4140 	my ($hash) = @_;
   4141 	my %result;
   4142 
   4143 	foreach (keys(%{$hash})) {
   4144 		$result{$hash->{$_}} = $_;
   4145 	}
   4146 
   4147 	return \%result;
   4148 }
   4149 
   4150 #
   4151 # write_source(filehandle, source_filename, count_data, checksum_data,
   4152 #              converted_data, func_data)
   4153 #
   4154 # Write an HTML view of a source code file. Returns a list containing
   4155 # data as needed by gen_png().
   4156 #
   4157 # Die on error.
   4158 #
   4159 
   4160 sub write_source($$$$$$)
   4161 {
   4162 	local *HTML_HANDLE = $_[0];
   4163 	local *SOURCE_HANDLE;
   4164 	my $source_filename = $_[1];
   4165 	my %count_data;
   4166 	my $line_number;
   4167 	my @result;
   4168 	my $checkdata = $_[3];
   4169 	my $converted = $_[4];
   4170 	my $funcdata  = $_[5];
   4171 	my $datafunc = get_hash_reverse($funcdata);
   4172 	my $add_anchor;
   4173 
   4174 	if ($_[2])
   4175 	{
   4176 		%count_data = %{$_[2]};
   4177 	}
   4178 
   4179 	open(SOURCE_HANDLE, "<".$source_filename)
   4180           # or die("ERROR: cannot open $source_filename for reading!\n");
   4181           or open(SOURCE_HANDLE, "</dev/null");
   4182 	
   4183 	write_source_prolog(*HTML_HANDLE);
   4184 
   4185 	for ($line_number = 1; <SOURCE_HANDLE> ; $line_number++)
   4186 	{
   4187 		chomp($_);
   4188 
   4189 		# Source code matches coverage data?
   4190 		if (defined($checkdata->{$line_number}) &&
   4191 		    ($checkdata->{$line_number} ne md5_base64($_)))
   4192 		{
   4193 			die("ERROR: checksum mismatch  at $source_filename:".
   4194 			    "$line_number\n");
   4195 		}
   4196 
   4197 		$add_anchor = 0;
   4198 		if ($frames) {
   4199 			if (($line_number - 1) % $nav_resolution == 0) {
   4200 				$add_anchor = 1;
   4201 			}
   4202 		}
   4203 		if ($func_coverage) {
   4204 			if ($line_number == 1) {
   4205 				$add_anchor = 1;
   4206 			} elsif (defined($datafunc->{$line_number +
   4207 						     $func_offset})) {
   4208 				$add_anchor = 1;
   4209 			}
   4210 		}
   4211 		push (@result,
   4212 		      write_source_line(HTML_HANDLE, $line_number,
   4213 					$_, $count_data{$line_number},
   4214 					$converted->{$line_number},
   4215 					$add_anchor));
   4216 	}
   4217 
   4218 	close(SOURCE_HANDLE);
   4219 	write_source_epilog(*HTML_HANDLE);
   4220 	return(@result);
   4221 }
   4222 
   4223 
   4224 sub funcview_get_func_code($$$)
   4225 {
   4226 	my ($name, $base, $type) = @_;
   4227 	my $result;
   4228 	my $link;
   4229 
   4230 	if ($sort && $type == 1) {
   4231 		$link = "$name.func.$html_ext";
   4232 	}
   4233 	$result = "Function Name";
   4234 	$result .= get_sort_code($link, "Sort by function name", $base);
   4235 
   4236 	return $result;
   4237 }
   4238 
   4239 sub funcview_get_count_code($$$)
   4240 {
   4241 	my ($name, $base, $type) = @_;
   4242 	my $result;
   4243 	my $link;
   4244 
   4245 	if ($sort && $type == 0) {
   4246 		$link = "$name.func-sort-c.$html_ext";
   4247 	}
   4248 	$result = "Hit count";
   4249 	$result .= get_sort_code($link, "Sort by hit count", $base);
   4250 
   4251 	return $result;
   4252 }
   4253 
   4254 #
   4255 # funcview_get_sorted(funcdata, sumfncdata, sort_type)
   4256 #
   4257 # Depending on the value of sort_type, return a list of functions sorted
   4258 # by name (type 0) or by the associated call count (type 1).
   4259 #
   4260 
   4261 sub funcview_get_sorted($$$)
   4262 {
   4263 	my ($funcdata, $sumfncdata, $type) = @_;
   4264 
   4265 	if ($type == 0) {
   4266 		return sort(keys(%{$funcdata}));
   4267 	}
   4268 	return sort({$sumfncdata->{$b} <=> $sumfncdata->{$a}}
   4269 		    keys(%{$sumfncdata}));
   4270 }
   4271 
   4272 #
   4273 # write_function_table(filehandle, source_file, sumcount, funcdata,
   4274 #		       sumfnccount, testfncdata)
   4275 #
   4276 # Write an HTML table listing all functions in a source file, including
   4277 # also function call counts and line coverages inside of each function.
   4278 #
   4279 # Die on error.
   4280 #
   4281 
   4282 sub write_function_table(*$$$$$$$$)
   4283 {
   4284 	local *HTML_HANDLE = $_[0];
   4285 	my $source = $_[1];
   4286 	my $sumcount = $_[2];
   4287 	my $funcdata = $_[3];
   4288 	my $sumfncdata = $_[4];
   4289 	my $testfncdata = $_[5];
   4290 	my $name = $_[6];
   4291 	my $base = $_[7];
   4292 	my $type = $_[8];
   4293 	my $func;
   4294 	my $func_code;
   4295 	my $count_code;
   4296 
   4297 	# Get HTML code for headings
   4298 	$func_code = funcview_get_func_code($name, $base, $type);
   4299 	$count_code = funcview_get_count_code($name, $base, $type);
   4300 	write_html(*HTML_HANDLE, <<END_OF_HTML)
   4301 	  <center>
   4302 	  <table width="60%" cellpadding=1 cellspacing=1 border=0>
   4303 	    <tr><td><br></td></tr>
   4304 	    <tr>
   4305 	      <td width="80%" class="tableHead">$func_code</td>
   4306 	      <td width="20%" class="tableHead">$count_code</td>
   4307 	    </tr>
   4308 END_OF_HTML
   4309 	;
   4310 	
   4311 	# Get a sorted table
   4312 	foreach $func (funcview_get_sorted($funcdata, $sumfncdata, $type)) {
   4313 		my $startline = $funcdata->{$func} - $func_offset;
   4314 		my $name = escape_html($func);
   4315 		my $count = $sumfncdata->{$name};
   4316 		my $countstyle;
   4317 
   4318 		if ($startline < 1) {
   4319 			$startline = 1;
   4320 		}
   4321 		if ($count == 0) {
   4322 			$countstyle = "coverFnLo";
   4323 		} else {
   4324 			$countstyle = "coverFnHi";
   4325 		}
   4326 
   4327 		write_html(*HTML_HANDLE, <<END_OF_HTML)
   4328 	    <tr>
   4329               <td class="coverFn"><a href="$source#$startline">$name</a></td>
   4330               <td class="$countstyle">$count</td>
   4331             </tr>
   4332 END_OF_HTML
   4333                 ;
   4334 	}
   4335 	write_html(*HTML_HANDLE, <<END_OF_HTML)
   4336 	  </table>
   4337 	  <br>
   4338 	  </center>
   4339 END_OF_HTML
   4340 	;
   4341 }
   4342 
   4343 
   4344 #
   4345 # info(printf_parameter)
   4346 #
   4347 # Use printf to write PRINTF_PARAMETER to stdout only when the $quiet flag
   4348 # is not set.
   4349 #
   4350 
   4351 sub info(@)
   4352 {
   4353 	if (!$quiet)
   4354 	{
   4355 		# Print info string
   4356 		printf(@_);
   4357 	}
   4358 }
   4359 
   4360 
   4361 #
   4362 # subtract_counts(data_ref, base_ref)
   4363 #
   4364 
   4365 sub subtract_counts($$)
   4366 {
   4367 	my %data = %{$_[0]};
   4368 	my %base = %{$_[1]};
   4369 	my $line;
   4370 	my $data_count;
   4371 	my $base_count;
   4372 	my $hit = 0;
   4373 	my $found = 0;
   4374 
   4375 	foreach $line (keys(%data))
   4376 	{
   4377 		$found++;
   4378 		$data_count = $data{$line};
   4379 		$base_count = $base{$line};
   4380 
   4381 		if (defined($base_count))
   4382 		{
   4383 			$data_count -= $base_count;
   4384 
   4385 			# Make sure we don't get negative numbers
   4386 			if ($data_count<0) { $data_count = 0; }
   4387 		}
   4388 
   4389 		$data{$line} = $data_count;
   4390 		if ($data_count > 0) { $hit++; }
   4391 	}
   4392 
   4393 	return (\%data, $found, $hit);
   4394 }
   4395 
   4396 
   4397 #
   4398 # subtract_fnccounts(data, base)
   4399 #
   4400 # Subtract function call counts found in base from those in data.
   4401 # Return (data, f_found, f_hit).
   4402 #
   4403 
   4404 sub subtract_fnccounts($$)
   4405 {
   4406 	my %data = %{$_[0]};
   4407 	my %base = %{$_[1]};
   4408 	my $func;
   4409 	my $data_count;
   4410 	my $base_count;
   4411 	my $fn_hit = 0;
   4412 	my $fn_found = 0;
   4413 
   4414 	foreach $func (keys(%data)) {
   4415 		$fn_found++;
   4416 		$data_count = $data{$func};
   4417 		$base_count = $base{$func};
   4418 
   4419 		if (defined($base_count)) {
   4420 			$data_count -= $base_count;
   4421 
   4422 			# Make sure we don't get negative numbers
   4423 			if ($data_count < 0) {
   4424 				$data_count = 0;
   4425 			}
   4426 		}
   4427 
   4428 		$data{$func} = $data_count;
   4429 		if ($data_count > 0) {
   4430 			$fn_hit++;
   4431 		}
   4432 	}
   4433 
   4434 	return (\%data, $fn_found, $fn_hit);
   4435 }
   4436 
   4437 
   4438 #
   4439 # apply_baseline(data_ref, baseline_ref)
   4440 #
   4441 # Subtract the execution counts found in the baseline hash referenced by
   4442 # BASELINE_REF from actual data in DATA_REF.
   4443 #
   4444 
   4445 sub apply_baseline($$)
   4446 {
   4447 	my %data_hash = %{$_[0]};
   4448 	my %base_hash = %{$_[1]};
   4449 	my $filename;
   4450 	my $testname;
   4451 	my $data;
   4452 	my $data_testdata;
   4453 	my $data_funcdata;
   4454 	my $data_checkdata;
   4455 	my $data_testfncdata;
   4456 	my $data_count;
   4457 	my $data_testfnccount;
   4458 	my $base;
   4459 	my $base_checkdata;
   4460 	my $base_sumfnccount;
   4461 	my $base_count;
   4462 	my $sumcount;
   4463 	my $sumfnccount;
   4464 	my $found;
   4465 	my $hit;
   4466 	my $fn_found;
   4467 	my $fn_hit;
   4468 
   4469 	foreach $filename (keys(%data_hash))
   4470 	{
   4471 		# Get data set for data and baseline
   4472 		$data = $data_hash{$filename};
   4473 		$base = $base_hash{$filename};
   4474 
   4475 		# Skip data entries for which no base entry exists
   4476 		if (!defined($base))
   4477 		{
   4478 			next;
   4479 		}
   4480 
   4481 		# Get set entries for data and baseline
   4482 		($data_testdata, undef, $data_funcdata, $data_checkdata,
   4483 		 $data_testfncdata) = get_info_entry($data);
   4484 		(undef, $base_count, undef, $base_checkdata, undef,
   4485 		 $base_sumfnccount) = get_info_entry($base);
   4486 
   4487 		# Check for compatible checksums
   4488 		merge_checksums($data_checkdata, $base_checkdata, $filename);
   4489 
   4490 		# sumcount has to be calculated anew
   4491 		$sumcount = {};
   4492 		$sumfnccount = {};
   4493 
   4494 		# For each test case, subtract test specific counts
   4495 		foreach $testname (keys(%{$data_testdata}))
   4496 		{
   4497 			# Get counts of both data and baseline
   4498 			$data_count = $data_testdata->{$testname};
   4499 			$data_testfnccount = $data_testfncdata->{$testname};
   4500 
   4501 			($data_count, undef, $hit) =
   4502 				subtract_counts($data_count, $base_count);
   4503 			($data_testfnccount) =
   4504 				subtract_fnccounts($data_testfnccount,
   4505 						   $base_sumfnccount);
   4506 
   4507 			# Check whether this test case did hit any line at all
   4508 			if ($hit > 0)
   4509 			{
   4510 				# Write back resulting hash
   4511 				$data_testdata->{$testname} = $data_count;
   4512 				$data_testfncdata->{$testname} =
   4513 					$data_testfnccount;
   4514 			}
   4515 			else
   4516 			{
   4517 				# Delete test case which did not impact this
   4518 				# file
   4519 				delete($data_testdata->{$testname});
   4520 				delete($data_testfncdata->{$testname});
   4521 			}
   4522 
   4523 			# Add counts to sum of counts
   4524 			($sumcount, $found, $hit) =
   4525 				add_counts($sumcount, $data_count);
   4526 			($sumfnccount, $fn_found, $fn_hit) =
   4527 				add_fnccounts($sumfnccount, $data_testfnccount);
   4528 		}
   4529 
   4530 		# Write back resulting entry
   4531 		set_info_entry($data, $data_testdata, $sumcount, $data_funcdata,
   4532 			       $data_checkdata, $data_testfncdata, $sumfnccount,
   4533 			       $found, $hit, $fn_found, $fn_hit);
   4534 
   4535 		$data_hash{$filename} = $data;
   4536 	}
   4537 
   4538 	return (\%data_hash);
   4539 }
   4540 
   4541 
   4542 #
   4543 # remove_unused_descriptions()
   4544 #
   4545 # Removes all test descriptions from the global hash %test_description which
   4546 # are not present in %info_data.
   4547 #
   4548 
   4549 sub remove_unused_descriptions()
   4550 {
   4551 	my $filename;		# The current filename
   4552 	my %test_list;		# Hash containing found test names
   4553 	my $test_data;		# Reference to hash test_name -> count_data
   4554 	my $before;		# Initial number of descriptions
   4555 	my $after;		# Remaining number of descriptions
   4556 	
   4557 	$before = scalar(keys(%test_description));
   4558 
   4559 	foreach $filename (keys(%info_data))
   4560 	{
   4561 		($test_data) = get_info_entry($info_data{$filename});
   4562 		foreach (keys(%{$test_data}))
   4563 		{
   4564 			$test_list{$_} = "";
   4565 		}
   4566 	}
   4567 
   4568 	# Remove descriptions for tests which are not in our list
   4569 	foreach (keys(%test_description))
   4570 	{
   4571 		if (!defined($test_list{$_}))
   4572 		{
   4573 			delete($test_description{$_});
   4574 		}
   4575 	}
   4576 
   4577 	$after = scalar(keys(%test_description));
   4578 	if ($after < $before)
   4579 	{
   4580 		info("Removed ".($before - $after).
   4581 		     " unused descriptions, $after remaining.\n");
   4582 	}
   4583 }
   4584 
   4585 
   4586 #
   4587 # apply_prefix(filename, prefix)
   4588 #
   4589 # If FILENAME begins with PREFIX, remove PREFIX from FILENAME and return
   4590 # resulting string, otherwise return FILENAME.
   4591 #
   4592 
   4593 sub apply_prefix($$)
   4594 {
   4595 	my $filename = $_[0];
   4596 	my $prefix = $_[1];
   4597 
   4598 	if (defined($prefix) && ($prefix ne ""))
   4599 	{
   4600 		if ($filename =~ /^\Q$prefix\E\/(.*)$/)
   4601 		{
   4602 			return substr($filename, length($prefix) + 1);
   4603 		}
   4604 	}
   4605 
   4606 	return $filename;
   4607 }
   4608 
   4609 
   4610 #
   4611 # system_no_output(mode, parameters)
   4612 #
   4613 # Call an external program using PARAMETERS while suppressing depending on
   4614 # the value of MODE:
   4615 #
   4616 #   MODE & 1: suppress STDOUT
   4617 #   MODE & 2: suppress STDERR
   4618 #
   4619 # Return 0 on success, non-zero otherwise.
   4620 #
   4621 
   4622 sub system_no_output($@)
   4623 {
   4624 	my $mode = shift;
   4625 	my $result;
   4626 	local *OLD_STDERR;
   4627 	local *OLD_STDOUT;
   4628 
   4629 	# Save old stdout and stderr handles
   4630 	($mode & 1) && open(OLD_STDOUT, ">>&STDOUT");
   4631 	($mode & 2) && open(OLD_STDERR, ">>&STDERR");
   4632 
   4633 	# Redirect to /dev/null
   4634 	($mode & 1) && open(STDOUT, ">/dev/null");
   4635 	($mode & 2) && open(STDERR, ">/dev/null");
   4636 
   4637 	system(@_);
   4638 	$result = $?;
   4639 
   4640 	# Close redirected handles
   4641 	($mode & 1) && close(STDOUT);
   4642 	($mode & 2) && close(STDERR);
   4643 
   4644 	# Restore old handles
   4645 	($mode & 1) && open(STDOUT, ">>&OLD_STDOUT");
   4646 	($mode & 2) && open(STDERR, ">>&OLD_STDERR");
   4647 
   4648 	return $result;
   4649 }
   4650 
   4651 
   4652 #
   4653 # read_config(filename)
   4654 #
   4655 # Read configuration file FILENAME and return a reference to a hash containing
   4656 # all valid key=value pairs found.
   4657 #
   4658 
   4659 sub read_config($)
   4660 {
   4661 	my $filename = $_[0];
   4662 	my %result;
   4663 	my $key;
   4664 	my $value;
   4665 	local *HANDLE;
   4666 
   4667 	if (!open(HANDLE, "<$filename"))
   4668 	{
   4669 		warn("WARNING: cannot read configuration file $filename\n");
   4670 		return undef;
   4671 	}
   4672 	while (<HANDLE>)
   4673 	{
   4674 		chomp;
   4675 		# Skip comments
   4676 		s/#.*//;
   4677 		# Remove leading blanks
   4678 		s/^\s+//;
   4679 		# Remove trailing blanks
   4680 		s/\s+$//;
   4681 		next unless length;
   4682 		($key, $value) = split(/\s*=\s*/, $_, 2);
   4683 		if (defined($key) && defined($value))
   4684 		{
   4685 			$result{$key} = $value;
   4686 		}
   4687 		else
   4688 		{
   4689 			warn("WARNING: malformed statement in line $. ".
   4690 			     "of configuration file $filename\n");
   4691 		}
   4692 	}
   4693 	close(HANDLE);
   4694 	return \%result;
   4695 }
   4696 
   4697 
   4698 #
   4699 # apply_config(REF)
   4700 #
   4701 # REF is a reference to a hash containing the following mapping:
   4702 #
   4703 #   key_string => var_ref
   4704 #
   4705 # where KEY_STRING is a keyword and VAR_REF is a reference to an associated
   4706 # variable. If the global configuration hash CONFIG contains a value for
   4707 # keyword KEY_STRING, VAR_REF will be assigned the value for that keyword. 
   4708 #
   4709 
   4710 sub apply_config($)
   4711 {
   4712 	my $ref = $_[0];
   4713 
   4714 	foreach (keys(%{$ref}))
   4715 	{
   4716 		if (defined($config->{$_}))
   4717 		{
   4718 			${$ref->{$_}} = $config->{$_};
   4719 		}
   4720 	}
   4721 }
   4722 
   4723 
   4724 #
   4725 # get_html_prolog(FILENAME)
   4726 #
   4727 # If FILENAME is defined, return contents of file. Otherwise return default
   4728 # HTML prolog. Die on error.
   4729 #
   4730 
   4731 sub get_html_prolog($)
   4732 {
   4733 	my $filename = $_[0];
   4734 	my $result = "";
   4735 
   4736 	if (defined($filename))
   4737 	{
   4738 		local *HANDLE;
   4739 
   4740 		open(HANDLE, "<".$filename)
   4741 			or die("ERROR: cannot open html prolog $filename!\n");
   4742 		while (<HANDLE>)
   4743 		{
   4744 			$result .= $_;
   4745 		}
   4746 		close(HANDLE);
   4747 	}
   4748 	else
   4749 	{
   4750 		$result = <<END_OF_HTML
   4751 <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">
   4752 
   4753 <html lang="en">
   4754 
   4755 <head>
   4756   <meta http-equiv="Content-Type" content="text/html; charset=ISO-8859-1">
   4757   <title>\@pagetitle\@</title>
   4758   <link rel="stylesheet" type="text/css" href="\@basedir\@gcov.css">
   4759 </head>
   4760 
   4761 <body>
   4762 
   4763 END_OF_HTML
   4764 		;
   4765 	}
   4766 
   4767 	return $result;
   4768 }
   4769 
   4770 
   4771 #
   4772 # get_html_epilog(FILENAME)
   4773 #
   4774 # If FILENAME is defined, return contents of file. Otherwise return default
   4775 # HTML epilog. Die on error.
   4776 #
   4777 sub get_html_epilog($)
   4778 {
   4779 	my $filename = $_[0];
   4780 	my $result = "";
   4781 
   4782 	if (defined($filename))
   4783 	{
   4784 		local *HANDLE;
   4785 
   4786 		open(HANDLE, "<".$filename)
   4787 			or die("ERROR: cannot open html epilog $filename!\n");
   4788 		while (<HANDLE>)
   4789 		{
   4790 			$result .= $_;
   4791 		}
   4792 		close(HANDLE);
   4793 	}
   4794 	else
   4795 	{
   4796 		$result = <<END_OF_HTML
   4797 
   4798 </body>
   4799 </html>
   4800 END_OF_HTML
   4801 		;
   4802 	}
   4803 
   4804 	return $result;
   4805 
   4806 }
   4807 
   4808 sub warn_handler($)
   4809 {
   4810 	my ($msg) = @_;
   4811 
   4812 	warn("$tool_name: $msg");
   4813 }
   4814 
   4815 sub die_handler($)
   4816 {
   4817 	my ($msg) = @_;
   4818 
   4819 	die("$tool_name: $msg");
   4820 }
   4821