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