Index: third_party/lcov/bin/genhtml |
=================================================================== |
--- third_party/lcov/bin/genhtml (revision 0) |
+++ third_party/lcov/bin/genhtml (revision 0) |
@@ -0,0 +1,4819 @@ |
+#!/usr/bin/perl -w |
+# |
+# Copyright (c) International Business Machines Corp., 2002 |
+# |
+# This program is free software; you can redistribute it and/or modify |
+# it under the terms of the GNU General Public License as published by |
+# the Free Software Foundation; either version 2 of the License, or (at |
+# your option) any later version. |
+# |
+# This program is distributed in the hope that it will be useful, but |
+# WITHOUT ANY WARRANTY; without even the implied warranty of |
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU |
+# General Public License for more details. |
+# |
+# You should have received a copy of the GNU General Public License |
+# along with this program; if not, write to the Free Software |
+# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA |
+# |
+# |
+# genhtml |
+# |
+# This script generates HTML output from .info files as created by the |
+# geninfo script. Call it with --help and refer to the genhtml man page |
+# to get information on usage and available options. |
+# |
+# |
+# History: |
+# 2002-08-23 created by Peter Oberparleiter <Peter.Oberparleiter@de.ibm.com> |
+# IBM Lab Boeblingen |
+# based on code by Manoj Iyer <manjo@mail.utexas.edu> and |
+# Megan Bock <mbock@us.ibm.com> |
+# IBM Austin |
+# 2002-08-27 / Peter Oberparleiter: implemented frame view |
+# 2002-08-29 / Peter Oberparleiter: implemented test description filtering |
+# so that by default only descriptions for test cases which |
+# actually hit some source lines are kept |
+# 2002-09-05 / Peter Oberparleiter: implemented --no-sourceview |
+# 2002-09-05 / Mike Kobler: One of my source file paths includes a "+" in |
+# the directory name. I found that genhtml.pl died when it |
+# encountered it. I was able to fix the problem by modifying |
+# the string with the escape character before parsing it. |
+# 2002-10-26 / Peter Oberparleiter: implemented --num-spaces |
+# 2003-04-07 / Peter Oberparleiter: fixed bug which resulted in an error |
+# when trying to combine .info files containing data without |
+# a test name |
+# 2003-04-10 / Peter Oberparleiter: extended fix by Mike to also cover |
+# other special characters |
+# 2003-04-30 / Peter Oberparleiter: made info write to STDERR, not STDOUT |
+# 2003-07-10 / Peter Oberparleiter: added line checksum support |
+# 2004-08-09 / Peter Oberparleiter: added configuration file support |
+# 2005-03-04 / Cal Pierog: added legend to HTML output, fixed coloring of |
+# "good coverage" background |
+# 2006-03-18 / Marcus Boerger: added --custom-intro, --custom-outro and |
+# overwrite --no-prefix if --prefix is present |
+# 2006-03-20 / Peter Oberparleiter: changes to custom_* function (rename |
+# to html_prolog/_epilog, minor modifications to implementation), |
+# changed prefix/noprefix handling to be consistent with current |
+# logic |
+# 2006-03-20 / Peter Oberparleiter: added --html-extension option |
+# 2008-07-14 / Tom Zoerner: added --function-coverage command line option; |
+# added function table to source file page |
+# 2008-08-13 / Peter Oberparleiter: modified function coverage |
+# implementation (now enabled per default), |
+# introduced sorting option (enabled per default) |
+# |
+ |
+use strict; |
+use File::Basename; |
+use Getopt::Long; |
+use Digest::MD5 qw(md5_base64); |
+ |
+ |
+# Global constants |
+our $title = "LCOV - code coverage report"; |
+our $lcov_version = "LCOV version 1.7"; |
+our $lcov_url = "http://ltp.sourceforge.net/coverage/lcov.php"; |
+our $tool_name = basename($0); |
+ |
+# Specify coverage rate limits (in %) for classifying file entries |
+# HI: $hi_limit <= rate <= 100 graph color: green |
+# MED: $med_limit <= rate < $hi_limit graph color: orange |
+# LO: 0 <= rate < $med_limit graph color: red |
+ |
+# For line coverage |
+our $hi_limit = 50; |
+our $med_limit = 15; |
+ |
+# For function coverage |
+our $fn_hi_limit = 90; |
+our $fn_med_limit = 75; |
+ |
+# Width of overview image |
+our $overview_width = 80; |
+ |
+# Resolution of overview navigation: this number specifies the maximum |
+# difference in lines between the position a user selected from the overview |
+# and the position the source code window is scrolled to. |
+our $nav_resolution = 4; |
+ |
+# Clicking a line in the overview image should show the source code view at |
+# a position a bit further up so that the requested line is not the first |
+# line in the window. This number specifies that offset in lines. |
+our $nav_offset = 10; |
+ |
+# Clicking on a function name should show the source code at a position a |
+# few lines before the first line of code of that function. This number |
+# specifies that offset in lines. |
+our $func_offset = 2; |
+ |
+our $overview_title = "directory"; |
+ |
+# Data related prototypes |
+sub print_usage(*); |
+sub gen_html(); |
+sub html_create($$); |
+sub process_dir($); |
+sub process_file($$$); |
+sub info(@); |
+sub read_info_file($); |
+sub get_info_entry($); |
+sub set_info_entry($$$$$$$;$$$$); |
+sub get_prefix(@); |
+sub shorten_prefix($); |
+sub get_dir_list(@); |
+sub get_relative_base_path($); |
+sub read_testfile($); |
+sub get_date_string(); |
+sub split_filename($); |
+sub create_sub_dir($); |
+sub subtract_counts($$); |
+sub add_counts($$); |
+sub apply_baseline($$); |
+sub remove_unused_descriptions(); |
+sub get_found_and_hit($); |
+sub get_affecting_tests($$); |
+sub combine_info_files($$); |
+sub merge_checksums($$$); |
+sub combine_info_entries($$$); |
+sub apply_prefix($$); |
+sub system_no_output($@); |
+sub read_config($); |
+sub apply_config($); |
+sub get_html_prolog($); |
+sub get_html_epilog($); |
+ |
+ |
+# HTML related prototypes |
+sub escape_html($); |
+sub get_bar_graph_code($$$); |
+ |
+sub write_png_files(); |
+sub write_htaccess_file(); |
+sub write_css_file(); |
+sub write_description_file($$$$$); |
+sub write_function_rable(*$$$); |
+ |
+sub write_html(*$); |
+sub write_html_prolog(*$$); |
+sub write_html_epilog(*$;$); |
+ |
+sub write_header(*$$$$$$$$); |
+sub write_header_prolog(*$); |
+sub write_header_line(*$@); |
+sub write_header_epilog(*$); |
+ |
+sub write_file_table(*$$$$$$); |
+sub write_file_table_prolog(*$$$); |
+sub write_file_table_entry(*$$$$$$$); |
+sub write_file_table_detail_heading(*$$$); |
+sub write_file_table_detail_entry(*$$$$$); |
+sub write_file_table_epilog(*); |
+ |
+sub write_test_table_prolog(*$); |
+sub write_test_table_entry(*$$); |
+sub write_test_table_epilog(*); |
+ |
+sub write_source($$$$$$); |
+sub write_source_prolog(*); |
+sub write_source_line(*$$$$$); |
+sub write_source_epilog(*); |
+ |
+sub write_frameset(*$$$); |
+sub write_overview_line(*$$$); |
+sub write_overview(*$$$$); |
+ |
+# External prototype (defined in genpng) |
+sub gen_png($$$@); |
+ |
+ |
+# Global variables & initialization |
+our %info_data; # Hash containing all data from .info file |
+our $dir_prefix; # Prefix to remove from all sub directories |
+our %test_description; # Hash containing test descriptions if available |
+our $date = get_date_string(); |
+ |
+our @info_filenames; # List of .info files to use as data source |
+our $test_title; # Title for output as written to each page header |
+our $output_directory; # Name of directory in which to store output |
+our $base_filename; # Optional name of file containing baseline data |
+our $desc_filename; # Name of file containing test descriptions |
+our $css_filename; # Optional name of external stylesheet file to use |
+our $quiet; # If set, suppress information messages |
+our $help; # Help option flag |
+our $version; # Version option flag |
+our $show_details; # If set, generate detailed directory view |
+our $no_prefix; # If set, do not remove filename prefix |
+our $func_coverage = 1; # If set, generate function coverage statistics |
+our $no_func_coverage; # Disable func_coverage |
+our $sort = 1; # If set, provide directory listings with sorted entries |
+our $no_sort; # Disable sort |
+our $frames; # If set, use frames for source code view |
+our $keep_descriptions; # If set, do not remove unused test case descriptions |
+our $no_sourceview; # If set, do not create a source code view for each file |
+our $highlight; # If set, highlight lines covered by converted data only |
+our $legend; # If set, include legend in output |
+our $tab_size = 8; # Number of spaces to use in place of tab |
+our $config; # Configuration file contents |
+our $html_prolog_file; # Custom HTML prolog file (up to and including <body>) |
+our $html_epilog_file; # Custom HTML epilog file (from </body> onwards) |
+our $html_prolog; # Actual HTML prolog |
+our $html_epilog; # Actual HTML epilog |
+our $html_ext = "html"; # Extension for generated HTML files |
+our $html_gzip = 0; # Compress with gzip |
+our @fileview_sortlist; |
+our @fileview_sortname = ("", "-sort-l", "-sort-f"); |
+our @funcview_sortlist; |
+our @rate_name = ("Lo", "Med", "Hi"); |
+our @rate_png = ("ruby.png", "amber.png", "emerald.png"); |
+ |
+our $cwd = `pwd`; # Current working directory |
+chomp($cwd); |
+our $tool_dir = dirname($0); # Directory where genhtml tool is installed |
+ |
+ |
+# |
+# Code entry point |
+# |
+ |
+$SIG{__WARN__} = \&warn_handler; |
+$SIG{__DIE__} = \&die_handler; |
+ |
+# Add current working directory if $tool_dir is not already an absolute path |
+if (! ($tool_dir =~ /^\/(.*)$/)) |
+{ |
+ $tool_dir = "$cwd/$tool_dir"; |
+} |
+ |
+# Read configuration file if available |
+if (-r $ENV{"HOME"}."/.lcovrc") |
+{ |
+ $config = read_config($ENV{"HOME"}."/.lcovrc"); |
+} |
+elsif (-r "/etc/lcovrc") |
+{ |
+ $config = read_config("/etc/lcovrc"); |
+} |
+ |
+if ($config) |
+{ |
+ # Copy configuration file values to variables |
+ apply_config({ |
+ "genhtml_css_file" => \$css_filename, |
+ "genhtml_hi_limit" => \$hi_limit, |
+ "genhtml_med_limit" => \$med_limit, |
+ "genhtml_overview_width" => \$overview_width, |
+ "genhtml_nav_resolution" => \$nav_resolution, |
+ "genhtml_nav_offset" => \$nav_offset, |
+ "genhtml_keep_descriptions" => \$keep_descriptions, |
+ "genhtml_no_prefix" => \$no_prefix, |
+ "genhtml_no_source" => \$no_sourceview, |
+ "genhtml_num_spaces" => \$tab_size, |
+ "genhtml_highlight" => \$highlight, |
+ "genhtml_legend" => \$legend, |
+ "genhtml_html_prolog" => \$html_prolog_file, |
+ "genhtml_html_epilog" => \$html_epilog_file, |
+ "genhtml_html_extension" => \$html_ext, |
+ "genhtml_html_gzip" => \$html_gzip, |
+ "genhtml_function_hi_limit" => \$fn_hi_limit, |
+ "genhtml_function_med_limit" => \$fn_med_limit, |
+ "genhtml_function_coverage" => \$func_coverage, |
+ "genhtml_sort" => \$sort, |
+ }); |
+} |
+ |
+# Parse command line options |
+if (!GetOptions("output-directory=s" => \$output_directory, |
+ "title=s" => \$test_title, |
+ "description-file=s" => \$desc_filename, |
+ "keep-descriptions" => \$keep_descriptions, |
+ "css-file=s" => \$css_filename, |
+ "baseline-file=s" => \$base_filename, |
+ "prefix=s" => \$dir_prefix, |
+ "num-spaces=i" => \$tab_size, |
+ "no-prefix" => \$no_prefix, |
+ "no-sourceview" => \$no_sourceview, |
+ "show-details" => \$show_details, |
+ "frames" => \$frames, |
+ "highlight" => \$highlight, |
+ "legend" => \$legend, |
+ "quiet" => \$quiet, |
+ "help|h|?" => \$help, |
+ "version" => \$version, |
+ "html-prolog=s" => \$html_prolog_file, |
+ "html-epilog=s" => \$html_epilog_file, |
+ "html-extension=s" => \$html_ext, |
+ "html-gzip" => \$html_gzip, |
+ "function-coverage" => \$func_coverage, |
+ "no-function-coverage" => \$no_func_coverage, |
+ "sort" => \$sort, |
+ "no-sort" => \$no_sort, |
+ )) |
+{ |
+ print(STDERR "Use $tool_name --help to get usage information\n"); |
+ exit(1); |
+} else { |
+ # Merge options |
+ if ($no_func_coverage) { |
+ $func_coverage = 0; |
+ } |
+ |
+ # Merge sort options |
+ if ($no_sort) { |
+ $sort = 0; |
+ } |
+} |
+ |
+@info_filenames = @ARGV; |
+ |
+# Check for help option |
+if ($help) |
+{ |
+ print_usage(*STDOUT); |
+ exit(0); |
+} |
+ |
+# Check for version option |
+if ($version) |
+{ |
+ print("$tool_name: $lcov_version\n"); |
+ exit(0); |
+} |
+ |
+# Check for info filename |
+if (!@info_filenames) |
+{ |
+ die("No filename specified\n". |
+ "Use $tool_name --help to get usage information\n"); |
+} |
+ |
+# Generate a title if none is specified |
+if (!$test_title) |
+{ |
+ if (scalar(@info_filenames) == 1) |
+ { |
+ # Only one filename specified, use it as title |
+ $test_title = basename($info_filenames[0]); |
+ } |
+ else |
+ { |
+ # More than one filename specified, used default title |
+ $test_title = "unnamed"; |
+ } |
+} |
+ |
+# Make sure css_filename is an absolute path (in case we're changing |
+# directories) |
+if ($css_filename) |
+{ |
+ if (!($css_filename =~ /^\/(.*)$/)) |
+ { |
+ $css_filename = $cwd."/".$css_filename; |
+ } |
+} |
+ |
+# Make sure tab_size is within valid range |
+if ($tab_size < 1) |
+{ |
+ print(STDERR "ERROR: invalid number of spaces specified: ". |
+ "$tab_size!\n"); |
+ exit(1); |
+} |
+ |
+# Get HTML prolog and epilog |
+$html_prolog = get_html_prolog($html_prolog_file); |
+$html_epilog = get_html_epilog($html_epilog_file); |
+ |
+# Issue a warning if --no-sourceview is enabled together with --frames |
+if ($no_sourceview && defined($frames)) |
+{ |
+ warn("WARNING: option --frames disabled because --no-sourceview ". |
+ "was specified!\n"); |
+ $frames = undef; |
+} |
+ |
+# Issue a warning if --no-prefix is enabled together with --prefix |
+if ($no_prefix && defined($dir_prefix)) |
+{ |
+ warn("WARNING: option --prefix disabled because --no-prefix was ". |
+ "specified!\n"); |
+ $dir_prefix = undef; |
+} |
+ |
+if ($sort) { |
+ @funcview_sortlist = (0, 1); |
+ if ($func_coverage) { |
+ @fileview_sortlist = (0, 1, 2); |
+ } else { |
+ @fileview_sortlist = (0, 1); |
+ } |
+} else { |
+ @fileview_sortlist = (0); |
+ @funcview_sortlist = (0); |
+} |
+ |
+if ($frames) |
+{ |
+ # Include genpng code needed for overview image generation |
+ do("$tool_dir/genpng"); |
+} |
+ |
+# Make sure output_directory exists, create it if necessary |
+if ($output_directory) |
+{ |
+ stat($output_directory); |
+ |
+ if (! -e _) |
+ { |
+ system("mkdir", "-p", $output_directory) |
+ and die("ERROR: cannot create directory $_!\n"); |
+ } |
+} |
+ |
+# Do something |
+gen_html(); |
+ |
+exit(0); |
+ |
+ |
+ |
+# |
+# print_usage(handle) |
+# |
+# Print usage information. |
+# |
+ |
+sub print_usage(*) |
+{ |
+ local *HANDLE = $_[0]; |
+ |
+ print(HANDLE <<END_OF_USAGE); |
+Usage: $tool_name [OPTIONS] INFOFILE(S) |
+ |
+Create HTML output for coverage data found in INFOFILE. Note that INFOFILE |
+may also be a list of filenames. |
+ |
+Misc: |
+ -h, --help Print this help, then exit |
+ -v, --version Print version number, then exit |
+ -q, --quiet Do not print progress messages |
+ |
+Operation: |
+ -o, --output-directory OUTDIR Write HTML output to OUTDIR |
+ -s, --show-details Generate detailed directory view |
+ -d, --description-file DESCFILE Read test case descriptions from DESCFILE |
+ -k, --keep-descriptions Do not remove unused test descriptions |
+ -b, --baseline-file BASEFILE Use BASEFILE as baseline file |
+ -p, --prefix PREFIX Remove PREFIX from all directory names |
+ --no-prefix Do not remove prefix from directory names |
+ --(no-)function-coverage Enable (disable) function coverage display |
+ |
+HTML output: |
+ -f, --frames Use HTML frames for source code view |
+ -t, --title TITLE Display TITLE in header of all pages |
+ -c, --css-file CSSFILE Use external style sheet file CSSFILE |
+ --no-source Do not create source code view |
+ --num-spaces NUM Replace tabs with NUM spaces in source view |
+ --highlight Highlight lines with converted-only data |
+ --legend Include color legend in HTML output |
+ --html-prolog FILE Use FILE as HTML prolog for generated pages |
+ --html-epilog FILE Use FILE as HTML epilog for generated pages |
+ --html-extension EXT Use EXT as filename extension for pages |
+ --html-gzip Use gzip to compress HTML |
+ --(no-)sort Enable (disable) sorted coverage views |
+ |
+For more information see: $lcov_url |
+END_OF_USAGE |
+ ; |
+} |
+ |
+ |
+# |
+# get_rate(found, hit) |
+# |
+# Return a relative value for the specified found&hit values |
+# which is used for sorting the corresponding entries in a |
+# file list. |
+# |
+ |
+sub get_rate($$) |
+{ |
+ my ($found, $hit) = @_; |
+ |
+ if ($found == 0) { |
+ return 10000; |
+ } |
+ return int($hit * 1000 / $found) * 10 + 2 - (1 / $found); |
+} |
+ |
+ |
+# |
+# gen_html() |
+# |
+# Generate a set of HTML pages from contents of .info file INFO_FILENAME. |
+# Files will be written to the current directory. If provided, test case |
+# descriptions will be read from .tests file TEST_FILENAME and included |
+# in ouput. |
+# |
+# Die on error. |
+# |
+ |
+sub gen_html() |
+{ |
+ local *HTML_HANDLE; |
+ my %overview; |
+ my %base_data; |
+ my $lines_found; |
+ my $lines_hit; |
+ my $fn_found; |
+ my $fn_hit; |
+ my $overall_found = 0; |
+ my $overall_hit = 0; |
+ my $total_fn_found = 0; |
+ my $total_fn_hit = 0; |
+ my $dir_name; |
+ my $link_name; |
+ my @dir_list; |
+ my %new_info; |
+ |
+ # Read in all specified .info files |
+ foreach (@info_filenames) |
+ { |
+ %new_info = %{read_info_file($_)}; |
+ |
+ # Combine %new_info with %info_data |
+ %info_data = %{combine_info_files(\%info_data, \%new_info)}; |
+ } |
+ |
+ info("Found %d entries.\n", scalar(keys(%info_data))); |
+ |
+ # Read and apply baseline data if specified |
+ if ($base_filename) |
+ { |
+ # Read baseline file |
+ info("Reading baseline file $base_filename\n"); |
+ %base_data = %{read_info_file($base_filename)}; |
+ info("Found %d entries.\n", scalar(keys(%base_data))); |
+ |
+ # Apply baseline |
+ info("Subtracting baseline data.\n"); |
+ %info_data = %{apply_baseline(\%info_data, \%base_data)}; |
+ } |
+ |
+ @dir_list = get_dir_list(keys(%info_data)); |
+ |
+ if ($no_prefix) |
+ { |
+ # User requested that we leave filenames alone |
+ info("User asked not to remove filename prefix\n"); |
+ } |
+ elsif (!defined($dir_prefix)) |
+ { |
+ # Get prefix common to most directories in list |
+ $dir_prefix = get_prefix(@dir_list); |
+ |
+ if ($dir_prefix) |
+ { |
+ info("Found common filename prefix \"$dir_prefix\"\n"); |
+ } |
+ else |
+ { |
+ info("No common filename prefix found!\n"); |
+ $no_prefix=1; |
+ } |
+ } |
+ else |
+ { |
+ info("Using user-specified filename prefix \"". |
+ "$dir_prefix\"\n"); |
+ } |
+ |
+ # Read in test description file if specified |
+ if ($desc_filename) |
+ { |
+ info("Reading test description file $desc_filename\n"); |
+ %test_description = %{read_testfile($desc_filename)}; |
+ |
+ # Remove test descriptions which are not referenced |
+ # from %info_data if user didn't tell us otherwise |
+ if (!$keep_descriptions) |
+ { |
+ remove_unused_descriptions(); |
+ } |
+ } |
+ |
+ # Change to output directory if specified |
+ if ($output_directory) |
+ { |
+ chdir($output_directory) |
+ or die("ERROR: cannot change to directory ". |
+ "$output_directory!\n"); |
+ } |
+ |
+ info("Writing .css and .png files.\n"); |
+ write_css_file(); |
+ write_png_files(); |
+ |
+ if ($html_gzip) |
+ { |
+ info("Writing .htaccess file.\n"); |
+ write_htaccess_file(); |
+ } |
+ |
+ info("Generating output.\n"); |
+ |
+ # Process each subdirectory and collect overview information |
+ foreach $dir_name (@dir_list) |
+ { |
+ ($lines_found, $lines_hit, $fn_found, $fn_hit) |
+ = process_dir($dir_name); |
+ |
+ # Remove prefix if applicable |
+ if (!$no_prefix && $dir_prefix) |
+ { |
+ # Match directory names beginning with $dir_prefix |
+ $dir_name = apply_prefix($dir_name, $dir_prefix); |
+ } |
+ |
+ # Generate name for directory overview HTML page |
+ if ($dir_name =~ /^\/(.*)$/) |
+ { |
+ $link_name = substr($dir_name, 1)."/index.$html_ext"; |
+ } |
+ else |
+ { |
+ $link_name = $dir_name."/index.$html_ext"; |
+ } |
+ |
+ $overview{$dir_name} = [$lines_found, $lines_hit, $fn_found, |
+ $fn_hit, $link_name, |
+ get_rate($lines_found, $lines_hit), |
+ get_rate($fn_found, $fn_hit)]; |
+ $overall_found += $lines_found; |
+ $overall_hit += $lines_hit; |
+ $total_fn_found += $fn_found; |
+ $total_fn_hit += $fn_hit; |
+ } |
+ |
+ # Generate overview page |
+ info("Writing directory view page.\n"); |
+ |
+ # Create sorted pages |
+ foreach (@fileview_sortlist) { |
+ write_dir_page($fileview_sortname[$_], ".", "", $test_title, |
+ undef, $overall_found, $overall_hit, |
+ $total_fn_found, $total_fn_hit, \%overview, |
+ {}, {}, 0, $_); |
+ } |
+ |
+ # Check if there are any test case descriptions to write out |
+ if (%test_description) |
+ { |
+ info("Writing test case description file.\n"); |
+ write_description_file( \%test_description, |
+ $overall_found, $overall_hit, |
+ $total_fn_found, $total_fn_hit); |
+ } |
+ |
+ chdir($cwd); |
+ |
+ info("Overall coverage rate:\n"); |
+ |
+ if ($overall_found == 0) |
+ { |
+ info(" lines......: no data found\n"); |
+ return; |
+ } |
+ info(" lines......: %.1f%% (%d of %d lines)\n", |
+ $overall_hit * 100 / $overall_found, $overall_hit, |
+ $overall_found,); |
+ |
+ if ($func_coverage) |
+ { |
+ if ($total_fn_found == 0) |
+ { |
+ info(" functions..: no data found\n"); |
+ } |
+ else |
+ { |
+ info(" functions..: %.1f%% (%d of %d functions)\n", |
+ $total_fn_hit * 100 / $total_fn_found, |
+ $total_fn_hit, $total_fn_found); |
+ |
+ } |
+ } |
+ |
+} |
+ |
+# |
+# html_create(handle, filename) |
+# |
+ |
+sub html_create($$) |
+{ |
+ my $handle = $_[0]; |
+ my $filename = $_[1]; |
+ |
+ if ($html_gzip) |
+ { |
+ open($handle, "|gzip -c >$filename") |
+ or die("ERROR: cannot open $filename for writing ". |
+ "(gzip)!\n"); |
+ } |
+ else |
+ { |
+ open($handle, ">$filename") |
+ or die("ERROR: cannot open $filename for writing!\n"); |
+ } |
+} |
+ |
+sub write_dir_page($$$$$$$$$$$$$$) |
+{ |
+ my ($name, $rel_dir, $base_dir, $title, $trunc_dir, $overall_found, |
+ $overall_hit, $total_fn_found, $total_fn_hit, $overview, |
+ $testhash, $testfnchash, $view_type, $sort_type) = @_; |
+ |
+ # Generate directory overview page including details |
+ html_create(*HTML_HANDLE, "$rel_dir/index$name.$html_ext"); |
+ if (!defined($trunc_dir)) { |
+ $trunc_dir = ""; |
+ } |
+ write_html_prolog(*HTML_HANDLE, $base_dir, "LCOV - $title$trunc_dir"); |
+ write_header(*HTML_HANDLE, $view_type, $trunc_dir, $rel_dir, |
+ $overall_found, $overall_hit, $total_fn_found, |
+ $total_fn_hit, $sort_type); |
+ write_file_table(*HTML_HANDLE, $base_dir, $overview, $testhash, |
+ $testfnchash, $view_type, $sort_type); |
+ write_html_epilog(*HTML_HANDLE, $base_dir); |
+ close(*HTML_HANDLE); |
+} |
+ |
+ |
+# |
+# process_dir(dir_name) |
+# |
+ |
+sub process_dir($) |
+{ |
+ my $abs_dir = $_[0]; |
+ my $trunc_dir; |
+ my $rel_dir = $abs_dir; |
+ my $base_dir; |
+ my $filename; |
+ my %overview; |
+ my $lines_found; |
+ my $lines_hit; |
+ my $fn_found; |
+ my $fn_hit; |
+ my $overall_found=0; |
+ my $overall_hit=0; |
+ my $total_fn_found=0; |
+ my $total_fn_hit=0; |
+ my $base_name; |
+ my $extension; |
+ my $testdata; |
+ my %testhash; |
+ my $testfncdata; |
+ my %testfnchash; |
+ my @sort_list; |
+ local *HTML_HANDLE; |
+ |
+ # Remove prefix if applicable |
+ if (!$no_prefix) |
+ { |
+ # Match directory name beginning with $dir_prefix |
+ $rel_dir = apply_prefix($rel_dir, $dir_prefix); |
+ } |
+ |
+ $trunc_dir = $rel_dir; |
+ |
+ # Remove leading / |
+ if ($rel_dir =~ /^\/(.*)$/) |
+ { |
+ $rel_dir = substr($rel_dir, 1); |
+ } |
+ |
+ $base_dir = get_relative_base_path($rel_dir); |
+ |
+ create_sub_dir($rel_dir); |
+ |
+ # Match filenames which specify files in this directory, not including |
+ # sub-directories |
+ foreach $filename (grep(/^\Q$abs_dir\E\/[^\/]*$/,keys(%info_data))) |
+ { |
+ my $page_link; |
+ my $func_link; |
+ |
+ ($lines_found, $lines_hit, $fn_found, $fn_hit, $testdata, |
+ $testfncdata) = process_file($trunc_dir, $rel_dir, $filename); |
+ |
+ $base_name = basename($filename); |
+ |
+ if ($no_sourceview) { |
+ $page_link = ""; |
+ } elsif ($frames) { |
+ # Link to frameset page |
+ $page_link = "$base_name.gcov.frameset.$html_ext"; |
+ } else { |
+ # Link directory to source code view page |
+ $page_link = "$base_name.gcov.$html_ext"; |
+ } |
+ $overview{$base_name} = [$lines_found, $lines_hit, $fn_found, |
+ $fn_hit, $page_link, |
+ get_rate($lines_found, $lines_hit), |
+ get_rate($fn_found, $fn_hit)]; |
+ |
+ $testhash{$base_name} = $testdata; |
+ $testfnchash{$base_name} = $testfncdata; |
+ |
+ $overall_found += $lines_found; |
+ $overall_hit += $lines_hit; |
+ |
+ $total_fn_found += $fn_found; |
+ $total_fn_hit += $fn_hit; |
+ } |
+ |
+ # Create sorted pages |
+ foreach (@fileview_sortlist) { |
+ # Generate directory overview page (without details) |
+ write_dir_page($fileview_sortname[$_], $rel_dir, $base_dir, |
+ $test_title, $trunc_dir, $overall_found, |
+ $overall_hit, $total_fn_found, $total_fn_hit, |
+ \%overview, {}, {}, 1, $_); |
+ if (!$show_details) { |
+ next; |
+ } |
+ # Generate directory overview page including details |
+ write_dir_page("-detail".$fileview_sortname[$_], $rel_dir, |
+ $base_dir, $test_title, $trunc_dir, |
+ $overall_found, $overall_hit, $total_fn_found, |
+ $total_fn_hit, \%overview, \%testhash, |
+ \%testfnchash, 1, $_); |
+ } |
+ |
+ # Calculate resulting line counts |
+ return ($overall_found, $overall_hit, $total_fn_found, $total_fn_hit); |
+} |
+ |
+ |
+# |
+# get_converted_lines(testdata) |
+# |
+# Return hash of line numbers of those lines which were only covered in |
+# converted data sets. |
+# |
+ |
+sub get_converted_lines($) |
+{ |
+ my $testdata = $_[0]; |
+ my $testcount; |
+ my %converted; |
+ my %nonconverted; |
+ my $hash; |
+ my $testcase; |
+ my $line; |
+ my %result; |
+ |
+ |
+ # Get a hash containing line numbers with positive counts both for |
+ # converted and original data sets |
+ foreach $testcase (keys(%{$testdata})) |
+ { |
+ # Check to see if this is a converted data set |
+ if ($testcase =~ /,diff$/) |
+ { |
+ $hash = \%converted; |
+ } |
+ else |
+ { |
+ $hash = \%nonconverted; |
+ } |
+ |
+ $testcount = $testdata->{$testcase}; |
+ # Add lines with a positive count to hash |
+ foreach $line (keys%{$testcount}) |
+ { |
+ if ($testcount->{$line} > 0) |
+ { |
+ $hash->{$line} = 1; |
+ } |
+ } |
+ } |
+ |
+ # Combine both hashes to resulting list |
+ foreach $line (keys(%converted)) |
+ { |
+ if (!defined($nonconverted{$line})) |
+ { |
+ $result{$line} = 1; |
+ } |
+ } |
+ |
+ return \%result; |
+} |
+ |
+ |
+sub write_function_page($$$$$$$$$$$$$$) |
+{ |
+ my ($base_dir, $rel_dir, $trunc_dir, $base_name, $title, |
+ $lines_found, $lines_hit, $fn_found, $fn_hit, |
+ $sumcount, $funcdata, $sumfnccount, $testfncdata, $sort_type) = @_; |
+ my $pagetitle; |
+ my $filename; |
+ |
+ # Generate function table for this file |
+ if ($sort_type == 0) { |
+ $filename = "$rel_dir/$base_name.func.$html_ext"; |
+ } else { |
+ $filename = "$rel_dir/$base_name.func-sort-c.$html_ext"; |
+ } |
+ html_create(*HTML_HANDLE, $filename); |
+ $pagetitle = "LCOV - $title - $trunc_dir/$base_name - functions"; |
+ write_html_prolog(*HTML_HANDLE, $base_dir, $pagetitle); |
+ write_header(*HTML_HANDLE, 4, "$trunc_dir/$base_name", |
+ "$rel_dir/$base_name", $lines_found, $lines_hit, |
+ $fn_found, $fn_hit, $sort_type); |
+ write_function_table(*HTML_HANDLE, "$base_name.gcov.$html_ext", |
+ $sumcount, $funcdata, |
+ $sumfnccount, $testfncdata, $base_name, |
+ $base_dir, $sort_type); |
+ write_html_epilog(*HTML_HANDLE, $base_dir, 1); |
+ close(*HTML_HANDLE); |
+} |
+ |
+ |
+# |
+# process_file(trunc_dir, rel_dir, filename) |
+# |
+ |
+sub process_file($$$) |
+{ |
+ info("Processing file ".apply_prefix($_[2], $dir_prefix)."\n"); |
+ |
+ my $trunc_dir = $_[0]; |
+ my $rel_dir = $_[1]; |
+ my $filename = $_[2]; |
+ my $base_name = basename($filename); |
+ my $base_dir = get_relative_base_path($rel_dir); |
+ my $testdata; |
+ my $testcount; |
+ my $sumcount; |
+ my $funcdata; |
+ my $checkdata; |
+ my $testfncdata; |
+ my $sumfnccount; |
+ my $lines_found; |
+ my $lines_hit; |
+ my $fn_found; |
+ my $fn_hit; |
+ my $converted; |
+ my @source; |
+ my $pagetitle; |
+ local *HTML_HANDLE; |
+ |
+ ($testdata, $sumcount, $funcdata, $checkdata, $testfncdata, |
+ $sumfnccount, $lines_found, $lines_hit, $fn_found, $fn_hit) |
+ = get_info_entry($info_data{$filename}); |
+ |
+ # Return after this point in case user asked us not to generate |
+ # source code view |
+ if ($no_sourceview) |
+ { |
+ return ($lines_found, $lines_hit, |
+ $fn_found, $fn_hit, $testdata); |
+ } |
+ |
+ $converted = get_converted_lines($testdata); |
+ # Generate source code view for this file |
+ html_create(*HTML_HANDLE, "$rel_dir/$base_name.gcov.$html_ext"); |
+ $pagetitle = "LCOV - $test_title - $trunc_dir/$base_name"; |
+ write_html_prolog(*HTML_HANDLE, $base_dir, $pagetitle); |
+ write_header(*HTML_HANDLE, 2, "$trunc_dir/$base_name", |
+ "$rel_dir/$base_name", $lines_found, $lines_hit, |
+ $fn_found, $fn_hit, 0); |
+ @source = write_source(*HTML_HANDLE, $filename, $sumcount, $checkdata, |
+ $converted, $funcdata); |
+ |
+ write_html_epilog(*HTML_HANDLE, $base_dir, 1); |
+ close(*HTML_HANDLE); |
+ |
+ if ($func_coverage) { |
+ # Create function tables |
+ foreach (@funcview_sortlist) { |
+ write_function_page($base_dir, $rel_dir, $trunc_dir, |
+ $base_name, $test_title, |
+ $lines_found, $lines_hit, |
+ $fn_found, $fn_hit, $sumcount, |
+ $funcdata, $sumfnccount, |
+ $testfncdata, $_); |
+ } |
+ } |
+ |
+ # Additional files are needed in case of frame output |
+ if (!$frames) |
+ { |
+ return ($lines_found, $lines_hit, |
+ $fn_found, $fn_hit, $testdata); |
+ } |
+ |
+ # Create overview png file |
+ gen_png("$rel_dir/$base_name.gcov.png", $overview_width, $tab_size, |
+ @source); |
+ |
+ # Create frameset page |
+ html_create(*HTML_HANDLE, |
+ "$rel_dir/$base_name.gcov.frameset.$html_ext"); |
+ write_frameset(*HTML_HANDLE, $base_dir, $base_name, $pagetitle); |
+ close(*HTML_HANDLE); |
+ |
+ # Write overview frame |
+ html_create(*HTML_HANDLE, |
+ "$rel_dir/$base_name.gcov.overview.$html_ext"); |
+ write_overview(*HTML_HANDLE, $base_dir, $base_name, $pagetitle, |
+ scalar(@source)); |
+ close(*HTML_HANDLE); |
+ |
+ return ($lines_found, $lines_hit, $fn_found, $fn_hit, $testdata, |
+ $testfncdata); |
+} |
+ |
+ |
+# |
+# read_info_file(info_filename) |
+# |
+# Read in the contents of the .info file specified by INFO_FILENAME. Data will |
+# be returned as a reference to a hash containing the following mappings: |
+# |
+# %result: for each filename found in file -> \%data |
+# |
+# %data: "test" -> \%testdata |
+# "sum" -> \%sumcount |
+# "func" -> \%funcdata |
+# "found" -> $lines_found (number of instrumented lines found in file) |
+# "hit" -> $lines_hit (number of executed lines in file) |
+# "check" -> \%checkdata |
+# "testfnc" -> \%testfncdata |
+# "sumfnc" -> \%sumfnccount |
+# |
+# %testdata : name of test affecting this file -> \%testcount |
+# %testfncdata: name of test affecting this file -> \%testfnccount |
+# |
+# %testcount : line number -> execution count for a single test |
+# %testfnccount: function name -> execution count for a single test |
+# %sumcount : line number -> execution count for all tests |
+# %sumfnccount : function name -> execution count for all tests |
+# %funcdata : function name -> line number |
+# %checkdata : line number -> checksum of source code line |
+# |
+# Note that .info file sections referring to the same file and test name |
+# will automatically be combined by adding all execution counts. |
+# |
+# Note that if INFO_FILENAME ends with ".gz", it is assumed that the file |
+# is compressed using GZIP. If available, GUNZIP will be used to decompress |
+# this file. |
+# |
+# Die on error. |
+# |
+ |
+sub read_info_file($) |
+{ |
+ my $tracefile = $_[0]; # Name of tracefile |
+ my %result; # Resulting hash: file -> data |
+ my $data; # Data handle for current entry |
+ my $testdata; # " " |
+ my $testcount; # " " |
+ my $sumcount; # " " |
+ my $funcdata; # " " |
+ my $checkdata; # " " |
+ my $testfncdata; |
+ my $testfnccount; |
+ my $sumfnccount; |
+ my $line; # Current line read from .info file |
+ my $testname; # Current test name |
+ my $filename; # Current filename |
+ my $hitcount; # Count for lines hit |
+ my $count; # Execution count of current line |
+ my $negative; # If set, warn about negative counts |
+ my $changed_testname; # If set, warn about changed testname |
+ my $line_checksum; # Checksum of current line |
+ local *INFO_HANDLE; # Filehandle for .info file |
+ |
+ info("Reading data file $tracefile\n"); |
+ |
+ # Check if file exists and is readable |
+ stat($_[0]); |
+ if (!(-r _)) |
+ { |
+ die("ERROR: cannot read file $_[0]!\n"); |
+ } |
+ |
+ # Check if this is really a plain file |
+ if (!(-f _)) |
+ { |
+ die("ERROR: not a plain file: $_[0]!\n"); |
+ } |
+ |
+ # Check for .gz extension |
+ if ($_[0] =~ /\.gz$/) |
+ { |
+ # Check for availability of GZIP tool |
+ system_no_output(1, "gunzip" ,"-h") |
+ and die("ERROR: gunzip command not available!\n"); |
+ |
+ # Check integrity of compressed file |
+ system_no_output(1, "gunzip", "-t", $_[0]) |
+ and die("ERROR: integrity check failed for ". |
+ "compressed file $_[0]!\n"); |
+ |
+ # Open compressed file |
+ open(INFO_HANDLE, "gunzip -c $_[0]|") |
+ or die("ERROR: cannot start gunzip to decompress ". |
+ "file $_[0]!\n"); |
+ } |
+ else |
+ { |
+ # Open decompressed file |
+ open(INFO_HANDLE, $_[0]) |
+ or die("ERROR: cannot read file $_[0]!\n"); |
+ } |
+ |
+ $testname = ""; |
+ while (<INFO_HANDLE>) |
+ { |
+ chomp($_); |
+ $line = $_; |
+ |
+ # Switch statement |
+ foreach ($line) |
+ { |
+ /^TN:([^,]*)/ && do |
+ { |
+ # Test name information found |
+ $testname = defined($1) ? $1 : ""; |
+ if ($testname =~ s/\W/_/g) |
+ { |
+ $changed_testname = 1; |
+ } |
+ last; |
+ }; |
+ |
+ /^[SK]F:(.*)/ && do |
+ { |
+ # Filename information found |
+ # Retrieve data for new entry |
+ $filename = $1; |
+ |
+ $data = $result{$filename}; |
+ ($testdata, $sumcount, $funcdata, $checkdata, |
+ $testfncdata, $sumfnccount) = |
+ get_info_entry($data); |
+ |
+ if (defined($testname)) |
+ { |
+ $testcount = $testdata->{$testname}; |
+ $testfnccount = $testfncdata->{$testname}; |
+ } |
+ else |
+ { |
+ $testcount = {}; |
+ $testfnccount = {}; |
+ } |
+ last; |
+ }; |
+ |
+ /^DA:(\d+),(-?\d+)(,[^,\s]+)?/ && do |
+ { |
+ # Fix negative counts |
+ $count = $2 < 0 ? 0 : $2; |
+ if ($2 < 0) |
+ { |
+ $negative = 1; |
+ } |
+ # Execution count found, add to structure |
+ # Add summary counts |
+ $sumcount->{$1} += $count; |
+ |
+ # Add test-specific counts |
+ if (defined($testname)) |
+ { |
+ $testcount->{$1} += $count; |
+ } |
+ |
+ # Store line checksum if available |
+ if (defined($3)) |
+ { |
+ $line_checksum = substr($3, 1); |
+ |
+ # Does it match a previous definition |
+ if (defined($checkdata->{$1}) && |
+ ($checkdata->{$1} ne |
+ $line_checksum)) |
+ { |
+ die("ERROR: checksum mismatch ". |
+ "at $filename:$1\n"); |
+ } |
+ |
+ $checkdata->{$1} = $line_checksum; |
+ } |
+ last; |
+ }; |
+ |
+ /^FN:(\d+),([^,]+)/ && do |
+ { |
+ # Function data found, add to structure |
+ $funcdata->{$2} = $1; |
+ |
+ # Also initialize function call data |
+ if (!defined($sumfnccount->{$2})) { |
+ $sumfnccount->{$2} = 0; |
+ } |
+ if (defined($testname)) |
+ { |
+ if (!defined($testfnccount->{$2})) { |
+ $testfnccount->{$2} = 0; |
+ } |
+ } |
+ last; |
+ }; |
+ |
+ /^FNDA:(\d+),([^,]+)/ && do |
+ { |
+ # Function call count found, add to structure |
+ # Add summary counts |
+ $sumfnccount->{$2} += $1; |
+ |
+ # Add test-specific counts |
+ if (defined($testname)) |
+ { |
+ $testfnccount->{$2} += $1; |
+ } |
+ last; |
+ }; |
+ /^end_of_record/ && do |
+ { |
+ # Found end of section marker |
+ if ($filename) |
+ { |
+ # Store current section data |
+ if (defined($testname)) |
+ { |
+ $testdata->{$testname} = |
+ $testcount; |
+ $testfncdata->{$testname} = |
+ $testfnccount; |
+ } |
+ |
+ set_info_entry($data, $testdata, |
+ $sumcount, $funcdata, |
+ $checkdata, $testfncdata, |
+ $sumfnccount); |
+ $result{$filename} = $data; |
+ last; |
+ } |
+ }; |
+ |
+ # default |
+ last; |
+ } |
+ } |
+ close(INFO_HANDLE); |
+ |
+ # Calculate lines_found and lines_hit for each file |
+ foreach $filename (keys(%result)) |
+ { |
+ $data = $result{$filename}; |
+ |
+ ($testdata, $sumcount, undef, undef, $testfncdata, |
+ $sumfnccount) = get_info_entry($data); |
+ |
+ # Filter out empty files |
+ if (scalar(keys(%{$sumcount})) == 0) |
+ { |
+ delete($result{$filename}); |
+ next; |
+ } |
+ # Filter out empty test cases |
+ foreach $testname (keys(%{$testdata})) |
+ { |
+ if (!defined($testdata->{$testname}) || |
+ scalar(keys(%{$testdata->{$testname}})) == 0) |
+ { |
+ delete($testdata->{$testname}); |
+ delete($testfncdata->{$testname}); |
+ } |
+ } |
+ |
+ $data->{"found"} = scalar(keys(%{$sumcount})); |
+ $hitcount = 0; |
+ |
+ foreach (keys(%{$sumcount})) |
+ { |
+ if ($sumcount->{$_} > 0) { $hitcount++; } |
+ } |
+ |
+ $data->{"hit"} = $hitcount; |
+ |
+ # Get found/hit values for function call data |
+ $data->{"f_found"} = scalar(keys(%{$sumfnccount})); |
+ $hitcount = 0; |
+ |
+ foreach (keys(%{$sumfnccount})) { |
+ if ($sumfnccount->{$_} > 0) { |
+ $hitcount++; |
+ } |
+ } |
+ $data->{"f_hit"} = $hitcount; |
+ } |
+ |
+ if (scalar(keys(%result)) == 0) |
+ { |
+ die("ERROR: no valid records found in tracefile $tracefile\n"); |
+ } |
+ if ($negative) |
+ { |
+ warn("WARNING: negative counts found in tracefile ". |
+ "$tracefile\n"); |
+ } |
+ if ($changed_testname) |
+ { |
+ warn("WARNING: invalid characters removed from testname in ". |
+ "tracefile $tracefile\n"); |
+ } |
+ |
+ return(\%result); |
+} |
+ |
+ |
+# |
+# get_info_entry(hash_ref) |
+# |
+# Retrieve data from an entry of the structure generated by read_info_file(). |
+# Return a list of references to hashes: |
+# (test data hash ref, sum count hash ref, funcdata hash ref, checkdata hash |
+# ref, testfncdata hash ref, sumfnccount hash ref, lines found, lines hit, |
+# functions found, functions hit) |
+# |
+ |
+sub get_info_entry($) |
+{ |
+ my $testdata_ref = $_[0]->{"test"}; |
+ my $sumcount_ref = $_[0]->{"sum"}; |
+ my $funcdata_ref = $_[0]->{"func"}; |
+ my $checkdata_ref = $_[0]->{"check"}; |
+ my $testfncdata = $_[0]->{"testfnc"}; |
+ my $sumfnccount = $_[0]->{"sumfnc"}; |
+ my $lines_found = $_[0]->{"found"}; |
+ my $lines_hit = $_[0]->{"hit"}; |
+ my $fn_found = $_[0]->{"f_found"}; |
+ my $fn_hit = $_[0]->{"f_hit"}; |
+ |
+ return ($testdata_ref, $sumcount_ref, $funcdata_ref, $checkdata_ref, |
+ $testfncdata, $sumfnccount, $lines_found, $lines_hit, |
+ $fn_found, $fn_hit); |
+} |
+ |
+ |
+# |
+# set_info_entry(hash_ref, testdata_ref, sumcount_ref, funcdata_ref, |
+# checkdata_ref, testfncdata_ref, sumfcncount_ref[,lines_found, |
+# lines_hit, f_found, f_hit]) |
+# |
+# Update the hash referenced by HASH_REF with the provided data references. |
+# |
+ |
+sub set_info_entry($$$$$$$;$$$$) |
+{ |
+ my $data_ref = $_[0]; |
+ |
+ $data_ref->{"test"} = $_[1]; |
+ $data_ref->{"sum"} = $_[2]; |
+ $data_ref->{"func"} = $_[3]; |
+ $data_ref->{"check"} = $_[4]; |
+ $data_ref->{"testfnc"} = $_[5]; |
+ $data_ref->{"sumfnc"} = $_[6]; |
+ |
+ if (defined($_[7])) { $data_ref->{"found"} = $_[7]; } |
+ if (defined($_[8])) { $data_ref->{"hit"} = $_[8]; } |
+ if (defined($_[9])) { $data_ref->{"f_found"} = $_[9]; } |
+ if (defined($_[10])) { $data_ref->{"f_hit"} = $_[10]; } |
+} |
+ |
+ |
+# |
+# add_counts(data1_ref, data2_ref) |
+# |
+# DATA1_REF and DATA2_REF are references to hashes containing a mapping |
+# |
+# line number -> execution count |
+# |
+# Return a list (RESULT_REF, LINES_FOUND, LINES_HIT) where RESULT_REF |
+# is a reference to a hash containing the combined mapping in which |
+# execution counts are added. |
+# |
+ |
+sub add_counts($$) |
+{ |
+ my %data1 = %{$_[0]}; # Hash 1 |
+ my %data2 = %{$_[1]}; # Hash 2 |
+ my %result; # Resulting hash |
+ my $line; # Current line iteration scalar |
+ my $data1_count; # Count of line in hash1 |
+ my $data2_count; # Count of line in hash2 |
+ my $found = 0; # Total number of lines found |
+ my $hit = 0; # Number of lines with a count > 0 |
+ |
+ foreach $line (keys(%data1)) |
+ { |
+ $data1_count = $data1{$line}; |
+ $data2_count = $data2{$line}; |
+ |
+ # Add counts if present in both hashes |
+ if (defined($data2_count)) { $data1_count += $data2_count; } |
+ |
+ # Store sum in %result |
+ $result{$line} = $data1_count; |
+ |
+ $found++; |
+ if ($data1_count > 0) { $hit++; } |
+ } |
+ |
+ # Add lines unique to data2 |
+ foreach $line (keys(%data2)) |
+ { |
+ # Skip lines already in data1 |
+ if (defined($data1{$line})) { next; } |
+ |
+ # Copy count from data2 |
+ $result{$line} = $data2{$line}; |
+ |
+ $found++; |
+ if ($result{$line} > 0) { $hit++; } |
+ } |
+ |
+ return (\%result, $found, $hit); |
+} |
+ |
+ |
+# |
+# merge_checksums(ref1, ref2, filename) |
+# |
+# REF1 and REF2 are references to hashes containing a mapping |
+# |
+# line number -> checksum |
+# |
+# Merge checksum lists defined in REF1 and REF2 and return reference to |
+# resulting hash. Die if a checksum for a line is defined in both hashes |
+# but does not match. |
+# |
+ |
+sub merge_checksums($$$) |
+{ |
+ my $ref1 = $_[0]; |
+ my $ref2 = $_[1]; |
+ my $filename = $_[2]; |
+ my %result; |
+ my $line; |
+ |
+ foreach $line (keys(%{$ref1})) |
+ { |
+ if (defined($ref2->{$line}) && |
+ ($ref1->{$line} ne $ref2->{$line})) |
+ { |
+ die("ERROR: checksum mismatch at $filename:$line\n"); |
+ } |
+ $result{$line} = $ref1->{$line}; |
+ } |
+ |
+ foreach $line (keys(%{$ref2})) |
+ { |
+ $result{$line} = $ref2->{$line}; |
+ } |
+ |
+ return \%result; |
+} |
+ |
+ |
+# |
+# merge_func_data(funcdata1, funcdata2, filename) |
+# |
+ |
+sub merge_func_data($$$) |
+{ |
+ my ($funcdata1, $funcdata2, $filename) = @_; |
+ my %result; |
+ my $func; |
+ |
+ %result = %{$funcdata1}; |
+ |
+ foreach $func (keys(%{$funcdata2})) { |
+ my $line1 = $result{$func}; |
+ my $line2 = $funcdata2->{$func}; |
+ |
+ if (defined($line1) && ($line1 != $line2)) { |
+ warn("WARNING: function data mismatch at ". |
+ "$filename:$line2\n"); |
+ next; |
+ } |
+ $result{$func} = $line2; |
+ } |
+ |
+ return \%result; |
+} |
+ |
+ |
+# |
+# add_fnccount(fnccount1, fnccount2) |
+# |
+# Add function call count data. Return list (fnccount_added, f_found, f_hit) |
+# |
+ |
+sub add_fnccount($$) |
+{ |
+ my ($fnccount1, $fnccount2) = @_; |
+ my %result; |
+ my $fn_found; |
+ my $fn_hit; |
+ my $function; |
+ |
+ %result = %{$fnccount1}; |
+ foreach $function (keys(%{$fnccount2})) { |
+ $result{$function} += $fnccount2->{$function}; |
+ } |
+ $fn_found = scalar(keys(%result)); |
+ $fn_hit = 0; |
+ foreach $function (keys(%result)) { |
+ if ($result{$function} > 0) { |
+ $fn_hit++; |
+ } |
+ } |
+ |
+ return (\%result, $fn_found, $fn_hit); |
+} |
+ |
+# |
+# add_testfncdata(testfncdata1, testfncdata2) |
+# |
+# Add function call count data for several tests. Return reference to |
+# added_testfncdata. |
+# |
+ |
+sub add_testfncdata($$) |
+{ |
+ my ($testfncdata1, $testfncdata2) = @_; |
+ my %result; |
+ my $testname; |
+ |
+ foreach $testname (keys(%{$testfncdata1})) { |
+ if (defined($testfncdata2->{$testname})) { |
+ my $fnccount; |
+ |
+ # Function call count data for this testname exists |
+ # in both data sets: add |
+ ($fnccount) = add_fnccount( |
+ $testfncdata1->{$testname}, |
+ $testfncdata2->{$testname}); |
+ $result{$testname} = $fnccount; |
+ next; |
+ } |
+ # Function call count data for this testname is unique to |
+ # data set 1: copy |
+ $result{$testname} = $testfncdata1->{$testname}; |
+ } |
+ |
+ # Add count data for testnames unique to data set 2 |
+ foreach $testname (keys(%{$testfncdata2})) { |
+ if (!defined($result{$testname})) { |
+ $result{$testname} = $testfncdata2->{$testname}; |
+ } |
+ } |
+ return \%result; |
+} |
+ |
+# |
+# combine_info_entries(entry_ref1, entry_ref2, filename) |
+# |
+# Combine .info data entry hashes referenced by ENTRY_REF1 and ENTRY_REF2. |
+# Return reference to resulting hash. |
+# |
+ |
+sub combine_info_entries($$$) |
+{ |
+ my $entry1 = $_[0]; # Reference to hash containing first entry |
+ my $testdata1; |
+ my $sumcount1; |
+ my $funcdata1; |
+ my $checkdata1; |
+ my $testfncdata1; |
+ my $sumfnccount1; |
+ |
+ my $entry2 = $_[1]; # Reference to hash containing second entry |
+ my $testdata2; |
+ my $sumcount2; |
+ my $funcdata2; |
+ my $checkdata2; |
+ my $testfncdata2; |
+ my $sumfnccount2; |
+ |
+ my %result; # Hash containing combined entry |
+ my %result_testdata; |
+ my $result_sumcount = {}; |
+ my $result_funcdata; |
+ my $result_testfncdata; |
+ my $result_sumfnccount; |
+ my $lines_found; |
+ my $lines_hit; |
+ my $fn_found; |
+ my $fn_hit; |
+ |
+ my $testname; |
+ my $filename = $_[2]; |
+ |
+ # Retrieve data |
+ ($testdata1, $sumcount1, $funcdata1, $checkdata1, $testfncdata1, |
+ $sumfnccount1) = get_info_entry($entry1); |
+ ($testdata2, $sumcount2, $funcdata2, $checkdata2, $testfncdata2, |
+ $sumfnccount2) = get_info_entry($entry2); |
+ |
+ # Merge checksums |
+ $checkdata1 = merge_checksums($checkdata1, $checkdata2, $filename); |
+ |
+ # Combine funcdata |
+ $result_funcdata = merge_func_data($funcdata1, $funcdata2, $filename); |
+ |
+ # Combine function call count data |
+ $result_testfncdata = add_testfncdata($testfncdata1, $testfncdata2); |
+ ($result_sumfnccount, $fn_found, $fn_hit) = |
+ add_fnccount($sumfnccount1, $sumfnccount2); |
+ |
+ # Combine testdata |
+ foreach $testname (keys(%{$testdata1})) |
+ { |
+ if (defined($testdata2->{$testname})) |
+ { |
+ # testname is present in both entries, requires |
+ # combination |
+ ($result_testdata{$testname}) = |
+ add_counts($testdata1->{$testname}, |
+ $testdata2->{$testname}); |
+ } |
+ else |
+ { |
+ # testname only present in entry1, add to result |
+ $result_testdata{$testname} = $testdata1->{$testname}; |
+ } |
+ |
+ # update sum count hash |
+ ($result_sumcount, $lines_found, $lines_hit) = |
+ add_counts($result_sumcount, |
+ $result_testdata{$testname}); |
+ } |
+ |
+ foreach $testname (keys(%{$testdata2})) |
+ { |
+ # Skip testnames already covered by previous iteration |
+ if (defined($testdata1->{$testname})) { next; } |
+ |
+ # testname only present in entry2, add to result hash |
+ $result_testdata{$testname} = $testdata2->{$testname}; |
+ |
+ # update sum count hash |
+ ($result_sumcount, $lines_found, $lines_hit) = |
+ add_counts($result_sumcount, |
+ $result_testdata{$testname}); |
+ } |
+ |
+ # Calculate resulting sumcount |
+ |
+ # Store result |
+ set_info_entry(\%result, \%result_testdata, $result_sumcount, |
+ $result_funcdata, $checkdata1, $result_testfncdata, |
+ $result_sumfnccount, $lines_found, $lines_hit, |
+ $fn_found, $fn_hit); |
+ |
+ return(\%result); |
+} |
+ |
+ |
+# |
+# combine_info_files(info_ref1, info_ref2) |
+# |
+# Combine .info data in hashes referenced by INFO_REF1 and INFO_REF2. Return |
+# reference to resulting hash. |
+# |
+ |
+sub combine_info_files($$) |
+{ |
+ my %hash1 = %{$_[0]}; |
+ my %hash2 = %{$_[1]}; |
+ my $filename; |
+ |
+ foreach $filename (keys(%hash2)) |
+ { |
+ if ($hash1{$filename}) |
+ { |
+ # Entry already exists in hash1, combine them |
+ $hash1{$filename} = |
+ combine_info_entries($hash1{$filename}, |
+ $hash2{$filename}, |
+ $filename); |
+ } |
+ else |
+ { |
+ # Entry is unique in both hashes, simply add to |
+ # resulting hash |
+ $hash1{$filename} = $hash2{$filename}; |
+ } |
+ } |
+ |
+ return(\%hash1); |
+} |
+ |
+ |
+# |
+# get_prefix(filename_list) |
+# |
+# Search FILENAME_LIST for a directory prefix which is common to as many |
+# list entries as possible, so that removing this prefix will minimize the |
+# sum of the lengths of all resulting shortened filenames. |
+# |
+ |
+sub get_prefix(@) |
+{ |
+ my @filename_list = @_; # provided list of filenames |
+ my %prefix; # mapping: prefix -> sum of lengths |
+ my $current; # Temporary iteration variable |
+ |
+ # Find list of prefixes |
+ foreach (@filename_list) |
+ { |
+ # Need explicit assignment to get a copy of $_ so that |
+ # shortening the contained prefix does not affect the list |
+ $current = shorten_prefix($_); |
+ while ($current = shorten_prefix($current)) |
+ { |
+ # Skip rest if the remaining prefix has already been |
+ # added to hash |
+ if ($prefix{$current}) { last; } |
+ |
+ # Initialize with 0 |
+ $prefix{$current}="0"; |
+ } |
+ |
+ } |
+ |
+ # Calculate sum of lengths for all prefixes |
+ foreach $current (keys(%prefix)) |
+ { |
+ foreach (@filename_list) |
+ { |
+ # Add original length |
+ $prefix{$current} += length($_); |
+ |
+ # Check whether prefix matches |
+ if (substr($_, 0, length($current)) eq $current) |
+ { |
+ # Subtract prefix length for this filename |
+ $prefix{$current} -= length($current); |
+ } |
+ } |
+ } |
+ |
+ # Find and return prefix with minimal sum |
+ $current = (keys(%prefix))[0]; |
+ |
+ foreach (keys(%prefix)) |
+ { |
+ if ($prefix{$_} < $prefix{$current}) |
+ { |
+ $current = $_; |
+ } |
+ } |
+ |
+ return($current); |
+} |
+ |
+ |
+# |
+# shorten_prefix(prefix) |
+# |
+# Return PREFIX shortened by last directory component. |
+# |
+ |
+sub shorten_prefix($) |
+{ |
+ my @list = split("/", $_[0]); |
+ |
+ pop(@list); |
+ return join("/", @list); |
+} |
+ |
+ |
+ |
+# |
+# get_dir_list(filename_list) |
+# |
+# Return sorted list of directories for each entry in given FILENAME_LIST. |
+# |
+ |
+sub get_dir_list(@) |
+{ |
+ my %result; |
+ |
+ foreach (@_) |
+ { |
+ $result{shorten_prefix($_)} = ""; |
+ } |
+ |
+ return(sort(keys(%result))); |
+} |
+ |
+ |
+# |
+# get_relative_base_path(subdirectory) |
+# |
+# Return a relative path string which references the base path when applied |
+# in SUBDIRECTORY. |
+# |
+# Example: get_relative_base_path("fs/mm") -> "../../" |
+# |
+ |
+sub get_relative_base_path($) |
+{ |
+ my $result = ""; |
+ my $index; |
+ |
+ # Make an empty directory path a special case |
+ if (!$_[0]) { return(""); } |
+ |
+ # Count number of /s in path |
+ $index = ($_[0] =~ s/\//\//g); |
+ |
+ # Add a ../ to $result for each / in the directory path + 1 |
+ for (; $index>=0; $index--) |
+ { |
+ $result .= "../"; |
+ } |
+ |
+ return $result; |
+} |
+ |
+ |
+# |
+# read_testfile(test_filename) |
+# |
+# Read in file TEST_FILENAME which contains test descriptions in the format: |
+# |
+# TN:<whitespace><test name> |
+# TD:<whitespace><test description> |
+# |
+# for each test case. Return a reference to a hash containing a mapping |
+# |
+# test name -> test description. |
+# |
+# Die on error. |
+# |
+ |
+sub read_testfile($) |
+{ |
+ my %result; |
+ my $test_name; |
+ my $changed_testname; |
+ local *TEST_HANDLE; |
+ |
+ open(TEST_HANDLE, "<".$_[0]) |
+ or die("ERROR: cannot open $_[0]!\n"); |
+ |
+ while (<TEST_HANDLE>) |
+ { |
+ chomp($_); |
+ |
+ # Match lines beginning with TN:<whitespace(s)> |
+ if (/^TN:\s+(.*?)\s*$/) |
+ { |
+ # Store name for later use |
+ $test_name = $1; |
+ if ($test_name =~ s/\W/_/g) |
+ { |
+ $changed_testname = 1; |
+ } |
+ } |
+ |
+ # Match lines beginning with TD:<whitespace(s)> |
+ if (/^TD:\s+(.*?)\s*$/) |
+ { |
+ # Check for empty line |
+ if ($1) |
+ { |
+ # Add description to hash |
+ $result{$test_name} .= " $1"; |
+ } |
+ else |
+ { |
+ # Add empty line |
+ $result{$test_name} .= "\n\n"; |
+ } |
+ } |
+ } |
+ |
+ close(TEST_HANDLE); |
+ |
+ if ($changed_testname) |
+ { |
+ warn("WARNING: invalid characters removed from testname in ". |
+ "descriptions file $_[0]\n"); |
+ } |
+ |
+ return \%result; |
+} |
+ |
+ |
+# |
+# escape_html(STRING) |
+# |
+# Return a copy of STRING in which all occurrences of HTML special characters |
+# are escaped. |
+# |
+ |
+sub escape_html($) |
+{ |
+ my $string = $_[0]; |
+ |
+ if (!$string) { return ""; } |
+ |
+ $string =~ s/&/&/g; # & -> & |
+ $string =~ s/</</g; # < -> < |
+ $string =~ s/>/>/g; # > -> > |
+ $string =~ s/\"/"/g; # " -> " |
+ |
+ while ($string =~ /^([^\t]*)(\t)/) |
+ { |
+ my $replacement = " "x($tab_size - (length($1) % $tab_size)); |
+ $string =~ s/^([^\t]*)(\t)/$1$replacement/; |
+ } |
+ |
+ $string =~ s/\n/<br>/g; # \n -> <br> |
+ |
+ return $string; |
+} |
+ |
+ |
+# |
+# get_date_string() |
+# |
+# Return the current date in the form: yyyy-mm-dd |
+# |
+ |
+sub get_date_string() |
+{ |
+ my $year; |
+ my $month; |
+ my $day; |
+ |
+ ($year, $month, $day) = (localtime())[5, 4, 3]; |
+ |
+ return sprintf("%d-%02d-%02d", $year+1900, $month+1, $day); |
+} |
+ |
+ |
+# |
+# create_sub_dir(dir_name) |
+# |
+# Create subdirectory DIR_NAME if it does not already exist, including all its |
+# parent directories. |
+# |
+# Die on error. |
+# |
+ |
+sub create_sub_dir($) |
+{ |
+ system("mkdir", "-p" ,$_[0]) |
+ and die("ERROR: cannot create directory $_!\n"); |
+} |
+ |
+ |
+# |
+# write_description_file(descriptions, overall_found, overall_hit, |
+# total_fn_found, total_fn_hit) |
+# |
+# Write HTML file containing all test case descriptions. DESCRIPTIONS is a |
+# reference to a hash containing a mapping |
+# |
+# test case name -> test case description |
+# |
+# Die on error. |
+# |
+ |
+sub write_description_file($$$$$) |
+{ |
+ my %description = %{$_[0]}; |
+ my $found = $_[1]; |
+ my $hit = $_[2]; |
+ my $fn_found = $_[3]; |
+ my $fn_hit = $_[4]; |
+ my $test_name; |
+ local *HTML_HANDLE; |
+ |
+ html_create(*HTML_HANDLE,"descriptions.$html_ext"); |
+ write_html_prolog(*HTML_HANDLE, "", "LCOV - test case descriptions"); |
+ write_header(*HTML_HANDLE, 3, "", "", $found, $hit, $fn_found, |
+ $fn_hit, 0); |
+ |
+ write_test_table_prolog(*HTML_HANDLE, |
+ "Test case descriptions - alphabetical list"); |
+ |
+ foreach $test_name (sort(keys(%description))) |
+ { |
+ write_test_table_entry(*HTML_HANDLE, $test_name, |
+ escape_html($description{$test_name})); |
+ } |
+ |
+ write_test_table_epilog(*HTML_HANDLE); |
+ write_html_epilog(*HTML_HANDLE, ""); |
+ |
+ close(*HTML_HANDLE); |
+} |
+ |
+ |
+ |
+# |
+# write_png_files() |
+# |
+# Create all necessary .png files for the HTML-output in the current |
+# directory. .png-files are used as bar graphs. |
+# |
+# Die on error. |
+# |
+ |
+sub write_png_files() |
+{ |
+ my %data; |
+ local *PNG_HANDLE; |
+ |
+ $data{"ruby.png"} = |
+ [0x89, 0x50, 0x4e, 0x47, 0x0d, 0x0a, 0x1a, 0x0a, 0x00, 0x00, |
+ 0x00, 0x0d, 0x49, 0x48, 0x44, 0x52, 0x00, 0x00, 0x00, 0x01, |
+ 0x00, 0x00, 0x00, 0x01, 0x01, 0x03, 0x00, 0x00, 0x00, 0x25, |
+ 0xdb, 0x56, 0xca, 0x00, 0x00, 0x00, 0x07, 0x74, 0x49, 0x4d, |
+ 0x45, 0x07, 0xd2, 0x07, 0x11, 0x0f, 0x18, 0x10, 0x5d, 0x57, |
+ 0x34, 0x6e, 0x00, 0x00, 0x00, 0x09, 0x70, 0x48, 0x59, 0x73, |
+ 0x00, 0x00, 0x0b, 0x12, 0x00, 0x00, 0x0b, 0x12, 0x01, 0xd2, |
+ 0xdd, 0x7e, 0xfc, 0x00, 0x00, 0x00, 0x04, 0x67, 0x41, 0x4d, |
+ 0x41, 0x00, 0x00, 0xb1, 0x8f, 0x0b, 0xfc, 0x61, 0x05, 0x00, |
+ 0x00, 0x00, 0x06, 0x50, 0x4c, 0x54, 0x45, 0xff, 0x35, 0x2f, |
+ 0x00, 0x00, 0x00, 0xd0, 0x33, 0x9a, 0x9d, 0x00, 0x00, 0x00, |
+ 0x0a, 0x49, 0x44, 0x41, 0x54, 0x78, 0xda, 0x63, 0x60, 0x00, |
+ 0x00, 0x00, 0x02, 0x00, 0x01, 0xe5, 0x27, 0xde, 0xfc, 0x00, |
+ 0x00, 0x00, 0x00, 0x49, 0x45, 0x4e, 0x44, 0xae, 0x42, 0x60, |
+ 0x82]; |
+ $data{"amber.png"} = |
+ [0x89, 0x50, 0x4e, 0x47, 0x0d, 0x0a, 0x1a, 0x0a, 0x00, 0x00, |
+ 0x00, 0x0d, 0x49, 0x48, 0x44, 0x52, 0x00, 0x00, 0x00, 0x01, |
+ 0x00, 0x00, 0x00, 0x01, 0x01, 0x03, 0x00, 0x00, 0x00, 0x25, |
+ 0xdb, 0x56, 0xca, 0x00, 0x00, 0x00, 0x07, 0x74, 0x49, 0x4d, |
+ 0x45, 0x07, 0xd2, 0x07, 0x11, 0x0f, 0x28, 0x04, 0x98, 0xcb, |
+ 0xd6, 0xe0, 0x00, 0x00, 0x00, 0x09, 0x70, 0x48, 0x59, 0x73, |
+ 0x00, 0x00, 0x0b, 0x12, 0x00, 0x00, 0x0b, 0x12, 0x01, 0xd2, |
+ 0xdd, 0x7e, 0xfc, 0x00, 0x00, 0x00, 0x04, 0x67, 0x41, 0x4d, |
+ 0x41, 0x00, 0x00, 0xb1, 0x8f, 0x0b, 0xfc, 0x61, 0x05, 0x00, |
+ 0x00, 0x00, 0x06, 0x50, 0x4c, 0x54, 0x45, 0xff, 0xe0, 0x50, |
+ 0x00, 0x00, 0x00, 0xa2, 0x7a, 0xda, 0x7e, 0x00, 0x00, 0x00, |
+ 0x0a, 0x49, 0x44, 0x41, 0x54, 0x78, 0xda, 0x63, 0x60, 0x00, |
+ 0x00, 0x00, 0x02, 0x00, 0x01, 0xe5, 0x27, 0xde, 0xfc, 0x00, |
+ 0x00, 0x00, 0x00, 0x49, 0x45, 0x4e, 0x44, 0xae, 0x42, 0x60, |
+ 0x82]; |
+ $data{"emerald.png"} = |
+ [0x89, 0x50, 0x4e, 0x47, 0x0d, 0x0a, 0x1a, 0x0a, 0x00, 0x00, |
+ 0x00, 0x0d, 0x49, 0x48, 0x44, 0x52, 0x00, 0x00, 0x00, 0x01, |
+ 0x00, 0x00, 0x00, 0x01, 0x01, 0x03, 0x00, 0x00, 0x00, 0x25, |
+ 0xdb, 0x56, 0xca, 0x00, 0x00, 0x00, 0x07, 0x74, 0x49, 0x4d, |
+ 0x45, 0x07, 0xd2, 0x07, 0x11, 0x0f, 0x22, 0x2b, 0xc9, 0xf5, |
+ 0x03, 0x33, 0x00, 0x00, 0x00, 0x09, 0x70, 0x48, 0x59, 0x73, |
+ 0x00, 0x00, 0x0b, 0x12, 0x00, 0x00, 0x0b, 0x12, 0x01, 0xd2, |
+ 0xdd, 0x7e, 0xfc, 0x00, 0x00, 0x00, 0x04, 0x67, 0x41, 0x4d, |
+ 0x41, 0x00, 0x00, 0xb1, 0x8f, 0x0b, 0xfc, 0x61, 0x05, 0x00, |
+ 0x00, 0x00, 0x06, 0x50, 0x4c, 0x54, 0x45, 0x1b, 0xea, 0x59, |
+ 0x0a, 0x0a, 0x0a, 0x0f, 0xba, 0x50, 0x83, 0x00, 0x00, 0x00, |
+ 0x0a, 0x49, 0x44, 0x41, 0x54, 0x78, 0xda, 0x63, 0x60, 0x00, |
+ 0x00, 0x00, 0x02, 0x00, 0x01, 0xe5, 0x27, 0xde, 0xfc, 0x00, |
+ 0x00, 0x00, 0x00, 0x49, 0x45, 0x4e, 0x44, 0xae, 0x42, 0x60, |
+ 0x82]; |
+ $data{"snow.png"} = |
+ [0x89, 0x50, 0x4e, 0x47, 0x0d, 0x0a, 0x1a, 0x0a, 0x00, 0x00, |
+ 0x00, 0x0d, 0x49, 0x48, 0x44, 0x52, 0x00, 0x00, 0x00, 0x01, |
+ 0x00, 0x00, 0x00, 0x01, 0x01, 0x03, 0x00, 0x00, 0x00, 0x25, |
+ 0xdb, 0x56, 0xca, 0x00, 0x00, 0x00, 0x07, 0x74, 0x49, 0x4d, |
+ 0x45, 0x07, 0xd2, 0x07, 0x11, 0x0f, 0x1e, 0x1d, 0x75, 0xbc, |
+ 0xef, 0x55, 0x00, 0x00, 0x00, 0x09, 0x70, 0x48, 0x59, 0x73, |
+ 0x00, 0x00, 0x0b, 0x12, 0x00, 0x00, 0x0b, 0x12, 0x01, 0xd2, |
+ 0xdd, 0x7e, 0xfc, 0x00, 0x00, 0x00, 0x04, 0x67, 0x41, 0x4d, |
+ 0x41, 0x00, 0x00, 0xb1, 0x8f, 0x0b, 0xfc, 0x61, 0x05, 0x00, |
+ 0x00, 0x00, 0x06, 0x50, 0x4c, 0x54, 0x45, 0xff, 0xff, 0xff, |
+ 0x00, 0x00, 0x00, 0x55, 0xc2, 0xd3, 0x7e, 0x00, 0x00, 0x00, |
+ 0x0a, 0x49, 0x44, 0x41, 0x54, 0x78, 0xda, 0x63, 0x60, 0x00, |
+ 0x00, 0x00, 0x02, 0x00, 0x01, 0xe5, 0x27, 0xde, 0xfc, 0x00, |
+ 0x00, 0x00, 0x00, 0x49, 0x45, 0x4e, 0x44, 0xae, 0x42, 0x60, |
+ 0x82]; |
+ $data{"glass.png"} = |
+ [0x89, 0x50, 0x4e, 0x47, 0x0d, 0x0a, 0x1a, 0x0a, 0x00, 0x00, |
+ 0x00, 0x0d, 0x49, 0x48, 0x44, 0x52, 0x00, 0x00, 0x00, 0x01, |
+ 0x00, 0x00, 0x00, 0x01, 0x01, 0x03, 0x00, 0x00, 0x00, 0x25, |
+ 0xdb, 0x56, 0xca, 0x00, 0x00, 0x00, 0x04, 0x67, 0x41, 0x4d, |
+ 0x41, 0x00, 0x00, 0xb1, 0x8f, 0x0b, 0xfc, 0x61, 0x05, 0x00, |
+ 0x00, 0x00, 0x06, 0x50, 0x4c, 0x54, 0x45, 0xff, 0xff, 0xff, |
+ 0x00, 0x00, 0x00, 0x55, 0xc2, 0xd3, 0x7e, 0x00, 0x00, 0x00, |
+ 0x01, 0x74, 0x52, 0x4e, 0x53, 0x00, 0x40, 0xe6, 0xd8, 0x66, |
+ 0x00, 0x00, 0x00, 0x01, 0x62, 0x4b, 0x47, 0x44, 0x00, 0x88, |
+ 0x05, 0x1d, 0x48, 0x00, 0x00, 0x00, 0x09, 0x70, 0x48, 0x59, |
+ 0x73, 0x00, 0x00, 0x0b, 0x12, 0x00, 0x00, 0x0b, 0x12, 0x01, |
+ 0xd2, 0xdd, 0x7e, 0xfc, 0x00, 0x00, 0x00, 0x07, 0x74, 0x49, |
+ 0x4d, 0x45, 0x07, 0xd2, 0x07, 0x13, 0x0f, 0x08, 0x19, 0xc4, |
+ 0x40, 0x56, 0x10, 0x00, 0x00, 0x00, 0x0a, 0x49, 0x44, 0x41, |
+ 0x54, 0x78, 0x9c, 0x63, 0x60, 0x00, 0x00, 0x00, 0x02, 0x00, |
+ 0x01, 0x48, 0xaf, 0xa4, 0x71, 0x00, 0x00, 0x00, 0x00, 0x49, |
+ 0x45, 0x4e, 0x44, 0xae, 0x42, 0x60, 0x82]; |
+ $data{"updown.png"} = |
+ [0x89, 0x50, 0x4e, 0x47, 0x0d, 0x0a, 0x1a, 0x0a, 0x00, 0x00, |
+ 0x00, 0x0d, 0x49, 0x48, 0x44, 0x52, 0x00, 0x00, 0x00, 0x0a, |
+ 0x00, 0x00, 0x00, 0x0e, 0x08, 0x06, 0x00, 0x00, 0x00, 0x16, |
+ 0xa3, 0x8d, 0xab, 0x00, 0x00, 0x00, 0x3c, 0x49, 0x44, 0x41, |
+ 0x54, 0x28, 0xcf, 0x63, 0x60, 0x40, 0x03, 0xff, 0xa1, 0x00, |
+ 0x5d, 0x9c, 0x11, 0x5d, 0x11, 0x8a, 0x24, 0x23, 0x23, 0x23, |
+ 0x86, 0x42, 0x6c, 0xa6, 0x20, 0x2b, 0x66, 0xc4, 0xa7, 0x08, |
+ 0x59, 0x31, 0x23, 0x21, 0x45, 0x30, 0xc0, 0xc4, 0x30, 0x60, |
+ 0x80, 0xfa, 0x6e, 0x24, 0x3e, 0x78, 0x48, 0x0a, 0x70, 0x62, |
+ 0xa2, 0x90, 0x81, 0xd8, 0x44, 0x01, 0x00, 0xe9, 0x5c, 0x2f, |
+ 0xf5, 0xe2, 0x9d, 0x0f, 0xf9, 0x00, 0x00, 0x00, 0x00, 0x49, |
+ 0x45, 0x4e, 0x44, 0xae, 0x42, 0x60, 0x82] if ($sort); |
+ foreach (keys(%data)) |
+ { |
+ open(PNG_HANDLE, ">".$_) |
+ or die("ERROR: cannot create $_!\n"); |
+ binmode(PNG_HANDLE); |
+ print(PNG_HANDLE map(chr,@{$data{$_}})); |
+ close(PNG_HANDLE); |
+ } |
+} |
+ |
+ |
+# |
+# write_htaccess_file() |
+# |
+ |
+sub write_htaccess_file() |
+{ |
+ local *HTACCESS_HANDLE; |
+ my $htaccess_data; |
+ |
+ open(*HTACCESS_HANDLE, ">.htaccess") |
+ or die("ERROR: cannot open .htaccess for writing!\n"); |
+ |
+ $htaccess_data = (<<"END_OF_HTACCESS") |
+AddEncoding x-gzip .html |
+END_OF_HTACCESS |
+ ; |
+ |
+ print(HTACCESS_HANDLE $htaccess_data); |
+ close(*HTACCESS_HANDLE); |
+} |
+ |
+ |
+# |
+# write_css_file() |
+# |
+# Write the cascading style sheet file gcov.css to the current directory. |
+# This file defines basic layout attributes of all generated HTML pages. |
+# |
+ |
+sub write_css_file() |
+{ |
+ local *CSS_HANDLE; |
+ |
+ # Check for a specified external style sheet file |
+ if ($css_filename) |
+ { |
+ # Simply copy that file |
+ system("cp", $css_filename, "gcov.css") |
+ and die("ERROR: cannot copy file $css_filename!\n"); |
+ return; |
+ } |
+ |
+ open(CSS_HANDLE, ">gcov.css") |
+ or die ("ERROR: cannot open gcov.css for writing!\n"); |
+ |
+ |
+ # ************************************************************* |
+ |
+ my $css_data = ($_=<<"END_OF_CSS") |
+ /* All views: initial background and text color */ |
+ body |
+ { |
+ color: #000000; |
+ background-color: #FFFFFF; |
+ } |
+ |
+ /* All views: standard link format*/ |
+ a:link |
+ { |
+ color: #284FA8; |
+ text-decoration: underline; |
+ } |
+ |
+ /* All views: standard link - visited format */ |
+ a:visited |
+ { |
+ color: #00CB40; |
+ text-decoration: underline; |
+ } |
+ |
+ /* All views: standard link - activated format */ |
+ a:active |
+ { |
+ color: #FF0040; |
+ text-decoration: underline; |
+ } |
+ |
+ /* All views: main title format */ |
+ td.title |
+ { |
+ text-align: center; |
+ padding-bottom: 10px; |
+ font-family: sans-serif; |
+ font-size: 20pt; |
+ font-style: italic; |
+ font-weight: bold; |
+ } |
+ |
+ /* All views: header item format */ |
+ td.headerItem |
+ { |
+ text-align: right; |
+ padding-right: 6px; |
+ font-family: sans-serif; |
+ font-weight: bold; |
+ vertical-align: top; |
+ white-space: nowrap; |
+ } |
+ |
+ /* All views: header item value format */ |
+ td.headerValue |
+ { |
+ text-align: left; |
+ color: #284FA8; |
+ font-family: sans-serif; |
+ font-weight: bold; |
+ white-space: nowrap; |
+ } |
+ |
+ /* All views: header item coverage table heading */ |
+ td.headerCovTableHead |
+ { |
+ text-align: center; |
+ padding-right: 6px; |
+ padding-left: 6px; |
+ padding-bottom: 0px; |
+ font-family: sans-serif; |
+ font-size: 80%; |
+ white-space: nowrap; |
+ } |
+ |
+ /* All views: header item coverage table entry */ |
+ td.headerCovTableEntry |
+ { |
+ text-align: right; |
+ color: #284FA8; |
+ font-family: sans-serif; |
+ font-weight: bold; |
+ white-space: nowrap; |
+ padding-left: 12px; |
+ padding-right: 4px; |
+ background-color: #DAE7FE; |
+ } |
+ |
+ /* All views: header item coverage table entry for high coverage rate */ |
+ td.headerCovTableEntryHi |
+ { |
+ text-align: right; |
+ color: #000000; |
+ font-family: sans-serif; |
+ font-weight: bold; |
+ white-space: nowrap; |
+ padding-left: 12px; |
+ padding-right: 4px; |
+ background-color: #A7FC9D; |
+ } |
+ |
+ /* All views: header item coverage table entry for medium coverage rate */ |
+ td.headerCovTableEntryMed |
+ { |
+ text-align: right; |
+ color: #000000; |
+ font-family: sans-serif; |
+ font-weight: bold; |
+ white-space: nowrap; |
+ padding-left: 12px; |
+ padding-right: 4px; |
+ background-color: #FFEA20; |
+ } |
+ |
+ /* All views: header item coverage table entry for ow coverage rate */ |
+ td.headerCovTableEntryLo |
+ { |
+ text-align: right; |
+ color: #000000; |
+ font-family: sans-serif; |
+ font-weight: bold; |
+ white-space: nowrap; |
+ padding-left: 12px; |
+ padding-right: 4px; |
+ background-color: #FF0000; |
+ } |
+ |
+ /* All views: header legend item for legend entry */ |
+ td.headerItemLeg |
+ { |
+ text-align: right; |
+ padding-right: 6px; |
+ font-family: sans-serif; |
+ font-weight: bold; |
+ vertical-align: bottom; |
+ white-space: nowrap; |
+ } |
+ |
+ /* All views: header legend value for legend entry */ |
+ td.headerValueLeg |
+ { |
+ text-align: left; |
+ color: #000000; |
+ font-family: sans-serif; |
+ font-size: 80%; |
+ white-space: nowrap; |
+ padding-top: 4px; |
+ } |
+ |
+ /* All views: color of horizontal ruler */ |
+ td.ruler |
+ { |
+ background-color: #6688D4; |
+ } |
+ |
+ /* All views: version string format */ |
+ td.versionInfo |
+ { |
+ text-align: center; |
+ padding-top: 2px; |
+ font-family: sans-serif; |
+ font-style: italic; |
+ } |
+ |
+ /* Directory view/File view (all)/Test case descriptions: |
+ table headline format */ |
+ td.tableHead |
+ { |
+ text-align: center; |
+ color: #FFFFFF; |
+ background-color: #6688D4; |
+ font-family: sans-serif; |
+ font-size: 120%; |
+ font-weight: bold; |
+ white-space: nowrap; |
+ padding-left: 4px; |
+ padding-right: 4px; |
+ } |
+ |
+ span.tableHeadSort |
+ { |
+ padding-right: 4px; |
+ } |
+ |
+ /* Directory view/File view (all): filename entry format */ |
+ td.coverFile |
+ { |
+ text-align: left; |
+ padding-left: 10px; |
+ padding-right: 20px; |
+ color: #284FA8; |
+ background-color: #DAE7FE; |
+ font-family: monospace; |
+ } |
+ |
+ /* Directory view/File view (all): bar-graph entry format*/ |
+ td.coverBar |
+ { |
+ padding-left: 10px; |
+ padding-right: 10px; |
+ background-color: #DAE7FE; |
+ } |
+ |
+ /* Directory view/File view (all): bar-graph outline color */ |
+ td.coverBarOutline |
+ { |
+ background-color: #000000; |
+ } |
+ |
+ /* Directory view/File view (all): percentage entry for files with |
+ high coverage rate */ |
+ td.coverPerHi |
+ { |
+ text-align: right; |
+ padding-left: 10px; |
+ padding-right: 10px; |
+ background-color: #A7FC9D; |
+ font-weight: bold; |
+ } |
+ |
+ /* Directory view/File view (all): line count entry for files with |
+ high coverage rate */ |
+ td.coverNumHi |
+ { |
+ text-align: right; |
+ padding-left: 10px; |
+ padding-right: 10px; |
+ background-color: #A7FC9D; |
+ white-space: nowrap; |
+ } |
+ |
+ /* Directory view/File view (all): legend entry for high coverage |
+ rate */ |
+ span.coverLegendHi |
+ { |
+ padding-left: 10px; |
+ padding-right: 10px; |
+ padding-bottom: 2px; |
+ background-color: #A7FC9D; |
+ } |
+ |
+ /* Directory view/File view (all): percentage entry for files with |
+ medium coverage rate */ |
+ td.coverPerMed |
+ { |
+ text-align: right; |
+ padding-left: 10px; |
+ padding-right: 10px; |
+ background-color: #FFEA20; |
+ font-weight: bold; |
+ } |
+ |
+ /* Directory view/File view (all): line count entry for files with |
+ medium coverage rate */ |
+ td.coverNumMed |
+ { |
+ text-align: right; |
+ padding-left: 10px; |
+ padding-right: 10px; |
+ background-color: #FFEA20; |
+ white-space: nowrap; |
+ } |
+ |
+ /* Directory view/File view (all): legend entry for medium coverage |
+ rate */ |
+ span.coverLegendMed |
+ { |
+ padding-left: 10px; |
+ padding-right: 10px; |
+ padding-bottom: 2px; |
+ background-color: #FFEA20; |
+ } |
+ |
+ /* Directory view/File view (all): percentage entry for files with |
+ low coverage rate */ |
+ td.coverPerLo |
+ { |
+ text-align: right; |
+ padding-left: 10px; |
+ padding-right: 10px; |
+ background-color: #FF0000; |
+ font-weight: bold; |
+ } |
+ |
+ /* Directory view/File view (all): line count entry for files with |
+ low coverage rate */ |
+ td.coverNumLo |
+ { |
+ text-align: right; |
+ padding-left: 10px; |
+ padding-right: 10px; |
+ background-color: #FF0000; |
+ white-space: nowrap; |
+ } |
+ |
+ /* Directory view/File view (all): legend entry for low coverage |
+ rate */ |
+ span.coverLegendLo |
+ { |
+ padding-left: 10px; |
+ padding-right: 10px; |
+ padding-bottom: 2px; |
+ background-color: #FF0000; |
+ } |
+ |
+ /* File view (all): "show/hide details" link format */ |
+ a.detail:link |
+ { |
+ color: #B8D0FF; |
+ } |
+ |
+ /* File view (all): "show/hide details" link - visited format */ |
+ a.detail:visited |
+ { |
+ color: #B8D0FF; |
+ } |
+ |
+ /* File view (all): "show/hide details" link - activated format */ |
+ a.detail:active |
+ { |
+ color: #FFFFFF; |
+ } |
+ |
+ /* File view (detail): test name table headline format */ |
+ td.testNameHead |
+ { |
+ text-align: right; |
+ padding-right: 10px; |
+ background-color: #DAE7FE; |
+ font-family: sans-serif; |
+ font-weight: bold; |
+ } |
+ |
+ /* File view (detail): test lines table headline format */ |
+ td.testLinesHead |
+ { |
+ text-align: center; |
+ background-color: #DAE7FE; |
+ font-family: sans-serif; |
+ font-weight: bold; |
+ } |
+ |
+ /* File view (detail): test name entry */ |
+ td.testName |
+ { |
+ text-align: right; |
+ padding-right: 10px; |
+ background-color: #DAE7FE; |
+ } |
+ |
+ /* File view (detail): test percentage entry */ |
+ td.testPer |
+ { |
+ text-align: right; |
+ padding-left: 10px; |
+ padding-right: 10px; |
+ background-color: #DAE7FE; |
+ } |
+ |
+ /* File view (detail): test lines count entry */ |
+ td.testNum |
+ { |
+ text-align: right; |
+ padding-left: 10px; |
+ padding-right: 10px; |
+ background-color: #DAE7FE; |
+ } |
+ |
+ /* Test case descriptions: test name format*/ |
+ dt |
+ { |
+ font-family: sans-serif; |
+ font-weight: bold; |
+ } |
+ |
+ /* Test case descriptions: description table body */ |
+ td.testDescription |
+ { |
+ padding-top: 10px; |
+ padding-left: 30px; |
+ padding-bottom: 10px; |
+ padding-right: 30px; |
+ background-color: #DAE7FE; |
+ } |
+ |
+ /* Source code view: function entry */ |
+ td.coverFn |
+ { |
+ text-align: left; |
+ padding-left: 10px; |
+ padding-right: 20px; |
+ color: #284FA8; |
+ background-color: #DAE7FE; |
+ font-family: monospace; |
+ } |
+ |
+ /* Source code view: function entry zero count*/ |
+ td.coverFnLo |
+ { |
+ text-align: right; |
+ padding-left: 10px; |
+ padding-right: 10px; |
+ background-color: #FF0000; |
+ font-weight: bold; |
+ } |
+ |
+ /* Source code view: function entry nonzero count*/ |
+ td.coverFnHi |
+ { |
+ text-align: right; |
+ padding-left: 10px; |
+ padding-right: 10px; |
+ background-color: #DAE7FE; |
+ font-weight: bold; |
+ } |
+ |
+ /* Source code view: source code format */ |
+ /* Source code view: source code format */ |
+ pre.source |
+ { |
+ font-family: monospace; |
+ white-space: pre; |
+ } |
+ |
+ /* Source code view: line number format */ |
+ span.lineNum |
+ { |
+ background-color: #EFE383; |
+ } |
+ |
+ /* Source code view: format for lines which were executed */ |
+ td.lineCov, |
+ span.lineCov |
+ { |
+ background-color: #CAD7FE; |
+ } |
+ |
+ /* Source code view: format for Cov legend */ |
+ span.coverLegendCov |
+ { |
+ padding-left: 10px; |
+ padding-right: 10px; |
+ padding-bottom: 2px; |
+ background-color: #CAD7FE; |
+ } |
+ |
+ /* Source code view: format for lines which were not executed */ |
+ td.lineNoCov, |
+ span.lineNoCov |
+ { |
+ background-color: #FF6230; |
+ } |
+ |
+ /* Source code view: format for NoCov legend */ |
+ span.coverLegendNoCov |
+ { |
+ padding-left: 10px; |
+ padding-right: 10px; |
+ padding-bottom: 2px; |
+ background-color: #FF0000; |
+ } |
+ |
+ /* Source code view (function table): standard link - visited format */ |
+ td.lineNoCov > a:visited, |
+ td.lineCov > a:visited |
+ { |
+ color: black; |
+ text-decoration: underline; |
+ } |
+ |
+ /* Source code view: format for lines which were executed only in a |
+ previous version */ |
+ span.lineDiffCov |
+ { |
+ background-color: #B5F7AF; |
+ } |
+ |
+ /* Source code view: format for DiffCov legend */ |
+ span.LegendDiffCov |
+ { |
+ text-align: center; |
+ padding-left: 10px; |
+ padding-right: 10px; |
+ background-color: #B5F7AF; |
+ } |
+END_OF_CSS |
+ ; |
+ |
+ # ************************************************************* |
+ |
+ |
+ # Remove leading tab from all lines |
+ $css_data =~ s/^\t//gm; |
+ |
+ print(CSS_HANDLE $css_data); |
+ |
+ close(CSS_HANDLE); |
+} |
+ |
+ |
+# |
+# get_bar_graph_code(base_dir, cover_found, cover_hit) |
+# |
+# Return a string containing HTML code which implements a bar graph display |
+# for a coverage rate of cover_hit * 100 / cover_found. |
+# |
+ |
+sub get_bar_graph_code($$$) |
+{ |
+ my $rate; |
+ my $alt; |
+ my $width; |
+ my $remainder; |
+ my $png_name; |
+ my $graph_code; |
+ |
+ # Check number of instrumented lines |
+ if ($_[1] == 0) { return ""; } |
+ |
+ $rate = $_[2] * 100 / $_[1]; |
+ $alt = sprintf("%.1f", $rate)."%"; |
+ $width = sprintf("%.0f", $rate); |
+ $remainder = sprintf("%d", 100-$width); |
+ |
+ # Decide which .png file to use |
+ $png_name = $rate_png[classify_rate($_[1], $_[2], $med_limit, |
+ $hi_limit)]; |
+ |
+ if ($width == 0) |
+ { |
+ # Zero coverage |
+ $graph_code = (<<END_OF_HTML) |
+ <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> |
+END_OF_HTML |
+ ; |
+ } |
+ elsif ($width == 100) |
+ { |
+ # Full coverage |
+ $graph_code = (<<END_OF_HTML) |
+ <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> |
+END_OF_HTML |
+ ; |
+ } |
+ else |
+ { |
+ # Positive coverage |
+ $graph_code = (<<END_OF_HTML) |
+ <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> |
+END_OF_HTML |
+ ; |
+ } |
+ |
+ # Remove leading tabs from all lines |
+ $graph_code =~ s/^\t+//gm; |
+ chomp($graph_code); |
+ |
+ return($graph_code); |
+} |
+ |
+# |
+# sub classify_rate(found, hit, med_limit, high_limit) |
+# |
+# Return 0 for low rate, 1 for medium rate and 2 for hi rate. |
+# |
+ |
+sub classify_rate($$$$) |
+{ |
+ my ($found, $hit, $med, $hi) = @_; |
+ my $rate; |
+ |
+ if ($found == 0) { |
+ return 2; |
+ } |
+ $rate = $hit * 100 / $found; |
+ if ($rate < $med) { |
+ return 0; |
+ } elsif ($rate < $hi) { |
+ return 1; |
+ } |
+ return 2; |
+} |
+ |
+ |
+# |
+# write_html(filehandle, html_code) |
+# |
+# Write out HTML_CODE to FILEHANDLE while removing a leading tabulator mark |
+# in each line of HTML_CODE. |
+# |
+ |
+sub write_html(*$) |
+{ |
+ local *HTML_HANDLE = $_[0]; |
+ my $html_code = $_[1]; |
+ |
+ # Remove leading tab from all lines |
+ $html_code =~ s/^\t//gm; |
+ |
+ print(HTML_HANDLE $html_code) |
+ or die("ERROR: cannot write HTML data ($!)\n"); |
+} |
+ |
+ |
+# |
+# write_html_prolog(filehandle, base_dir, pagetitle) |
+# |
+# Write an HTML prolog common to all HTML files to FILEHANDLE. PAGETITLE will |
+# be used as HTML page title. BASE_DIR contains a relative path which points |
+# to the base directory. |
+# |
+ |
+sub write_html_prolog(*$$) |
+{ |
+ my $basedir = $_[1]; |
+ my $pagetitle = $_[2]; |
+ my $prolog; |
+ |
+ $prolog = $html_prolog; |
+ $prolog =~ s/\@pagetitle\@/$pagetitle/g; |
+ $prolog =~ s/\@basedir\@/$basedir/g; |
+ |
+ write_html($_[0], $prolog); |
+} |
+ |
+ |
+# |
+# write_header_prolog(filehandle, base_dir) |
+# |
+# Write beginning of page header HTML code. |
+# |
+ |
+sub write_header_prolog(*$) |
+{ |
+ # ************************************************************* |
+ |
+ write_html($_[0], <<END_OF_HTML) |
+ <table width="100%" border=0 cellspacing=0 cellpadding=0> |
+ <tr><td class="title">$title</td></tr> |
+ <tr><td class="ruler"><img src="$_[1]glass.png" width=3 height=3 alt=""></td></tr> |
+ |
+ <tr> |
+ <td width="100%"> |
+ <table cellpadding=1 border=0 width="100%"> |
+END_OF_HTML |
+ ; |
+ |
+ # ************************************************************* |
+} |
+ |
+ |
+# |
+# write_header_line(filehandle, type, additional params..) |
+# |
+# Write a header line. |
+# |
+ |
+sub write_header_line(*$@) |
+{ |
+ my $HANDLE = shift; |
+ my $type = shift; |
+ my @args = @_; |
+ |
+ # Reduce indentation by using gotos |
+ if ($type eq 0) { |
+ goto header; |
+ } elsif ($type eq 1) { |
+ goto body; |
+ } elsif ($type eq 2) { |
+ goto legend_dir; |
+ } elsif ($type eq 3) { |
+ goto legend_source; |
+ } elsif ($type eq 4) { |
+ goto half_body; |
+ } |
+ |
+header: |
+ # ************************************************************* |
+ write_html($HANDLE, <<END_OF_HTML); |
+ <tr> |
+ <td width="5%"></td> |
+ <td width="10%" class="headerItem">$args[0]</td> |
+ <td width="35%" class="headerValue">$args[1]</td> |
+ <td width="10%"></td> |
+ <td width="10%" class="headerCovTableHead">$args[2]</td> |
+ <td width="10%" class="headerCovTableHead">$args[3]</td> |
+ <td width="15%" class="headerCovTableHead">$args[4]</td> |
+ <td width="5%"></td> |
+ </tr> |
+END_OF_HTML |
+ # ************************************************************* |
+ return; |
+ |
+body: |
+ # ************************************************************* |
+ write_html($HANDLE, <<END_OF_HTML); |
+ <tr> |
+ <td></td> |
+ <td class="headerItem">$args[0]</td> |
+ <td class="headerValue">$args[1]</td> |
+ <td class="headerItem">$args[2]</td> |
+ <td class="headerCovTableEntry">$args[3]</td> |
+ <td class="headerCovTableEntry">$args[4]</td> |
+ <td class="headerCovTableEntry$args[5]">$args[6]</td> |
+ </tr> |
+END_OF_HTML |
+ # ************************************************************* |
+ return; |
+ |
+half_body: |
+ # ************************************************************* |
+ write_html($HANDLE, <<END_OF_HTML); |
+ <tr> |
+ <td></td> |
+ <td class="headerItem">$args[0]</td> |
+ <td class="headerValue">$args[1]</td> |
+ </tr> |
+END_OF_HTML |
+ # ************************************************************* |
+ return; |
+ |
+legend_dir: |
+ # ************************************************************* |
+ write_html($HANDLE, <<END_OF_HTML); |
+ <tr> |
+ <td></td> |
+ <td class="headerItemLeg">$args[0]</td> |
+ <td class="headerValueLeg"> |
+$args[1] </td> |
+ <td></td> |
+ <td class="headerValueLeg" colspan=3> |
+$args[2] </td> |
+ </tr> |
+END_OF_HTML |
+ # ************************************************************* |
+ return; |
+ |
+legend_source: |
+ # ************************************************************* |
+ write_html($HANDLE, <<END_OF_HTML); |
+ <tr> |
+ <td></td> |
+ <td class="headerItem">$args[0]</td> |
+ <td class="headerValueLeg" colspan=5> |
+ <span class="coverLegendNoCov">$args[1]</span> |
+ <span class="coverLegendCov">$args[2]</span> |
+ </td> |
+ </tr> |
+END_OF_HTML |
+ # ************************************************************* |
+} |
+ |
+ |
+# |
+# write_header_epilog(filehandle, base_dir) |
+# |
+# Write end of page header HTML code. |
+# |
+ |
+sub write_header_epilog(*$) |
+{ |
+ # ************************************************************* |
+ |
+ write_html($_[0], <<END_OF_HTML) |
+ <tr><td><img src="$_[1]glass.png" width=3 height=3 alt=""></td></tr> |
+ </table> |
+ </td> |
+ </tr> |
+ <tr><td class="ruler"><img src="$_[1]glass.png" width=3 height=3 alt=""></td></tr> |
+ </table> |
+ |
+END_OF_HTML |
+ ; |
+ |
+ # ************************************************************* |
+} |
+ |
+ |
+# |
+# write_file_table_prolog(filehandle, file_heading, lines_heading, func_heading) |
+# |
+# Write heading for file table. |
+# |
+ |
+sub write_file_table_prolog(*$$$) |
+{ |
+ # ************************************************************* |
+ |
+ if ($func_coverage) |
+ { |
+ write_html($_[0], <<END_OF_HTML) |
+ <center> |
+ <table width="80%" cellpadding=1 cellspacing=1 border=0> |
+ |
+ <tr> |
+ <td width="45%"><br></td> |
+ <td width="15%"></td> |
+ <td width="10%"></td> |
+ <td width="10%"></td> |
+ <td width="10%"></td> |
+ <td width="10%"></td> |
+ </tr> |
+ |
+ <tr> |
+ <td class="tableHead">$_[1]</td> |
+ <td class="tableHead" colspan=3>$_[2]</td> |
+ <td class="tableHead" colspan=2>$_[3]</td> |
+ </tr> |
+ |
+END_OF_HTML |
+ ; |
+ } |
+ else |
+ { |
+ write_html($_[0], <<END_OF_HTML) |
+ <center> |
+ <table width="80%" cellpadding=1 cellspacing=1 border=0> |
+ |
+ <tr> |
+ <td width="50%"><br></td> |
+ <td width="15%"></td> |
+ <td width="15%"></td> |
+ <td width="20%"></td> |
+ </tr> |
+ |
+ <tr> |
+ <td class="tableHead">$_[1]</td> |
+ <td class="tableHead" colspan=3>$_[2]</td> |
+ </tr> |
+ |
+END_OF_HTML |
+ ; |
+ } |
+ |
+ # ************************************************************* |
+} |
+ |
+ |
+# |
+# write_file_table_entry(filehandle, cover_filename, cover_bar_graph, |
+# cover_found, cover_hit, fn_found, fn_hit, |
+# page_link, func_link) |
+# |
+# Write an entry of the file table. |
+# |
+ |
+sub write_file_table_entry(*$$$$$$$) |
+{ |
+ local *HANDLE = shift; |
+ my ($filename, $bar_graph, $found, $hit, $fn_found, $fn_hit, |
+ $page_link) = @_; |
+ my $rate; |
+ my $rate_string; |
+ my $funcs_string; |
+ my $class_lines = "Lo"; |
+ my $class_funcs = "Hi"; |
+ my $file_code; |
+ |
+ # Add link to source if provided |
+ if (defined($page_link) && $page_link ne "") { |
+ $file_code = "<a href=\"$page_link\">$filename</a>"; |
+ } else { |
+ $file_code = $filename; |
+ } |
+ |
+ # Get line coverage rate |
+ if ($found > 0) |
+ { |
+ $rate = $hit * 100 / $found; |
+ $rate_string = sprintf("%.1f", $rate)." %"; |
+ |
+ $class_lines = $rate_name[classify_rate($found, $hit, |
+ $med_limit, $hi_limit)]; |
+ } |
+ else |
+ { |
+ $rate_string = "-"; |
+ } |
+ |
+ # Get function coverage rate |
+ if ($fn_found > 0) |
+ { |
+ $rate = $fn_hit * 100 / $fn_found; |
+ $class_funcs = $rate_name[classify_rate($fn_found, $fn_hit, |
+ $fn_med_limit, $fn_hi_limit)]; |
+ $funcs_string = sprintf("%.1f", $rate)." %"; |
+ } |
+ else |
+ { |
+ # Define 0 of 0 functions as 100% |
+ $rate = 100; |
+ $funcs_string = "-"; |
+ } |
+ |
+ # ************************************************************* |
+ |
+ write_html(*HANDLE, <<END_OF_HTML) |
+ <tr> |
+ <td class="coverFile">$file_code</td> |
+ <td class="coverBar" align="center"> |
+ $bar_graph |
+ </td> |
+ <td class="coverPer$class_lines">$rate_string</td> |
+ <td class="coverNum$class_lines">$hit / $found</td> |
+END_OF_HTML |
+ ; |
+ |
+ if ($func_coverage) |
+ { |
+ write_html(*HANDLE, <<END_OF_HTML) |
+ <td class="coverPer$class_funcs">$funcs_string</td> |
+ <td class="coverNum$class_funcs">$fn_hit / $fn_found</td> |
+END_OF_HTML |
+ ; |
+ } |
+ write_html(*HANDLE, <<END_OF_HTML) |
+ </tr> |
+END_OF_HTML |
+ ; |
+ |
+ # ************************************************************* |
+} |
+ |
+ |
+# |
+# write_file_table_detail_heading(filehandle, left_heading, right_heading) |
+# |
+# Write heading for detail section in file table. |
+# |
+ |
+sub write_file_table_detail_heading(*$$$) |
+{ |
+ my $func_rows = ""; |
+ |
+ if ($func_coverage) |
+ { |
+ $func_rows = "<td class=\"testLinesHead\" colspan=2>$_[3]</td>"; |
+ } |
+ |
+ # ************************************************************* |
+ write_html($_[0], <<END_OF_HTML) |
+ <tr> |
+ <td class="testNameHead" colspan=2>$_[1]</td> |
+ <td class="testLinesHead" colspan=2>$_[2]</td> |
+ $func_rows |
+ </tr> |
+ |
+END_OF_HTML |
+ ; |
+ |
+ # ************************************************************* |
+} |
+ |
+ |
+# |
+# write_file_table_detail_entry(filehandle, test_name, |
+# cover_found, cover_hit, func_found, func_hit) |
+# |
+# Write entry for detail section in file table. |
+# |
+ |
+sub write_file_table_detail_entry(*$$$$$) |
+{ |
+ my $rate; |
+ my $func_rate; |
+ my $name = $_[1]; |
+ |
+ if ($_[2]>0) |
+ { |
+ $rate = sprintf("%.1f", $_[3]*100/$_[2])." %"; |
+ } |
+ else |
+ { |
+ $rate = "-"; |
+ } |
+ |
+ if ($_[4]>0) |
+ { |
+ $func_rate = sprintf("%.1f", $_[5]*100/$_[4])." %"; |
+ } |
+ else |
+ { |
+ $func_rate = "-"; |
+ } |
+ |
+ if ($name =~ /^(.*),diff$/) |
+ { |
+ $name = $1." (converted)"; |
+ } |
+ |
+ if ($name eq "") |
+ { |
+ $name = "<span style=\"font-style:italic\"><unnamed></span>"; |
+ } |
+ |
+ # ************************************************************* |
+ |
+ write_html($_[0], <<END_OF_HTML) |
+ <tr> |
+ <td class="testName" colspan=2>$name</td> |
+ <td class="testPer">$rate</td> |
+ <td class="testNum">$_[3] / $_[2] lines</td> |
+END_OF_HTML |
+ ; |
+ if ($func_coverage) |
+ { |
+ write_html($_[0], <<END_OF_HTML) |
+ <td class="testPer">$func_rate</td> |
+ <td class="testNum">$_[5] / $_[4]</td> |
+END_OF_HTML |
+ ; |
+ } |
+ write_html($_[0], <<END_OF_HTML) |
+ </tr> |
+ |
+END_OF_HTML |
+ ; |
+ |
+ # ************************************************************* |
+} |
+ |
+ |
+# |
+# write_file_table_epilog(filehandle) |
+# |
+# Write end of file table HTML code. |
+# |
+ |
+sub write_file_table_epilog(*) |
+{ |
+ # ************************************************************* |
+ |
+ write_html($_[0], <<END_OF_HTML) |
+ </table> |
+ </center> |
+ <br> |
+ |
+END_OF_HTML |
+ ; |
+ |
+ # ************************************************************* |
+} |
+ |
+ |
+# |
+# write_test_table_prolog(filehandle, table_heading) |
+# |
+# Write heading for test case description table. |
+# |
+ |
+sub write_test_table_prolog(*$) |
+{ |
+ # ************************************************************* |
+ |
+ write_html($_[0], <<END_OF_HTML) |
+ <center> |
+ <table width="80%" cellpadding=2 cellspacing=1 border=0> |
+ |
+ <tr> |
+ <td><br></td> |
+ </tr> |
+ |
+ <tr> |
+ <td class="tableHead">$_[1]</td> |
+ </tr> |
+ |
+ <tr> |
+ <td class="testDescription"> |
+ <dl> |
+END_OF_HTML |
+ ; |
+ |
+ # ************************************************************* |
+} |
+ |
+ |
+# |
+# write_test_table_entry(filehandle, test_name, test_description) |
+# |
+# Write entry for the test table. |
+# |
+ |
+sub write_test_table_entry(*$$) |
+{ |
+ # ************************************************************* |
+ |
+ write_html($_[0], <<END_OF_HTML) |
+ <dt>$_[1]<a name="$_[1]"> </a></dt> |
+ <dd>$_[2]<br><br></dd> |
+END_OF_HTML |
+ ; |
+ |
+ # ************************************************************* |
+} |
+ |
+ |
+# |
+# write_test_table_epilog(filehandle) |
+# |
+# Write end of test description table HTML code. |
+# |
+ |
+sub write_test_table_epilog(*) |
+{ |
+ # ************************************************************* |
+ |
+ write_html($_[0], <<END_OF_HTML) |
+ </dl> |
+ </td> |
+ </tr> |
+ </table> |
+ </center> |
+ <br> |
+ |
+END_OF_HTML |
+ ; |
+ |
+ # ************************************************************* |
+} |
+ |
+ |
+# |
+# write_source_prolog(filehandle) |
+# |
+# Write start of source code table. |
+# |
+ |
+sub write_source_prolog(*) |
+{ |
+ # ************************************************************* |
+ |
+ write_html($_[0], <<END_OF_HTML) |
+ <table cellpadding=0 cellspacing=0 border=0> |
+ <tr> |
+ <td><br></td> |
+ </tr> |
+ <tr> |
+ <td><pre class="source"> |
+END_OF_HTML |
+ ; |
+ |
+ # ************************************************************* |
+} |
+ |
+ |
+# |
+# write_source_line(filehandle, line_num, source, hit_count, converted, |
+# add_anchor) |
+# |
+# Write formatted source code line. Return a line in a format as needed |
+# by gen_png() |
+# |
+ |
+sub write_source_line(*$$$$$) |
+{ |
+ my $source_format; |
+ my $count; |
+ my $result; |
+ my $anchor_start = ""; |
+ my $anchor_end = ""; |
+ |
+ if (!(defined$_[3])) |
+ { |
+ $result = ""; |
+ $source_format = ""; |
+ $count = " "x15; |
+ } |
+ elsif ($_[3] == 0) |
+ { |
+ $result = $_[3]; |
+ $source_format = '<span class="lineNoCov">'; |
+ $count = sprintf("%15d", $_[3]); |
+ } |
+ elsif ($_[4] && defined($highlight)) |
+ { |
+ $result = "*".$_[3]; |
+ $source_format = '<span class="lineDiffCov">'; |
+ $count = sprintf("%15d", $_[3]); |
+ } |
+ else |
+ { |
+ $result = $_[3]; |
+ $source_format = '<span class="lineCov">'; |
+ $count = sprintf("%15d", $_[3]); |
+ } |
+ |
+ $result .= ":".$_[2]; |
+ |
+ # Write out a line number navigation anchor every $nav_resolution |
+ # lines if necessary |
+ if ($_[5]) |
+ { |
+ $anchor_start = "<a name=\"$_[1]\">"; |
+ $anchor_end = "</a>"; |
+ } |
+ |
+ |
+ # ************************************************************* |
+ |
+ write_html($_[0], |
+ $anchor_start. |
+ '<span class="lineNum">'.sprintf("%8d", $_[1]). |
+ " </span>$source_format$count : ". |
+ escape_html($_[2]).($source_format?"</span>":""). |
+ $anchor_end."\n"); |
+ |
+ # ************************************************************* |
+ |
+ return($result); |
+} |
+ |
+ |
+# |
+# write_source_epilog(filehandle) |
+# |
+# Write end of source code table. |
+# |
+ |
+sub write_source_epilog(*) |
+{ |
+ # ************************************************************* |
+ |
+ write_html($_[0], <<END_OF_HTML) |
+ </pre> |
+ </td> |
+ </tr> |
+ </table> |
+ <br> |
+ |
+END_OF_HTML |
+ ; |
+ |
+ # ************************************************************* |
+} |
+ |
+ |
+# |
+# write_html_epilog(filehandle, base_dir[, break_frames]) |
+# |
+# Write HTML page footer to FILEHANDLE. BREAK_FRAMES should be set when |
+# this page is embedded in a frameset, clicking the URL link will then |
+# break this frameset. |
+# |
+ |
+sub write_html_epilog(*$;$) |
+{ |
+ my $basedir = $_[1]; |
+ my $break_code = ""; |
+ my $epilog; |
+ |
+ if (defined($_[2])) |
+ { |
+ $break_code = " target=\"_parent\""; |
+ } |
+ |
+ # ************************************************************* |
+ |
+ write_html($_[0], <<END_OF_HTML) |
+ <table width="100%" border=0 cellspacing=0 cellpadding=0> |
+ <tr><td class="ruler"><img src="$_[1]glass.png" width=3 height=3 alt=""></td></tr> |
+ <tr><td class="versionInfo">Generated by: <a href="$lcov_url"$break_code>$lcov_version</a></td></tr> |
+ </table> |
+ <br> |
+END_OF_HTML |
+ ; |
+ |
+ $epilog = $html_epilog; |
+ $epilog =~ s/\@basedir\@/$basedir/g; |
+ |
+ write_html($_[0], $epilog); |
+} |
+ |
+ |
+# |
+# write_frameset(filehandle, basedir, basename, pagetitle) |
+# |
+# |
+ |
+sub write_frameset(*$$$) |
+{ |
+ my $frame_width = $overview_width + 40; |
+ |
+ # ************************************************************* |
+ |
+ write_html($_[0], <<END_OF_HTML) |
+ <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Frameset//EN"> |
+ |
+ <html lang="en"> |
+ |
+ <head> |
+ <meta http-equiv="Content-Type" content="text/html; charset=ISO-8859-1"> |
+ <title>$_[3]</title> |
+ <link rel="stylesheet" type="text/css" href="$_[1]gcov.css"> |
+ </head> |
+ |
+ <frameset cols="$frame_width,*"> |
+ <frame src="$_[2].gcov.overview.$html_ext" name="overview"> |
+ <frame src="$_[2].gcov.$html_ext" name="source"> |
+ <noframes> |
+ <center>Frames not supported by your browser!<br></center> |
+ </noframes> |
+ </frameset> |
+ |
+ </html> |
+END_OF_HTML |
+ ; |
+ |
+ # ************************************************************* |
+} |
+ |
+ |
+# |
+# sub write_overview_line(filehandle, basename, line, link) |
+# |
+# |
+ |
+sub write_overview_line(*$$$) |
+{ |
+ my $y1 = $_[2] - 1; |
+ my $y2 = $y1 + $nav_resolution - 1; |
+ my $x2 = $overview_width - 1; |
+ |
+ # ************************************************************* |
+ |
+ write_html($_[0], <<END_OF_HTML) |
+ <area shape="rect" coords="0,$y1,$x2,$y2" href="$_[1].gcov.$html_ext#$_[3]" target="source" alt="overview"> |
+END_OF_HTML |
+ ; |
+ |
+ # ************************************************************* |
+} |
+ |
+ |
+# |
+# write_overview(filehandle, basedir, basename, pagetitle, lines) |
+# |
+# |
+ |
+sub write_overview(*$$$$) |
+{ |
+ my $index; |
+ my $max_line = $_[4] - 1; |
+ my $offset; |
+ |
+ # ************************************************************* |
+ |
+ write_html($_[0], <<END_OF_HTML) |
+ <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN"> |
+ |
+ <html lang="en"> |
+ |
+ <head> |
+ <title>$_[3]</title> |
+ <meta http-equiv="Content-Type" content="text/html; charset=ISO-8859-1"> |
+ <link rel="stylesheet" type="text/css" href="$_[1]gcov.css"> |
+ </head> |
+ |
+ <body> |
+ <map name="overview"> |
+END_OF_HTML |
+ ; |
+ |
+ # ************************************************************* |
+ |
+ # Make $offset the next higher multiple of $nav_resolution |
+ $offset = ($nav_offset + $nav_resolution - 1) / $nav_resolution; |
+ $offset = sprintf("%d", $offset ) * $nav_resolution; |
+ |
+ # Create image map for overview image |
+ for ($index = 1; $index <= $_[4]; $index += $nav_resolution) |
+ { |
+ # Enforce nav_offset |
+ if ($index < $offset + 1) |
+ { |
+ write_overview_line($_[0], $_[2], $index, 1); |
+ } |
+ else |
+ { |
+ write_overview_line($_[0], $_[2], $index, $index - $offset); |
+ } |
+ } |
+ |
+ # ************************************************************* |
+ |
+ write_html($_[0], <<END_OF_HTML) |
+ </map> |
+ |
+ <center> |
+ <a href="$_[2].gcov.$html_ext#top" target="source">Top</a><br><br> |
+ <img src="$_[2].gcov.png" width=$overview_width height=$max_line alt="Overview" border=0 usemap="#overview"> |
+ </center> |
+ </body> |
+ </html> |
+END_OF_HTML |
+ ; |
+ |
+ # ************************************************************* |
+} |
+ |
+ |
+# rate_to_col(found, hit) |
+# |
+# Return Lo, Med or Hi, depending on the coverage rate. |
+# |
+ |
+sub rate_to_col($$) |
+{ |
+ my ($found, $hit) = @_; |
+ my $rate; |
+ |
+ if ($found == 0) { |
+ return "Hi"; |
+ } |
+ $rate = 100 * $hit / $found; |
+ if ($rate < $med_limit) { |
+ return "Lo"; |
+ } elsif ($rate < $hi_limit) { |
+ return "Med"; |
+ } |
+ return "Hi"; |
+} |
+ |
+# format_rate(found, hit) |
+# |
+# Return formatted percent string for coverage rate. |
+# |
+ |
+sub format_rate($$) |
+{ |
+ return $_[0] == 0 ? "-" : sprintf("%.1f", $_[1] * 100 / $_[0])." %"; |
+} |
+ |
+sub get_legend_code($$$) |
+{ |
+ my ($text, $med, $hi) = @_; |
+ my $result; |
+ |
+ $result = <<EOF; |
+ $text<br> |
+ <span class="coverLegendLo">0% to $med%</span> |
+ <span class="coverLegendMed">$med% to $hi%</span> |
+ <span class="coverLegendHi">$hi% to 100%</span> |
+EOF |
+ return $result; |
+} |
+ |
+# |
+# write_header(filehandle, type, trunc_file_name, rel_file_name, lines_found, |
+# lines_hit, funcs_found, funcs_hit, sort_type) |
+# |
+# Write a complete standard page header. TYPE may be (0, 1, 2, 3, 4) |
+# corresponding to (directory view header, file view header, source view |
+# header, test case description header, function view header) |
+# |
+ |
+sub write_header(*$$$$$$$$) |
+{ |
+ local *HTML_HANDLE = $_[0]; |
+ my $type = $_[1]; |
+ my $trunc_name = $_[2]; |
+ my $rel_filename = $_[3]; |
+ my $lines_found = $_[4]; |
+ my $lines_hit = $_[5]; |
+ my $fn_found = $_[6]; |
+ my $fn_hit = $_[7]; |
+ my $sort_type = $_[8]; |
+ my $base_dir; |
+ my $view; |
+ my $test; |
+ my $base_name; |
+ |
+ $base_name = basename($rel_filename); |
+ |
+ # Prepare text for "current view" field |
+ if ($type == 0) |
+ { |
+ # Main overview |
+ $base_dir = ""; |
+ $view = $overview_title; |
+ } |
+ elsif ($type == 1) |
+ { |
+ # Directory overview |
+ $base_dir = get_relative_base_path($rel_filename); |
+ $view = "<a href=\"$base_dir"."index.$html_ext\">". |
+ "$overview_title</a> - $trunc_name"; |
+ } |
+ elsif ($type == 2 || $type == 4) |
+ { |
+ # File view |
+ my $dir_name = dirname($rel_filename); |
+ |
+ $base_dir = get_relative_base_path($dir_name); |
+ if ($frames) |
+ { |
+ # Need to break frameset when clicking any of these |
+ # links |
+ $view = "<a href=\"$base_dir"."index.$html_ext\" ". |
+ "target=\"_parent\">$overview_title</a> - ". |
+ "<a href=\"index.$html_ext\" target=\"_parent\">". |
+ "$dir_name</a> - $base_name"; |
+ } |
+ else |
+ { |
+ $view = "<a href=\"$base_dir"."index.$html_ext\">". |
+ "$overview_title</a> - ". |
+ "<a href=\"index.$html_ext\">". |
+ "$dir_name</a> - $base_name"; |
+ } |
+ |
+ # Add function suffix |
+ if ($func_coverage) { |
+ if ($type == 2) { |
+ $view .= " (source / <a href=\"$base_name.func.$html_ext\">functions</a>)"; |
+ } elsif ($type == 4) { |
+ $view .= " (<a href=\"$base_name.gcov.$html_ext\">source</a> / functions)"; |
+ } |
+ } |
+ } |
+ elsif ($type == 3) |
+ { |
+ # Test description header |
+ $base_dir = ""; |
+ $view = "<a href=\"$base_dir"."index.$html_ext\">". |
+ "$overview_title</a> - test case descriptions"; |
+ } |
+ |
+ # Prepare text for "test" field |
+ $test = escape_html($test_title); |
+ |
+ # Append link to test description page if available |
+ if (%test_description && ($type != 3)) |
+ { |
+ if ($frames && ($type == 2 || $type == 4)) |
+ { |
+ # Need to break frameset when clicking this link |
+ $test .= " ( <a href=\"$base_dir". |
+ "descriptions.$html_ext\" target=\"_parent\">". |
+ "view descriptions</a> )"; |
+ } |
+ else |
+ { |
+ $test .= " ( <a href=\"$base_dir". |
+ "descriptions.$html_ext\">". |
+ "view descriptions</a> )"; |
+ } |
+ } |
+ |
+ # Write header |
+ write_header_prolog(*HTML_HANDLE, $base_dir); |
+ write_header_line(*HTML_HANDLE, 0, "Current view:", $view, |
+ "Found", "Hit", "Coverage"); |
+ write_header_line(*HTML_HANDLE, 1, "Test:", $test, "Lines:", |
+ $lines_found, $lines_hit, |
+ $rate_name[classify_rate($lines_found, $lines_hit, |
+ $med_limit, $hi_limit)], |
+ format_rate($lines_found, $lines_hit)); |
+ if ($func_coverage) { |
+ write_header_line(*HTML_HANDLE, 1, "Date:", $date, "Functions:", |
+ $fn_found, $fn_hit, |
+ $rate_name[classify_rate($fn_found, |
+ $fn_hit, |
+ $fn_med_limit, |
+ $fn_hi_limit)], |
+ format_rate($fn_found, $fn_hit)); |
+ } else { |
+ write_header_line(*HTML_HANDLE, 4, "Date:", $date); |
+ } |
+ if ($legend) { |
+ if ($type == 0 || $type == 1) { |
+ my $line_code = get_legend_code("Line coverage:", |
+ $med_limit, $hi_limit); |
+ my $func_code = ""; |
+ |
+ if ($func_coverage) { |
+ $func_code = get_legend_code( |
+ "Function coverage:", |
+ $fn_med_limit, |
+ $fn_hi_limit); |
+ } |
+ write_header_line(*HTML_HANDLE, 2, "Colors:", |
+ $line_code, $func_code); |
+ } elsif ($type == 2 || $type == 4) { |
+ write_header_line(*HTML_HANDLE, 3, "Colors:", |
+ "not hit", "hit"); |
+ } |
+ } |
+ write_header_epilog(*HTML_HANDLE, $base_dir); |
+} |
+ |
+ |
+# |
+# split_filename(filename) |
+# |
+# Return (path, filename, extension) for a given FILENAME. |
+# |
+ |
+sub split_filename($) |
+{ |
+ if (!$_[0]) { return(); } |
+ my @path_components = split('/', $_[0]); |
+ my @file_components = split('\.', pop(@path_components)); |
+ my $extension = pop(@file_components); |
+ |
+ return (join("/",@path_components), join(".",@file_components), |
+ $extension); |
+} |
+ |
+# |
+# get_sorted_keys(hash_ref, sort_type) |
+# |
+ |
+sub get_sorted_keys($$) |
+{ |
+ my ($hash, $type) = @_; |
+ |
+ if ($type == 0) { |
+ # Sort by name |
+ return sort(keys(%{$hash})); |
+ } elsif ($type == 1) { |
+ # Sort by line coverage |
+ return sort({$hash->{$a}[5] <=> $hash->{$b}[5]} keys(%{$hash})); |
+ } elsif ($type == 2) { |
+ # Sort by function coverage; |
+ return sort({$hash->{$a}[6] <=> $hash->{$b}[6]} keys(%{$hash})); |
+ } |
+} |
+ |
+sub get_sort_code($$$) |
+{ |
+ my ($link, $alt, $base) = @_; |
+ my $png; |
+ my $link_start; |
+ my $link_end; |
+ |
+ if (!defined($link)) { |
+ $png = "glass.png"; |
+ $link_start = ""; |
+ $link_end = ""; |
+ } else { |
+ $png = "updown.png"; |
+ $link_start = '<a href="'.$link.'">'; |
+ $link_end = "</a>"; |
+ } |
+ |
+ return ' <span class="tableHeadSort">'.$link_start. |
+ '<img src="'.$base.$png.'" width=10 height=14 '. |
+ 'alt="'.$alt.'" title="'.$alt.'" border=0>'.$link_end.'</span>'; |
+} |
+ |
+sub get_file_code($$$$) |
+{ |
+ my ($type, $text, $sort_button, $base) = @_; |
+ my $result = $text; |
+ my $link; |
+ |
+ if ($sort_button) { |
+ if ($type == 1) { |
+ $link = "index.$html_ext"; |
+ } else { |
+ $link = "index-detail.$html_ext"; |
+ } |
+ } |
+ $result .= get_sort_code($link, "Sort by name", $base); |
+ |
+ return $result; |
+} |
+ |
+sub get_line_code($$$$$) |
+{ |
+ my ($type, $sort_type, $text, $sort_button, $base) = @_; |
+ my $result = $text; |
+ my $sort_link; |
+ |
+ if ($type == 1) { |
+ # Just text |
+ if ($sort_button) { |
+ $sort_link = "index-sort-l.$html_ext"; |
+ } |
+ } elsif ($type == 2) { |
+ # Text + link to detail view |
+ $result .= ' ( <a class="detail" href="index-detail'. |
+ $fileview_sortname[$sort_type].'.'.$html_ext. |
+ '">show details</a> )'; |
+ if ($sort_button) { |
+ $sort_link = "index-sort-l.$html_ext"; |
+ } |
+ } else { |
+ # Text + link to standard view |
+ $result .= ' ( <a class="detail" href="index'. |
+ $fileview_sortname[$sort_type].'.'.$html_ext. |
+ '">hide details</a> )'; |
+ if ($sort_button) { |
+ $sort_link = "index-detail-sort-l.$html_ext"; |
+ } |
+ } |
+ # Add sort button |
+ $result .= get_sort_code($sort_link, "Sort by line coverage", $base); |
+ |
+ return $result; |
+} |
+ |
+sub get_func_code($$$$) |
+{ |
+ my ($type, $text, $sort_button, $base) = @_; |
+ my $result = $text; |
+ my $link; |
+ |
+ if ($sort_button) { |
+ if ($type == 1) { |
+ $link = "index-sort-f.$html_ext"; |
+ } else { |
+ $link = "index-detail-sort-f.$html_ext"; |
+ } |
+ } |
+ $result .= get_sort_code($link, "Sort by function coverage", $base); |
+ return $result; |
+} |
+ |
+# |
+# write_file_table(filehandle, base_dir, overview, testhash, testfnchash, |
+# fileview, sort_type) |
+# |
+# Write a complete file table. OVERVIEW is a reference to a hash containing |
+# the following mapping: |
+# |
+# filename -> "lines_found,lines_hit,funcs_found,funcs_hit,page_link, |
+# func_link" |
+# |
+# TESTHASH is a reference to the following hash: |
+# |
+# filename -> \%testdata |
+# %testdata: name of test affecting this file -> \%testcount |
+# %testcount: line number -> execution count for a single test |
+# |
+# Heading of first column is "Filename" if FILEVIEW is true, "Directory name" |
+# otherwise. |
+# |
+ |
+sub write_file_table(*$$$$$$) |
+{ |
+ local *HTML_HANDLE = $_[0]; |
+ my $base_dir = $_[1]; |
+ my $overview = $_[2]; |
+ my $testhash = $_[3]; |
+ my $testfnchash = $_[4]; |
+ my $fileview = $_[5]; |
+ my $sort_type = $_[6]; |
+ my $filename; |
+ my $bar_graph; |
+ my $hit; |
+ my $found; |
+ my $fn_found; |
+ my $fn_hit; |
+ my $page_link; |
+ my $testname; |
+ my $testdata; |
+ my $testfncdata; |
+ my $testcount; |
+ my $testfnccount; |
+ my %affecting_tests; |
+ my $line_code = ""; |
+ my $func_code; |
+ my $file_code; |
+ |
+ # Determine HTML code for column headings |
+ if (($base_dir ne "") && $show_details) |
+ { |
+ my $detailed = keys(%{$testhash}); |
+ |
+ $file_code = get_file_code($detailed ? 2 : 1, |
+ $fileview ? "Filename" : "Directory", |
+ $sort && $sort_type != 0, $base_dir); |
+ $line_code = get_line_code($detailed ? 3 : 2, $sort_type, |
+ "Line Coverage", |
+ $sort && $sort_type != 1, $base_dir); |
+ $func_code = get_func_code($detailed ? 2 : 1, "Functions", |
+ $sort && $sort_type != 2, $base_dir); |
+ } else { |
+ $file_code = get_file_code(1, |
+ $fileview ? "Filename" : "Directory", |
+ $sort && $sort_type != 0, $base_dir); |
+ $line_code = get_line_code(1, $sort_type, "Line Coverage", |
+ $sort && $sort_type != 1, $base_dir); |
+ $func_code = get_func_code(1, "Functions", |
+ $sort && $sort_type != 2, $base_dir); |
+ } |
+ |
+ write_file_table_prolog(*HTML_HANDLE, $file_code, $line_code, |
+ $func_code); |
+ |
+ foreach $filename (get_sorted_keys($overview, $sort_type)) |
+ { |
+ ($found, $hit, $fn_found, $fn_hit, $page_link) |
+ = @{$overview->{$filename}}; |
+ $bar_graph = get_bar_graph_code($base_dir, $found, $hit); |
+ |
+ $testdata = $testhash->{$filename}; |
+ $testfncdata = $testfnchash->{$filename}; |
+ |
+ write_file_table_entry(*HTML_HANDLE, $filename, $bar_graph, |
+ $found, $hit, $fn_found, $fn_hit, |
+ $page_link); |
+ |
+ # Check whether we should write test specific coverage |
+ # as well |
+ if (!($show_details && $testdata)) { next; } |
+ |
+ # Filter out those tests that actually affect this file |
+ %affecting_tests = %{ get_affecting_tests($testdata, |
+ $testfncdata) }; |
+ |
+ # Does any of the tests affect this file at all? |
+ if (!%affecting_tests) { next; } |
+ |
+ # Write test details for this entry |
+ write_file_table_detail_heading(*HTML_HANDLE, "Test name", |
+ "Lines hit", "Functions hit"); |
+ |
+ foreach $testname (keys(%affecting_tests)) |
+ { |
+ ($found, $hit, $fn_found, $fn_hit) = |
+ split(",", $affecting_tests{$testname}); |
+ |
+ # Insert link to description of available |
+ if ($test_description{$testname}) |
+ { |
+ $testname = "<a href=\"$base_dir". |
+ "descriptions.$html_ext#$testname\">". |
+ "$testname</a>"; |
+ } |
+ |
+ write_file_table_detail_entry(*HTML_HANDLE, $testname, |
+ $found, $hit, $fn_found, $fn_hit); |
+ } |
+ } |
+ |
+ write_file_table_epilog(*HTML_HANDLE); |
+} |
+ |
+ |
+# |
+# get_found_and_hit(hash) |
+# |
+# Return the count for entries (found) and entries with an execution count |
+# greater than zero (hit) in a hash (linenumber -> execution count) as |
+# a list (found, hit) |
+# |
+ |
+sub get_found_and_hit($) |
+{ |
+ my %hash = %{$_[0]}; |
+ my $found = 0; |
+ my $hit = 0; |
+ |
+ # Calculate sum |
+ $found = 0; |
+ $hit = 0; |
+ |
+ foreach (keys(%hash)) |
+ { |
+ $found++; |
+ if ($hash{$_}>0) { $hit++; } |
+ } |
+ |
+ return ($found, $hit); |
+} |
+ |
+ |
+# |
+# get_func_found_and_hit(sumfnccount) |
+# |
+# Return (f_found, f_hit) for sumfnccount |
+# |
+ |
+sub get_func_found_and_hit($) |
+{ |
+ my ($sumfnccount) = @_; |
+ my $function; |
+ my $fn_found; |
+ my $fn_hit; |
+ |
+ $fn_found = scalar(keys(%{$sumfnccount})); |
+ $fn_hit = 0; |
+ foreach $function (keys(%{$sumfnccount})) { |
+ if ($sumfnccount->{$function} > 0) { |
+ $fn_hit++; |
+ } |
+ } |
+ return ($fn_found, $fn_hit); |
+} |
+ |
+ |
+# |
+# get_affecting_tests(testdata, testfncdata) |
+# |
+# HASHREF contains a mapping filename -> (linenumber -> exec count). Return |
+# a hash containing mapping filename -> "lines found, lines hit" for each |
+# filename which has a nonzero hit count. |
+# |
+ |
+sub get_affecting_tests($$) |
+{ |
+ my $testdata = $_[0]; |
+ my $testfncdata = $_[1]; |
+ my $testname; |
+ my $testcount; |
+ my $testfnccount; |
+ my %result; |
+ my $found; |
+ my $hit; |
+ my $fn_found; |
+ my $fn_hit; |
+ |
+ foreach $testname (keys(%{$testdata})) |
+ { |
+ # Get (line number -> count) hash for this test case |
+ $testcount = $testdata->{$testname}; |
+ $testfnccount = $testfncdata->{$testname}; |
+ |
+ # Calculate sum |
+ ($found, $hit) = get_found_and_hit($testcount); |
+ ($fn_found, $fn_hit) = get_func_found_and_hit($testfnccount); |
+ |
+ if ($hit>0) |
+ { |
+ $result{$testname} = "$found,$hit,$fn_found,$fn_hit"; |
+ } |
+ } |
+ |
+ return(\%result); |
+} |
+ |
+ |
+sub get_hash_reverse($) |
+{ |
+ my ($hash) = @_; |
+ my %result; |
+ |
+ foreach (keys(%{$hash})) { |
+ $result{$hash->{$_}} = $_; |
+ } |
+ |
+ return \%result; |
+} |
+ |
+# |
+# write_source(filehandle, source_filename, count_data, checksum_data, |
+# converted_data, func_data) |
+# |
+# Write an HTML view of a source code file. Returns a list containing |
+# data as needed by gen_png(). |
+# |
+# Die on error. |
+# |
+ |
+sub write_source($$$$$$) |
+{ |
+ local *HTML_HANDLE = $_[0]; |
+ local *SOURCE_HANDLE; |
+ my $source_filename = $_[1]; |
+ my %count_data; |
+ my $line_number; |
+ my @result; |
+ my $checkdata = $_[3]; |
+ my $converted = $_[4]; |
+ my $funcdata = $_[5]; |
+ my $datafunc = get_hash_reverse($funcdata); |
+ my $add_anchor; |
+ |
+ if ($_[2]) |
+ { |
+ %count_data = %{$_[2]}; |
+ } |
+ |
+ open(SOURCE_HANDLE, "<".$source_filename) |
+ or die("ERROR: cannot open $source_filename for reading!\n"); |
+ |
+ write_source_prolog(*HTML_HANDLE); |
+ |
+ for ($line_number = 1; <SOURCE_HANDLE> ; $line_number++) |
+ { |
+ chomp($_); |
+ |
+ # Source code matches coverage data? |
+ if (defined($checkdata->{$line_number}) && |
+ ($checkdata->{$line_number} ne md5_base64($_))) |
+ { |
+ die("ERROR: checksum mismatch at $source_filename:". |
+ "$line_number\n"); |
+ } |
+ |
+ $add_anchor = 0; |
+ if ($frames) { |
+ if (($line_number - 1) % $nav_resolution == 0) { |
+ $add_anchor = 1; |
+ } |
+ } |
+ if ($func_coverage) { |
+ if ($line_number == 1) { |
+ $add_anchor = 1; |
+ } elsif (defined($datafunc->{$line_number + |
+ $func_offset})) { |
+ $add_anchor = 1; |
+ } |
+ } |
+ push (@result, |
+ write_source_line(HTML_HANDLE, $line_number, |
+ $_, $count_data{$line_number}, |
+ $converted->{$line_number}, |
+ $add_anchor)); |
+ } |
+ |
+ close(SOURCE_HANDLE); |
+ write_source_epilog(*HTML_HANDLE); |
+ return(@result); |
+} |
+ |
+ |
+sub funcview_get_func_code($$$) |
+{ |
+ my ($name, $base, $type) = @_; |
+ my $result; |
+ my $link; |
+ |
+ if ($sort && $type == 1) { |
+ $link = "$name.func.$html_ext"; |
+ } |
+ $result = "Function Name"; |
+ $result .= get_sort_code($link, "Sort by function name", $base); |
+ |
+ return $result; |
+} |
+ |
+sub funcview_get_count_code($$$) |
+{ |
+ my ($name, $base, $type) = @_; |
+ my $result; |
+ my $link; |
+ |
+ if ($sort && $type == 0) { |
+ $link = "$name.func-sort-c.$html_ext"; |
+ } |
+ $result = "Hit count"; |
+ $result .= get_sort_code($link, "Sort by hit count", $base); |
+ |
+ return $result; |
+} |
+ |
+# |
+# funcview_get_sorted(funcdata, sumfncdata, sort_type) |
+# |
+# Depending on the value of sort_type, return a list of functions sorted |
+# by name (type 0) or by the associated call count (type 1). |
+# |
+ |
+sub funcview_get_sorted($$$) |
+{ |
+ my ($funcdata, $sumfncdata, $type) = @_; |
+ |
+ if ($type == 0) { |
+ return sort(keys(%{$funcdata})); |
+ } |
+ return sort({$sumfncdata->{$b} <=> $sumfncdata->{$a}} |
+ keys(%{$sumfncdata})); |
+} |
+ |
+# |
+# write_function_table(filehandle, source_file, sumcount, funcdata, |
+# sumfnccount, testfncdata) |
+# |
+# Write an HTML table listing all functions in a source file, including |
+# also function call counts and line coverages inside of each function. |
+# |
+# Die on error. |
+# |
+ |
+sub write_function_table(*$$$$$$$$) |
+{ |
+ local *HTML_HANDLE = $_[0]; |
+ my $source = $_[1]; |
+ my $sumcount = $_[2]; |
+ my $funcdata = $_[3]; |
+ my $sumfncdata = $_[4]; |
+ my $testfncdata = $_[5]; |
+ my $name = $_[6]; |
+ my $base = $_[7]; |
+ my $type = $_[8]; |
+ my $func; |
+ my $func_code; |
+ my $count_code; |
+ |
+ # Get HTML code for headings |
+ $func_code = funcview_get_func_code($name, $base, $type); |
+ $count_code = funcview_get_count_code($name, $base, $type); |
+ write_html(*HTML_HANDLE, <<END_OF_HTML) |
+ <center> |
+ <table width="60%" cellpadding=1 cellspacing=1 border=0> |
+ <tr><td><br></td></tr> |
+ <tr> |
+ <td width="80%" class="tableHead">$func_code</td> |
+ <td width="20%" class="tableHead">$count_code</td> |
+ </tr> |
+END_OF_HTML |
+ ; |
+ |
+ # Get a sorted table |
+ foreach $func (funcview_get_sorted($funcdata, $sumfncdata, $type)) { |
+ my $startline = $funcdata->{$func} - $func_offset; |
+ my $name = escape_html($func); |
+ my $count = $sumfncdata->{$name}; |
+ my $countstyle; |
+ |
+ if ($startline < 1) { |
+ $startline = 1; |
+ } |
+ if ($count == 0) { |
+ $countstyle = "coverFnLo"; |
+ } else { |
+ $countstyle = "coverFnHi"; |
+ } |
+ |
+ write_html(*HTML_HANDLE, <<END_OF_HTML) |
+ <tr> |
+ <td class="coverFn"><a href="$source#$startline">$name</a></td> |
+ <td class="$countstyle">$count</td> |
+ </tr> |
+END_OF_HTML |
+ ; |
+ } |
+ write_html(*HTML_HANDLE, <<END_OF_HTML) |
+ </table> |
+ <br> |
+ </center> |
+END_OF_HTML |
+ ; |
+} |
+ |
+ |
+# |
+# info(printf_parameter) |
+# |
+# Use printf to write PRINTF_PARAMETER to stdout only when the $quiet flag |
+# is not set. |
+# |
+ |
+sub info(@) |
+{ |
+ if (!$quiet) |
+ { |
+ # Print info string |
+ printf(@_); |
+ } |
+} |
+ |
+ |
+# |
+# subtract_counts(data_ref, base_ref) |
+# |
+ |
+sub subtract_counts($$) |
+{ |
+ my %data = %{$_[0]}; |
+ my %base = %{$_[1]}; |
+ my $line; |
+ my $data_count; |
+ my $base_count; |
+ my $hit = 0; |
+ my $found = 0; |
+ |
+ foreach $line (keys(%data)) |
+ { |
+ $found++; |
+ $data_count = $data{$line}; |
+ $base_count = $base{$line}; |
+ |
+ if (defined($base_count)) |
+ { |
+ $data_count -= $base_count; |
+ |
+ # Make sure we don't get negative numbers |
+ if ($data_count<0) { $data_count = 0; } |
+ } |
+ |
+ $data{$line} = $data_count; |
+ if ($data_count > 0) { $hit++; } |
+ } |
+ |
+ return (\%data, $found, $hit); |
+} |
+ |
+ |
+# |
+# subtract_fnccounts(data, base) |
+# |
+# Subtract function call counts found in base from those in data. |
+# Return (data, f_found, f_hit). |
+# |
+ |
+sub subtract_fnccounts($$) |
+{ |
+ my %data = %{$_[0]}; |
+ my %base = %{$_[1]}; |
+ my $func; |
+ my $data_count; |
+ my $base_count; |
+ my $fn_hit = 0; |
+ my $fn_found = 0; |
+ |
+ foreach $func (keys(%data)) { |
+ $fn_found++; |
+ $data_count = $data{$func}; |
+ $base_count = $base{$func}; |
+ |
+ if (defined($base_count)) { |
+ $data_count -= $base_count; |
+ |
+ # Make sure we don't get negative numbers |
+ if ($data_count < 0) { |
+ $data_count = 0; |
+ } |
+ } |
+ |
+ $data{$func} = $data_count; |
+ if ($data_count > 0) { |
+ $fn_hit++; |
+ } |
+ } |
+ |
+ return (\%data, $fn_found, $fn_hit); |
+} |
+ |
+ |
+# |
+# apply_baseline(data_ref, baseline_ref) |
+# |
+# Subtract the execution counts found in the baseline hash referenced by |
+# BASELINE_REF from actual data in DATA_REF. |
+# |
+ |
+sub apply_baseline($$) |
+{ |
+ my %data_hash = %{$_[0]}; |
+ my %base_hash = %{$_[1]}; |
+ my $filename; |
+ my $testname; |
+ my $data; |
+ my $data_testdata; |
+ my $data_funcdata; |
+ my $data_checkdata; |
+ my $data_testfncdata; |
+ my $data_count; |
+ my $data_testfnccount; |
+ my $base; |
+ my $base_checkdata; |
+ my $base_sumfnccount; |
+ my $base_count; |
+ my $sumcount; |
+ my $sumfnccount; |
+ my $found; |
+ my $hit; |
+ my $fn_found; |
+ my $fn_hit; |
+ |
+ foreach $filename (keys(%data_hash)) |
+ { |
+ # Get data set for data and baseline |
+ $data = $data_hash{$filename}; |
+ $base = $base_hash{$filename}; |
+ |
+ # Skip data entries for which no base entry exists |
+ if (!defined($base)) |
+ { |
+ next; |
+ } |
+ |
+ # Get set entries for data and baseline |
+ ($data_testdata, undef, $data_funcdata, $data_checkdata, |
+ $data_testfncdata) = get_info_entry($data); |
+ (undef, $base_count, undef, $base_checkdata, undef, |
+ $base_sumfnccount) = get_info_entry($base); |
+ |
+ # Check for compatible checksums |
+ merge_checksums($data_checkdata, $base_checkdata, $filename); |
+ |
+ # sumcount has to be calculated anew |
+ $sumcount = {}; |
+ $sumfnccount = {}; |
+ |
+ # For each test case, subtract test specific counts |
+ foreach $testname (keys(%{$data_testdata})) |
+ { |
+ # Get counts of both data and baseline |
+ $data_count = $data_testdata->{$testname}; |
+ $data_testfnccount = $data_testfncdata->{$testname}; |
+ |
+ ($data_count, undef, $hit) = |
+ subtract_counts($data_count, $base_count); |
+ ($data_testfnccount) = |
+ subtract_fnccounts($data_testfnccount, |
+ $base_sumfnccount); |
+ |
+ # Check whether this test case did hit any line at all |
+ if ($hit > 0) |
+ { |
+ # Write back resulting hash |
+ $data_testdata->{$testname} = $data_count; |
+ $data_testfncdata->{$testname} = |
+ $data_testfnccount; |
+ } |
+ else |
+ { |
+ # Delete test case which did not impact this |
+ # file |
+ delete($data_testdata->{$testname}); |
+ delete($data_testfncdata->{$testname}); |
+ } |
+ |
+ # Add counts to sum of counts |
+ ($sumcount, $found, $hit) = |
+ add_counts($sumcount, $data_count); |
+ ($sumfnccount, $fn_found, $fn_hit) = |
+ add_fnccounts($sumfnccount, $data_testfnccount); |
+ } |
+ |
+ # Write back resulting entry |
+ set_info_entry($data, $data_testdata, $sumcount, $data_funcdata, |
+ $data_checkdata, $data_testfncdata, $sumfnccount, |
+ $found, $hit, $fn_found, $fn_hit); |
+ |
+ $data_hash{$filename} = $data; |
+ } |
+ |
+ return (\%data_hash); |
+} |
+ |
+ |
+# |
+# remove_unused_descriptions() |
+# |
+# Removes all test descriptions from the global hash %test_description which |
+# are not present in %info_data. |
+# |
+ |
+sub remove_unused_descriptions() |
+{ |
+ my $filename; # The current filename |
+ my %test_list; # Hash containing found test names |
+ my $test_data; # Reference to hash test_name -> count_data |
+ my $before; # Initial number of descriptions |
+ my $after; # Remaining number of descriptions |
+ |
+ $before = scalar(keys(%test_description)); |
+ |
+ foreach $filename (keys(%info_data)) |
+ { |
+ ($test_data) = get_info_entry($info_data{$filename}); |
+ foreach (keys(%{$test_data})) |
+ { |
+ $test_list{$_} = ""; |
+ } |
+ } |
+ |
+ # Remove descriptions for tests which are not in our list |
+ foreach (keys(%test_description)) |
+ { |
+ if (!defined($test_list{$_})) |
+ { |
+ delete($test_description{$_}); |
+ } |
+ } |
+ |
+ $after = scalar(keys(%test_description)); |
+ if ($after < $before) |
+ { |
+ info("Removed ".($before - $after). |
+ " unused descriptions, $after remaining.\n"); |
+ } |
+} |
+ |
+ |
+# |
+# apply_prefix(filename, prefix) |
+# |
+# If FILENAME begins with PREFIX, remove PREFIX from FILENAME and return |
+# resulting string, otherwise return FILENAME. |
+# |
+ |
+sub apply_prefix($$) |
+{ |
+ my $filename = $_[0]; |
+ my $prefix = $_[1]; |
+ |
+ if (defined($prefix) && ($prefix ne "")) |
+ { |
+ if ($filename =~ /^\Q$prefix\E\/(.*)$/) |
+ { |
+ return substr($filename, length($prefix) + 1); |
+ } |
+ } |
+ |
+ return $filename; |
+} |
+ |
+ |
+# |
+# system_no_output(mode, parameters) |
+# |
+# Call an external program using PARAMETERS while suppressing depending on |
+# the value of MODE: |
+# |
+# MODE & 1: suppress STDOUT |
+# MODE & 2: suppress STDERR |
+# |
+# Return 0 on success, non-zero otherwise. |
+# |
+ |
+sub system_no_output($@) |
+{ |
+ my $mode = shift; |
+ my $result; |
+ local *OLD_STDERR; |
+ local *OLD_STDOUT; |
+ |
+ # Save old stdout and stderr handles |
+ ($mode & 1) && open(OLD_STDOUT, ">>&STDOUT"); |
+ ($mode & 2) && open(OLD_STDERR, ">>&STDERR"); |
+ |
+ # Redirect to /dev/null |
+ ($mode & 1) && open(STDOUT, ">/dev/null"); |
+ ($mode & 2) && open(STDERR, ">/dev/null"); |
+ |
+ system(@_); |
+ $result = $?; |
+ |
+ # Close redirected handles |
+ ($mode & 1) && close(STDOUT); |
+ ($mode & 2) && close(STDERR); |
+ |
+ # Restore old handles |
+ ($mode & 1) && open(STDOUT, ">>&OLD_STDOUT"); |
+ ($mode & 2) && open(STDERR, ">>&OLD_STDERR"); |
+ |
+ return $result; |
+} |
+ |
+ |
+# |
+# read_config(filename) |
+# |
+# Read configuration file FILENAME and return a reference to a hash containing |
+# all valid key=value pairs found. |
+# |
+ |
+sub read_config($) |
+{ |
+ my $filename = $_[0]; |
+ my %result; |
+ my $key; |
+ my $value; |
+ local *HANDLE; |
+ |
+ if (!open(HANDLE, "<$filename")) |
+ { |
+ warn("WARNING: cannot read configuration file $filename\n"); |
+ return undef; |
+ } |
+ while (<HANDLE>) |
+ { |
+ chomp; |
+ # Skip comments |
+ s/#.*//; |
+ # Remove leading blanks |
+ s/^\s+//; |
+ # Remove trailing blanks |
+ s/\s+$//; |
+ next unless length; |
+ ($key, $value) = split(/\s*=\s*/, $_, 2); |
+ if (defined($key) && defined($value)) |
+ { |
+ $result{$key} = $value; |
+ } |
+ else |
+ { |
+ warn("WARNING: malformed statement in line $. ". |
+ "of configuration file $filename\n"); |
+ } |
+ } |
+ close(HANDLE); |
+ return \%result; |
+} |
+ |
+ |
+# |
+# apply_config(REF) |
+# |
+# REF is a reference to a hash containing the following mapping: |
+# |
+# key_string => var_ref |
+# |
+# where KEY_STRING is a keyword and VAR_REF is a reference to an associated |
+# variable. If the global configuration hash CONFIG contains a value for |
+# keyword KEY_STRING, VAR_REF will be assigned the value for that keyword. |
+# |
+ |
+sub apply_config($) |
+{ |
+ my $ref = $_[0]; |
+ |
+ foreach (keys(%{$ref})) |
+ { |
+ if (defined($config->{$_})) |
+ { |
+ ${$ref->{$_}} = $config->{$_}; |
+ } |
+ } |
+} |
+ |
+ |
+# |
+# get_html_prolog(FILENAME) |
+# |
+# If FILENAME is defined, return contents of file. Otherwise return default |
+# HTML prolog. Die on error. |
+# |
+ |
+sub get_html_prolog($) |
+{ |
+ my $filename = $_[0]; |
+ my $result = ""; |
+ |
+ if (defined($filename)) |
+ { |
+ local *HANDLE; |
+ |
+ open(HANDLE, "<".$filename) |
+ or die("ERROR: cannot open html prolog $filename!\n"); |
+ while (<HANDLE>) |
+ { |
+ $result .= $_; |
+ } |
+ close(HANDLE); |
+ } |
+ else |
+ { |
+ $result = <<END_OF_HTML |
+<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN"> |
+ |
+<html lang="en"> |
+ |
+<head> |
+ <meta http-equiv="Content-Type" content="text/html; charset=ISO-8859-1"> |
+ <title>\@pagetitle\@</title> |
+ <link rel="stylesheet" type="text/css" href="\@basedir\@gcov.css"> |
+</head> |
+ |
+<body> |
+ |
+END_OF_HTML |
+ ; |
+ } |
+ |
+ return $result; |
+} |
+ |
+ |
+# |
+# get_html_epilog(FILENAME) |
+# |
+# If FILENAME is defined, return contents of file. Otherwise return default |
+# HTML epilog. Die on error. |
+# |
+sub get_html_epilog($) |
+{ |
+ my $filename = $_[0]; |
+ my $result = ""; |
+ |
+ if (defined($filename)) |
+ { |
+ local *HANDLE; |
+ |
+ open(HANDLE, "<".$filename) |
+ or die("ERROR: cannot open html epilog $filename!\n"); |
+ while (<HANDLE>) |
+ { |
+ $result .= $_; |
+ } |
+ close(HANDLE); |
+ } |
+ else |
+ { |
+ $result = <<END_OF_HTML |
+ |
+</body> |
+</html> |
+END_OF_HTML |
+ ; |
+ } |
+ |
+ return $result; |
+ |
+} |
+ |
+sub warn_handler($) |
+{ |
+ my ($msg) = @_; |
+ |
+ warn("$tool_name: $msg"); |
+} |
+ |
+sub die_handler($) |
+{ |
+ my ($msg) = @_; |
+ |
+ die("$tool_name: $msg"); |
+} |
Property changes on: third_party/lcov/bin/genhtml |
___________________________________________________________________ |
Name: svn:executable |
+ * |