1#!/usr/bin/perl -w
2#
3#   Copyright (c) International Business Machines  Corp., 2002,2012
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@de.ibm.com>
29#                         IBM Lab Boeblingen
30#        based on code by Manoj Iyer <manjo@mail.utexas.edu> and
31#                         Megan Bock <mbock@us.ibm.com>
32#                         IBM Austin
33#   2002-08-27 / Peter Oberparleiter: implemented frame view
34#   2002-08-29 / Peter Oberparleiter: implemented test description filtering
35#                so that by default only descriptions for test cases which
36#                actually hit some source lines are kept
37#   2002-09-05 / Peter Oberparleiter: implemented --no-sourceview
38#   2002-09-05 / Mike Kobler: One of my source file paths includes a "+" in
39#                the directory name.  I found that genhtml.pl died when it
40#                encountered it. I was able to fix the problem by modifying
41#                the string with the escape character before parsing it.
42#   2002-10-26 / Peter Oberparleiter: implemented --num-spaces
43#   2003-04-07 / Peter Oberparleiter: fixed bug which resulted in an error
44#                when trying to combine .info files containing data without
45#                a test name
46#   2003-04-10 / Peter Oberparleiter: extended fix by Mike to also cover
47#                other special characters
48#   2003-04-30 / Peter Oberparleiter: made info write to STDERR, not STDOUT
49#   2003-07-10 / Peter Oberparleiter: added line checksum support
50#   2004-08-09 / Peter Oberparleiter: added configuration file support
51#   2005-03-04 / Cal Pierog: added legend to HTML output, fixed coloring of
52#                "good coverage" background
53#   2006-03-18 / Marcus Boerger: added --custom-intro, --custom-outro and
54#                overwrite --no-prefix if --prefix is present
55#   2006-03-20 / Peter Oberparleiter: changes to custom_* function (rename
56#                to html_prolog/_epilog, minor modifications to implementation),
57#                changed prefix/noprefix handling to be consistent with current
58#                logic
59#   2006-03-20 / Peter Oberparleiter: added --html-extension option
60#   2008-07-14 / Tom Zoerner: added --function-coverage command line option;
61#                added function table to source file page
62#   2008-08-13 / Peter Oberparleiter: modified function coverage
63#                implementation (now enabled per default),
64#                introduced sorting option (enabled per default)
65#
66
67use strict;
68use File::Basename;
69use File::Temp qw(tempfile);
70use Getopt::Long;
71use Digest::MD5 qw(md5_base64);
72use Cwd qw/abs_path/;
73
74
75# Global constants
76our $title		= "LCOV - code coverage report";
77our $tool_dir		= abs_path(dirname($0));
78our $lcov_version	= "LCOV version 1.13";
79our $lcov_url		= "http://ltp.sourceforge.net/coverage/lcov.php";
80our $tool_name		= basename($0);
81
82# Specify coverage rate default precision
83our $default_precision = 1;
84
85# Specify coverage rate limits (in %) for classifying file entries
86# HI:   $hi_limit <= rate <= 100          graph color: green
87# MED: $med_limit <= rate <  $hi_limit    graph color: orange
88# LO:          0  <= rate <  $med_limit   graph color: red
89
90# For line coverage/all coverage types if not specified
91our $hi_limit = 90;
92our $med_limit = 75;
93
94# For function coverage
95our $fn_hi_limit;
96our $fn_med_limit;
97
98# For branch coverage
99our $br_hi_limit;
100our $br_med_limit;
101
102# Width of overview image
103our $overview_width = 80;
104
105# Resolution of overview navigation: this number specifies the maximum
106# difference in lines between the position a user selected from the overview
107# and the position the source code window is scrolled to.
108our $nav_resolution = 4;
109
110# Clicking a line in the overview image should show the source code view at
111# a position a bit further up so that the requested line is not the first
112# line in the window. This number specifies that offset in lines.
113our $nav_offset = 10;
114
115# Clicking on a function name should show the source code at a position a
116# few lines before the first line of code of that function. This number
117# specifies that offset in lines.
118our $func_offset = 2;
119
120our $overview_title = "top level";
121
122# Width for line coverage information in the source code view
123our $line_field_width = 12;
124
125# Width for branch coverage information in the source code view
126our $br_field_width = 16;
127
128# Internal Constants
129
130# Header types
131our $HDR_DIR		= 0;
132our $HDR_FILE		= 1;
133our $HDR_SOURCE		= 2;
134our $HDR_TESTDESC	= 3;
135our $HDR_FUNC		= 4;
136
137# Sort types
138our $SORT_FILE		= 0;
139our $SORT_LINE		= 1;
140our $SORT_FUNC		= 2;
141our $SORT_BRANCH	= 3;
142
143# Fileview heading types
144our $HEAD_NO_DETAIL	= 1;
145our $HEAD_DETAIL_HIDDEN	= 2;
146our $HEAD_DETAIL_SHOWN	= 3;
147
148# Offsets for storing branch coverage data in vectors
149our $BR_BLOCK		= 0;
150our $BR_BRANCH		= 1;
151our $BR_TAKEN		= 2;
152our $BR_VEC_ENTRIES	= 3;
153our $BR_VEC_WIDTH	= 32;
154our $BR_VEC_MAX		= vec(pack('b*', 1 x $BR_VEC_WIDTH), 0, $BR_VEC_WIDTH);
155
156# Additional offsets used when converting branch coverage data to HTML
157our $BR_LEN	= 3;
158our $BR_OPEN	= 4;
159our $BR_CLOSE	= 5;
160
161# Branch data combination types
162our $BR_SUB = 0;
163our $BR_ADD = 1;
164
165# Error classes which users may specify to ignore during processing
166our $ERROR_SOURCE	= 0;
167our %ERROR_ID = (
168	"source" => $ERROR_SOURCE,
169);
170
171# Data related prototypes
172sub print_usage(*);
173sub gen_html();
174sub html_create($$);
175sub process_dir($);
176sub process_file($$$);
177sub info(@);
178sub read_info_file($);
179sub get_info_entry($);
180sub set_info_entry($$$$$$$$$;$$$$$$);
181sub get_prefix($@);
182sub shorten_prefix($);
183sub get_dir_list(@);
184sub get_relative_base_path($);
185sub read_testfile($);
186sub get_date_string();
187sub create_sub_dir($);
188sub subtract_counts($$);
189sub add_counts($$);
190sub apply_baseline($$);
191sub remove_unused_descriptions();
192sub get_found_and_hit($);
193sub get_affecting_tests($$$);
194sub combine_info_files($$);
195sub merge_checksums($$$);
196sub combine_info_entries($$$);
197sub apply_prefix($@);
198sub system_no_output($@);
199sub read_config($);
200sub apply_config($);
201sub get_html_prolog($);
202sub get_html_epilog($);
203sub write_dir_page($$$$$$$$$$$$$$$$$);
204sub classify_rate($$$$);
205sub br_taken_add($$);
206sub br_taken_sub($$);
207sub br_ivec_len($);
208sub br_ivec_get($$);
209sub br_ivec_push($$$$);
210sub combine_brcount($$$);
211sub get_br_found_and_hit($);
212sub warn_handler($);
213sub die_handler($);
214sub parse_ignore_errors(@);
215sub parse_dir_prefix(@);
216sub rate($$;$$$);
217
218
219# HTML related prototypes
220sub escape_html($);
221sub get_bar_graph_code($$$);
222
223sub write_png_files();
224sub write_htaccess_file();
225sub write_css_file();
226sub write_description_file($$$$$$$);
227sub write_function_table(*$$$$$$$$$$);
228
229sub write_html(*$);
230sub write_html_prolog(*$$);
231sub write_html_epilog(*$;$);
232
233sub write_header(*$$$$$$$$$$);
234sub write_header_prolog(*$);
235sub write_header_line(*@);
236sub write_header_epilog(*$);
237
238sub write_file_table(*$$$$$$$);
239sub write_file_table_prolog(*$@);
240sub write_file_table_entry(*$$$@);
241sub write_file_table_detail_entry(*$@);
242sub write_file_table_epilog(*);
243
244sub write_test_table_prolog(*$);
245sub write_test_table_entry(*$$);
246sub write_test_table_epilog(*);
247
248sub write_source($$$$$$$);
249sub write_source_prolog(*);
250sub write_source_line(*$$$$$$);
251sub write_source_epilog(*);
252
253sub write_frameset(*$$$);
254sub write_overview_line(*$$$);
255sub write_overview(*$$$$);
256
257# External prototype (defined in genpng)
258sub gen_png($$$@);
259
260
261# Global variables & initialization
262our %info_data;		# Hash containing all data from .info file
263our @opt_dir_prefix;	# Array of prefixes to remove from all sub directories
264our @dir_prefix;
265our %test_description;	# Hash containing test descriptions if available
266our $date = get_date_string();
267
268our @info_filenames;	# List of .info files to use as data source
269our $test_title;	# Title for output as written to each page header
270our $output_directory;	# Name of directory in which to store output
271our $base_filename;	# Optional name of file containing baseline data
272our $desc_filename;	# Name of file containing test descriptions
273our $css_filename;	# Optional name of external stylesheet file to use
274our $quiet;		# If set, suppress information messages
275our $help;		# Help option flag
276our $version;		# Version option flag
277our $show_details;	# If set, generate detailed directory view
278our $no_prefix;		# If set, do not remove filename prefix
279our $func_coverage;	# If set, generate function coverage statistics
280our $no_func_coverage;	# Disable func_coverage
281our $br_coverage;	# If set, generate branch coverage statistics
282our $no_br_coverage;	# Disable br_coverage
283our $sort = 1;		# If set, provide directory listings with sorted entries
284our $no_sort;		# Disable sort
285our $frames;		# If set, use frames for source code view
286our $keep_descriptions;	# If set, do not remove unused test case descriptions
287our $no_sourceview;	# If set, do not create a source code view for each file
288our $highlight;		# If set, highlight lines covered by converted data only
289our $legend;		# If set, include legend in output
290our $tab_size = 8;	# Number of spaces to use in place of tab
291our $config;		# Configuration file contents
292our $html_prolog_file;	# Custom HTML prolog file (up to and including <body>)
293our $html_epilog_file;	# Custom HTML epilog file (from </body> onwards)
294our $html_prolog;	# Actual HTML prolog
295our $html_epilog;	# Actual HTML epilog
296our $html_ext = "html";	# Extension for generated HTML files
297our $html_gzip = 0;	# Compress with gzip
298our $demangle_cpp = 0;	# Demangle C++ function names
299our @opt_ignore_errors;	# Ignore certain error classes during processing
300our @ignore;
301our $opt_config_file;	# User-specified configuration file location
302our %opt_rc;
303our $charset = "UTF-8";	# Default charset for HTML pages
304our @fileview_sortlist;
305our @fileview_sortname = ("", "-sort-l", "-sort-f", "-sort-b");
306our @funcview_sortlist;
307our @rate_name = ("Lo", "Med", "Hi");
308our @rate_png = ("ruby.png", "amber.png", "emerald.png");
309our $lcov_func_coverage = 1;
310our $lcov_branch_coverage = 0;
311our $rc_desc_html = 0;	# lcovrc: genhtml_desc_html
312
313our $cwd = `pwd`;	# Current working directory
314chomp($cwd);
315
316
317#
318# Code entry point
319#
320
321$SIG{__WARN__} = \&warn_handler;
322$SIG{__DIE__} = \&die_handler;
323
324# Check command line for a configuration file name
325Getopt::Long::Configure("pass_through", "no_auto_abbrev");
326GetOptions("config-file=s" => \$opt_config_file,
327	   "rc=s%" => \%opt_rc);
328Getopt::Long::Configure("default");
329
330{
331	# Remove spaces around rc options
332	my %new_opt_rc;
333
334	while (my ($key, $value) = each(%opt_rc)) {
335		$key =~ s/^\s+|\s+$//g;
336		$value =~ s/^\s+|\s+$//g;
337
338		$new_opt_rc{$key} = $value;
339	}
340	%opt_rc = %new_opt_rc;
341}
342
343# Read configuration file if available
344if (defined($opt_config_file)) {
345	$config = read_config($opt_config_file);
346} elsif (defined($ENV{"HOME"}) && (-r $ENV{"HOME"}."/.lcovrc"))
347{
348	$config = read_config($ENV{"HOME"}."/.lcovrc");
349}
350elsif (-r "/etc/lcovrc")
351{
352	$config = read_config("/etc/lcovrc");
353} elsif (-r "/usr/local/etc/lcovrc")
354{
355	$config = read_config("/usr/local/etc/lcovrc");
356}
357
358if ($config || %opt_rc)
359{
360	# Copy configuration file and --rc values to variables
361	apply_config({
362		"genhtml_css_file"		=> \$css_filename,
363		"genhtml_hi_limit"		=> \$hi_limit,
364		"genhtml_med_limit"		=> \$med_limit,
365		"genhtml_line_field_width"	=> \$line_field_width,
366		"genhtml_overview_width"	=> \$overview_width,
367		"genhtml_nav_resolution"	=> \$nav_resolution,
368		"genhtml_nav_offset"		=> \$nav_offset,
369		"genhtml_keep_descriptions"	=> \$keep_descriptions,
370		"genhtml_no_prefix"		=> \$no_prefix,
371		"genhtml_no_source"		=> \$no_sourceview,
372		"genhtml_num_spaces"		=> \$tab_size,
373		"genhtml_highlight"		=> \$highlight,
374		"genhtml_legend"		=> \$legend,
375		"genhtml_html_prolog"		=> \$html_prolog_file,
376		"genhtml_html_epilog"		=> \$html_epilog_file,
377		"genhtml_html_extension"	=> \$html_ext,
378		"genhtml_html_gzip"		=> \$html_gzip,
379		"genhtml_precision"		=> \$default_precision,
380		"genhtml_function_hi_limit"	=> \$fn_hi_limit,
381		"genhtml_function_med_limit"	=> \$fn_med_limit,
382		"genhtml_function_coverage"	=> \$func_coverage,
383		"genhtml_branch_hi_limit"	=> \$br_hi_limit,
384		"genhtml_branch_med_limit"	=> \$br_med_limit,
385		"genhtml_branch_coverage"	=> \$br_coverage,
386		"genhtml_branch_field_width"	=> \$br_field_width,
387		"genhtml_sort"			=> \$sort,
388		"genhtml_charset"		=> \$charset,
389		"genhtml_desc_html"		=> \$rc_desc_html,
390		"lcov_function_coverage"	=> \$lcov_func_coverage,
391		"lcov_branch_coverage"		=> \$lcov_branch_coverage,
392		});
393}
394
395# Copy related values if not specified
396$fn_hi_limit	= $hi_limit if (!defined($fn_hi_limit));
397$fn_med_limit	= $med_limit if (!defined($fn_med_limit));
398$br_hi_limit	= $hi_limit if (!defined($br_hi_limit));
399$br_med_limit	= $med_limit if (!defined($br_med_limit));
400$func_coverage	= $lcov_func_coverage if (!defined($func_coverage));
401$br_coverage	= $lcov_branch_coverage if (!defined($br_coverage));
402
403# Parse command line options
404if (!GetOptions("output-directory|o=s"	=> \$output_directory,
405		"title|t=s"		=> \$test_title,
406		"description-file|d=s"	=> \$desc_filename,
407		"keep-descriptions|k"	=> \$keep_descriptions,
408		"css-file|c=s"		=> \$css_filename,
409		"baseline-file|b=s"	=> \$base_filename,
410		"prefix|p=s"		=> \@opt_dir_prefix,
411		"num-spaces=i"		=> \$tab_size,
412		"no-prefix"		=> \$no_prefix,
413		"no-sourceview"		=> \$no_sourceview,
414		"show-details|s"	=> \$show_details,
415		"frames|f"		=> \$frames,
416		"highlight"		=> \$highlight,
417		"legend"		=> \$legend,
418		"quiet|q"		=> \$quiet,
419		"help|h|?"		=> \$help,
420		"version|v"		=> \$version,
421		"html-prolog=s"		=> \$html_prolog_file,
422		"html-epilog=s"		=> \$html_epilog_file,
423		"html-extension=s"	=> \$html_ext,
424		"html-gzip"		=> \$html_gzip,
425		"function-coverage"	=> \$func_coverage,
426		"no-function-coverage"	=> \$no_func_coverage,
427		"branch-coverage"	=> \$br_coverage,
428		"no-branch-coverage"	=> \$no_br_coverage,
429		"sort"			=> \$sort,
430		"no-sort"		=> \$no_sort,
431		"demangle-cpp"		=> \$demangle_cpp,
432		"ignore-errors=s"	=> \@opt_ignore_errors,
433		"config-file=s"		=> \$opt_config_file,
434		"rc=s%"			=> \%opt_rc,
435		"precision=i"		=> \$default_precision,
436		))
437{
438	print(STDERR "Use $tool_name --help to get usage information\n");
439	exit(1);
440} else {
441	# Merge options
442	if ($no_func_coverage) {
443		$func_coverage = 0;
444	}
445	if ($no_br_coverage) {
446		$br_coverage = 0;
447	}
448
449	# Merge sort options
450	if ($no_sort) {
451		$sort = 0;
452	}
453}
454
455@info_filenames = @ARGV;
456
457# Check for help option
458if ($help)
459{
460	print_usage(*STDOUT);
461	exit(0);
462}
463
464# Check for version option
465if ($version)
466{
467	print("$tool_name: $lcov_version\n");
468	exit(0);
469}
470
471# Determine which errors the user wants us to ignore
472parse_ignore_errors(@opt_ignore_errors);
473
474# Split the list of prefixes if needed
475parse_dir_prefix(@opt_dir_prefix);
476
477# Check for info filename
478if (!@info_filenames)
479{
480	die("No filename specified\n".
481	    "Use $tool_name --help to get usage information\n");
482}
483
484# Generate a title if none is specified
485if (!$test_title)
486{
487	if (scalar(@info_filenames) == 1)
488	{
489		# Only one filename specified, use it as title
490		$test_title = basename($info_filenames[0]);
491	}
492	else
493	{
494		# More than one filename specified, used default title
495		$test_title = "unnamed";
496	}
497}
498
499# Make sure css_filename is an absolute path (in case we're changing
500# directories)
501if ($css_filename)
502{
503	if (!($css_filename =~ /^\/(.*)$/))
504	{
505		$css_filename = $cwd."/".$css_filename;
506	}
507}
508
509# Make sure tab_size is within valid range
510if ($tab_size < 1)
511{
512	print(STDERR "ERROR: invalid number of spaces specified: ".
513		     "$tab_size!\n");
514	exit(1);
515}
516
517# Get HTML prolog and epilog
518$html_prolog = get_html_prolog($html_prolog_file);
519$html_epilog = get_html_epilog($html_epilog_file);
520
521# Issue a warning if --no-sourceview is enabled together with --frames
522if ($no_sourceview && defined($frames))
523{
524	warn("WARNING: option --frames disabled because --no-sourceview ".
525	     "was specified!\n");
526	$frames = undef;
527}
528
529# Issue a warning if --no-prefix is enabled together with --prefix
530if ($no_prefix && @dir_prefix)
531{
532	warn("WARNING: option --prefix disabled because --no-prefix was ".
533	     "specified!\n");
534	@dir_prefix = undef;
535}
536
537@fileview_sortlist = ($SORT_FILE);
538@funcview_sortlist = ($SORT_FILE);
539
540if ($sort) {
541	push(@fileview_sortlist, $SORT_LINE);
542	push(@fileview_sortlist, $SORT_FUNC) if ($func_coverage);
543	push(@fileview_sortlist, $SORT_BRANCH) if ($br_coverage);
544	push(@funcview_sortlist, $SORT_LINE);
545}
546
547if ($frames)
548{
549	# Include genpng code needed for overview image generation
550	do("$tool_dir/genpng");
551}
552
553# Ensure that the c++filt tool is available when using --demangle-cpp
554if ($demangle_cpp)
555{
556	if (system_no_output(3, "c++filt", "--version")) {
557		die("ERROR: could not find c++filt tool needed for ".
558		    "--demangle-cpp\n");
559	}
560}
561
562# Make sure precision is within valid range
563if ($default_precision < 1 || $default_precision > 4)
564{
565	die("ERROR: specified precision is out of range (1 to 4)\n");
566}
567
568
569# Make sure output_directory exists, create it if necessary
570if ($output_directory)
571{
572	stat($output_directory);
573
574	if (! -e _)
575	{
576		create_sub_dir($output_directory);
577	}
578}
579
580# Do something
581gen_html();
582
583exit(0);
584
585
586
587#
588# print_usage(handle)
589#
590# Print usage information.
591#
592
593sub print_usage(*)
594{
595	local *HANDLE = $_[0];
596
597	print(HANDLE <<END_OF_USAGE);
598Usage: $tool_name [OPTIONS] INFOFILE(S)
599
600Create HTML output for coverage data found in INFOFILE. Note that INFOFILE
601may also be a list of filenames.
602
603Misc:
604  -h, --help                        Print this help, then exit
605  -v, --version                     Print version number, then exit
606  -q, --quiet                       Do not print progress messages
607      --config-file FILENAME        Specify configuration file location
608      --rc SETTING=VALUE            Override configuration file setting
609      --ignore-errors ERRORS        Continue after ERRORS (source)
610
611Operation:
612  -o, --output-directory OUTDIR     Write HTML output to OUTDIR
613  -s, --show-details                Generate detailed directory view
614  -d, --description-file DESCFILE   Read test case descriptions from DESCFILE
615  -k, --keep-descriptions           Do not remove unused test descriptions
616  -b, --baseline-file BASEFILE      Use BASEFILE as baseline file
617  -p, --prefix PREFIX               Remove PREFIX from all directory names
618      --no-prefix                   Do not remove prefix from directory names
619      --(no-)function-coverage      Enable (disable) function coverage display
620      --(no-)branch-coverage        Enable (disable) branch coverage display
621
622HTML output:
623  -f, --frames                      Use HTML frames for source code view
624  -t, --title TITLE                 Display TITLE in header of all pages
625  -c, --css-file CSSFILE            Use external style sheet file CSSFILE
626      --no-source                   Do not create source code view
627      --num-spaces NUM              Replace tabs with NUM spaces in source view
628      --highlight                   Highlight lines with converted-only data
629      --legend                      Include color legend in HTML output
630      --html-prolog FILE            Use FILE as HTML prolog for generated pages
631      --html-epilog FILE            Use FILE as HTML epilog for generated pages
632      --html-extension EXT          Use EXT as filename extension for pages
633      --html-gzip                   Use gzip to compress HTML
634      --(no-)sort                   Enable (disable) sorted coverage views
635      --demangle-cpp                Demangle C++ function names
636      --precision NUM               Set precision of coverage rate
637
638For more information see: $lcov_url
639END_OF_USAGE
640	;
641}
642
643
644#
645# get_rate(found, hit)
646#
647# Return a relative value for the specified found&hit values
648# which is used for sorting the corresponding entries in a
649# file list.
650#
651
652sub get_rate($$)
653{
654	my ($found, $hit) = @_;
655
656	if ($found == 0) {
657		return 10000;
658	}
659	return int($hit * 1000 / $found) * 10 + 2 - (1 / $found);
660}
661
662
663#
664# get_overall_line(found, hit, name_singular, name_plural)
665#
666# Return a string containing overall information for the specified
667# found/hit data.
668#
669
670sub get_overall_line($$$$)
671{
672	my ($found, $hit, $name_sn, $name_pl) = @_;
673	my $name;
674
675	return "no data found" if (!defined($found) || $found == 0);
676	$name = ($found == 1) ? $name_sn : $name_pl;
677	return rate($hit, $found, "% ($hit of $found $name)");
678}
679
680
681#
682# print_overall_rate(ln_do, ln_found, ln_hit, fn_do, fn_found, fn_hit, br_do
683#                    br_found, br_hit)
684#
685# Print overall coverage rates for the specified coverage types.
686#
687
688sub print_overall_rate($$$$$$$$$)
689{
690	my ($ln_do, $ln_found, $ln_hit, $fn_do, $fn_found, $fn_hit,
691	    $br_do, $br_found, $br_hit) = @_;
692
693	info("Overall coverage rate:\n");
694	info("  lines......: %s\n",
695	     get_overall_line($ln_found, $ln_hit, "line", "lines"))
696		if ($ln_do);
697	info("  functions..: %s\n",
698	     get_overall_line($fn_found, $fn_hit, "function", "functions"))
699		if ($fn_do);
700	info("  branches...: %s\n",
701	     get_overall_line($br_found, $br_hit, "branch", "branches"))
702		if ($br_do);
703}
704
705sub get_fn_list($)
706{
707	my ($info) = @_;
708	my %fns;
709	my @result;
710
711	foreach my $filename (keys(%{$info})) {
712		my $data = $info->{$filename};
713		my $funcdata = $data->{"func"};
714		my $sumfnccount = $data->{"sumfnc"};
715
716		if (defined($funcdata)) {
717			foreach my $func_name (keys(%{$funcdata})) {
718				$fns{$func_name} = 1;
719			}
720		}
721
722		if (defined($sumfnccount)) {
723			foreach my $func_name (keys(%{$sumfnccount})) {
724				$fns{$func_name} = 1;
725			}
726		}
727	}
728
729	@result = keys(%fns);
730
731	return \@result;
732}
733
734#
735# rename_functions(info, conv)
736#
737# Rename all function names in INFO according to CONV: OLD_NAME -> NEW_NAME.
738# In case two functions demangle to the same name, assume that they are
739# different object code implementations for the same source function.
740#
741
742sub rename_functions($$)
743{
744	my ($info, $conv) = @_;
745
746	foreach my $filename (keys(%{$info})) {
747		my $data = $info->{$filename};
748		my $funcdata;
749		my $testfncdata;
750		my $sumfnccount;
751		my %newfuncdata;
752		my %newsumfnccount;
753		my $f_found;
754		my $f_hit;
755
756		# funcdata: function name -> line number
757		$funcdata = $data->{"func"};
758		foreach my $fn (keys(%{$funcdata})) {
759			my $cn = $conv->{$fn};
760
761			# Abort if two functions on different lines map to the
762			# same demangled name.
763			if (defined($newfuncdata{$cn}) &&
764			    $newfuncdata{$cn} != $funcdata->{$fn}) {
765				die("ERROR: Demangled function name $cn ".
766				    "maps to different lines (".
767				    $newfuncdata{$cn}." vs ".
768				    $funcdata->{$fn}.") in $filename\n");
769			}
770			$newfuncdata{$cn} = $funcdata->{$fn};
771		}
772		$data->{"func"} = \%newfuncdata;
773
774		# testfncdata: test name -> testfnccount
775		# testfnccount: function name -> execution count
776		$testfncdata = $data->{"testfnc"};
777		foreach my $tn (keys(%{$testfncdata})) {
778			my $testfnccount = $testfncdata->{$tn};
779			my %newtestfnccount;
780
781			foreach my $fn (keys(%{$testfnccount})) {
782				my $cn = $conv->{$fn};
783
784				# Add counts for different functions that map
785				# to the same name.
786				$newtestfnccount{$cn} +=
787					$testfnccount->{$fn};
788			}
789			$testfncdata->{$tn} = \%newtestfnccount;
790		}
791
792		# sumfnccount: function name -> execution count
793		$sumfnccount = $data->{"sumfnc"};
794		foreach my $fn (keys(%{$sumfnccount})) {
795			my $cn = $conv->{$fn};
796
797			# Add counts for different functions that map
798			# to the same name.
799			$newsumfnccount{$cn} += $sumfnccount->{$fn};
800		}
801		$data->{"sumfnc"} = \%newsumfnccount;
802
803		# Update function found and hit counts since they may have
804		# changed
805		$f_found = 0;
806		$f_hit = 0;
807		foreach my $fn (keys(%newsumfnccount)) {
808			$f_found++;
809			$f_hit++ if ($newsumfnccount{$fn} > 0);
810		}
811		$data->{"f_found"} = $f_found;
812		$data->{"f_hit"} = $f_hit;
813	}
814}
815
816#
817# gen_html()
818#
819# Generate a set of HTML pages from contents of .info file INFO_FILENAME.
820# Files will be written to the current directory. If provided, test case
821# descriptions will be read from .tests file TEST_FILENAME and included
822# in ouput.
823#
824# Die on error.
825#
826
827sub gen_html()
828{
829	local *HTML_HANDLE;
830	my %overview;
831	my %base_data;
832	my $lines_found;
833	my $lines_hit;
834	my $fn_found;
835	my $fn_hit;
836	my $br_found;
837	my $br_hit;
838	my $overall_found = 0;
839	my $overall_hit = 0;
840	my $total_fn_found = 0;
841	my $total_fn_hit = 0;
842	my $total_br_found = 0;
843	my $total_br_hit = 0;
844	my $dir_name;
845	my $link_name;
846	my @dir_list;
847	my %new_info;
848
849	# Read in all specified .info files
850	foreach (@info_filenames)
851	{
852		%new_info = %{read_info_file($_)};
853
854		# Combine %new_info with %info_data
855		%info_data = %{combine_info_files(\%info_data, \%new_info)};
856	}
857
858	info("Found %d entries.\n", scalar(keys(%info_data)));
859
860	# Read and apply baseline data if specified
861	if ($base_filename)
862	{
863		# Read baseline file
864		info("Reading baseline file $base_filename\n");
865		%base_data = %{read_info_file($base_filename)};
866		info("Found %d entries.\n", scalar(keys(%base_data)));
867
868		# Apply baseline
869		info("Subtracting baseline data.\n");
870		%info_data = %{apply_baseline(\%info_data, \%base_data)};
871	}
872
873	@dir_list = get_dir_list(keys(%info_data));
874
875	if ($no_prefix)
876	{
877		# User requested that we leave filenames alone
878		info("User asked not to remove filename prefix\n");
879	}
880	elsif (! @dir_prefix)
881	{
882		# Get prefix common to most directories in list
883		my $prefix = get_prefix(1, keys(%info_data));
884
885		if ($prefix)
886		{
887			info("Found common filename prefix \"$prefix\"\n");
888			$dir_prefix[0] = $prefix;
889
890		}
891		else
892		{
893			info("No common filename prefix found!\n");
894			$no_prefix=1;
895		}
896	}
897	else
898	{
899		my $msg = "Using user-specified filename prefix ";
900		for my $i (0 .. $#dir_prefix)
901		{
902				$dir_prefix[$i] =~ s/\/+$//;
903				$msg .= ", " unless 0 == $i;
904				$msg .= "\"" . $dir_prefix[$i] . "\"";
905		}
906		info($msg . "\n");
907	}
908
909
910	# Read in test description file if specified
911	if ($desc_filename)
912	{
913		info("Reading test description file $desc_filename\n");
914		%test_description = %{read_testfile($desc_filename)};
915
916		# Remove test descriptions which are not referenced
917		# from %info_data if user didn't tell us otherwise
918		if (!$keep_descriptions)
919		{
920			remove_unused_descriptions();
921		}
922	}
923
924	# Change to output directory if specified
925	if ($output_directory)
926	{
927		chdir($output_directory)
928			or die("ERROR: cannot change to directory ".
929			"$output_directory!\n");
930	}
931
932	info("Writing .css and .png files.\n");
933	write_css_file();
934	write_png_files();
935
936	if ($html_gzip)
937	{
938		info("Writing .htaccess file.\n");
939		write_htaccess_file();
940	}
941
942	info("Generating output.\n");
943
944	# Process each subdirectory and collect overview information
945	foreach $dir_name (@dir_list)
946	{
947		($lines_found, $lines_hit, $fn_found, $fn_hit,
948		 $br_found, $br_hit)
949			= process_dir($dir_name);
950
951		# Handle files in root directory gracefully
952		$dir_name = "root" if ($dir_name eq "");
953
954		# Remove prefix if applicable
955		if (!$no_prefix && @dir_prefix)
956		{
957			# Match directory names beginning with one of @dir_prefix
958			$dir_name = apply_prefix($dir_name,@dir_prefix);
959		}
960
961		# Generate name for directory overview HTML page
962		if ($dir_name =~ /^\/(.*)$/)
963		{
964			$link_name = substr($dir_name, 1)."/index.$html_ext";
965		}
966		else
967		{
968			$link_name = $dir_name."/index.$html_ext";
969		}
970
971		$overview{$dir_name} = [$lines_found, $lines_hit, $fn_found,
972					$fn_hit, $br_found, $br_hit, $link_name,
973					get_rate($lines_found, $lines_hit),
974					get_rate($fn_found, $fn_hit),
975					get_rate($br_found, $br_hit)];
976		$overall_found	+= $lines_found;
977		$overall_hit	+= $lines_hit;
978		$total_fn_found	+= $fn_found;
979		$total_fn_hit	+= $fn_hit;
980		$total_br_found	+= $br_found;
981		$total_br_hit	+= $br_hit;
982	}
983
984	# Generate overview page
985	info("Writing directory view page.\n");
986
987	# Create sorted pages
988	foreach (@fileview_sortlist) {
989		write_dir_page($fileview_sortname[$_], ".", "", $test_title,
990			       undef, $overall_found, $overall_hit,
991			       $total_fn_found, $total_fn_hit, $total_br_found,
992			       $total_br_hit, \%overview, {}, {}, {}, 0, $_);
993	}
994
995	# Check if there are any test case descriptions to write out
996	if (%test_description)
997	{
998		info("Writing test case description file.\n");
999		write_description_file( \%test_description,
1000					$overall_found, $overall_hit,
1001					$total_fn_found, $total_fn_hit,
1002					$total_br_found, $total_br_hit);
1003	}
1004
1005	print_overall_rate(1, $overall_found, $overall_hit,
1006			   $func_coverage, $total_fn_found, $total_fn_hit,
1007			   $br_coverage, $total_br_found, $total_br_hit);
1008
1009	chdir($cwd);
1010}
1011
1012#
1013# html_create(handle, filename)
1014#
1015
1016sub html_create($$)
1017{
1018	my $handle = $_[0];
1019	my $filename = $_[1];
1020
1021	if ($html_gzip)
1022	{
1023		open($handle, "|-", "gzip -c >'$filename'")
1024			or die("ERROR: cannot open $filename for writing ".
1025			       "(gzip)!\n");
1026	}
1027	else
1028	{
1029		open($handle, ">", $filename)
1030			or die("ERROR: cannot open $filename for writing!\n");
1031	}
1032}
1033
1034sub write_dir_page($$$$$$$$$$$$$$$$$)
1035{
1036	my ($name, $rel_dir, $base_dir, $title, $trunc_dir, $overall_found,
1037	    $overall_hit, $total_fn_found, $total_fn_hit, $total_br_found,
1038	    $total_br_hit, $overview, $testhash, $testfnchash, $testbrhash,
1039	    $view_type, $sort_type) = @_;
1040
1041	# Generate directory overview page including details
1042	html_create(*HTML_HANDLE, "$rel_dir/index$name.$html_ext");
1043	if (!defined($trunc_dir)) {
1044		$trunc_dir = "";
1045	}
1046	$title .= " - " if ($trunc_dir ne "");
1047	write_html_prolog(*HTML_HANDLE, $base_dir, "LCOV - $title$trunc_dir");
1048	write_header(*HTML_HANDLE, $view_type, $trunc_dir, $rel_dir,
1049		     $overall_found, $overall_hit, $total_fn_found,
1050		     $total_fn_hit, $total_br_found, $total_br_hit, $sort_type);
1051	write_file_table(*HTML_HANDLE, $base_dir, $overview, $testhash,
1052			 $testfnchash, $testbrhash, $view_type, $sort_type);
1053	write_html_epilog(*HTML_HANDLE, $base_dir);
1054	close(*HTML_HANDLE);
1055}
1056
1057
1058#
1059# process_dir(dir_name)
1060#
1061
1062sub process_dir($)
1063{
1064	my $abs_dir = $_[0];
1065	my $trunc_dir;
1066	my $rel_dir = $abs_dir;
1067	my $base_dir;
1068	my $filename;
1069	my %overview;
1070	my $lines_found;
1071	my $lines_hit;
1072	my $fn_found;
1073	my $fn_hit;
1074	my $br_found;
1075	my $br_hit;
1076	my $overall_found=0;
1077	my $overall_hit=0;
1078	my $total_fn_found=0;
1079	my $total_fn_hit=0;
1080	my $total_br_found = 0;
1081	my $total_br_hit = 0;
1082	my $base_name;
1083	my $extension;
1084	my $testdata;
1085	my %testhash;
1086	my $testfncdata;
1087	my %testfnchash;
1088	my $testbrdata;
1089	my %testbrhash;
1090	my @sort_list;
1091	local *HTML_HANDLE;
1092
1093	# Remove prefix if applicable
1094	if (!$no_prefix)
1095	{
1096		# Match directory name beginning with one of @dir_prefix
1097		$rel_dir = apply_prefix($rel_dir,@dir_prefix);
1098	}
1099
1100	$trunc_dir = $rel_dir;
1101
1102	# Remove leading /
1103	if ($rel_dir =~ /^\/(.*)$/)
1104	{
1105		$rel_dir = substr($rel_dir, 1);
1106	}
1107
1108	# Handle files in root directory gracefully
1109	$rel_dir = "root" if ($rel_dir eq "");
1110	$trunc_dir = "root" if ($trunc_dir eq "");
1111
1112	$base_dir = get_relative_base_path($rel_dir);
1113
1114	create_sub_dir($rel_dir);
1115
1116	# Match filenames which specify files in this directory, not including
1117	# sub-directories
1118	foreach $filename (grep(/^\Q$abs_dir\E\/[^\/]*$/,keys(%info_data)))
1119	{
1120		my $page_link;
1121		my $func_link;
1122
1123		($lines_found, $lines_hit, $fn_found, $fn_hit, $br_found,
1124		 $br_hit, $testdata, $testfncdata, $testbrdata) =
1125			process_file($trunc_dir, $rel_dir, $filename);
1126
1127		$base_name = basename($filename);
1128
1129		if ($no_sourceview) {
1130			$page_link = "";
1131		} elsif ($frames) {
1132			# Link to frameset page
1133			$page_link = "$base_name.gcov.frameset.$html_ext";
1134		} else {
1135			# Link directory to source code view page
1136			$page_link = "$base_name.gcov.$html_ext";
1137		}
1138		$overview{$base_name} = [$lines_found, $lines_hit, $fn_found,
1139					 $fn_hit, $br_found, $br_hit,
1140					 $page_link,
1141					 get_rate($lines_found, $lines_hit),
1142					 get_rate($fn_found, $fn_hit),
1143					 get_rate($br_found, $br_hit)];
1144
1145		$testhash{$base_name} = $testdata;
1146		$testfnchash{$base_name} = $testfncdata;
1147		$testbrhash{$base_name} = $testbrdata;
1148
1149		$overall_found	+= $lines_found;
1150		$overall_hit	+= $lines_hit;
1151
1152		$total_fn_found += $fn_found;
1153		$total_fn_hit   += $fn_hit;
1154
1155		$total_br_found += $br_found;
1156		$total_br_hit   += $br_hit;
1157	}
1158
1159	# Create sorted pages
1160	foreach (@fileview_sortlist) {
1161		# Generate directory overview page (without details)
1162		write_dir_page($fileview_sortname[$_], $rel_dir, $base_dir,
1163			       $test_title, $trunc_dir, $overall_found,
1164			       $overall_hit, $total_fn_found, $total_fn_hit,
1165			       $total_br_found, $total_br_hit, \%overview, {},
1166			       {}, {}, 1, $_);
1167		if (!$show_details) {
1168			next;
1169		}
1170		# Generate directory overview page including details
1171		write_dir_page("-detail".$fileview_sortname[$_], $rel_dir,
1172			       $base_dir, $test_title, $trunc_dir,
1173			       $overall_found, $overall_hit, $total_fn_found,
1174			       $total_fn_hit, $total_br_found, $total_br_hit,
1175			       \%overview, \%testhash, \%testfnchash,
1176			       \%testbrhash, 1, $_);
1177	}
1178
1179	# Calculate resulting line counts
1180	return ($overall_found, $overall_hit, $total_fn_found, $total_fn_hit,
1181		$total_br_found, $total_br_hit);
1182}
1183
1184
1185#
1186# get_converted_lines(testdata)
1187#
1188# Return hash of line numbers of those lines which were only covered in
1189# converted data sets.
1190#
1191
1192sub get_converted_lines($)
1193{
1194	my $testdata = $_[0];
1195	my $testcount;
1196	my %converted;
1197	my %nonconverted;
1198	my $hash;
1199	my $testcase;
1200	my $line;
1201	my %result;
1202
1203
1204	# Get a hash containing line numbers with positive counts both for
1205	# converted and original data sets
1206	foreach $testcase (keys(%{$testdata}))
1207	{
1208		# Check to see if this is a converted data set
1209		if ($testcase =~ /,diff$/)
1210		{
1211			$hash = \%converted;
1212		}
1213		else
1214		{
1215			$hash = \%nonconverted;
1216		}
1217
1218		$testcount = $testdata->{$testcase};
1219		# Add lines with a positive count to hash
1220		foreach $line (keys%{$testcount})
1221		{
1222			if ($testcount->{$line} > 0)
1223			{
1224				$hash->{$line} = 1;
1225			}
1226		}
1227	}
1228
1229	# Combine both hashes to resulting list
1230	foreach $line (keys(%converted))
1231	{
1232		if (!defined($nonconverted{$line}))
1233		{
1234			$result{$line} = 1;
1235		}
1236	}
1237
1238	return \%result;
1239}
1240
1241
1242sub write_function_page($$$$$$$$$$$$$$$$$$)
1243{
1244	my ($base_dir, $rel_dir, $trunc_dir, $base_name, $title,
1245	    $lines_found, $lines_hit, $fn_found, $fn_hit, $br_found, $br_hit,
1246	    $sumcount, $funcdata, $sumfnccount, $testfncdata, $sumbrcount,
1247	    $testbrdata, $sort_type) = @_;
1248	my $pagetitle;
1249	my $filename;
1250
1251	# Generate function table for this file
1252	if ($sort_type == 0) {
1253		$filename = "$rel_dir/$base_name.func.$html_ext";
1254	} else {
1255		$filename = "$rel_dir/$base_name.func-sort-c.$html_ext";
1256	}
1257	html_create(*HTML_HANDLE, $filename);
1258	$pagetitle = "LCOV - $title - $trunc_dir/$base_name - functions";
1259	write_html_prolog(*HTML_HANDLE, $base_dir, $pagetitle);
1260	write_header(*HTML_HANDLE, 4, "$trunc_dir/$base_name",
1261		     "$rel_dir/$base_name", $lines_found, $lines_hit,
1262		     $fn_found, $fn_hit, $br_found, $br_hit, $sort_type);
1263	write_function_table(*HTML_HANDLE, "$base_name.gcov.$html_ext",
1264			     $sumcount, $funcdata,
1265			     $sumfnccount, $testfncdata, $sumbrcount,
1266			     $testbrdata, $base_name,
1267			     $base_dir, $sort_type);
1268	write_html_epilog(*HTML_HANDLE, $base_dir, 1);
1269	close(*HTML_HANDLE);
1270}
1271
1272
1273#
1274# process_file(trunc_dir, rel_dir, filename)
1275#
1276
1277sub process_file($$$)
1278{
1279	info("Processing file ".apply_prefix($_[2], @dir_prefix)."\n");
1280
1281	my $trunc_dir = $_[0];
1282	my $rel_dir = $_[1];
1283	my $filename = $_[2];
1284	my $base_name = basename($filename);
1285	my $base_dir = get_relative_base_path($rel_dir);
1286	my $testdata;
1287	my $testcount;
1288	my $sumcount;
1289	my $funcdata;
1290	my $checkdata;
1291	my $testfncdata;
1292	my $sumfnccount;
1293	my $testbrdata;
1294	my $sumbrcount;
1295	my $lines_found;
1296	my $lines_hit;
1297	my $fn_found;
1298	my $fn_hit;
1299	my $br_found;
1300	my $br_hit;
1301	my $converted;
1302	my @source;
1303	my $pagetitle;
1304	local *HTML_HANDLE;
1305
1306	($testdata, $sumcount, $funcdata, $checkdata, $testfncdata,
1307	 $sumfnccount, $testbrdata, $sumbrcount, $lines_found, $lines_hit,
1308	 $fn_found, $fn_hit, $br_found, $br_hit)
1309		= get_info_entry($info_data{$filename});
1310
1311	# Return after this point in case user asked us not to generate
1312	# source code view
1313	if ($no_sourceview)
1314	{
1315		return ($lines_found, $lines_hit, $fn_found, $fn_hit,
1316			$br_found, $br_hit, $testdata, $testfncdata,
1317			$testbrdata);
1318	}
1319
1320	$converted = get_converted_lines($testdata);
1321	# Generate source code view for this file
1322	html_create(*HTML_HANDLE, "$rel_dir/$base_name.gcov.$html_ext");
1323	$pagetitle = "LCOV - $test_title - $trunc_dir/$base_name";
1324	write_html_prolog(*HTML_HANDLE, $base_dir, $pagetitle);
1325	write_header(*HTML_HANDLE, 2, "$trunc_dir/$base_name",
1326		     "$rel_dir/$base_name", $lines_found, $lines_hit,
1327		     $fn_found, $fn_hit, $br_found, $br_hit, 0);
1328	@source = write_source(*HTML_HANDLE, $filename, $sumcount, $checkdata,
1329			       $converted, $funcdata, $sumbrcount);
1330
1331	write_html_epilog(*HTML_HANDLE, $base_dir, 1);
1332	close(*HTML_HANDLE);
1333
1334	if ($func_coverage) {
1335		# Create function tables
1336		foreach (@funcview_sortlist) {
1337			write_function_page($base_dir, $rel_dir, $trunc_dir,
1338					    $base_name, $test_title,
1339					    $lines_found, $lines_hit,
1340					    $fn_found, $fn_hit, $br_found,
1341					    $br_hit, $sumcount,
1342					    $funcdata, $sumfnccount,
1343					    $testfncdata, $sumbrcount,
1344					    $testbrdata, $_);
1345		}
1346	}
1347
1348	# Additional files are needed in case of frame output
1349	if (!$frames)
1350	{
1351		return ($lines_found, $lines_hit, $fn_found, $fn_hit,
1352			$br_found, $br_hit, $testdata, $testfncdata,
1353			$testbrdata);
1354	}
1355
1356	# Create overview png file
1357	gen_png("$rel_dir/$base_name.gcov.png", $overview_width, $tab_size,
1358		@source);
1359
1360	# Create frameset page
1361	html_create(*HTML_HANDLE,
1362		    "$rel_dir/$base_name.gcov.frameset.$html_ext");
1363	write_frameset(*HTML_HANDLE, $base_dir, $base_name, $pagetitle);
1364	close(*HTML_HANDLE);
1365
1366	# Write overview frame
1367	html_create(*HTML_HANDLE,
1368		    "$rel_dir/$base_name.gcov.overview.$html_ext");
1369	write_overview(*HTML_HANDLE, $base_dir, $base_name, $pagetitle,
1370		       scalar(@source));
1371	close(*HTML_HANDLE);
1372
1373	return ($lines_found, $lines_hit, $fn_found, $fn_hit, $br_found,
1374		$br_hit, $testdata, $testfncdata, $testbrdata);
1375}
1376
1377
1378#
1379# read_info_file(info_filename)
1380#
1381# Read in the contents of the .info file specified by INFO_FILENAME. Data will
1382# be returned as a reference to a hash containing the following mappings:
1383#
1384# %result: for each filename found in file -> \%data
1385#
1386# %data: "test"  -> \%testdata
1387#        "sum"   -> \%sumcount
1388#        "func"  -> \%funcdata
1389#        "found" -> $lines_found (number of instrumented lines found in file)
1390#	 "hit"   -> $lines_hit (number of executed lines in file)
1391#        "f_found" -> $fn_found (number of instrumented functions found in file)
1392#	 "f_hit"   -> $fn_hit (number of executed functions in file)
1393#        "b_found" -> $br_found (number of instrumented branches found in file)
1394#	 "b_hit"   -> $br_hit (number of executed branches in file)
1395#        "check" -> \%checkdata
1396#        "testfnc" -> \%testfncdata
1397#        "sumfnc"  -> \%sumfnccount
1398#        "testbr"  -> \%testbrdata
1399#        "sumbr"   -> \%sumbrcount
1400#
1401# %testdata   : name of test affecting this file -> \%testcount
1402# %testfncdata: name of test affecting this file -> \%testfnccount
1403# %testbrdata:  name of test affecting this file -> \%testbrcount
1404#
1405# %testcount   : line number   -> execution count for a single test
1406# %testfnccount: function name -> execution count for a single test
1407# %testbrcount : line number   -> branch coverage data for a single test
1408# %sumcount    : line number   -> execution count for all tests
1409# %sumfnccount : function name -> execution count for all tests
1410# %sumbrcount  : line number   -> branch coverage data for all tests
1411# %funcdata    : function name -> line number
1412# %checkdata   : line number   -> checksum of source code line
1413# $brdata      : vector of items: block, branch, taken
1414#
1415# Note that .info file sections referring to the same file and test name
1416# will automatically be combined by adding all execution counts.
1417#
1418# Note that if INFO_FILENAME ends with ".gz", it is assumed that the file
1419# is compressed using GZIP. If available, GUNZIP will be used to decompress
1420# this file.
1421#
1422# Die on error.
1423#
1424
1425sub read_info_file($)
1426{
1427	my $tracefile = $_[0];		# Name of tracefile
1428	my %result;			# Resulting hash: file -> data
1429	my $data;			# Data handle for current entry
1430	my $testdata;			#       "             "
1431	my $testcount;			#       "             "
1432	my $sumcount;			#       "             "
1433	my $funcdata;			#       "             "
1434	my $checkdata;			#       "             "
1435	my $testfncdata;
1436	my $testfnccount;
1437	my $sumfnccount;
1438	my $testbrdata;
1439	my $testbrcount;
1440	my $sumbrcount;
1441	my $line;			# Current line read from .info file
1442	my $testname;			# Current test name
1443	my $filename;			# Current filename
1444	my $hitcount;			# Count for lines hit
1445	my $count;			# Execution count of current line
1446	my $negative;			# If set, warn about negative counts
1447	my $changed_testname;		# If set, warn about changed testname
1448	my $line_checksum;		# Checksum of current line
1449	my $br_found;
1450	my $br_hit;
1451	my $notified_about_relative_paths;
1452	local *INFO_HANDLE;		# Filehandle for .info file
1453
1454	info("Reading data file $tracefile\n");
1455
1456	# Check if file exists and is readable
1457	stat($_[0]);
1458	if (!(-r _))
1459	{
1460		die("ERROR: cannot read file $_[0]!\n");
1461	}
1462
1463	# Check if this is really a plain file
1464	if (!(-f _))
1465	{
1466		die("ERROR: not a plain file: $_[0]!\n");
1467	}
1468
1469	# Check for .gz extension
1470	if ($_[0] =~ /\.gz$/)
1471	{
1472		# Check for availability of GZIP tool
1473		system_no_output(1, "gunzip" ,"-h")
1474			and die("ERROR: gunzip command not available!\n");
1475
1476		# Check integrity of compressed file
1477		system_no_output(1, "gunzip", "-t", $_[0])
1478			and die("ERROR: integrity check failed for ".
1479				"compressed file $_[0]!\n");
1480
1481		# Open compressed file
1482		open(INFO_HANDLE, "-|", "gunzip -c '$_[0]'")
1483			or die("ERROR: cannot start gunzip to decompress ".
1484			       "file $_[0]!\n");
1485	}
1486	else
1487	{
1488		# Open decompressed file
1489		open(INFO_HANDLE, "<", $_[0])
1490			or die("ERROR: cannot read file $_[0]!\n");
1491	}
1492
1493	$testname = "";
1494	while (<INFO_HANDLE>)
1495	{
1496		chomp($_);
1497		$line = $_;
1498
1499		# Switch statement
1500		foreach ($line)
1501		{
1502			/^TN:([^,]*)(,diff)?/ && do
1503			{
1504				# Test name information found
1505				$testname = defined($1) ? $1 : "";
1506				if ($testname =~ s/\W/_/g)
1507				{
1508					$changed_testname = 1;
1509				}
1510				$testname .= $2 if (defined($2));
1511				last;
1512			};
1513
1514			/^[SK]F:(.*)/ && do
1515			{
1516				# Filename information found
1517				# Retrieve data for new entry
1518				$filename = File::Spec->rel2abs($1, Cwd::cwd());
1519
1520				if (!File::Spec->file_name_is_absolute($1) &&
1521				    !$notified_about_relative_paths)
1522				{
1523					info("Resolved relative source file ".
1524					     "path \"$1\" with CWD to ".
1525					     "\"$filename\".\n");
1526					$notified_about_relative_paths = 1;
1527				}
1528
1529				$data = $result{$filename};
1530				($testdata, $sumcount, $funcdata, $checkdata,
1531				 $testfncdata, $sumfnccount, $testbrdata,
1532				 $sumbrcount) =
1533					get_info_entry($data);
1534
1535				if (defined($testname))
1536				{
1537					$testcount = $testdata->{$testname};
1538					$testfnccount = $testfncdata->{$testname};
1539					$testbrcount = $testbrdata->{$testname};
1540				}
1541				else
1542				{
1543					$testcount = {};
1544					$testfnccount = {};
1545					$testbrcount = {};
1546				}
1547				last;
1548			};
1549
1550			/^DA:(\d+),(-?\d+)(,[^,\s]+)?/ && do
1551			{
1552				# Fix negative counts
1553				$count = $2 < 0 ? 0 : $2;
1554				if ($2 < 0)
1555				{
1556					$negative = 1;
1557				}
1558				# Execution count found, add to structure
1559				# Add summary counts
1560				$sumcount->{$1} += $count;
1561
1562				# Add test-specific counts
1563				if (defined($testname))
1564				{
1565					$testcount->{$1} += $count;
1566				}
1567
1568				# Store line checksum if available
1569				if (defined($3))
1570				{
1571					$line_checksum = substr($3, 1);
1572
1573					# Does it match a previous definition
1574					if (defined($checkdata->{$1}) &&
1575					    ($checkdata->{$1} ne
1576					     $line_checksum))
1577					{
1578						die("ERROR: checksum mismatch ".
1579						    "at $filename:$1\n");
1580					}
1581
1582					$checkdata->{$1} = $line_checksum;
1583				}
1584				last;
1585			};
1586
1587			/^FN:(\d+),([^,]+)/ && do
1588			{
1589				last if (!$func_coverage);
1590
1591				# Function data found, add to structure
1592				$funcdata->{$2} = $1;
1593
1594				# Also initialize function call data
1595				if (!defined($sumfnccount->{$2})) {
1596					$sumfnccount->{$2} = 0;
1597				}
1598				if (defined($testname))
1599				{
1600					if (!defined($testfnccount->{$2})) {
1601						$testfnccount->{$2} = 0;
1602					}
1603				}
1604				last;
1605			};
1606
1607			/^FNDA:(\d+),([^,]+)/ && do
1608			{
1609				last if (!$func_coverage);
1610				# Function call count found, add to structure
1611				# Add summary counts
1612				$sumfnccount->{$2} += $1;
1613
1614				# Add test-specific counts
1615				if (defined($testname))
1616				{
1617					$testfnccount->{$2} += $1;
1618				}
1619				last;
1620			};
1621
1622			/^BRDA:(\d+),(\d+),(\d+),(\d+|-)/ && do {
1623				# Branch coverage data found
1624				my ($line, $block, $branch, $taken) =
1625				   ($1, $2, $3, $4);
1626
1627				last if (!$br_coverage);
1628				$sumbrcount->{$line} =
1629					br_ivec_push($sumbrcount->{$line},
1630						     $block, $branch, $taken);
1631
1632				# Add test-specific counts
1633				if (defined($testname)) {
1634					$testbrcount->{$line} =
1635						br_ivec_push(
1636							$testbrcount->{$line},
1637							$block, $branch,
1638							$taken);
1639				}
1640				last;
1641			};
1642
1643			/^end_of_record/ && do
1644			{
1645				# Found end of section marker
1646				if ($filename)
1647				{
1648					# Store current section data
1649					if (defined($testname))
1650					{
1651						$testdata->{$testname} =
1652							$testcount;
1653						$testfncdata->{$testname} =
1654							$testfnccount;
1655						$testbrdata->{$testname} =
1656							$testbrcount;
1657					}
1658
1659					set_info_entry($data, $testdata,
1660						       $sumcount, $funcdata,
1661						       $checkdata, $testfncdata,
1662						       $sumfnccount,
1663						       $testbrdata,
1664						       $sumbrcount);
1665					$result{$filename} = $data;
1666					last;
1667				}
1668			};
1669
1670			# default
1671			last;
1672		}
1673	}
1674	close(INFO_HANDLE);
1675
1676	# Calculate lines_found and lines_hit for each file
1677	foreach $filename (keys(%result))
1678	{
1679		$data = $result{$filename};
1680
1681		($testdata, $sumcount, undef, undef, $testfncdata,
1682		 $sumfnccount, $testbrdata, $sumbrcount) =
1683			get_info_entry($data);
1684
1685		# Filter out empty files
1686		if (scalar(keys(%{$sumcount})) == 0)
1687		{
1688			delete($result{$filename});
1689			next;
1690		}
1691		# Filter out empty test cases
1692		foreach $testname (keys(%{$testdata}))
1693		{
1694			if (!defined($testdata->{$testname}) ||
1695			    scalar(keys(%{$testdata->{$testname}})) == 0)
1696			{
1697				delete($testdata->{$testname});
1698				delete($testfncdata->{$testname});
1699			}
1700		}
1701
1702		$data->{"found"} = scalar(keys(%{$sumcount}));
1703		$hitcount = 0;
1704
1705		foreach (keys(%{$sumcount}))
1706		{
1707			if ($sumcount->{$_} > 0) { $hitcount++; }
1708		}
1709
1710		$data->{"hit"} = $hitcount;
1711
1712		# Get found/hit values for function call data
1713		$data->{"f_found"} = scalar(keys(%{$sumfnccount}));
1714		$hitcount = 0;
1715
1716		foreach (keys(%{$sumfnccount})) {
1717			if ($sumfnccount->{$_} > 0) {
1718				$hitcount++;
1719			}
1720		}
1721		$data->{"f_hit"} = $hitcount;
1722
1723		# Get found/hit values for branch data
1724		($br_found, $br_hit) = get_br_found_and_hit($sumbrcount);
1725
1726		$data->{"b_found"} = $br_found;
1727		$data->{"b_hit"} = $br_hit;
1728	}
1729
1730	if (scalar(keys(%result)) == 0)
1731	{
1732		die("ERROR: no valid records found in tracefile $tracefile\n");
1733	}
1734	if ($negative)
1735	{
1736		warn("WARNING: negative counts found in tracefile ".
1737		     "$tracefile\n");
1738	}
1739	if ($changed_testname)
1740	{
1741		warn("WARNING: invalid characters removed from testname in ".
1742		     "tracefile $tracefile\n");
1743	}
1744
1745	return(\%result);
1746}
1747
1748
1749#
1750# get_info_entry(hash_ref)
1751#
1752# Retrieve data from an entry of the structure generated by read_info_file().
1753# Return a list of references to hashes:
1754# (test data hash ref, sum count hash ref, funcdata hash ref, checkdata hash
1755#  ref, testfncdata hash ref, sumfnccount hash ref, lines found, lines hit,
1756#  functions found, functions hit)
1757#
1758
1759sub get_info_entry($)
1760{
1761	my $testdata_ref = $_[0]->{"test"};
1762	my $sumcount_ref = $_[0]->{"sum"};
1763	my $funcdata_ref = $_[0]->{"func"};
1764	my $checkdata_ref = $_[0]->{"check"};
1765	my $testfncdata = $_[0]->{"testfnc"};
1766	my $sumfnccount = $_[0]->{"sumfnc"};
1767	my $testbrdata = $_[0]->{"testbr"};
1768	my $sumbrcount = $_[0]->{"sumbr"};
1769	my $lines_found = $_[0]->{"found"};
1770	my $lines_hit = $_[0]->{"hit"};
1771	my $fn_found = $_[0]->{"f_found"};
1772	my $fn_hit = $_[0]->{"f_hit"};
1773	my $br_found = $_[0]->{"b_found"};
1774	my $br_hit = $_[0]->{"b_hit"};
1775
1776	return ($testdata_ref, $sumcount_ref, $funcdata_ref, $checkdata_ref,
1777		$testfncdata, $sumfnccount, $testbrdata, $sumbrcount,
1778		$lines_found, $lines_hit, $fn_found, $fn_hit,
1779		$br_found, $br_hit);
1780}
1781
1782
1783#
1784# set_info_entry(hash_ref, testdata_ref, sumcount_ref, funcdata_ref,
1785#                checkdata_ref, testfncdata_ref, sumfcncount_ref,
1786#                testbrdata_ref, sumbrcount_ref[,lines_found,
1787#                lines_hit, f_found, f_hit, $b_found, $b_hit])
1788#
1789# Update the hash referenced by HASH_REF with the provided data references.
1790#
1791
1792sub set_info_entry($$$$$$$$$;$$$$$$)
1793{
1794	my $data_ref = $_[0];
1795
1796	$data_ref->{"test"} = $_[1];
1797	$data_ref->{"sum"} = $_[2];
1798	$data_ref->{"func"} = $_[3];
1799	$data_ref->{"check"} = $_[4];
1800	$data_ref->{"testfnc"} = $_[5];
1801	$data_ref->{"sumfnc"} = $_[6];
1802	$data_ref->{"testbr"} = $_[7];
1803	$data_ref->{"sumbr"} = $_[8];
1804
1805	if (defined($_[9])) { $data_ref->{"found"} = $_[9]; }
1806	if (defined($_[10])) { $data_ref->{"hit"} = $_[10]; }
1807	if (defined($_[11])) { $data_ref->{"f_found"} = $_[11]; }
1808	if (defined($_[12])) { $data_ref->{"f_hit"} = $_[12]; }
1809	if (defined($_[13])) { $data_ref->{"b_found"} = $_[13]; }
1810	if (defined($_[14])) { $data_ref->{"b_hit"} = $_[14]; }
1811}
1812
1813
1814#
1815# add_counts(data1_ref, data2_ref)
1816#
1817# DATA1_REF and DATA2_REF are references to hashes containing a mapping
1818#
1819#   line number -> execution count
1820#
1821# Return a list (RESULT_REF, LINES_FOUND, LINES_HIT) where RESULT_REF
1822# is a reference to a hash containing the combined mapping in which
1823# execution counts are added.
1824#
1825
1826sub add_counts($$)
1827{
1828	my $data1_ref = $_[0];	# Hash 1
1829	my $data2_ref = $_[1];	# Hash 2
1830	my %result;		# Resulting hash
1831	my $line;		# Current line iteration scalar
1832	my $data1_count;	# Count of line in hash1
1833	my $data2_count;	# Count of line in hash2
1834	my $found = 0;		# Total number of lines found
1835	my $hit = 0;		# Number of lines with a count > 0
1836
1837	foreach $line (keys(%$data1_ref))
1838	{
1839		$data1_count = $data1_ref->{$line};
1840		$data2_count = $data2_ref->{$line};
1841
1842		# Add counts if present in both hashes
1843		if (defined($data2_count)) { $data1_count += $data2_count; }
1844
1845		# Store sum in %result
1846		$result{$line} = $data1_count;
1847
1848		$found++;
1849		if ($data1_count > 0) { $hit++; }
1850	}
1851
1852	# Add lines unique to data2_ref
1853	foreach $line (keys(%$data2_ref))
1854	{
1855		# Skip lines already in data1_ref
1856		if (defined($data1_ref->{$line})) { next; }
1857
1858		# Copy count from data2_ref
1859		$result{$line} = $data2_ref->{$line};
1860
1861		$found++;
1862		if ($result{$line} > 0) { $hit++; }
1863	}
1864
1865	return (\%result, $found, $hit);
1866}
1867
1868
1869#
1870# merge_checksums(ref1, ref2, filename)
1871#
1872# REF1 and REF2 are references to hashes containing a mapping
1873#
1874#   line number -> checksum
1875#
1876# Merge checksum lists defined in REF1 and REF2 and return reference to
1877# resulting hash. Die if a checksum for a line is defined in both hashes
1878# but does not match.
1879#
1880
1881sub merge_checksums($$$)
1882{
1883	my $ref1 = $_[0];
1884	my $ref2 = $_[1];
1885	my $filename = $_[2];
1886	my %result;
1887	my $line;
1888
1889	foreach $line (keys(%{$ref1}))
1890	{
1891		if (defined($ref2->{$line}) &&
1892		    ($ref1->{$line} ne $ref2->{$line}))
1893		{
1894			die("ERROR: checksum mismatch at $filename:$line\n");
1895		}
1896		$result{$line} = $ref1->{$line};
1897	}
1898
1899	foreach $line (keys(%{$ref2}))
1900	{
1901		$result{$line} = $ref2->{$line};
1902	}
1903
1904	return \%result;
1905}
1906
1907
1908#
1909# merge_func_data(funcdata1, funcdata2, filename)
1910#
1911
1912sub merge_func_data($$$)
1913{
1914	my ($funcdata1, $funcdata2, $filename) = @_;
1915	my %result;
1916	my $func;
1917
1918	if (defined($funcdata1)) {
1919		%result = %{$funcdata1};
1920	}
1921
1922	foreach $func (keys(%{$funcdata2})) {
1923		my $line1 = $result{$func};
1924		my $line2 = $funcdata2->{$func};
1925
1926		if (defined($line1) && ($line1 != $line2)) {
1927			warn("WARNING: function data mismatch at ".
1928			     "$filename:$line2\n");
1929			next;
1930		}
1931		$result{$func} = $line2;
1932	}
1933
1934	return \%result;
1935}
1936
1937
1938#
1939# add_fnccount(fnccount1, fnccount2)
1940#
1941# Add function call count data. Return list (fnccount_added, f_found, f_hit)
1942#
1943
1944sub add_fnccount($$)
1945{
1946	my ($fnccount1, $fnccount2) = @_;
1947	my %result;
1948	my $fn_found;
1949	my $fn_hit;
1950	my $function;
1951
1952	if (defined($fnccount1)) {
1953		%result = %{$fnccount1};
1954	}
1955	foreach $function (keys(%{$fnccount2})) {
1956		$result{$function} += $fnccount2->{$function};
1957	}
1958	$fn_found = scalar(keys(%result));
1959	$fn_hit = 0;
1960	foreach $function (keys(%result)) {
1961		if ($result{$function} > 0) {
1962			$fn_hit++;
1963		}
1964	}
1965
1966	return (\%result, $fn_found, $fn_hit);
1967}
1968
1969#
1970# add_testfncdata(testfncdata1, testfncdata2)
1971#
1972# Add function call count data for several tests. Return reference to
1973# added_testfncdata.
1974#
1975
1976sub add_testfncdata($$)
1977{
1978	my ($testfncdata1, $testfncdata2) = @_;
1979	my %result;
1980	my $testname;
1981
1982	foreach $testname (keys(%{$testfncdata1})) {
1983		if (defined($testfncdata2->{$testname})) {
1984			my $fnccount;
1985
1986			# Function call count data for this testname exists
1987			# in both data sets: add
1988			($fnccount) = add_fnccount(
1989				$testfncdata1->{$testname},
1990				$testfncdata2->{$testname});
1991			$result{$testname} = $fnccount;
1992			next;
1993		}
1994		# Function call count data for this testname is unique to
1995		# data set 1: copy
1996		$result{$testname} = $testfncdata1->{$testname};
1997	}
1998
1999	# Add count data for testnames unique to data set 2
2000	foreach $testname (keys(%{$testfncdata2})) {
2001		if (!defined($result{$testname})) {
2002			$result{$testname} = $testfncdata2->{$testname};
2003		}
2004	}
2005	return \%result;
2006}
2007
2008
2009#
2010# brcount_to_db(brcount)
2011#
2012# Convert brcount data to the following format:
2013#
2014# db:          line number    -> block hash
2015# block hash:  block number   -> branch hash
2016# branch hash: branch number  -> taken value
2017#
2018
2019sub brcount_to_db($)
2020{
2021	my ($brcount) = @_;
2022	my $line;
2023	my $db;
2024
2025	# Add branches from first count to database
2026	foreach $line (keys(%{$brcount})) {
2027		my $brdata = $brcount->{$line};
2028		my $i;
2029		my $num = br_ivec_len($brdata);
2030
2031		for ($i = 0; $i < $num; $i++) {
2032			my ($block, $branch, $taken) = br_ivec_get($brdata, $i);
2033
2034			$db->{$line}->{$block}->{$branch} = $taken;
2035		}
2036	}
2037
2038	return $db;
2039}
2040
2041
2042#
2043# db_to_brcount(db)
2044#
2045# Convert branch coverage data back to brcount format.
2046#
2047
2048sub db_to_brcount($)
2049{
2050	my ($db) = @_;
2051	my $line;
2052	my $brcount = {};
2053	my $br_found = 0;
2054	my $br_hit = 0;
2055
2056	# Convert database back to brcount format
2057	foreach $line (sort({$a <=> $b} keys(%{$db}))) {
2058		my $ldata = $db->{$line};
2059		my $brdata;
2060		my $block;
2061
2062		foreach $block (sort({$a <=> $b} keys(%{$ldata}))) {
2063			my $bdata = $ldata->{$block};
2064			my $branch;
2065
2066			foreach $branch (sort({$a <=> $b} keys(%{$bdata}))) {
2067				my $taken = $bdata->{$branch};
2068
2069				$br_found++;
2070				$br_hit++ if ($taken ne "-" && $taken > 0);
2071				$brdata = br_ivec_push($brdata, $block,
2072						       $branch, $taken);
2073			}
2074		}
2075		$brcount->{$line} = $brdata;
2076	}
2077
2078	return ($brcount, $br_found, $br_hit);
2079}
2080
2081
2082#
2083# combine_brcount(brcount1, brcount2, type)
2084#
2085# If add is BR_ADD, add branch coverage data and return list (brcount_added,
2086# br_found, br_hit). If add is BR_SUB, subtract the taken values of brcount2
2087# from brcount1 and return (brcount_sub, br_found, br_hit).
2088#
2089
2090sub combine_brcount($$$)
2091{
2092	my ($brcount1, $brcount2, $type) = @_;
2093	my $line;
2094	my $block;
2095	my $branch;
2096	my $taken;
2097	my $db;
2098	my $br_found = 0;
2099	my $br_hit = 0;
2100	my $result;
2101
2102	# Convert branches from first count to database
2103	$db = brcount_to_db($brcount1);
2104	# Combine values from database and second count
2105	foreach $line (keys(%{$brcount2})) {
2106		my $brdata = $brcount2->{$line};
2107		my $num = br_ivec_len($brdata);
2108		my $i;
2109
2110		for ($i = 0; $i < $num; $i++) {
2111			($block, $branch, $taken) = br_ivec_get($brdata, $i);
2112			my $new_taken = $db->{$line}->{$block}->{$branch};
2113
2114			if ($type == $BR_ADD) {
2115				$new_taken = br_taken_add($new_taken, $taken);
2116			} elsif ($type == $BR_SUB) {
2117				$new_taken = br_taken_sub($new_taken, $taken);
2118			}
2119			$db->{$line}->{$block}->{$branch} = $new_taken
2120				if (defined($new_taken));
2121		}
2122	}
2123	# Convert database back to brcount format
2124	($result, $br_found, $br_hit) = db_to_brcount($db);
2125
2126	return ($result, $br_found, $br_hit);
2127}
2128
2129
2130#
2131# add_testbrdata(testbrdata1, testbrdata2)
2132#
2133# Add branch coverage data for several tests. Return reference to
2134# added_testbrdata.
2135#
2136
2137sub add_testbrdata($$)
2138{
2139	my ($testbrdata1, $testbrdata2) = @_;
2140	my %result;
2141	my $testname;
2142
2143	foreach $testname (keys(%{$testbrdata1})) {
2144		if (defined($testbrdata2->{$testname})) {
2145			my $brcount;
2146
2147			# Branch coverage data for this testname exists
2148			# in both data sets: add
2149			($brcount) = combine_brcount($testbrdata1->{$testname},
2150					 $testbrdata2->{$testname}, $BR_ADD);
2151			$result{$testname} = $brcount;
2152			next;
2153		}
2154		# Branch coverage data for this testname is unique to
2155		# data set 1: copy
2156		$result{$testname} = $testbrdata1->{$testname};
2157	}
2158
2159	# Add count data for testnames unique to data set 2
2160	foreach $testname (keys(%{$testbrdata2})) {
2161		if (!defined($result{$testname})) {
2162			$result{$testname} = $testbrdata2->{$testname};
2163		}
2164	}
2165	return \%result;
2166}
2167
2168
2169#
2170# combine_info_entries(entry_ref1, entry_ref2, filename)
2171#
2172# Combine .info data entry hashes referenced by ENTRY_REF1 and ENTRY_REF2.
2173# Return reference to resulting hash.
2174#
2175
2176sub combine_info_entries($$$)
2177{
2178	my $entry1 = $_[0];	# Reference to hash containing first entry
2179	my $testdata1;
2180	my $sumcount1;
2181	my $funcdata1;
2182	my $checkdata1;
2183	my $testfncdata1;
2184	my $sumfnccount1;
2185	my $testbrdata1;
2186	my $sumbrcount1;
2187
2188	my $entry2 = $_[1];	# Reference to hash containing second entry
2189	my $testdata2;
2190	my $sumcount2;
2191	my $funcdata2;
2192	my $checkdata2;
2193	my $testfncdata2;
2194	my $sumfnccount2;
2195	my $testbrdata2;
2196	my $sumbrcount2;
2197
2198	my %result;		# Hash containing combined entry
2199	my %result_testdata;
2200	my $result_sumcount = {};
2201	my $result_funcdata;
2202	my $result_testfncdata;
2203	my $result_sumfnccount;
2204	my $result_testbrdata;
2205	my $result_sumbrcount;
2206	my $lines_found;
2207	my $lines_hit;
2208	my $fn_found;
2209	my $fn_hit;
2210	my $br_found;
2211	my $br_hit;
2212
2213	my $testname;
2214	my $filename = $_[2];
2215
2216	# Retrieve data
2217	($testdata1, $sumcount1, $funcdata1, $checkdata1, $testfncdata1,
2218	 $sumfnccount1, $testbrdata1, $sumbrcount1) = get_info_entry($entry1);
2219	($testdata2, $sumcount2, $funcdata2, $checkdata2, $testfncdata2,
2220	 $sumfnccount2, $testbrdata2, $sumbrcount2) = get_info_entry($entry2);
2221
2222	# Merge checksums
2223	$checkdata1 = merge_checksums($checkdata1, $checkdata2, $filename);
2224
2225	# Combine funcdata
2226	$result_funcdata = merge_func_data($funcdata1, $funcdata2, $filename);
2227
2228	# Combine function call count data
2229	$result_testfncdata = add_testfncdata($testfncdata1, $testfncdata2);
2230	($result_sumfnccount, $fn_found, $fn_hit) =
2231		add_fnccount($sumfnccount1, $sumfnccount2);
2232
2233	# Combine branch coverage data
2234	$result_testbrdata = add_testbrdata($testbrdata1, $testbrdata2);
2235	($result_sumbrcount, $br_found, $br_hit) =
2236		combine_brcount($sumbrcount1, $sumbrcount2, $BR_ADD);
2237
2238	# Combine testdata
2239	foreach $testname (keys(%{$testdata1}))
2240	{
2241		if (defined($testdata2->{$testname}))
2242		{
2243			# testname is present in both entries, requires
2244			# combination
2245			($result_testdata{$testname}) =
2246				add_counts($testdata1->{$testname},
2247					   $testdata2->{$testname});
2248		}
2249		else
2250		{
2251			# testname only present in entry1, add to result
2252			$result_testdata{$testname} = $testdata1->{$testname};
2253		}
2254
2255		# update sum count hash
2256		($result_sumcount, $lines_found, $lines_hit) =
2257			add_counts($result_sumcount,
2258				   $result_testdata{$testname});
2259	}
2260
2261	foreach $testname (keys(%{$testdata2}))
2262	{
2263		# Skip testnames already covered by previous iteration
2264		if (defined($testdata1->{$testname})) { next; }
2265
2266		# testname only present in entry2, add to result hash
2267		$result_testdata{$testname} = $testdata2->{$testname};
2268
2269		# update sum count hash
2270		($result_sumcount, $lines_found, $lines_hit) =
2271			add_counts($result_sumcount,
2272				   $result_testdata{$testname});
2273	}
2274
2275	# Calculate resulting sumcount
2276
2277	# Store result
2278	set_info_entry(\%result, \%result_testdata, $result_sumcount,
2279		       $result_funcdata, $checkdata1, $result_testfncdata,
2280		       $result_sumfnccount, $result_testbrdata,
2281		       $result_sumbrcount, $lines_found, $lines_hit,
2282		       $fn_found, $fn_hit, $br_found, $br_hit);
2283
2284	return(\%result);
2285}
2286
2287
2288#
2289# combine_info_files(info_ref1, info_ref2)
2290#
2291# Combine .info data in hashes referenced by INFO_REF1 and INFO_REF2. Return
2292# reference to resulting hash.
2293#
2294
2295sub combine_info_files($$)
2296{
2297	my %hash1 = %{$_[0]};
2298	my %hash2 = %{$_[1]};
2299	my $filename;
2300
2301	foreach $filename (keys(%hash2))
2302	{
2303		if ($hash1{$filename})
2304		{
2305			# Entry already exists in hash1, combine them
2306			$hash1{$filename} =
2307				combine_info_entries($hash1{$filename},
2308						     $hash2{$filename},
2309						     $filename);
2310		}
2311		else
2312		{
2313			# Entry is unique in both hashes, simply add to
2314			# resulting hash
2315			$hash1{$filename} = $hash2{$filename};
2316		}
2317	}
2318
2319	return(\%hash1);
2320}
2321
2322
2323#
2324# get_prefix(min_dir, filename_list)
2325#
2326# Search FILENAME_LIST for a directory prefix which is common to as many
2327# list entries as possible, so that removing this prefix will minimize the
2328# sum of the lengths of all resulting shortened filenames while observing
2329# that no filename has less than MIN_DIR parent directories.
2330#
2331
2332sub get_prefix($@)
2333{
2334	my ($min_dir, @filename_list) = @_;
2335	my %prefix;			# mapping: prefix -> sum of lengths
2336	my $current;			# Temporary iteration variable
2337
2338	# Find list of prefixes
2339	foreach (@filename_list)
2340	{
2341		# Need explicit assignment to get a copy of $_ so that
2342		# shortening the contained prefix does not affect the list
2343		$current = $_;
2344		while ($current = shorten_prefix($current))
2345		{
2346			$current .= "/";
2347
2348			# Skip rest if the remaining prefix has already been
2349			# added to hash
2350			if (exists($prefix{$current})) { last; }
2351
2352			# Initialize with 0
2353			$prefix{$current}="0";
2354		}
2355
2356	}
2357
2358	# Remove all prefixes that would cause filenames to have less than
2359	# the minimum number of parent directories
2360	foreach my $filename (@filename_list) {
2361		my $dir = dirname($filename);
2362
2363		for (my $i = 0; $i < $min_dir; $i++) {
2364			delete($prefix{$dir."/"});
2365			$dir = shorten_prefix($dir);
2366		}
2367	}
2368
2369	# Check if any prefix remains
2370	return undef if (!%prefix);
2371
2372	# Calculate sum of lengths for all prefixes
2373	foreach $current (keys(%prefix))
2374	{
2375		foreach (@filename_list)
2376		{
2377			# Add original length
2378			$prefix{$current} += length($_);
2379
2380			# Check whether prefix matches
2381			if (substr($_, 0, length($current)) eq $current)
2382			{
2383				# Subtract prefix length for this filename
2384				$prefix{$current} -= length($current);
2385			}
2386		}
2387	}
2388
2389	# Find and return prefix with minimal sum
2390	$current = (keys(%prefix))[0];
2391
2392	foreach (keys(%prefix))
2393	{
2394		if ($prefix{$_} < $prefix{$current})
2395		{
2396			$current = $_;
2397		}
2398	}
2399
2400	$current =~ s/\/$//;
2401
2402	return($current);
2403}
2404
2405
2406#
2407# shorten_prefix(prefix)
2408#
2409# Return PREFIX shortened by last directory component.
2410#
2411
2412sub shorten_prefix($)
2413{
2414	my @list = split("/", $_[0]);
2415
2416	pop(@list);
2417	return join("/", @list);
2418}
2419
2420
2421
2422#
2423# get_dir_list(filename_list)
2424#
2425# Return sorted list of directories for each entry in given FILENAME_LIST.
2426#
2427
2428sub get_dir_list(@)
2429{
2430	my %result;
2431
2432	foreach (@_)
2433	{
2434		$result{shorten_prefix($_)} = "";
2435	}
2436
2437	return(sort(keys(%result)));
2438}
2439
2440
2441#
2442# get_relative_base_path(subdirectory)
2443#
2444# Return a relative path string which references the base path when applied
2445# in SUBDIRECTORY.
2446#
2447# Example: get_relative_base_path("fs/mm") -> "../../"
2448#
2449
2450sub get_relative_base_path($)
2451{
2452	my $result = "";
2453	my $index;
2454
2455	# Make an empty directory path a special case
2456	if (!$_[0]) { return(""); }
2457
2458	# Count number of /s in path
2459	$index = ($_[0] =~ s/\//\//g);
2460
2461	# Add a ../ to $result for each / in the directory path + 1
2462	for (; $index>=0; $index--)
2463	{
2464		$result .= "../";
2465	}
2466
2467	return $result;
2468}
2469
2470
2471#
2472# read_testfile(test_filename)
2473#
2474# Read in file TEST_FILENAME which contains test descriptions in the format:
2475#
2476#   TN:<whitespace><test name>
2477#   TD:<whitespace><test description>
2478#
2479# for each test case. Return a reference to a hash containing a mapping
2480#
2481#   test name -> test description.
2482#
2483# Die on error.
2484#
2485
2486sub read_testfile($)
2487{
2488	my %result;
2489	my $test_name;
2490	my $changed_testname;
2491	local *TEST_HANDLE;
2492
2493	open(TEST_HANDLE, "<", $_[0])
2494		or die("ERROR: cannot open $_[0]!\n");
2495
2496	while (<TEST_HANDLE>)
2497	{
2498		chomp($_);
2499
2500		# Match lines beginning with TN:<whitespace(s)>
2501		if (/^TN:\s+(.*?)\s*$/)
2502		{
2503			# Store name for later use
2504			$test_name = $1;
2505			if ($test_name =~ s/\W/_/g)
2506			{
2507				$changed_testname = 1;
2508			}
2509		}
2510
2511		# Match lines beginning with TD:<whitespace(s)>
2512		if (/^TD:\s+(.*?)\s*$/)
2513		{
2514			if (!defined($test_name)) {
2515				die("ERROR: Found test description without prior test name in $_[0]:$.\n");
2516			}
2517			# Check for empty line
2518			if ($1)
2519			{
2520				# Add description to hash
2521				$result{$test_name} .= " $1";
2522			}
2523			else
2524			{
2525				# Add empty line
2526				$result{$test_name} .= "\n\n";
2527			}
2528		}
2529	}
2530
2531	close(TEST_HANDLE);
2532
2533	if ($changed_testname)
2534	{
2535		warn("WARNING: invalid characters removed from testname in ".
2536		     "descriptions file $_[0]\n");
2537	}
2538
2539	return \%result;
2540}
2541
2542
2543#
2544# escape_html(STRING)
2545#
2546# Return a copy of STRING in which all occurrences of HTML special characters
2547# are escaped.
2548#
2549
2550sub escape_html($)
2551{
2552	my $string = $_[0];
2553
2554	if (!$string) { return ""; }
2555
2556	$string =~ s/&/&amp;/g;		# & -> &amp;
2557	$string =~ s/</&lt;/g;		# < -> &lt;
2558	$string =~ s/>/&gt;/g;		# > -> &gt;
2559	$string =~ s/\"/&quot;/g;	# " -> &quot;
2560
2561	while ($string =~ /^([^\t]*)(\t)/)
2562	{
2563		my $replacement = " "x($tab_size - (length($1) % $tab_size));
2564		$string =~ s/^([^\t]*)(\t)/$1$replacement/;
2565	}
2566
2567	$string =~ s/\n/<br>/g;		# \n -> <br>
2568
2569	return $string;
2570}
2571
2572
2573#
2574# get_date_string()
2575#
2576# Return the current date in the form: yyyy-mm-dd
2577#
2578
2579sub get_date_string()
2580{
2581	my $year;
2582	my $month;
2583	my $day;
2584	my $hour;
2585	my $min;
2586	my $sec;
2587
2588	($year, $month, $day, $hour, $min, $sec) =
2589		(localtime())[5, 4, 3, 2, 1, 0];
2590
2591	return sprintf("%d-%02d-%02d %02d:%02d:%02d", $year+1900, $month+1,
2592		       $day, $hour, $min, $sec);
2593}
2594
2595
2596#
2597# create_sub_dir(dir_name)
2598#
2599# Create subdirectory DIR_NAME if it does not already exist, including all its
2600# parent directories.
2601#
2602# Die on error.
2603#
2604
2605sub create_sub_dir($)
2606{
2607	my ($dir) = @_;
2608
2609	system("mkdir", "-p" ,$dir)
2610		and die("ERROR: cannot create directory $dir!\n");
2611}
2612
2613
2614#
2615# write_description_file(descriptions, overall_found, overall_hit,
2616#                        total_fn_found, total_fn_hit, total_br_found,
2617#                        total_br_hit)
2618#
2619# Write HTML file containing all test case descriptions. DESCRIPTIONS is a
2620# reference to a hash containing a mapping
2621#
2622#   test case name -> test case description
2623#
2624# Die on error.
2625#
2626
2627sub write_description_file($$$$$$$)
2628{
2629	my %description = %{$_[0]};
2630	my $found = $_[1];
2631	my $hit = $_[2];
2632	my $fn_found = $_[3];
2633	my $fn_hit = $_[4];
2634	my $br_found = $_[5];
2635	my $br_hit = $_[6];
2636	my $test_name;
2637	local *HTML_HANDLE;
2638
2639	html_create(*HTML_HANDLE,"descriptions.$html_ext");
2640	write_html_prolog(*HTML_HANDLE, "", "LCOV - test case descriptions");
2641	write_header(*HTML_HANDLE, 3, "", "", $found, $hit, $fn_found,
2642		     $fn_hit, $br_found, $br_hit, 0);
2643
2644	write_test_table_prolog(*HTML_HANDLE,
2645			 "Test case descriptions - alphabetical list");
2646
2647	foreach $test_name (sort(keys(%description)))
2648	{
2649		my $desc = $description{$test_name};
2650
2651		$desc = escape_html($desc) if (!$rc_desc_html);
2652		write_test_table_entry(*HTML_HANDLE, $test_name, $desc);
2653	}
2654
2655	write_test_table_epilog(*HTML_HANDLE);
2656	write_html_epilog(*HTML_HANDLE, "");
2657
2658	close(*HTML_HANDLE);
2659}
2660
2661
2662
2663#
2664# write_png_files()
2665#
2666# Create all necessary .png files for the HTML-output in the current
2667# directory. .png-files are used as bar graphs.
2668#
2669# Die on error.
2670#
2671
2672sub write_png_files()
2673{
2674	my %data;
2675	local *PNG_HANDLE;
2676
2677	$data{"ruby.png"} =
2678		[0x89, 0x50, 0x4e, 0x47, 0x0d, 0x0a, 0x1a, 0x0a, 0x00, 0x00,
2679		 0x00, 0x0d, 0x49, 0x48, 0x44, 0x52, 0x00, 0x00, 0x00, 0x01,
2680		 0x00, 0x00, 0x00, 0x01, 0x01, 0x03, 0x00, 0x00, 0x00, 0x25,
2681		 0xdb, 0x56, 0xca, 0x00, 0x00, 0x00, 0x07, 0x74, 0x49, 0x4d,
2682		 0x45, 0x07, 0xd2, 0x07, 0x11, 0x0f, 0x18, 0x10, 0x5d, 0x57,
2683		 0x34, 0x6e, 0x00, 0x00, 0x00, 0x09, 0x70, 0x48, 0x59, 0x73,
2684		 0x00, 0x00, 0x0b, 0x12, 0x00, 0x00, 0x0b, 0x12, 0x01, 0xd2,
2685		 0xdd, 0x7e, 0xfc, 0x00, 0x00, 0x00, 0x04, 0x67, 0x41, 0x4d,
2686		 0x41, 0x00, 0x00, 0xb1, 0x8f, 0x0b, 0xfc, 0x61, 0x05, 0x00,
2687		 0x00, 0x00, 0x06, 0x50, 0x4c, 0x54, 0x45, 0xff, 0x35, 0x2f,
2688		 0x00, 0x00, 0x00, 0xd0, 0x33, 0x9a, 0x9d, 0x00, 0x00, 0x00,
2689		 0x0a, 0x49, 0x44, 0x41, 0x54, 0x78, 0xda, 0x63, 0x60, 0x00,
2690		 0x00, 0x00, 0x02, 0x00, 0x01, 0xe5, 0x27, 0xde, 0xfc, 0x00,
2691		 0x00, 0x00, 0x00, 0x49, 0x45, 0x4e, 0x44, 0xae, 0x42, 0x60,
2692		 0x82];
2693	$data{"amber.png"} =
2694		[0x89, 0x50, 0x4e, 0x47, 0x0d, 0x0a, 0x1a, 0x0a, 0x00, 0x00,
2695		 0x00, 0x0d, 0x49, 0x48, 0x44, 0x52, 0x00, 0x00, 0x00, 0x01,
2696		 0x00, 0x00, 0x00, 0x01, 0x01, 0x03, 0x00, 0x00, 0x00, 0x25,
2697		 0xdb, 0x56, 0xca, 0x00, 0x00, 0x00, 0x07, 0x74, 0x49, 0x4d,
2698		 0x45, 0x07, 0xd2, 0x07, 0x11, 0x0f, 0x28, 0x04, 0x98, 0xcb,
2699		 0xd6, 0xe0, 0x00, 0x00, 0x00, 0x09, 0x70, 0x48, 0x59, 0x73,
2700		 0x00, 0x00, 0x0b, 0x12, 0x00, 0x00, 0x0b, 0x12, 0x01, 0xd2,
2701		 0xdd, 0x7e, 0xfc, 0x00, 0x00, 0x00, 0x04, 0x67, 0x41, 0x4d,
2702		 0x41, 0x00, 0x00, 0xb1, 0x8f, 0x0b, 0xfc, 0x61, 0x05, 0x00,
2703		 0x00, 0x00, 0x06, 0x50, 0x4c, 0x54, 0x45, 0xff, 0xe0, 0x50,
2704		 0x00, 0x00, 0x00, 0xa2, 0x7a, 0xda, 0x7e, 0x00, 0x00, 0x00,
2705		 0x0a, 0x49, 0x44, 0x41, 0x54, 0x78, 0xda, 0x63, 0x60, 0x00,
2706	  	 0x00, 0x00, 0x02, 0x00, 0x01, 0xe5, 0x27, 0xde, 0xfc, 0x00,
2707		 0x00, 0x00, 0x00, 0x49, 0x45, 0x4e, 0x44, 0xae, 0x42, 0x60,
2708		 0x82];
2709	$data{"emerald.png"} =
2710		[0x89, 0x50, 0x4e, 0x47, 0x0d, 0x0a, 0x1a, 0x0a, 0x00, 0x00,
2711		 0x00, 0x0d, 0x49, 0x48, 0x44, 0x52, 0x00, 0x00, 0x00, 0x01,
2712		 0x00, 0x00, 0x00, 0x01, 0x01, 0x03, 0x00, 0x00, 0x00, 0x25,
2713		 0xdb, 0x56, 0xca, 0x00, 0x00, 0x00, 0x07, 0x74, 0x49, 0x4d,
2714		 0x45, 0x07, 0xd2, 0x07, 0x11, 0x0f, 0x22, 0x2b, 0xc9, 0xf5,
2715		 0x03, 0x33, 0x00, 0x00, 0x00, 0x09, 0x70, 0x48, 0x59, 0x73,
2716		 0x00, 0x00, 0x0b, 0x12, 0x00, 0x00, 0x0b, 0x12, 0x01, 0xd2,
2717		 0xdd, 0x7e, 0xfc, 0x00, 0x00, 0x00, 0x04, 0x67, 0x41, 0x4d,
2718		 0x41, 0x00, 0x00, 0xb1, 0x8f, 0x0b, 0xfc, 0x61, 0x05, 0x00,
2719		 0x00, 0x00, 0x06, 0x50, 0x4c, 0x54, 0x45, 0x1b, 0xea, 0x59,
2720		 0x0a, 0x0a, 0x0a, 0x0f, 0xba, 0x50, 0x83, 0x00, 0x00, 0x00,
2721		 0x0a, 0x49, 0x44, 0x41, 0x54, 0x78, 0xda, 0x63, 0x60, 0x00,
2722		 0x00, 0x00, 0x02, 0x00, 0x01, 0xe5, 0x27, 0xde, 0xfc, 0x00,
2723		 0x00, 0x00, 0x00, 0x49, 0x45, 0x4e, 0x44, 0xae, 0x42, 0x60,
2724		 0x82];
2725	$data{"snow.png"} =
2726		[0x89, 0x50, 0x4e, 0x47, 0x0d, 0x0a, 0x1a, 0x0a, 0x00, 0x00,
2727		 0x00, 0x0d, 0x49, 0x48, 0x44, 0x52, 0x00, 0x00, 0x00, 0x01,
2728		 0x00, 0x00, 0x00, 0x01, 0x01, 0x03, 0x00, 0x00, 0x00, 0x25,
2729		 0xdb, 0x56, 0xca, 0x00, 0x00, 0x00, 0x07, 0x74, 0x49, 0x4d,
2730		 0x45, 0x07, 0xd2, 0x07, 0x11, 0x0f, 0x1e, 0x1d, 0x75, 0xbc,
2731		 0xef, 0x55, 0x00, 0x00, 0x00, 0x09, 0x70, 0x48, 0x59, 0x73,
2732		 0x00, 0x00, 0x0b, 0x12, 0x00, 0x00, 0x0b, 0x12, 0x01, 0xd2,
2733		 0xdd, 0x7e, 0xfc, 0x00, 0x00, 0x00, 0x04, 0x67, 0x41, 0x4d,
2734		 0x41, 0x00, 0x00, 0xb1, 0x8f, 0x0b, 0xfc, 0x61, 0x05, 0x00,
2735		 0x00, 0x00, 0x06, 0x50, 0x4c, 0x54, 0x45, 0xff, 0xff, 0xff,
2736		 0x00, 0x00, 0x00, 0x55, 0xc2, 0xd3, 0x7e, 0x00, 0x00, 0x00,
2737		 0x0a, 0x49, 0x44, 0x41, 0x54, 0x78, 0xda, 0x63, 0x60, 0x00,
2738		 0x00, 0x00, 0x02, 0x00, 0x01, 0xe5, 0x27, 0xde, 0xfc, 0x00,
2739		 0x00, 0x00, 0x00, 0x49, 0x45, 0x4e, 0x44, 0xae, 0x42, 0x60,
2740		 0x82];
2741	$data{"glass.png"} =
2742		[0x89, 0x50, 0x4e, 0x47, 0x0d, 0x0a, 0x1a, 0x0a, 0x00, 0x00,
2743		 0x00, 0x0d, 0x49, 0x48, 0x44, 0x52, 0x00, 0x00, 0x00, 0x01,
2744		 0x00, 0x00, 0x00, 0x01, 0x01, 0x03, 0x00, 0x00, 0x00, 0x25,
2745		 0xdb, 0x56, 0xca, 0x00, 0x00, 0x00, 0x04, 0x67, 0x41, 0x4d,
2746		 0x41, 0x00, 0x00, 0xb1, 0x8f, 0x0b, 0xfc, 0x61, 0x05, 0x00,
2747		 0x00, 0x00, 0x06, 0x50, 0x4c, 0x54, 0x45, 0xff, 0xff, 0xff,
2748		 0x00, 0x00, 0x00, 0x55, 0xc2, 0xd3, 0x7e, 0x00, 0x00, 0x00,
2749		 0x01, 0x74, 0x52, 0x4e, 0x53, 0x00, 0x40, 0xe6, 0xd8, 0x66,
2750		 0x00, 0x00, 0x00, 0x01, 0x62, 0x4b, 0x47, 0x44, 0x00, 0x88,
2751		 0x05, 0x1d, 0x48, 0x00, 0x00, 0x00, 0x09, 0x70, 0x48, 0x59,
2752		 0x73, 0x00, 0x00, 0x0b, 0x12, 0x00, 0x00, 0x0b, 0x12, 0x01,
2753		 0xd2, 0xdd, 0x7e, 0xfc, 0x00, 0x00, 0x00, 0x07, 0x74, 0x49,
2754		 0x4d, 0x45, 0x07, 0xd2, 0x07, 0x13, 0x0f, 0x08, 0x19, 0xc4,
2755		 0x40, 0x56, 0x10, 0x00, 0x00, 0x00, 0x0a, 0x49, 0x44, 0x41,
2756		 0x54, 0x78, 0x9c, 0x63, 0x60, 0x00, 0x00, 0x00, 0x02, 0x00,
2757		 0x01, 0x48, 0xaf, 0xa4, 0x71, 0x00, 0x00, 0x00, 0x00, 0x49,
2758		 0x45, 0x4e, 0x44, 0xae, 0x42, 0x60, 0x82];
2759	$data{"updown.png"} =
2760		[0x89, 0x50, 0x4e, 0x47, 0x0d, 0x0a, 0x1a, 0x0a, 0x00, 0x00,
2761		 0x00, 0x0d, 0x49, 0x48, 0x44, 0x52, 0x00, 0x00, 0x00, 0x0a,
2762		 0x00, 0x00, 0x00, 0x0e, 0x08, 0x06, 0x00, 0x00, 0x00, 0x16,
2763		 0xa3, 0x8d, 0xab, 0x00, 0x00, 0x00, 0x3c, 0x49, 0x44, 0x41,
2764		 0x54, 0x28, 0xcf, 0x63, 0x60, 0x40, 0x03, 0xff, 0xa1, 0x00,
2765		 0x5d, 0x9c, 0x11, 0x5d, 0x11, 0x8a, 0x24, 0x23, 0x23, 0x23,
2766		 0x86, 0x42, 0x6c, 0xa6, 0x20, 0x2b, 0x66, 0xc4, 0xa7, 0x08,
2767		 0x59, 0x31, 0x23, 0x21, 0x45, 0x30, 0xc0, 0xc4, 0x30, 0x60,
2768		 0x80, 0xfa, 0x6e, 0x24, 0x3e, 0x78, 0x48, 0x0a, 0x70, 0x62,
2769		 0xa2, 0x90, 0x81, 0xd8, 0x44, 0x01, 0x00, 0xe9, 0x5c, 0x2f,
2770		 0xf5, 0xe2, 0x9d, 0x0f, 0xf9, 0x00, 0x00, 0x00, 0x00, 0x49,
2771		 0x45, 0x4e, 0x44, 0xae, 0x42, 0x60, 0x82] if ($sort);
2772	foreach (keys(%data))
2773	{
2774		open(PNG_HANDLE, ">", $_)
2775			or die("ERROR: cannot create $_!\n");
2776		binmode(PNG_HANDLE);
2777		print(PNG_HANDLE map(chr,@{$data{$_}}));
2778		close(PNG_HANDLE);
2779	}
2780}
2781
2782
2783#
2784# write_htaccess_file()
2785#
2786
2787sub write_htaccess_file()
2788{
2789	local *HTACCESS_HANDLE;
2790	my $htaccess_data;
2791
2792	open(*HTACCESS_HANDLE, ">", ".htaccess")
2793		or die("ERROR: cannot open .htaccess for writing!\n");
2794
2795	$htaccess_data = (<<"END_OF_HTACCESS")
2796AddEncoding x-gzip .html
2797END_OF_HTACCESS
2798	;
2799
2800	print(HTACCESS_HANDLE $htaccess_data);
2801	close(*HTACCESS_HANDLE);
2802}
2803
2804
2805#
2806# write_css_file()
2807#
2808# Write the cascading style sheet file gcov.css to the current directory.
2809# This file defines basic layout attributes of all generated HTML pages.
2810#
2811
2812sub write_css_file()
2813{
2814	local *CSS_HANDLE;
2815
2816	# Check for a specified external style sheet file
2817	if ($css_filename)
2818	{
2819		# Simply copy that file
2820		system("cp", $css_filename, "gcov.css")
2821			and die("ERROR: cannot copy file $css_filename!\n");
2822		return;
2823	}
2824
2825	open(CSS_HANDLE, ">", "gcov.css")
2826		or die ("ERROR: cannot open gcov.css for writing!\n");
2827
2828
2829	# *************************************************************
2830
2831	my $css_data = ($_=<<"END_OF_CSS")
2832	/* All views: initial background and text color */
2833	body
2834	{
2835	  color: #000000;
2836	  background-color: #FFFFFF;
2837	}
2838
2839	/* All views: standard link format*/
2840	a:link
2841	{
2842	  color: #284FA8;
2843	  text-decoration: underline;
2844	}
2845
2846	/* All views: standard link - visited format */
2847	a:visited
2848	{
2849	  color: #00CB40;
2850	  text-decoration: underline;
2851	}
2852
2853	/* All views: standard link - activated format */
2854	a:active
2855	{
2856	  color: #FF0040;
2857	  text-decoration: underline;
2858	}
2859
2860	/* All views: main title format */
2861	td.title
2862	{
2863	  text-align: center;
2864	  padding-bottom: 10px;
2865	  font-family: sans-serif;
2866	  font-size: 20pt;
2867	  font-style: italic;
2868	  font-weight: bold;
2869	}
2870
2871	/* All views: header item format */
2872	td.headerItem
2873	{
2874	  text-align: right;
2875	  padding-right: 6px;
2876	  font-family: sans-serif;
2877	  font-weight: bold;
2878	  vertical-align: top;
2879	  white-space: nowrap;
2880	}
2881
2882	/* All views: header item value format */
2883	td.headerValue
2884	{
2885	  text-align: left;
2886	  color: #284FA8;
2887	  font-family: sans-serif;
2888	  font-weight: bold;
2889	  white-space: nowrap;
2890	}
2891
2892	/* All views: header item coverage table heading */
2893	td.headerCovTableHead
2894	{
2895	  text-align: center;
2896	  padding-right: 6px;
2897	  padding-left: 6px;
2898	  padding-bottom: 0px;
2899	  font-family: sans-serif;
2900	  font-size: 80%;
2901	  white-space: nowrap;
2902	}
2903
2904	/* All views: header item coverage table entry */
2905	td.headerCovTableEntry
2906	{
2907	  text-align: right;
2908	  color: #284FA8;
2909	  font-family: sans-serif;
2910	  font-weight: bold;
2911	  white-space: nowrap;
2912	  padding-left: 12px;
2913	  padding-right: 4px;
2914	  background-color: #DAE7FE;
2915	}
2916
2917	/* All views: header item coverage table entry for high coverage rate */
2918	td.headerCovTableEntryHi
2919	{
2920	  text-align: right;
2921	  color: #000000;
2922	  font-family: sans-serif;
2923	  font-weight: bold;
2924	  white-space: nowrap;
2925	  padding-left: 12px;
2926	  padding-right: 4px;
2927	  background-color: #A7FC9D;
2928	}
2929
2930	/* All views: header item coverage table entry for medium coverage rate */
2931	td.headerCovTableEntryMed
2932	{
2933	  text-align: right;
2934	  color: #000000;
2935	  font-family: sans-serif;
2936	  font-weight: bold;
2937	  white-space: nowrap;
2938	  padding-left: 12px;
2939	  padding-right: 4px;
2940	  background-color: #FFEA20;
2941	}
2942
2943	/* All views: header item coverage table entry for ow coverage rate */
2944	td.headerCovTableEntryLo
2945	{
2946	  text-align: right;
2947	  color: #000000;
2948	  font-family: sans-serif;
2949	  font-weight: bold;
2950	  white-space: nowrap;
2951	  padding-left: 12px;
2952	  padding-right: 4px;
2953	  background-color: #FF0000;
2954	}
2955
2956	/* All views: header legend value for legend entry */
2957	td.headerValueLeg
2958	{
2959	  text-align: left;
2960	  color: #000000;
2961	  font-family: sans-serif;
2962	  font-size: 80%;
2963	  white-space: nowrap;
2964	  padding-top: 4px;
2965	}
2966
2967	/* All views: color of horizontal ruler */
2968	td.ruler
2969	{
2970	  background-color: #6688D4;
2971	}
2972
2973	/* All views: version string format */
2974	td.versionInfo
2975	{
2976	  text-align: center;
2977	  padding-top: 2px;
2978	  font-family: sans-serif;
2979	  font-style: italic;
2980	}
2981
2982	/* Directory view/File view (all)/Test case descriptions:
2983	   table headline format */
2984	td.tableHead
2985	{
2986	  text-align: center;
2987	  color: #FFFFFF;
2988	  background-color: #6688D4;
2989	  font-family: sans-serif;
2990	  font-size: 120%;
2991	  font-weight: bold;
2992	  white-space: nowrap;
2993	  padding-left: 4px;
2994	  padding-right: 4px;
2995	}
2996
2997	span.tableHeadSort
2998	{
2999	  padding-right: 4px;
3000	}
3001
3002	/* Directory view/File view (all): filename entry format */
3003	td.coverFile
3004	{
3005	  text-align: left;
3006	  padding-left: 10px;
3007	  padding-right: 20px;
3008	  color: #284FA8;
3009	  background-color: #DAE7FE;
3010	  font-family: monospace;
3011	}
3012
3013	/* Directory view/File view (all): bar-graph entry format*/
3014	td.coverBar
3015	{
3016	  padding-left: 10px;
3017	  padding-right: 10px;
3018	  background-color: #DAE7FE;
3019	}
3020
3021	/* Directory view/File view (all): bar-graph outline color */
3022	td.coverBarOutline
3023	{
3024	  background-color: #000000;
3025	}
3026
3027	/* Directory view/File view (all): percentage entry for files with
3028	   high coverage rate */
3029	td.coverPerHi
3030	{
3031	  text-align: right;
3032	  padding-left: 10px;
3033	  padding-right: 10px;
3034	  background-color: #A7FC9D;
3035	  font-weight: bold;
3036	  font-family: sans-serif;
3037	}
3038
3039	/* Directory view/File view (all): line count entry for files with
3040	   high coverage rate */
3041	td.coverNumHi
3042	{
3043	  text-align: right;
3044	  padding-left: 10px;
3045	  padding-right: 10px;
3046	  background-color: #A7FC9D;
3047	  white-space: nowrap;
3048	  font-family: sans-serif;
3049	}
3050
3051	/* Directory view/File view (all): percentage entry for files with
3052	   medium coverage rate */
3053	td.coverPerMed
3054	{
3055	  text-align: right;
3056	  padding-left: 10px;
3057	  padding-right: 10px;
3058	  background-color: #FFEA20;
3059	  font-weight: bold;
3060	  font-family: sans-serif;
3061	}
3062
3063	/* Directory view/File view (all): line count entry for files with
3064	   medium coverage rate */
3065	td.coverNumMed
3066	{
3067	  text-align: right;
3068	  padding-left: 10px;
3069	  padding-right: 10px;
3070	  background-color: #FFEA20;
3071	  white-space: nowrap;
3072	  font-family: sans-serif;
3073	}
3074
3075	/* Directory view/File view (all): percentage entry for files with
3076	   low coverage rate */
3077	td.coverPerLo
3078	{
3079	  text-align: right;
3080	  padding-left: 10px;
3081	  padding-right: 10px;
3082	  background-color: #FF0000;
3083	  font-weight: bold;
3084	  font-family: sans-serif;
3085	}
3086
3087	/* Directory view/File view (all): line count entry for files with
3088	   low coverage rate */
3089	td.coverNumLo
3090	{
3091	  text-align: right;
3092	  padding-left: 10px;
3093	  padding-right: 10px;
3094	  background-color: #FF0000;
3095	  white-space: nowrap;
3096	  font-family: sans-serif;
3097	}
3098
3099	/* File view (all): "show/hide details" link format */
3100	a.detail:link
3101	{
3102	  color: #B8D0FF;
3103	  font-size:80%;
3104	}
3105
3106	/* File view (all): "show/hide details" link - visited format */
3107	a.detail:visited
3108	{
3109	  color: #B8D0FF;
3110	  font-size:80%;
3111	}
3112
3113	/* File view (all): "show/hide details" link - activated format */
3114	a.detail:active
3115	{
3116	  color: #FFFFFF;
3117	  font-size:80%;
3118	}
3119
3120	/* File view (detail): test name entry */
3121	td.testName
3122	{
3123	  text-align: right;
3124	  padding-right: 10px;
3125	  background-color: #DAE7FE;
3126	  font-family: sans-serif;
3127	}
3128
3129	/* File view (detail): test percentage entry */
3130	td.testPer
3131	{
3132	  text-align: right;
3133	  padding-left: 10px;
3134	  padding-right: 10px;
3135	  background-color: #DAE7FE;
3136	  font-family: sans-serif;
3137	}
3138
3139	/* File view (detail): test lines count entry */
3140	td.testNum
3141	{
3142	  text-align: right;
3143	  padding-left: 10px;
3144	  padding-right: 10px;
3145	  background-color: #DAE7FE;
3146	  font-family: sans-serif;
3147	}
3148
3149	/* Test case descriptions: test name format*/
3150	dt
3151	{
3152	  font-family: sans-serif;
3153	  font-weight: bold;
3154	}
3155
3156	/* Test case descriptions: description table body */
3157	td.testDescription
3158	{
3159	  padding-top: 10px;
3160	  padding-left: 30px;
3161	  padding-bottom: 10px;
3162	  padding-right: 30px;
3163	  background-color: #DAE7FE;
3164	}
3165
3166	/* Source code view: function entry */
3167	td.coverFn
3168	{
3169	  text-align: left;
3170	  padding-left: 10px;
3171	  padding-right: 20px;
3172	  color: #284FA8;
3173	  background-color: #DAE7FE;
3174	  font-family: monospace;
3175	}
3176
3177	/* Source code view: function entry zero count*/
3178	td.coverFnLo
3179	{
3180	  text-align: right;
3181	  padding-left: 10px;
3182	  padding-right: 10px;
3183	  background-color: #FF0000;
3184	  font-weight: bold;
3185	  font-family: sans-serif;
3186	}
3187
3188	/* Source code view: function entry nonzero count*/
3189	td.coverFnHi
3190	{
3191	  text-align: right;
3192	  padding-left: 10px;
3193	  padding-right: 10px;
3194	  background-color: #DAE7FE;
3195	  font-weight: bold;
3196	  font-family: sans-serif;
3197	}
3198
3199	/* Source code view: source code format */
3200	pre.source
3201	{
3202	  font-family: monospace;
3203	  white-space: pre;
3204	  margin-top: 2px;
3205	}
3206
3207	/* Source code view: line number format */
3208	span.lineNum
3209	{
3210	  background-color: #EFE383;
3211	}
3212
3213	/* Source code view: format for lines which were executed */
3214	td.lineCov,
3215	span.lineCov
3216	{
3217	  background-color: #CAD7FE;
3218	}
3219
3220	/* Source code view: format for Cov legend */
3221	span.coverLegendCov
3222	{
3223	  padding-left: 10px;
3224	  padding-right: 10px;
3225	  padding-bottom: 2px;
3226	  background-color: #CAD7FE;
3227	}
3228
3229	/* Source code view: format for lines which were not executed */
3230	td.lineNoCov,
3231	span.lineNoCov
3232	{
3233	  background-color: #FF6230;
3234	}
3235
3236	/* Source code view: format for NoCov legend */
3237	span.coverLegendNoCov
3238	{
3239	  padding-left: 10px;
3240	  padding-right: 10px;
3241	  padding-bottom: 2px;
3242	  background-color: #FF6230;
3243	}
3244
3245	/* Source code view (function table): standard link - visited format */
3246	td.lineNoCov > a:visited,
3247	td.lineCov > a:visited
3248	{
3249	  color: black;
3250	  text-decoration: underline;
3251	}
3252
3253	/* Source code view: format for lines which were executed only in a
3254	   previous version */
3255	span.lineDiffCov
3256	{
3257	  background-color: #B5F7AF;
3258	}
3259
3260	/* Source code view: format for branches which were executed
3261	 * and taken */
3262	span.branchCov
3263	{
3264	  background-color: #CAD7FE;
3265	}
3266
3267	/* Source code view: format for branches which were executed
3268	 * but not taken */
3269	span.branchNoCov
3270	{
3271	  background-color: #FF6230;
3272	}
3273
3274	/* Source code view: format for branches which were not executed */
3275	span.branchNoExec
3276	{
3277	  background-color: #FF6230;
3278	}
3279
3280	/* Source code view: format for the source code heading line */
3281	pre.sourceHeading
3282	{
3283	  white-space: pre;
3284	  font-family: monospace;
3285	  font-weight: bold;
3286	  margin: 0px;
3287	}
3288
3289	/* All views: header legend value for low rate */
3290	td.headerValueLegL
3291	{
3292	  font-family: sans-serif;
3293	  text-align: center;
3294	  white-space: nowrap;
3295	  padding-left: 4px;
3296	  padding-right: 2px;
3297	  background-color: #FF0000;
3298	  font-size: 80%;
3299	}
3300
3301	/* All views: header legend value for med rate */
3302	td.headerValueLegM
3303	{
3304	  font-family: sans-serif;
3305	  text-align: center;
3306	  white-space: nowrap;
3307	  padding-left: 2px;
3308	  padding-right: 2px;
3309	  background-color: #FFEA20;
3310	  font-size: 80%;
3311	}
3312
3313	/* All views: header legend value for hi rate */
3314	td.headerValueLegH
3315	{
3316	  font-family: sans-serif;
3317	  text-align: center;
3318	  white-space: nowrap;
3319	  padding-left: 2px;
3320	  padding-right: 4px;
3321	  background-color: #A7FC9D;
3322	  font-size: 80%;
3323	}
3324
3325	/* All views except source code view: legend format for low coverage */
3326	span.coverLegendCovLo
3327	{
3328	  padding-left: 10px;
3329	  padding-right: 10px;
3330	  padding-top: 2px;
3331	  background-color: #FF0000;
3332	}
3333
3334	/* All views except source code view: legend format for med coverage */
3335	span.coverLegendCovMed
3336	{
3337	  padding-left: 10px;
3338	  padding-right: 10px;
3339	  padding-top: 2px;
3340	  background-color: #FFEA20;
3341	}
3342
3343	/* All views except source code view: legend format for hi coverage */
3344	span.coverLegendCovHi
3345	{
3346	  padding-left: 10px;
3347	  padding-right: 10px;
3348	  padding-top: 2px;
3349	  background-color: #A7FC9D;
3350	}
3351END_OF_CSS
3352	;
3353
3354	# *************************************************************
3355
3356
3357	# Remove leading tab from all lines
3358	$css_data =~ s/^\t//gm;
3359
3360	print(CSS_HANDLE $css_data);
3361
3362	close(CSS_HANDLE);
3363}
3364
3365
3366#
3367# get_bar_graph_code(base_dir, cover_found, cover_hit)
3368#
3369# Return a string containing HTML code which implements a bar graph display
3370# for a coverage rate of cover_hit * 100 / cover_found.
3371#
3372
3373sub get_bar_graph_code($$$)
3374{
3375	my ($base_dir, $found, $hit) = @_;
3376	my $rate;
3377	my $alt;
3378	my $width;
3379	my $remainder;
3380	my $png_name;
3381	my $graph_code;
3382
3383	# Check number of instrumented lines
3384	if ($_[1] == 0) { return ""; }
3385
3386	$alt		= rate($hit, $found, "%");
3387	$width		= rate($hit, $found, undef, 0);
3388	$remainder	= 100 - $width;
3389
3390	# Decide which .png file to use
3391	$png_name = $rate_png[classify_rate($found, $hit, $med_limit,
3392					    $hi_limit)];
3393
3394	if ($width == 0)
3395	{
3396		# Zero coverage
3397		$graph_code = (<<END_OF_HTML)
3398	        <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>
3399END_OF_HTML
3400		;
3401	}
3402	elsif ($width == 100)
3403	{
3404		# Full coverage
3405		$graph_code = (<<END_OF_HTML)
3406		<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>
3407END_OF_HTML
3408		;
3409	}
3410	else
3411	{
3412		# Positive coverage
3413		$graph_code = (<<END_OF_HTML)
3414		<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>
3415END_OF_HTML
3416		;
3417	}
3418
3419	# Remove leading tabs from all lines
3420	$graph_code =~ s/^\t+//gm;
3421	chomp($graph_code);
3422
3423	return($graph_code);
3424}
3425
3426#
3427# sub classify_rate(found, hit, med_limit, high_limit)
3428#
3429# Return 0 for low rate, 1 for medium rate and 2 for hi rate.
3430#
3431
3432sub classify_rate($$$$)
3433{
3434	my ($found, $hit, $med, $hi) = @_;
3435	my $rate;
3436
3437	if ($found == 0) {
3438		return 2;
3439	}
3440	$rate = rate($hit, $found);
3441	if ($rate < $med) {
3442		return 0;
3443	} elsif ($rate < $hi) {
3444		return 1;
3445	}
3446	return 2;
3447}
3448
3449
3450#
3451# write_html(filehandle, html_code)
3452#
3453# Write out HTML_CODE to FILEHANDLE while removing a leading tabulator mark
3454# in each line of HTML_CODE.
3455#
3456
3457sub write_html(*$)
3458{
3459	local *HTML_HANDLE = $_[0];
3460	my $html_code = $_[1];
3461
3462	# Remove leading tab from all lines
3463	$html_code =~ s/^\t//gm;
3464
3465	print(HTML_HANDLE $html_code)
3466		or die("ERROR: cannot write HTML data ($!)\n");
3467}
3468
3469
3470#
3471# write_html_prolog(filehandle, base_dir, pagetitle)
3472#
3473# Write an HTML prolog common to all HTML files to FILEHANDLE. PAGETITLE will
3474# be used as HTML page title. BASE_DIR contains a relative path which points
3475# to the base directory.
3476#
3477
3478sub write_html_prolog(*$$)
3479{
3480	my $basedir = $_[1];
3481	my $pagetitle = $_[2];
3482	my $prolog;
3483
3484	$prolog = $html_prolog;
3485	$prolog =~ s/\@pagetitle\@/$pagetitle/g;
3486	$prolog =~ s/\@basedir\@/$basedir/g;
3487
3488	write_html($_[0], $prolog);
3489}
3490
3491
3492#
3493# write_header_prolog(filehandle, base_dir)
3494#
3495# Write beginning of page header HTML code.
3496#
3497
3498sub write_header_prolog(*$)
3499{
3500	# *************************************************************
3501
3502	write_html($_[0], <<END_OF_HTML)
3503	  <table width="100%" border=0 cellspacing=0 cellpadding=0>
3504	    <tr><td class="title">$title</td></tr>
3505	    <tr><td class="ruler"><img src="$_[1]glass.png" width=3 height=3 alt=""></td></tr>
3506
3507	    <tr>
3508	      <td width="100%">
3509	        <table cellpadding=1 border=0 width="100%">
3510END_OF_HTML
3511	;
3512
3513	# *************************************************************
3514}
3515
3516
3517#
3518# write_header_line(handle, content)
3519#
3520# Write a header line with the specified table contents.
3521#
3522
3523sub write_header_line(*@)
3524{
3525	my ($handle, @content) = @_;
3526	my $entry;
3527
3528	write_html($handle, "          <tr>\n");
3529	foreach $entry (@content) {
3530		my ($width, $class, $text, $colspan) = @{$entry};
3531
3532		if (defined($width)) {
3533			$width = " width=\"$width\"";
3534		} else {
3535			$width = "";
3536		}
3537		if (defined($class)) {
3538			$class = " class=\"$class\"";
3539		} else {
3540			$class = "";
3541		}
3542		if (defined($colspan)) {
3543			$colspan = " colspan=\"$colspan\"";
3544		} else {
3545			$colspan = "";
3546		}
3547		$text = "" if (!defined($text));
3548		write_html($handle,
3549			   "            <td$width$class$colspan>$text</td>\n");
3550	}
3551	write_html($handle, "          </tr>\n");
3552}
3553
3554
3555#
3556# write_header_epilog(filehandle, base_dir)
3557#
3558# Write end of page header HTML code.
3559#
3560
3561sub write_header_epilog(*$)
3562{
3563	# *************************************************************
3564
3565	write_html($_[0], <<END_OF_HTML)
3566	          <tr><td><img src="$_[1]glass.png" width=3 height=3 alt=""></td></tr>
3567	        </table>
3568	      </td>
3569	    </tr>
3570
3571	    <tr><td class="ruler"><img src="$_[1]glass.png" width=3 height=3 alt=""></td></tr>
3572	  </table>
3573
3574END_OF_HTML
3575	;
3576
3577	# *************************************************************
3578}
3579
3580
3581#
3582# write_file_table_prolog(handle, file_heading, ([heading, num_cols], ...))
3583#
3584# Write heading for file table.
3585#
3586
3587sub write_file_table_prolog(*$@)
3588{
3589	my ($handle, $file_heading, @columns) = @_;
3590	my $num_columns = 0;
3591	my $file_width;
3592	my $col;
3593	my $width;
3594
3595	$width = 20 if (scalar(@columns) == 1);
3596	$width = 10 if (scalar(@columns) == 2);
3597	$width = 8 if (scalar(@columns) > 2);
3598
3599	foreach $col (@columns) {
3600		my ($heading, $cols) = @{$col};
3601
3602		$num_columns += $cols;
3603	}
3604	$file_width = 100 - $num_columns * $width;
3605
3606	# Table definition
3607	write_html($handle, <<END_OF_HTML);
3608	  <center>
3609	  <table width="80%" cellpadding=1 cellspacing=1 border=0>
3610
3611	    <tr>
3612	      <td width="$file_width%"><br></td>
3613END_OF_HTML
3614	# Empty first row
3615	foreach $col (@columns) {
3616		my ($heading, $cols) = @{$col};
3617
3618		while ($cols-- > 0) {
3619			write_html($handle, <<END_OF_HTML);
3620	      <td width="$width%"></td>
3621END_OF_HTML
3622		}
3623	}
3624	# Next row
3625	write_html($handle, <<END_OF_HTML);
3626	    </tr>
3627
3628	    <tr>
3629	      <td class="tableHead">$file_heading</td>
3630END_OF_HTML
3631	# Heading row
3632	foreach $col (@columns) {
3633		my ($heading, $cols) = @{$col};
3634		my $colspan = "";
3635
3636		$colspan = " colspan=$cols" if ($cols > 1);
3637		write_html($handle, <<END_OF_HTML);
3638	      <td class="tableHead"$colspan>$heading</td>
3639END_OF_HTML
3640	}
3641	write_html($handle, <<END_OF_HTML);
3642	    </tr>
3643END_OF_HTML
3644}
3645
3646
3647# write_file_table_entry(handle, base_dir, filename, page_link,
3648#			 ([ found, hit, med_limit, hi_limit, graph ], ..)
3649#
3650# Write an entry of the file table.
3651#
3652
3653sub write_file_table_entry(*$$$@)
3654{
3655	my ($handle, $base_dir, $filename, $page_link, @entries) = @_;
3656	my $file_code;
3657	my $entry;
3658	my $esc_filename = escape_html($filename);
3659
3660	# Add link to source if provided
3661	if (defined($page_link) && $page_link ne "") {
3662		$file_code = "<a href=\"$page_link\">$esc_filename</a>";
3663	} else {
3664		$file_code = $esc_filename;
3665	}
3666
3667	# First column: filename
3668	write_html($handle, <<END_OF_HTML);
3669	    <tr>
3670	      <td class="coverFile">$file_code</td>
3671END_OF_HTML
3672	# Columns as defined
3673	foreach $entry (@entries) {
3674		my ($found, $hit, $med, $hi, $graph) = @{$entry};
3675		my $bar_graph;
3676		my $class;
3677		my $rate;
3678
3679		# Generate bar graph if requested
3680		if ($graph) {
3681			$bar_graph = get_bar_graph_code($base_dir, $found,
3682							$hit);
3683			write_html($handle, <<END_OF_HTML);
3684	      <td class="coverBar" align="center">
3685	        $bar_graph
3686	      </td>
3687END_OF_HTML
3688		}
3689		# Get rate color and text
3690		if ($found == 0) {
3691			$rate = "-";
3692			$class = "Hi";
3693		} else {
3694			$rate = rate($hit, $found, "&nbsp;%");
3695			$class = $rate_name[classify_rate($found, $hit,
3696					    $med, $hi)];
3697		}
3698		write_html($handle, <<END_OF_HTML);
3699	      <td class="coverPer$class">$rate</td>
3700	      <td class="coverNum$class">$hit / $found</td>
3701END_OF_HTML
3702	}
3703	# End of row
3704        write_html($handle, <<END_OF_HTML);
3705	    </tr>
3706END_OF_HTML
3707}
3708
3709
3710#
3711# write_file_table_detail_entry(filehandle, test_name, ([found, hit], ...))
3712#
3713# Write entry for detail section in file table.
3714#
3715
3716sub write_file_table_detail_entry(*$@)
3717{
3718	my ($handle, $test, @entries) = @_;
3719	my $entry;
3720
3721	if ($test eq "") {
3722		$test = "<span style=\"font-style:italic\">&lt;unnamed&gt;</span>";
3723	} elsif ($test =~ /^(.*),diff$/) {
3724		$test = $1." (converted)";
3725	}
3726	# Testname
3727	write_html($handle, <<END_OF_HTML);
3728	    <tr>
3729	      <td class="testName" colspan=2>$test</td>
3730END_OF_HTML
3731	# Test data
3732	foreach $entry (@entries) {
3733		my ($found, $hit) = @{$entry};
3734		my $rate = rate($hit, $found, "&nbsp;%");
3735
3736		write_html($handle, <<END_OF_HTML);
3737	      <td class="testPer">$rate</td>
3738	      <td class="testNum">$hit&nbsp;/&nbsp;$found</td>
3739END_OF_HTML
3740	}
3741
3742        write_html($handle, <<END_OF_HTML);
3743	    </tr>
3744
3745END_OF_HTML
3746
3747	# *************************************************************
3748}
3749
3750
3751#
3752# write_file_table_epilog(filehandle)
3753#
3754# Write end of file table HTML code.
3755#
3756
3757sub write_file_table_epilog(*)
3758{
3759	# *************************************************************
3760
3761	write_html($_[0], <<END_OF_HTML)
3762	  </table>
3763	  </center>
3764	  <br>
3765
3766END_OF_HTML
3767	;
3768
3769	# *************************************************************
3770}
3771
3772
3773#
3774# write_test_table_prolog(filehandle, table_heading)
3775#
3776# Write heading for test case description table.
3777#
3778
3779sub write_test_table_prolog(*$)
3780{
3781	# *************************************************************
3782
3783	write_html($_[0], <<END_OF_HTML)
3784	  <center>
3785	  <table width="80%" cellpadding=2 cellspacing=1 border=0>
3786
3787	    <tr>
3788	      <td><br></td>
3789	    </tr>
3790
3791	    <tr>
3792	      <td class="tableHead">$_[1]</td>
3793	    </tr>
3794
3795	    <tr>
3796	      <td class="testDescription">
3797	        <dl>
3798END_OF_HTML
3799	;
3800
3801	# *************************************************************
3802}
3803
3804
3805#
3806# write_test_table_entry(filehandle, test_name, test_description)
3807#
3808# Write entry for the test table.
3809#
3810
3811sub write_test_table_entry(*$$)
3812{
3813	# *************************************************************
3814
3815	write_html($_[0], <<END_OF_HTML)
3816          <dt>$_[1]<a name="$_[1]">&nbsp;</a></dt>
3817          <dd>$_[2]<br><br></dd>
3818END_OF_HTML
3819	;
3820
3821	# *************************************************************
3822}
3823
3824
3825#
3826# write_test_table_epilog(filehandle)
3827#
3828# Write end of test description table HTML code.
3829#
3830
3831sub write_test_table_epilog(*)
3832{
3833	# *************************************************************
3834
3835	write_html($_[0], <<END_OF_HTML)
3836	        </dl>
3837	      </td>
3838	    </tr>
3839	  </table>
3840	  </center>
3841	  <br>
3842
3843END_OF_HTML
3844	;
3845
3846	# *************************************************************
3847}
3848
3849
3850sub fmt_centered($$)
3851{
3852	my ($width, $text) = @_;
3853	my $w0 = length($text);
3854	my $w1 = $width > $w0 ? int(($width - $w0) / 2) : 0;
3855	my $w2 = $width > $w0 ? $width - $w0 - $w1 : 0;
3856
3857	return (" "x$w1).$text.(" "x$w2);
3858}
3859
3860
3861#
3862# write_source_prolog(filehandle)
3863#
3864# Write start of source code table.
3865#
3866
3867sub write_source_prolog(*)
3868{
3869	my $lineno_heading = "         ";
3870	my $branch_heading = "";
3871	my $line_heading = fmt_centered($line_field_width, "Line data");
3872	my $source_heading = " Source code";
3873
3874	if ($br_coverage) {
3875		$branch_heading = fmt_centered($br_field_width, "Branch data").
3876				  " ";
3877	}
3878	# *************************************************************
3879
3880	write_html($_[0], <<END_OF_HTML)
3881	  <table cellpadding=0 cellspacing=0 border=0>
3882	    <tr>
3883	      <td><br></td>
3884	    </tr>
3885	    <tr>
3886	      <td>
3887<pre class="sourceHeading">${lineno_heading}${branch_heading}${line_heading} ${source_heading}</pre>
3888<pre class="source">
3889END_OF_HTML
3890	;
3891
3892	# *************************************************************
3893}
3894
3895
3896#
3897# get_branch_blocks(brdata)
3898#
3899# Group branches that belong to the same basic block.
3900#
3901# Returns: [block1, block2, ...]
3902# block:   [branch1, branch2, ...]
3903# branch:  [block_num, branch_num, taken_count, text_length, open, close]
3904#
3905
3906sub get_branch_blocks($)
3907{
3908	my ($brdata) = @_;
3909	my $last_block_num;
3910	my $block = [];
3911	my @blocks;
3912	my $i;
3913	my $num = br_ivec_len($brdata);
3914
3915	# Group branches
3916	for ($i = 0; $i < $num; $i++) {
3917		my ($block_num, $branch, $taken) = br_ivec_get($brdata, $i);
3918		my $br;
3919
3920		if (defined($last_block_num) && $block_num != $last_block_num) {
3921			push(@blocks, $block);
3922			$block = [];
3923		}
3924		$br = [$block_num, $branch, $taken, 3, 0, 0];
3925		push(@{$block}, $br);
3926		$last_block_num = $block_num;
3927	}
3928	push(@blocks, $block) if (scalar(@{$block}) > 0);
3929
3930	# Add braces to first and last branch in group
3931	foreach $block (@blocks) {
3932		$block->[0]->[$BR_OPEN] = 1;
3933		$block->[0]->[$BR_LEN]++;
3934		$block->[scalar(@{$block}) - 1]->[$BR_CLOSE] = 1;
3935		$block->[scalar(@{$block}) - 1]->[$BR_LEN]++;
3936	}
3937
3938	return @blocks;
3939}
3940
3941#
3942# get_block_len(block)
3943#
3944# Calculate total text length of all branches in a block of branches.
3945#
3946
3947sub get_block_len($)
3948{
3949	my ($block) = @_;
3950	my $len = 0;
3951	my $branch;
3952
3953	foreach $branch (@{$block}) {
3954		$len += $branch->[$BR_LEN];
3955	}
3956
3957	return $len;
3958}
3959
3960
3961#
3962# get_branch_html(brdata)
3963#
3964# Return a list of HTML lines which represent the specified branch coverage
3965# data in source code view.
3966#
3967
3968sub get_branch_html($)
3969{
3970	my ($brdata) = @_;
3971	my @blocks = get_branch_blocks($brdata);
3972	my $block;
3973	my $branch;
3974	my $line_len = 0;
3975	my $line = [];	# [branch2|" ", branch|" ", ...]
3976	my @lines;	# [line1, line2, ...]
3977	my @result;
3978
3979	# Distribute blocks to lines
3980	foreach $block (@blocks) {
3981		my $block_len = get_block_len($block);
3982
3983		# Does this block fit into the current line?
3984		if ($line_len + $block_len <= $br_field_width) {
3985			# Add it
3986			$line_len += $block_len;
3987			push(@{$line}, @{$block});
3988			next;
3989		} elsif ($block_len <= $br_field_width) {
3990			# It would fit if the line was empty - add it to new
3991			# line
3992			push(@lines, $line);
3993			$line_len = $block_len;
3994			$line = [ @{$block} ];
3995			next;
3996		}
3997		# Split the block into several lines
3998		foreach $branch (@{$block}) {
3999			if ($line_len + $branch->[$BR_LEN] >= $br_field_width) {
4000				# Start a new line
4001				if (($line_len + 1 <= $br_field_width) &&
4002				    scalar(@{$line}) > 0 &&
4003				    !$line->[scalar(@$line) - 1]->[$BR_CLOSE]) {
4004					# Try to align branch symbols to be in
4005					# one # row
4006					push(@{$line}, " ");
4007				}
4008				push(@lines, $line);
4009				$line_len = 0;
4010				$line = [];
4011			}
4012			push(@{$line}, $branch);
4013			$line_len += $branch->[$BR_LEN];
4014		}
4015	}
4016	push(@lines, $line);
4017
4018	# Convert to HTML
4019	foreach $line (@lines) {
4020		my $current = "";
4021		my $current_len = 0;
4022
4023		foreach $branch (@$line) {
4024			# Skip alignment space
4025			if ($branch eq " ") {
4026				$current .= " ";
4027				$current_len++;
4028				next;
4029			}
4030
4031			my ($block_num, $br_num, $taken, $len, $open, $close) =
4032			   @{$branch};
4033			my $class;
4034			my $title;
4035			my $text;
4036
4037			if ($taken eq '-') {
4038				$class	= "branchNoExec";
4039				$text	= " # ";
4040				$title	= "Branch $br_num was not executed";
4041			} elsif ($taken == 0) {
4042				$class	= "branchNoCov";
4043				$text	= " - ";
4044				$title	= "Branch $br_num was not taken";
4045			} else {
4046				$class	= "branchCov";
4047				$text	= " + ";
4048				$title	= "Branch $br_num was taken $taken ".
4049					  "time";
4050				$title .= "s" if ($taken > 1);
4051			}
4052			$current .= "[" if ($open);
4053			$current .= "<span class=\"$class\" title=\"$title\">";
4054			$current .= $text."</span>";
4055			$current .= "]" if ($close);
4056			$current_len += $len;
4057		}
4058
4059		# Right-align result text
4060		if ($current_len < $br_field_width) {
4061			$current = (" "x($br_field_width - $current_len)).
4062				   $current;
4063		}
4064		push(@result, $current);
4065	}
4066
4067	return @result;
4068}
4069
4070
4071#
4072# format_count(count, width)
4073#
4074# Return a right-aligned representation of count that fits in width characters.
4075#
4076
4077sub format_count($$)
4078{
4079	my ($count, $width) = @_;
4080	my $result;
4081	my $exp;
4082
4083	$result = sprintf("%*.0f", $width, $count);
4084	while (length($result) > $width) {
4085		last if ($count < 10);
4086		$exp++;
4087		$count = int($count/10);
4088		$result = sprintf("%*s", $width, ">$count*10^$exp");
4089	}
4090	return $result;
4091}
4092
4093#
4094# write_source_line(filehandle, line_num, source, hit_count, converted,
4095#                   brdata, add_anchor)
4096#
4097# Write formatted source code line. Return a line in a format as needed
4098# by gen_png()
4099#
4100
4101sub write_source_line(*$$$$$$)
4102{
4103	my ($handle, $line, $source, $count, $converted, $brdata,
4104	    $add_anchor) = @_;
4105	my $source_format;
4106	my $count_format;
4107	my $result;
4108	my $anchor_start = "";
4109	my $anchor_end = "";
4110	my $count_field_width = $line_field_width - 1;
4111	my @br_html;
4112	my $html;
4113
4114	# Get branch HTML data for this line
4115	@br_html = get_branch_html($brdata) if ($br_coverage);
4116
4117	if (!defined($count)) {
4118		$result		= "";
4119		$source_format	= "";
4120		$count_format	= " "x$count_field_width;
4121	}
4122	elsif ($count == 0) {
4123		$result		= $count;
4124		$source_format	= '<span class="lineNoCov">';
4125		$count_format	= format_count($count, $count_field_width);
4126	}
4127	elsif ($converted && defined($highlight)) {
4128		$result		= "*".$count;
4129		$source_format	= '<span class="lineDiffCov">';
4130		$count_format	= format_count($count, $count_field_width);
4131	}
4132	else {
4133		$result		= $count;
4134		$source_format	= '<span class="lineCov">';
4135		$count_format	= format_count($count, $count_field_width);
4136	}
4137	$result .= ":".$source;
4138
4139	# Write out a line number navigation anchor every $nav_resolution
4140	# lines if necessary
4141	if ($add_anchor)
4142	{
4143		$anchor_start	= "<a name=\"$_[1]\">";
4144		$anchor_end	= "</a>";
4145	}
4146
4147
4148	# *************************************************************
4149
4150	$html = $anchor_start;
4151	$html .= "<span class=\"lineNum\">".sprintf("%8d", $line)." </span>";
4152	$html .= shift(@br_html).":" if ($br_coverage);
4153	$html .= "$source_format$count_format : ";
4154	$html .= escape_html($source);
4155	$html .= "</span>" if ($source_format);
4156	$html .= $anchor_end."\n";
4157
4158	write_html($handle, $html);
4159
4160	if ($br_coverage) {
4161		# Add lines for overlong branch information
4162		foreach (@br_html) {
4163			write_html($handle, "<span class=\"lineNum\">".
4164				   "         </span>$_\n");
4165		}
4166	}
4167	# *************************************************************
4168
4169	return($result);
4170}
4171
4172
4173#
4174# write_source_epilog(filehandle)
4175#
4176# Write end of source code table.
4177#
4178
4179sub write_source_epilog(*)
4180{
4181	# *************************************************************
4182
4183	write_html($_[0], <<END_OF_HTML)
4184	</pre>
4185	      </td>
4186	    </tr>
4187	  </table>
4188	  <br>
4189
4190END_OF_HTML
4191	;
4192
4193	# *************************************************************
4194}
4195
4196
4197#
4198# write_html_epilog(filehandle, base_dir[, break_frames])
4199#
4200# Write HTML page footer to FILEHANDLE. BREAK_FRAMES should be set when
4201# this page is embedded in a frameset, clicking the URL link will then
4202# break this frameset.
4203#
4204
4205sub write_html_epilog(*$;$)
4206{
4207	my $basedir = $_[1];
4208	my $break_code = "";
4209	my $epilog;
4210
4211	if (defined($_[2]))
4212	{
4213		$break_code = " target=\"_parent\"";
4214	}
4215
4216	# *************************************************************
4217
4218	write_html($_[0], <<END_OF_HTML)
4219	  <table width="100%" border=0 cellspacing=0 cellpadding=0>
4220	    <tr><td class="ruler"><img src="$_[1]glass.png" width=3 height=3 alt=""></td></tr>
4221	    <tr><td class="versionInfo">Generated by: <a href="$lcov_url"$break_code>$lcov_version</a></td></tr>
4222	  </table>
4223	  <br>
4224END_OF_HTML
4225	;
4226
4227	$epilog = $html_epilog;
4228	$epilog =~ s/\@basedir\@/$basedir/g;
4229
4230	write_html($_[0], $epilog);
4231}
4232
4233
4234#
4235# write_frameset(filehandle, basedir, basename, pagetitle)
4236#
4237#
4238
4239sub write_frameset(*$$$)
4240{
4241	my $frame_width = $overview_width + 40;
4242
4243	# *************************************************************
4244
4245	write_html($_[0], <<END_OF_HTML)
4246	<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Frameset//EN">
4247
4248	<html lang="en">
4249
4250	<head>
4251	  <meta http-equiv="Content-Type" content="text/html; charset=$charset">
4252	  <title>$_[3]</title>
4253	  <link rel="stylesheet" type="text/css" href="$_[1]gcov.css">
4254	</head>
4255
4256	<frameset cols="$frame_width,*">
4257	  <frame src="$_[2].gcov.overview.$html_ext" name="overview">
4258	  <frame src="$_[2].gcov.$html_ext" name="source">
4259	  <noframes>
4260	    <center>Frames not supported by your browser!<br></center>
4261	  </noframes>
4262	</frameset>
4263
4264	</html>
4265END_OF_HTML
4266	;
4267
4268	# *************************************************************
4269}
4270
4271
4272#
4273# sub write_overview_line(filehandle, basename, line, link)
4274#
4275#
4276
4277sub write_overview_line(*$$$)
4278{
4279	my $y1 = $_[2] - 1;
4280	my $y2 = $y1 + $nav_resolution - 1;
4281	my $x2 = $overview_width - 1;
4282
4283	# *************************************************************
4284
4285	write_html($_[0], <<END_OF_HTML)
4286	    <area shape="rect" coords="0,$y1,$x2,$y2" href="$_[1].gcov.$html_ext#$_[3]" target="source" alt="overview">
4287END_OF_HTML
4288	;
4289
4290	# *************************************************************
4291}
4292
4293
4294#
4295# write_overview(filehandle, basedir, basename, pagetitle, lines)
4296#
4297#
4298
4299sub write_overview(*$$$$)
4300{
4301	my $index;
4302	my $max_line = $_[4] - 1;
4303	my $offset;
4304
4305	# *************************************************************
4306
4307	write_html($_[0], <<END_OF_HTML)
4308	<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">
4309
4310	<html lang="en">
4311
4312	<head>
4313	  <title>$_[3]</title>
4314	  <meta http-equiv="Content-Type" content="text/html; charset=$charset">
4315	  <link rel="stylesheet" type="text/css" href="$_[1]gcov.css">
4316	</head>
4317
4318	<body>
4319	  <map name="overview">
4320END_OF_HTML
4321	;
4322
4323	# *************************************************************
4324
4325	# Make $offset the next higher multiple of $nav_resolution
4326	$offset = ($nav_offset + $nav_resolution - 1) / $nav_resolution;
4327	$offset = sprintf("%d", $offset ) * $nav_resolution;
4328
4329	# Create image map for overview image
4330	for ($index = 1; $index <= $_[4]; $index += $nav_resolution)
4331	{
4332		# Enforce nav_offset
4333		if ($index < $offset + 1)
4334		{
4335			write_overview_line($_[0], $_[2], $index, 1);
4336		}
4337		else
4338		{
4339			write_overview_line($_[0], $_[2], $index, $index - $offset);
4340		}
4341	}
4342
4343	# *************************************************************
4344
4345	write_html($_[0], <<END_OF_HTML)
4346	  </map>
4347
4348	  <center>
4349	  <a href="$_[2].gcov.$html_ext#top" target="source">Top</a><br><br>
4350	  <img src="$_[2].gcov.png" width=$overview_width height=$max_line alt="Overview" border=0 usemap="#overview">
4351	  </center>
4352	</body>
4353	</html>
4354END_OF_HTML
4355	;
4356
4357	# *************************************************************
4358}
4359
4360
4361sub max($$)
4362{
4363	my ($a, $b) = @_;
4364
4365	return $a if ($a > $b);
4366	return $b;
4367}
4368
4369
4370#
4371# write_header(filehandle, type, trunc_file_name, rel_file_name, lines_found,
4372# lines_hit, funcs_found, funcs_hit, sort_type)
4373#
4374# Write a complete standard page header. TYPE may be (0, 1, 2, 3, 4)
4375# corresponding to (directory view header, file view header, source view
4376# header, test case description header, function view header)
4377#
4378
4379sub write_header(*$$$$$$$$$$)
4380{
4381	local *HTML_HANDLE = $_[0];
4382	my $type = $_[1];
4383	my $trunc_name = $_[2];
4384	my $rel_filename = $_[3];
4385	my $lines_found = $_[4];
4386	my $lines_hit = $_[5];
4387	my $fn_found = $_[6];
4388	my $fn_hit = $_[7];
4389	my $br_found = $_[8];
4390	my $br_hit = $_[9];
4391	my $sort_type = $_[10];
4392	my $base_dir;
4393	my $view;
4394	my $test;
4395	my $base_name;
4396	my $style;
4397	my $rate;
4398	my @row_left;
4399	my @row_right;
4400	my $num_rows;
4401	my $i;
4402	my $esc_trunc_name = escape_html($trunc_name);
4403
4404	$base_name = basename($rel_filename);
4405
4406	# Prepare text for "current view" field
4407	if ($type == $HDR_DIR)
4408	{
4409		# Main overview
4410		$base_dir = "";
4411		$view = $overview_title;
4412	}
4413	elsif ($type == $HDR_FILE)
4414	{
4415		# Directory overview
4416		$base_dir = get_relative_base_path($rel_filename);
4417		$view = "<a href=\"$base_dir"."index.$html_ext\">".
4418			"$overview_title</a> - $esc_trunc_name";
4419	}
4420	elsif ($type == $HDR_SOURCE || $type == $HDR_FUNC)
4421	{
4422		# File view
4423		my $dir_name = dirname($rel_filename);
4424		my $esc_base_name = escape_html($base_name);
4425		my $esc_dir_name = escape_html($dir_name);
4426
4427		$base_dir = get_relative_base_path($dir_name);
4428		if ($frames)
4429		{
4430			# Need to break frameset when clicking any of these
4431			# links
4432			$view = "<a href=\"$base_dir"."index.$html_ext\" ".
4433				"target=\"_parent\">$overview_title</a> - ".
4434				"<a href=\"index.$html_ext\" target=\"_parent\">".
4435				"$esc_dir_name</a> - $esc_base_name";
4436		}
4437		else
4438		{
4439			$view = "<a href=\"$base_dir"."index.$html_ext\">".
4440				"$overview_title</a> - ".
4441				"<a href=\"index.$html_ext\">".
4442				"$esc_dir_name</a> - $esc_base_name";
4443		}
4444
4445		# Add function suffix
4446		if ($func_coverage) {
4447			$view .= "<span style=\"font-size: 80%;\">";
4448			if ($type == $HDR_SOURCE) {
4449				if ($sort) {
4450					$view .= " (source / <a href=\"$base_name.func-sort-c.$html_ext\">functions</a>)";
4451				} else {
4452					$view .= " (source / <a href=\"$base_name.func.$html_ext\">functions</a>)";
4453				}
4454			} elsif ($type == $HDR_FUNC) {
4455				$view .= " (<a href=\"$base_name.gcov.$html_ext\">source</a> / functions)";
4456			}
4457			$view .= "</span>";
4458		}
4459	}
4460	elsif ($type == $HDR_TESTDESC)
4461	{
4462		# Test description header
4463		$base_dir = "";
4464		$view = "<a href=\"$base_dir"."index.$html_ext\">".
4465			"$overview_title</a> - test case descriptions";
4466	}
4467
4468	# Prepare text for "test" field
4469	$test = escape_html($test_title);
4470
4471	# Append link to test description page if available
4472	if (%test_description && ($type != $HDR_TESTDESC))
4473	{
4474		if ($frames && ($type == $HDR_SOURCE || $type == $HDR_FUNC))
4475		{
4476			# Need to break frameset when clicking this link
4477			$test .= " ( <span style=\"font-size:80%;\">".
4478				 "<a href=\"$base_dir".
4479				 "descriptions.$html_ext\" target=\"_parent\">".
4480				 "view descriptions</a></span> )";
4481		}
4482		else
4483		{
4484			$test .= " ( <span style=\"font-size:80%;\">".
4485				 "<a href=\"$base_dir".
4486				 "descriptions.$html_ext\">".
4487				 "view descriptions</a></span> )";
4488		}
4489	}
4490
4491	# Write header
4492	write_header_prolog(*HTML_HANDLE, $base_dir);
4493
4494	# Left row
4495	push(@row_left, [[ "10%", "headerItem", "Current view:" ],
4496			 [ "35%", "headerValue", $view ]]);
4497	push(@row_left, [[undef, "headerItem", "Test:"],
4498			 [undef, "headerValue", $test]]);
4499	push(@row_left, [[undef, "headerItem", "Date:"],
4500			 [undef, "headerValue", $date]]);
4501
4502	# Right row
4503	if ($legend && ($type == $HDR_SOURCE || $type == $HDR_FUNC)) {
4504		my $text = <<END_OF_HTML;
4505            Lines:
4506            <span class="coverLegendCov">hit</span>
4507            <span class="coverLegendNoCov">not hit</span>
4508END_OF_HTML
4509		if ($br_coverage) {
4510			$text .= <<END_OF_HTML;
4511            | Branches:
4512            <span class="coverLegendCov">+</span> taken
4513            <span class="coverLegendNoCov">-</span> not taken
4514            <span class="coverLegendNoCov">#</span> not executed
4515END_OF_HTML
4516		}
4517		push(@row_left, [[undef, "headerItem", "Legend:"],
4518				 [undef, "headerValueLeg", $text]]);
4519	} elsif ($legend && ($type != $HDR_TESTDESC)) {
4520		my $text = <<END_OF_HTML;
4521	    Rating:
4522            <span class="coverLegendCovLo" title="Coverage rates below $med_limit % are classified as low">low: &lt; $med_limit %</span>
4523            <span class="coverLegendCovMed" title="Coverage rates between $med_limit % and $hi_limit % are classified as medium">medium: &gt;= $med_limit %</span>
4524            <span class="coverLegendCovHi" title="Coverage rates of $hi_limit % and more are classified as high">high: &gt;= $hi_limit %</span>
4525END_OF_HTML
4526		push(@row_left, [[undef, "headerItem", "Legend:"],
4527				 [undef, "headerValueLeg", $text]]);
4528	}
4529	if ($type == $HDR_TESTDESC) {
4530		push(@row_right, [[ "55%" ]]);
4531	} else {
4532		push(@row_right, [["15%", undef, undef ],
4533				  ["10%", "headerCovTableHead", "Hit" ],
4534				  ["10%", "headerCovTableHead", "Total" ],
4535				  ["15%", "headerCovTableHead", "Coverage"]]);
4536	}
4537	# Line coverage
4538	$style = $rate_name[classify_rate($lines_found, $lines_hit,
4539					  $med_limit, $hi_limit)];
4540	$rate = rate($lines_hit, $lines_found, " %");
4541	push(@row_right, [[undef, "headerItem", "Lines:"],
4542			  [undef, "headerCovTableEntry", $lines_hit],
4543			  [undef, "headerCovTableEntry", $lines_found],
4544			  [undef, "headerCovTableEntry$style", $rate]])
4545			if ($type != $HDR_TESTDESC);
4546	# Function coverage
4547	if ($func_coverage) {
4548		$style = $rate_name[classify_rate($fn_found, $fn_hit,
4549						  $fn_med_limit, $fn_hi_limit)];
4550		$rate = rate($fn_hit, $fn_found, " %");
4551		push(@row_right, [[undef, "headerItem", "Functions:"],
4552				  [undef, "headerCovTableEntry", $fn_hit],
4553				  [undef, "headerCovTableEntry", $fn_found],
4554				  [undef, "headerCovTableEntry$style", $rate]])
4555			if ($type != $HDR_TESTDESC);
4556	}
4557	# Branch coverage
4558	if ($br_coverage) {
4559		$style = $rate_name[classify_rate($br_found, $br_hit,
4560						  $br_med_limit, $br_hi_limit)];
4561		$rate = rate($br_hit, $br_found, " %");
4562		push(@row_right, [[undef, "headerItem", "Branches:"],
4563				  [undef, "headerCovTableEntry", $br_hit],
4564				  [undef, "headerCovTableEntry", $br_found],
4565				  [undef, "headerCovTableEntry$style", $rate]])
4566			if ($type != $HDR_TESTDESC);
4567	}
4568
4569	# Print rows
4570	$num_rows = max(scalar(@row_left), scalar(@row_right));
4571	for ($i = 0; $i < $num_rows; $i++) {
4572		my $left = $row_left[$i];
4573		my $right = $row_right[$i];
4574
4575		if (!defined($left)) {
4576			$left = [[undef, undef, undef], [undef, undef, undef]];
4577		}
4578		if (!defined($right)) {
4579			$right = [];
4580		}
4581		write_header_line(*HTML_HANDLE, @{$left},
4582				  [ $i == 0 ? "5%" : undef, undef, undef],
4583				  @{$right});
4584	}
4585
4586	# Fourth line
4587	write_header_epilog(*HTML_HANDLE, $base_dir);
4588}
4589
4590
4591#
4592# get_sorted_keys(hash_ref, sort_type)
4593#
4594
4595sub get_sorted_keys($$)
4596{
4597	my ($hash, $type) = @_;
4598
4599	if ($type == $SORT_FILE) {
4600		# Sort by name
4601		return sort(keys(%{$hash}));
4602	} elsif ($type == $SORT_LINE) {
4603		# Sort by line coverage
4604		return sort({$hash->{$a}[7] <=> $hash->{$b}[7]} keys(%{$hash}));
4605	} elsif ($type == $SORT_FUNC) {
4606		# Sort by function coverage;
4607		return sort({$hash->{$a}[8] <=> $hash->{$b}[8]}	keys(%{$hash}));
4608	} elsif ($type == $SORT_BRANCH) {
4609		# Sort by br coverage;
4610		return sort({$hash->{$a}[9] <=> $hash->{$b}[9]}	keys(%{$hash}));
4611	}
4612}
4613
4614sub get_sort_code($$$)
4615{
4616	my ($link, $alt, $base) = @_;
4617	my $png;
4618	my $link_start;
4619	my $link_end;
4620
4621	if (!defined($link)) {
4622		$png = "glass.png";
4623		$link_start = "";
4624		$link_end = "";
4625	} else {
4626		$png = "updown.png";
4627		$link_start = '<a href="'.$link.'">';
4628		$link_end = "</a>";
4629	}
4630
4631	return ' <span class="tableHeadSort">'.$link_start.
4632	       '<img src="'.$base.$png.'" width=10 height=14 '.
4633	       'alt="'.$alt.'" title="'.$alt.'" border=0>'.$link_end.'</span>';
4634}
4635
4636sub get_file_code($$$$)
4637{
4638	my ($type, $text, $sort_button, $base) = @_;
4639	my $result = $text;
4640	my $link;
4641
4642	if ($sort_button) {
4643		if ($type == $HEAD_NO_DETAIL) {
4644			$link = "index.$html_ext";
4645		} else {
4646			$link = "index-detail.$html_ext";
4647		}
4648	}
4649	$result .= get_sort_code($link, "Sort by name", $base);
4650
4651	return $result;
4652}
4653
4654sub get_line_code($$$$$)
4655{
4656	my ($type, $sort_type, $text, $sort_button, $base) = @_;
4657	my $result = $text;
4658	my $sort_link;
4659
4660	if ($type == $HEAD_NO_DETAIL) {
4661		# Just text
4662		if ($sort_button) {
4663			$sort_link = "index-sort-l.$html_ext";
4664		}
4665	} elsif ($type == $HEAD_DETAIL_HIDDEN) {
4666		# Text + link to detail view
4667		$result .= ' ( <a class="detail" href="index-detail'.
4668			   $fileview_sortname[$sort_type].'.'.$html_ext.
4669			   '">show details</a> )';
4670		if ($sort_button) {
4671			$sort_link = "index-sort-l.$html_ext";
4672		}
4673	} else {
4674		# Text + link to standard view
4675		$result .= ' ( <a class="detail" href="index'.
4676			   $fileview_sortname[$sort_type].'.'.$html_ext.
4677			   '">hide details</a> )';
4678		if ($sort_button) {
4679			$sort_link = "index-detail-sort-l.$html_ext";
4680		}
4681	}
4682	# Add sort button
4683	$result .= get_sort_code($sort_link, "Sort by line coverage", $base);
4684
4685	return $result;
4686}
4687
4688sub get_func_code($$$$)
4689{
4690	my ($type, $text, $sort_button, $base) = @_;
4691	my $result = $text;
4692	my $link;
4693
4694	if ($sort_button) {
4695		if ($type == $HEAD_NO_DETAIL) {
4696			$link = "index-sort-f.$html_ext";
4697		} else {
4698			$link = "index-detail-sort-f.$html_ext";
4699		}
4700	}
4701	$result .= get_sort_code($link, "Sort by function coverage", $base);
4702	return $result;
4703}
4704
4705sub get_br_code($$$$)
4706{
4707	my ($type, $text, $sort_button, $base) = @_;
4708	my $result = $text;
4709	my $link;
4710
4711	if ($sort_button) {
4712		if ($type == $HEAD_NO_DETAIL) {
4713			$link = "index-sort-b.$html_ext";
4714		} else {
4715			$link = "index-detail-sort-b.$html_ext";
4716		}
4717	}
4718	$result .= get_sort_code($link, "Sort by branch coverage", $base);
4719	return $result;
4720}
4721
4722#
4723# write_file_table(filehandle, base_dir, overview, testhash, testfnchash,
4724#                  testbrhash, fileview, sort_type)
4725#
4726# Write a complete file table. OVERVIEW is a reference to a hash containing
4727# the following mapping:
4728#
4729#   filename -> "lines_found,lines_hit,funcs_found,funcs_hit,page_link,
4730#		 func_link"
4731#
4732# TESTHASH is a reference to the following hash:
4733#
4734#   filename -> \%testdata
4735#   %testdata: name of test affecting this file -> \%testcount
4736#   %testcount: line number -> execution count for a single test
4737#
4738# Heading of first column is "Filename" if FILEVIEW is true, "Directory name"
4739# otherwise.
4740#
4741
4742sub write_file_table(*$$$$$$$)
4743{
4744	local *HTML_HANDLE = $_[0];
4745	my $base_dir = $_[1];
4746	my $overview = $_[2];
4747	my $testhash = $_[3];
4748	my $testfnchash = $_[4];
4749	my $testbrhash = $_[5];
4750	my $fileview = $_[6];
4751	my $sort_type = $_[7];
4752	my $filename;
4753	my $bar_graph;
4754	my $hit;
4755	my $found;
4756	my $fn_found;
4757	my $fn_hit;
4758	my $br_found;
4759	my $br_hit;
4760	my $page_link;
4761	my $testname;
4762	my $testdata;
4763	my $testfncdata;
4764	my $testbrdata;
4765	my %affecting_tests;
4766	my $line_code = "";
4767	my $func_code;
4768	my $br_code;
4769	my $file_code;
4770	my @head_columns;
4771
4772	# Determine HTML code for column headings
4773	if (($base_dir ne "") && $show_details)
4774	{
4775		my $detailed = keys(%{$testhash});
4776
4777		$file_code = get_file_code($detailed ? $HEAD_DETAIL_HIDDEN :
4778					$HEAD_NO_DETAIL,
4779					$fileview ? "Filename" : "Directory",
4780					$sort && $sort_type != $SORT_FILE,
4781					$base_dir);
4782		$line_code = get_line_code($detailed ? $HEAD_DETAIL_SHOWN :
4783					$HEAD_DETAIL_HIDDEN,
4784					$sort_type,
4785					"Line Coverage",
4786					$sort && $sort_type != $SORT_LINE,
4787					$base_dir);
4788		$func_code = get_func_code($detailed ? $HEAD_DETAIL_HIDDEN :
4789					$HEAD_NO_DETAIL,
4790					"Functions",
4791					$sort && $sort_type != $SORT_FUNC,
4792					$base_dir);
4793		$br_code = get_br_code($detailed ? $HEAD_DETAIL_HIDDEN :
4794					$HEAD_NO_DETAIL,
4795					"Branches",
4796					$sort && $sort_type != $SORT_BRANCH,
4797					$base_dir);
4798	} else {
4799		$file_code = get_file_code($HEAD_NO_DETAIL,
4800					$fileview ? "Filename" : "Directory",
4801					$sort && $sort_type != $SORT_FILE,
4802					$base_dir);
4803		$line_code = get_line_code($HEAD_NO_DETAIL, $sort_type, "Line Coverage",
4804					$sort && $sort_type != $SORT_LINE,
4805					$base_dir);
4806		$func_code = get_func_code($HEAD_NO_DETAIL, "Functions",
4807					$sort && $sort_type != $SORT_FUNC,
4808					$base_dir);
4809		$br_code = get_br_code($HEAD_NO_DETAIL, "Branches",
4810					$sort && $sort_type != $SORT_BRANCH,
4811					$base_dir);
4812	}
4813	push(@head_columns, [ $line_code, 3 ]);
4814	push(@head_columns, [ $func_code, 2]) if ($func_coverage);
4815	push(@head_columns, [ $br_code, 2]) if ($br_coverage);
4816
4817	write_file_table_prolog(*HTML_HANDLE, $file_code, @head_columns);
4818
4819	foreach $filename (get_sorted_keys($overview, $sort_type))
4820	{
4821		my @columns;
4822		($found, $hit, $fn_found, $fn_hit, $br_found, $br_hit,
4823		 $page_link) = @{$overview->{$filename}};
4824
4825		# Line coverage
4826		push(@columns, [$found, $hit, $med_limit, $hi_limit, 1]);
4827		# Function coverage
4828		if ($func_coverage) {
4829			push(@columns, [$fn_found, $fn_hit, $fn_med_limit,
4830					$fn_hi_limit, 0]);
4831		}
4832		# Branch coverage
4833		if ($br_coverage) {
4834			push(@columns, [$br_found, $br_hit, $br_med_limit,
4835					$br_hi_limit, 0]);
4836		}
4837		write_file_table_entry(*HTML_HANDLE, $base_dir, $filename,
4838				       $page_link, @columns);
4839
4840		$testdata = $testhash->{$filename};
4841		$testfncdata = $testfnchash->{$filename};
4842		$testbrdata = $testbrhash->{$filename};
4843
4844		# Check whether we should write test specific coverage
4845		# as well
4846		if (!($show_details && $testdata)) { next; }
4847
4848		# Filter out those tests that actually affect this file
4849		%affecting_tests = %{ get_affecting_tests($testdata,
4850					$testfncdata, $testbrdata) };
4851
4852		# Does any of the tests affect this file at all?
4853		if (!%affecting_tests) { next; }
4854
4855		foreach $testname (keys(%affecting_tests))
4856		{
4857			my @results;
4858			($found, $hit, $fn_found, $fn_hit, $br_found, $br_hit) =
4859				split(",", $affecting_tests{$testname});
4860
4861			# Insert link to description of available
4862			if ($test_description{$testname})
4863			{
4864				$testname = "<a href=\"$base_dir".
4865					    "descriptions.$html_ext#$testname\">".
4866					    "$testname</a>";
4867			}
4868
4869			push(@results, [$found, $hit]);
4870			push(@results, [$fn_found, $fn_hit]) if ($func_coverage);
4871			push(@results, [$br_found, $br_hit]) if ($br_coverage);
4872			write_file_table_detail_entry(*HTML_HANDLE, $testname,
4873				@results);
4874		}
4875	}
4876
4877	write_file_table_epilog(*HTML_HANDLE);
4878}
4879
4880
4881#
4882# get_found_and_hit(hash)
4883#
4884# Return the count for entries (found) and entries with an execution count
4885# greater than zero (hit) in a hash (linenumber -> execution count) as
4886# a list (found, hit)
4887#
4888
4889sub get_found_and_hit($)
4890{
4891	my %hash = %{$_[0]};
4892	my $found = 0;
4893	my $hit = 0;
4894
4895	# Calculate sum
4896	$found = 0;
4897	$hit = 0;
4898
4899	foreach (keys(%hash))
4900	{
4901		$found++;
4902		if ($hash{$_}>0) { $hit++; }
4903	}
4904
4905	return ($found, $hit);
4906}
4907
4908
4909#
4910# get_func_found_and_hit(sumfnccount)
4911#
4912# Return (f_found, f_hit) for sumfnccount
4913#
4914
4915sub get_func_found_and_hit($)
4916{
4917	my ($sumfnccount) = @_;
4918	my $function;
4919	my $fn_found;
4920	my $fn_hit;
4921
4922	$fn_found = scalar(keys(%{$sumfnccount}));
4923	$fn_hit = 0;
4924	foreach $function (keys(%{$sumfnccount})) {
4925		if ($sumfnccount->{$function} > 0) {
4926			$fn_hit++;
4927		}
4928	}
4929	return ($fn_found, $fn_hit);
4930}
4931
4932
4933#
4934# br_taken_to_num(taken)
4935#
4936# Convert a branch taken value .info format to number format.
4937#
4938
4939sub br_taken_to_num($)
4940{
4941	my ($taken) = @_;
4942
4943	return 0 if ($taken eq '-');
4944	return $taken + 1;
4945}
4946
4947
4948#
4949# br_num_to_taken(taken)
4950#
4951# Convert a branch taken value in number format to .info format.
4952#
4953
4954sub br_num_to_taken($)
4955{
4956	my ($taken) = @_;
4957
4958	return '-' if ($taken == 0);
4959	return $taken - 1;
4960}
4961
4962
4963#
4964# br_taken_add(taken1, taken2)
4965#
4966# Return the result of taken1 + taken2 for 'branch taken' values.
4967#
4968
4969sub br_taken_add($$)
4970{
4971	my ($t1, $t2) = @_;
4972
4973	return $t1 if (!defined($t2));
4974	return $t2 if (!defined($t1));
4975	return $t1 if ($t2 eq '-');
4976	return $t2 if ($t1 eq '-');
4977	return $t1 + $t2;
4978}
4979
4980
4981#
4982# br_taken_sub(taken1, taken2)
4983#
4984# Return the result of taken1 - taken2 for 'branch taken' values. Return 0
4985# if the result would become negative.
4986#
4987
4988sub br_taken_sub($$)
4989{
4990	my ($t1, $t2) = @_;
4991
4992	return $t1 if (!defined($t2));
4993	return undef if (!defined($t1));
4994	return $t1 if ($t1 eq '-');
4995	return $t1 if ($t2 eq '-');
4996	return 0 if $t2 > $t1;
4997	return $t1 - $t2;
4998}
4999
5000
5001#
5002# br_ivec_len(vector)
5003#
5004# Return the number of entries in the branch coverage vector.
5005#
5006
5007sub br_ivec_len($)
5008{
5009	my ($vec) = @_;
5010
5011	return 0 if (!defined($vec));
5012	return (length($vec) * 8 / $BR_VEC_WIDTH) / $BR_VEC_ENTRIES;
5013}
5014
5015
5016#
5017# br_ivec_get(vector, number)
5018#
5019# Return an entry from the branch coverage vector.
5020#
5021
5022sub br_ivec_get($$)
5023{
5024	my ($vec, $num) = @_;
5025	my $block;
5026	my $branch;
5027	my $taken;
5028	my $offset = $num * $BR_VEC_ENTRIES;
5029
5030	# Retrieve data from vector
5031	$block	= vec($vec, $offset + $BR_BLOCK, $BR_VEC_WIDTH);
5032	$block = -1 if ($block == $BR_VEC_MAX);
5033	$branch = vec($vec, $offset + $BR_BRANCH, $BR_VEC_WIDTH);
5034	$taken	= vec($vec, $offset + $BR_TAKEN, $BR_VEC_WIDTH);
5035
5036	# Decode taken value from an integer
5037	$taken = br_num_to_taken($taken);
5038
5039	return ($block, $branch, $taken);
5040}
5041
5042
5043#
5044# br_ivec_push(vector, block, branch, taken)
5045#
5046# Add an entry to the branch coverage vector. If an entry with the same
5047# branch ID already exists, add the corresponding taken values.
5048#
5049
5050sub br_ivec_push($$$$)
5051{
5052	my ($vec, $block, $branch, $taken) = @_;
5053	my $offset;
5054	my $num = br_ivec_len($vec);
5055	my $i;
5056
5057	$vec = "" if (!defined($vec));
5058	$block = $BR_VEC_MAX if $block < 0;
5059
5060	# Check if branch already exists in vector
5061	for ($i = 0; $i < $num; $i++) {
5062		my ($v_block, $v_branch, $v_taken) = br_ivec_get($vec, $i);
5063		$v_block = $BR_VEC_MAX if $v_block < 0;
5064
5065		next if ($v_block != $block || $v_branch != $branch);
5066
5067		# Add taken counts
5068		$taken = br_taken_add($taken, $v_taken);
5069		last;
5070	}
5071
5072	$offset = $i * $BR_VEC_ENTRIES;
5073	$taken = br_taken_to_num($taken);
5074
5075	# Add to vector
5076	vec($vec, $offset + $BR_BLOCK, $BR_VEC_WIDTH) = $block;
5077	vec($vec, $offset + $BR_BRANCH, $BR_VEC_WIDTH) = $branch;
5078	vec($vec, $offset + $BR_TAKEN, $BR_VEC_WIDTH) = $taken;
5079
5080	return $vec;
5081}
5082
5083
5084#
5085# get_br_found_and_hit(sumbrcount)
5086#
5087# Return (br_found, br_hit) for sumbrcount
5088#
5089
5090sub get_br_found_and_hit($)
5091{
5092	my ($sumbrcount) = @_;
5093	my $line;
5094	my $br_found = 0;
5095	my $br_hit = 0;
5096
5097	foreach $line (keys(%{$sumbrcount})) {
5098		my $brdata = $sumbrcount->{$line};
5099		my $i;
5100		my $num = br_ivec_len($brdata);
5101
5102		for ($i = 0; $i < $num; $i++) {
5103			my $taken;
5104
5105			(undef, undef, $taken) = br_ivec_get($brdata, $i);
5106
5107			$br_found++;
5108			$br_hit++ if ($taken ne "-" && $taken > 0);
5109		}
5110	}
5111
5112	return ($br_found, $br_hit);
5113}
5114
5115
5116#
5117# get_affecting_tests(testdata, testfncdata, testbrdata)
5118#
5119# HASHREF contains a mapping filename -> (linenumber -> exec count). Return
5120# a hash containing mapping filename -> "lines found, lines hit" for each
5121# filename which has a nonzero hit count.
5122#
5123
5124sub get_affecting_tests($$$)
5125{
5126	my ($testdata, $testfncdata, $testbrdata) = @_;
5127	my $testname;
5128	my $testcount;
5129	my $testfnccount;
5130	my $testbrcount;
5131	my %result;
5132	my $found;
5133	my $hit;
5134	my $fn_found;
5135	my $fn_hit;
5136	my $br_found;
5137	my $br_hit;
5138
5139	foreach $testname (keys(%{$testdata}))
5140	{
5141		# Get (line number -> count) hash for this test case
5142		$testcount = $testdata->{$testname};
5143		$testfnccount = $testfncdata->{$testname};
5144		$testbrcount = $testbrdata->{$testname};
5145
5146		# Calculate sum
5147		($found, $hit) = get_found_and_hit($testcount);
5148		($fn_found, $fn_hit) = get_func_found_and_hit($testfnccount);
5149		($br_found, $br_hit) = get_br_found_and_hit($testbrcount);
5150
5151		if ($hit>0)
5152		{
5153			$result{$testname} = "$found,$hit,$fn_found,$fn_hit,".
5154					     "$br_found,$br_hit";
5155		}
5156	}
5157
5158	return(\%result);
5159}
5160
5161
5162sub get_hash_reverse($)
5163{
5164	my ($hash) = @_;
5165	my %result;
5166
5167	foreach (keys(%{$hash})) {
5168		$result{$hash->{$_}} = $_;
5169	}
5170
5171	return \%result;
5172}
5173
5174#
5175# write_source(filehandle, source_filename, count_data, checksum_data,
5176#              converted_data, func_data, sumbrcount)
5177#
5178# Write an HTML view of a source code file. Returns a list containing
5179# data as needed by gen_png().
5180#
5181# Die on error.
5182#
5183
5184sub write_source($$$$$$$)
5185{
5186	local *HTML_HANDLE = $_[0];
5187	local *SOURCE_HANDLE;
5188	my $source_filename = $_[1];
5189	my %count_data;
5190	my $line_number;
5191	my @result;
5192	my $checkdata = $_[3];
5193	my $converted = $_[4];
5194	my $funcdata  = $_[5];
5195	my $sumbrcount = $_[6];
5196	my $datafunc = get_hash_reverse($funcdata);
5197	my $add_anchor;
5198	my @file;
5199
5200	if ($_[2])
5201	{
5202		%count_data = %{$_[2]};
5203	}
5204
5205	if (!open(SOURCE_HANDLE, "<", $source_filename)) {
5206		my @lines;
5207		my $last_line = 0;
5208
5209		if (!$ignore[$ERROR_SOURCE]) {
5210			die("ERROR: cannot read $source_filename\n");
5211		}
5212
5213		# Continue without source file
5214		warn("WARNING: cannot read $source_filename!\n");
5215
5216		@lines = sort( { $a <=> $b }  keys(%count_data));
5217		if (@lines) {
5218			$last_line = $lines[scalar(@lines) - 1];
5219		}
5220		return ( ":" ) if ($last_line < 1);
5221
5222		# Simulate gcov behavior
5223		for ($line_number = 1; $line_number <= $last_line;
5224		     $line_number++) {
5225			push(@file, "/* EOF */");
5226		}
5227	} else {
5228		@file = <SOURCE_HANDLE>;
5229	}
5230
5231	write_source_prolog(*HTML_HANDLE);
5232	$line_number = 0;
5233	foreach (@file) {
5234		$line_number++;
5235		chomp($_);
5236
5237		# Also remove CR from line-end
5238		s/\015$//;
5239
5240		# Source code matches coverage data?
5241		if (defined($checkdata->{$line_number}) &&
5242		    ($checkdata->{$line_number} ne md5_base64($_)))
5243		{
5244			die("ERROR: checksum mismatch  at $source_filename:".
5245			    "$line_number\n");
5246		}
5247
5248		$add_anchor = 0;
5249		if ($frames) {
5250			if (($line_number - 1) % $nav_resolution == 0) {
5251				$add_anchor = 1;
5252			}
5253		}
5254		if ($func_coverage) {
5255			if ($line_number == 1) {
5256				$add_anchor = 1;
5257			} elsif (defined($datafunc->{$line_number +
5258						     $func_offset})) {
5259				$add_anchor = 1;
5260			}
5261		}
5262		push (@result,
5263		      write_source_line(HTML_HANDLE, $line_number,
5264					$_, $count_data{$line_number},
5265					$converted->{$line_number},
5266					$sumbrcount->{$line_number}, $add_anchor));
5267	}
5268
5269	close(SOURCE_HANDLE);
5270	write_source_epilog(*HTML_HANDLE);
5271	return(@result);
5272}
5273
5274
5275sub funcview_get_func_code($$$)
5276{
5277	my ($name, $base, $type) = @_;
5278	my $result;
5279	my $link;
5280
5281	if ($sort && $type == 1) {
5282		$link = "$name.func.$html_ext";
5283	}
5284	$result = "Function Name";
5285	$result .= get_sort_code($link, "Sort by function name", $base);
5286
5287	return $result;
5288}
5289
5290sub funcview_get_count_code($$$)
5291{
5292	my ($name, $base, $type) = @_;
5293	my $result;
5294	my $link;
5295
5296	if ($sort && $type == 0) {
5297		$link = "$name.func-sort-c.$html_ext";
5298	}
5299	$result = "Hit count";
5300	$result .= get_sort_code($link, "Sort by hit count", $base);
5301
5302	return $result;
5303}
5304
5305#
5306# funcview_get_sorted(funcdata, sumfncdata, sort_type)
5307#
5308# Depending on the value of sort_type, return a list of functions sorted
5309# by name (type 0) or by the associated call count (type 1).
5310#
5311
5312sub funcview_get_sorted($$$)
5313{
5314	my ($funcdata, $sumfncdata, $type) = @_;
5315
5316	if ($type == 0) {
5317		return sort(keys(%{$funcdata}));
5318	}
5319	return sort({
5320		$sumfncdata->{$b} == $sumfncdata->{$a} ?
5321			$a cmp $b : $sumfncdata->{$a} <=> $sumfncdata->{$b}
5322		} keys(%{$sumfncdata}));
5323}
5324
5325sub demangle_list($)
5326{
5327	my ($list) = @_;
5328	my $tmpfile;
5329	my $handle;
5330	my %demangle;
5331	my $demangle_arg = "";
5332	my %versions;
5333
5334	# Write function names to file
5335	($handle, $tmpfile) = tempfile();
5336	die("ERROR: could not create temporary file") if (!defined($tmpfile));
5337	print($handle join("\n", @$list));
5338	close($handle);
5339
5340	# Extra flag necessary on OS X so that symbols listed by gcov get demangled
5341	# properly.
5342	if ($^O eq "darwin") {
5343		$demangle_arg = "--no-strip-underscores";
5344	}
5345
5346	# Build translation hash from c++filt output
5347	open($handle, "-|", "c++filt $demangle_arg < $tmpfile") or
5348		die("ERROR: could not run c++filt: $!\n");
5349	foreach my $func (@$list) {
5350		my $translated = <$handle>;
5351		my $version;
5352
5353		last if (!defined($translated));
5354		chomp($translated);
5355
5356		$version = ++$versions{$translated};
5357		$translated .= ".$version" if ($version > 1);
5358		$demangle{$func} = $translated;
5359	}
5360	close($handle);
5361
5362	if (scalar(keys(%demangle)) != scalar(@$list)) {
5363		die("ERROR: c++filt output not as expected (".
5364		    scalar(keys(%demangle))." vs ".scalar(@$list).") lines\n");
5365	}
5366
5367	unlink($tmpfile) or
5368		warn("WARNING: could not remove temporary file $tmpfile: $!\n");
5369
5370	return \%demangle;
5371}
5372
5373#
5374# write_function_table(filehandle, source_file, sumcount, funcdata,
5375#		       sumfnccount, testfncdata, sumbrcount, testbrdata,
5376#		       base_name, base_dir, sort_type)
5377#
5378# Write an HTML table listing all functions in a source file, including
5379# also function call counts and line coverages inside of each function.
5380#
5381# Die on error.
5382#
5383
5384sub write_function_table(*$$$$$$$$$$)
5385{
5386	local *HTML_HANDLE = $_[0];
5387	my $source = $_[1];
5388	my $sumcount = $_[2];
5389	my $funcdata = $_[3];
5390	my $sumfncdata = $_[4];
5391	my $testfncdata = $_[5];
5392	my $sumbrcount = $_[6];
5393	my $testbrdata = $_[7];
5394	my $name = $_[8];
5395	my $base = $_[9];
5396	my $type = $_[10];
5397	my $func;
5398	my $func_code;
5399	my $count_code;
5400	my $demangle;
5401
5402	# Get HTML code for headings
5403	$func_code = funcview_get_func_code($name, $base, $type);
5404	$count_code = funcview_get_count_code($name, $base, $type);
5405	write_html(*HTML_HANDLE, <<END_OF_HTML)
5406	  <center>
5407	  <table width="60%" cellpadding=1 cellspacing=1 border=0>
5408	    <tr><td><br></td></tr>
5409	    <tr>
5410	      <td width="80%" class="tableHead">$func_code</td>
5411	      <td width="20%" class="tableHead">$count_code</td>
5412	    </tr>
5413END_OF_HTML
5414	;
5415
5416	# Get demangle translation hash
5417	if ($demangle_cpp) {
5418		$demangle = demangle_list([ sort(keys(%{$funcdata})) ]);
5419	}
5420
5421	# Get a sorted table
5422	foreach $func (funcview_get_sorted($funcdata, $sumfncdata, $type)) {
5423		if (!defined($funcdata->{$func}))
5424		{
5425			next;
5426		}
5427
5428		my $startline = $funcdata->{$func} - $func_offset;
5429		my $name = $func;
5430		my $count = $sumfncdata->{$name};
5431		my $countstyle;
5432
5433		# Replace function name with demangled version if available
5434		$name = $demangle->{$name} if (exists($demangle->{$name}));
5435
5436		# Escape special characters
5437		$name = escape_html($name);
5438		if ($startline < 1) {
5439			$startline = 1;
5440		}
5441		if ($count == 0) {
5442			$countstyle = "coverFnLo";
5443		} else {
5444			$countstyle = "coverFnHi";
5445		}
5446
5447		write_html(*HTML_HANDLE, <<END_OF_HTML)
5448	    <tr>
5449              <td class="coverFn"><a href="$source#$startline">$name</a></td>
5450              <td class="$countstyle">$count</td>
5451            </tr>
5452END_OF_HTML
5453                ;
5454	}
5455	write_html(*HTML_HANDLE, <<END_OF_HTML)
5456	  </table>
5457	  <br>
5458	  </center>
5459END_OF_HTML
5460	;
5461}
5462
5463
5464#
5465# info(printf_parameter)
5466#
5467# Use printf to write PRINTF_PARAMETER to stdout only when the $quiet flag
5468# is not set.
5469#
5470
5471sub info(@)
5472{
5473	if (!$quiet)
5474	{
5475		# Print info string
5476		printf(@_);
5477	}
5478}
5479
5480
5481#
5482# subtract_counts(data_ref, base_ref)
5483#
5484
5485sub subtract_counts($$)
5486{
5487	my %data = %{$_[0]};
5488	my %base = %{$_[1]};
5489	my $line;
5490	my $data_count;
5491	my $base_count;
5492	my $hit = 0;
5493	my $found = 0;
5494
5495	foreach $line (keys(%data))
5496	{
5497		$found++;
5498		$data_count = $data{$line};
5499		$base_count = $base{$line};
5500
5501		if (defined($base_count))
5502		{
5503			$data_count -= $base_count;
5504
5505			# Make sure we don't get negative numbers
5506			if ($data_count<0) { $data_count = 0; }
5507		}
5508
5509		$data{$line} = $data_count;
5510		if ($data_count > 0) { $hit++; }
5511	}
5512
5513	return (\%data, $found, $hit);
5514}
5515
5516
5517#
5518# subtract_fnccounts(data, base)
5519#
5520# Subtract function call counts found in base from those in data.
5521# Return (data, f_found, f_hit).
5522#
5523
5524sub subtract_fnccounts($$)
5525{
5526	my %data;
5527	my %base;
5528	my $func;
5529	my $data_count;
5530	my $base_count;
5531	my $fn_hit = 0;
5532	my $fn_found = 0;
5533
5534	%data = %{$_[0]} if (defined($_[0]));
5535	%base = %{$_[1]} if (defined($_[1]));
5536	foreach $func (keys(%data)) {
5537		$fn_found++;
5538		$data_count = $data{$func};
5539		$base_count = $base{$func};
5540
5541		if (defined($base_count)) {
5542			$data_count -= $base_count;
5543
5544			# Make sure we don't get negative numbers
5545			if ($data_count < 0) {
5546				$data_count = 0;
5547			}
5548		}
5549
5550		$data{$func} = $data_count;
5551		if ($data_count > 0) {
5552			$fn_hit++;
5553		}
5554	}
5555
5556	return (\%data, $fn_found, $fn_hit);
5557}
5558
5559
5560#
5561# apply_baseline(data_ref, baseline_ref)
5562#
5563# Subtract the execution counts found in the baseline hash referenced by
5564# BASELINE_REF from actual data in DATA_REF.
5565#
5566
5567sub apply_baseline($$)
5568{
5569	my %data_hash = %{$_[0]};
5570	my %base_hash = %{$_[1]};
5571	my $filename;
5572	my $testname;
5573	my $data;
5574	my $data_testdata;
5575	my $data_funcdata;
5576	my $data_checkdata;
5577	my $data_testfncdata;
5578	my $data_testbrdata;
5579	my $data_count;
5580	my $data_testfnccount;
5581	my $data_testbrcount;
5582	my $base;
5583	my $base_checkdata;
5584	my $base_sumfnccount;
5585	my $base_sumbrcount;
5586	my $base_count;
5587	my $sumcount;
5588	my $sumfnccount;
5589	my $sumbrcount;
5590	my $found;
5591	my $hit;
5592	my $fn_found;
5593	my $fn_hit;
5594	my $br_found;
5595	my $br_hit;
5596
5597	foreach $filename (keys(%data_hash))
5598	{
5599		# Get data set for data and baseline
5600		$data = $data_hash{$filename};
5601		$base = $base_hash{$filename};
5602
5603		# Skip data entries for which no base entry exists
5604		if (!defined($base))
5605		{
5606			next;
5607		}
5608
5609		# Get set entries for data and baseline
5610		($data_testdata, undef, $data_funcdata, $data_checkdata,
5611		 $data_testfncdata, undef, $data_testbrdata) =
5612			get_info_entry($data);
5613		(undef, $base_count, undef, $base_checkdata, undef,
5614		 $base_sumfnccount, undef, $base_sumbrcount) =
5615			get_info_entry($base);
5616
5617		# Check for compatible checksums
5618		merge_checksums($data_checkdata, $base_checkdata, $filename);
5619
5620		# sumcount has to be calculated anew
5621		$sumcount = {};
5622		$sumfnccount = {};
5623		$sumbrcount = {};
5624
5625		# For each test case, subtract test specific counts
5626		foreach $testname (keys(%{$data_testdata}))
5627		{
5628			# Get counts of both data and baseline
5629			$data_count = $data_testdata->{$testname};
5630			$data_testfnccount = $data_testfncdata->{$testname};
5631			$data_testbrcount = $data_testbrdata->{$testname};
5632
5633			($data_count, undef, $hit) =
5634				subtract_counts($data_count, $base_count);
5635			($data_testfnccount) =
5636				subtract_fnccounts($data_testfnccount,
5637						   $base_sumfnccount);
5638			($data_testbrcount) =
5639				combine_brcount($data_testbrcount,
5640						 $base_sumbrcount, $BR_SUB);
5641
5642
5643			# Check whether this test case did hit any line at all
5644			if ($hit > 0)
5645			{
5646				# Write back resulting hash
5647				$data_testdata->{$testname} = $data_count;
5648				$data_testfncdata->{$testname} =
5649					$data_testfnccount;
5650				$data_testbrdata->{$testname} =
5651					$data_testbrcount;
5652			}
5653			else
5654			{
5655				# Delete test case which did not impact this
5656				# file
5657				delete($data_testdata->{$testname});
5658				delete($data_testfncdata->{$testname});
5659				delete($data_testbrdata->{$testname});
5660			}
5661
5662			# Add counts to sum of counts
5663			($sumcount, $found, $hit) =
5664				add_counts($sumcount, $data_count);
5665			($sumfnccount, $fn_found, $fn_hit) =
5666				add_fnccount($sumfnccount, $data_testfnccount);
5667			($sumbrcount, $br_found, $br_hit) =
5668				combine_brcount($sumbrcount, $data_testbrcount,
5669						$BR_ADD);
5670		}
5671
5672		# Write back resulting entry
5673		set_info_entry($data, $data_testdata, $sumcount, $data_funcdata,
5674			       $data_checkdata, $data_testfncdata, $sumfnccount,
5675			       $data_testbrdata, $sumbrcount, $found, $hit,
5676			       $fn_found, $fn_hit, $br_found, $br_hit);
5677
5678		$data_hash{$filename} = $data;
5679	}
5680
5681	return (\%data_hash);
5682}
5683
5684
5685#
5686# remove_unused_descriptions()
5687#
5688# Removes all test descriptions from the global hash %test_description which
5689# are not present in %info_data.
5690#
5691
5692sub remove_unused_descriptions()
5693{
5694	my $filename;		# The current filename
5695	my %test_list;		# Hash containing found test names
5696	my $test_data;		# Reference to hash test_name -> count_data
5697	my $before;		# Initial number of descriptions
5698	my $after;		# Remaining number of descriptions
5699
5700	$before = scalar(keys(%test_description));
5701
5702	foreach $filename (keys(%info_data))
5703	{
5704		($test_data) = get_info_entry($info_data{$filename});
5705		foreach (keys(%{$test_data}))
5706		{
5707			$test_list{$_} = "";
5708		}
5709	}
5710
5711	# Remove descriptions for tests which are not in our list
5712	foreach (keys(%test_description))
5713	{
5714		if (!defined($test_list{$_}))
5715		{
5716			delete($test_description{$_});
5717		}
5718	}
5719
5720	$after = scalar(keys(%test_description));
5721	if ($after < $before)
5722	{
5723		info("Removed ".($before - $after).
5724		     " unused descriptions, $after remaining.\n");
5725	}
5726}
5727
5728
5729#
5730# apply_prefix(filename, PREFIXES)
5731#
5732# If FILENAME begins with PREFIX from PREFIXES, remove PREFIX from FILENAME
5733# and return resulting string, otherwise return FILENAME.
5734#
5735
5736sub apply_prefix($@)
5737{
5738	my $filename = shift;
5739	my @dir_prefix = @_;
5740
5741	if (@dir_prefix)
5742	{
5743		foreach my $prefix (@dir_prefix)
5744		{
5745			if ($prefix ne "" && $filename =~ /^\Q$prefix\E\/(.*)$/)
5746			{
5747				return substr($filename, length($prefix) + 1);
5748			}
5749		}
5750	}
5751
5752	return $filename;
5753}
5754
5755
5756#
5757# system_no_output(mode, parameters)
5758#
5759# Call an external program using PARAMETERS while suppressing depending on
5760# the value of MODE:
5761#
5762#   MODE & 1: suppress STDOUT
5763#   MODE & 2: suppress STDERR
5764#
5765# Return 0 on success, non-zero otherwise.
5766#
5767
5768sub system_no_output($@)
5769{
5770	my $mode = shift;
5771	my $result;
5772	local *OLD_STDERR;
5773	local *OLD_STDOUT;
5774
5775	# Save old stdout and stderr handles
5776	($mode & 1) && open(OLD_STDOUT, ">>&", "STDOUT");
5777	($mode & 2) && open(OLD_STDERR, ">>&", "STDERR");
5778
5779	# Redirect to /dev/null
5780	($mode & 1) && open(STDOUT, ">", "/dev/null");
5781	($mode & 2) && open(STDERR, ">", "/dev/null");
5782
5783	system(@_);
5784	$result = $?;
5785
5786	# Close redirected handles
5787	($mode & 1) && close(STDOUT);
5788	($mode & 2) && close(STDERR);
5789
5790	# Restore old handles
5791	($mode & 1) && open(STDOUT, ">>&", "OLD_STDOUT");
5792	($mode & 2) && open(STDERR, ">>&", "OLD_STDERR");
5793
5794	return $result;
5795}
5796
5797
5798#
5799# read_config(filename)
5800#
5801# Read configuration file FILENAME and return a reference to a hash containing
5802# all valid key=value pairs found.
5803#
5804
5805sub read_config($)
5806{
5807	my $filename = $_[0];
5808	my %result;
5809	my $key;
5810	my $value;
5811	local *HANDLE;
5812
5813	if (!open(HANDLE, "<", $filename))
5814	{
5815		warn("WARNING: cannot read configuration file $filename\n");
5816		return undef;
5817	}
5818	while (<HANDLE>)
5819	{
5820		chomp;
5821		# Skip comments
5822		s/#.*//;
5823		# Remove leading blanks
5824		s/^\s+//;
5825		# Remove trailing blanks
5826		s/\s+$//;
5827		next unless length;
5828		($key, $value) = split(/\s*=\s*/, $_, 2);
5829		if (defined($key) && defined($value))
5830		{
5831			$result{$key} = $value;
5832		}
5833		else
5834		{
5835			warn("WARNING: malformed statement in line $. ".
5836			     "of configuration file $filename\n");
5837		}
5838	}
5839	close(HANDLE);
5840	return \%result;
5841}
5842
5843
5844#
5845# apply_config(REF)
5846#
5847# REF is a reference to a hash containing the following mapping:
5848#
5849#   key_string => var_ref
5850#
5851# where KEY_STRING is a keyword and VAR_REF is a reference to an associated
5852# variable. If the global configuration hashes CONFIG or OPT_RC contain a value
5853# for keyword KEY_STRING, VAR_REF will be assigned the value for that keyword.
5854#
5855
5856sub apply_config($)
5857{
5858	my $ref = $_[0];
5859
5860	foreach (keys(%{$ref}))
5861	{
5862		if (defined($opt_rc{$_})) {
5863			${$ref->{$_}} = $opt_rc{$_};
5864		} elsif (defined($config->{$_})) {
5865			${$ref->{$_}} = $config->{$_};
5866		}
5867	}
5868}
5869
5870
5871#
5872# get_html_prolog(FILENAME)
5873#
5874# If FILENAME is defined, return contents of file. Otherwise return default
5875# HTML prolog. Die on error.
5876#
5877
5878sub get_html_prolog($)
5879{
5880	my $filename = $_[0];
5881	my $result = "";
5882
5883	if (defined($filename))
5884	{
5885		local *HANDLE;
5886
5887		open(HANDLE, "<", $filename)
5888			or die("ERROR: cannot open html prolog $filename!\n");
5889		while (<HANDLE>)
5890		{
5891			$result .= $_;
5892		}
5893		close(HANDLE);
5894	}
5895	else
5896	{
5897		$result = <<END_OF_HTML
5898<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">
5899
5900<html lang="en">
5901
5902<head>
5903  <meta http-equiv="Content-Type" content="text/html; charset=$charset">
5904  <title>\@pagetitle\@</title>
5905  <link rel="stylesheet" type="text/css" href="\@basedir\@gcov.css">
5906</head>
5907
5908<body>
5909
5910END_OF_HTML
5911		;
5912	}
5913
5914	return $result;
5915}
5916
5917
5918#
5919# get_html_epilog(FILENAME)
5920#
5921# If FILENAME is defined, return contents of file. Otherwise return default
5922# HTML epilog. Die on error.
5923#
5924sub get_html_epilog($)
5925{
5926	my $filename = $_[0];
5927	my $result = "";
5928
5929	if (defined($filename))
5930	{
5931		local *HANDLE;
5932
5933		open(HANDLE, "<", $filename)
5934			or die("ERROR: cannot open html epilog $filename!\n");
5935		while (<HANDLE>)
5936		{
5937			$result .= $_;
5938		}
5939		close(HANDLE);
5940	}
5941	else
5942	{
5943		$result = <<END_OF_HTML
5944
5945</body>
5946</html>
5947END_OF_HTML
5948		;
5949	}
5950
5951	return $result;
5952
5953}
5954
5955sub warn_handler($)
5956{
5957	my ($msg) = @_;
5958
5959	warn("$tool_name: $msg");
5960}
5961
5962sub die_handler($)
5963{
5964	my ($msg) = @_;
5965
5966	die("$tool_name: $msg");
5967}
5968
5969#
5970# parse_ignore_errors(@ignore_errors)
5971#
5972# Parse user input about which errors to ignore.
5973#
5974
5975sub parse_ignore_errors(@)
5976{
5977	my (@ignore_errors) = @_;
5978	my @items;
5979	my $item;
5980
5981	return if (!@ignore_errors);
5982
5983	foreach $item (@ignore_errors) {
5984		$item =~ s/\s//g;
5985		if ($item =~ /,/) {
5986			# Split and add comma-separated parameters
5987			push(@items, split(/,/, $item));
5988		} else {
5989			# Add single parameter
5990			push(@items, $item);
5991		}
5992	}
5993	foreach $item (@items) {
5994		my $item_id = $ERROR_ID{lc($item)};
5995
5996		if (!defined($item_id)) {
5997			die("ERROR: unknown argument for --ignore-errors: ".
5998			    "$item\n");
5999		}
6000		$ignore[$item_id] = 1;
6001	}
6002}
6003
6004#
6005# parse_dir_prefix(@dir_prefix)
6006#
6007# Parse user input about the prefix list
6008#
6009
6010sub parse_dir_prefix(@)
6011{
6012	my (@opt_dir_prefix) = @_;
6013	my $item;
6014
6015	return if (!@opt_dir_prefix);
6016
6017	foreach $item (@opt_dir_prefix) {
6018		if ($item =~ /,/) {
6019			# Split and add comma-separated parameters
6020			push(@dir_prefix, split(/,/, $item));
6021		} else {
6022			# Add single parameter
6023			push(@dir_prefix, $item);
6024		}
6025	}
6026}
6027
6028#
6029# rate(hit, found[, suffix, precision, width])
6030#
6031# Return the coverage rate [0..100] for HIT and FOUND values. 0 is only
6032# returned when HIT is 0. 100 is only returned when HIT equals FOUND.
6033# PRECISION specifies the precision of the result. SUFFIX defines a
6034# string that is appended to the result if FOUND is non-zero. Spaces
6035# are added to the start of the resulting string until it is at least WIDTH
6036# characters wide.
6037#
6038
6039sub rate($$;$$$)
6040{
6041        my ($hit, $found, $suffix, $precision, $width) = @_;
6042        my $rate;
6043
6044	# Assign defaults if necessary
6045	$precision	= $default_precision if (!defined($precision));
6046	$suffix		= ""	if (!defined($suffix));
6047	$width		= 0	if (!defined($width));
6048
6049        return sprintf("%*s", $width, "-") if (!defined($found) || $found == 0);
6050        $rate = sprintf("%.*f", $precision, $hit * 100 / $found);
6051
6052	# Adjust rates if necessary
6053        if ($rate == 0 && $hit > 0) {
6054		$rate = sprintf("%.*f", $precision, 1 / 10 ** $precision);
6055        } elsif ($rate == 100 && $hit != $found) {
6056		$rate = sprintf("%.*f", $precision, 100 - 1 / 10 ** $precision);
6057	}
6058
6059	return sprintf("%*s", $width, $rate.$suffix);
6060}
6061