| #!/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"); |
| or open(SOURCE_HANDLE, "</dev/null"); |
| |
| 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; |