diff options
author | Remko Tronçon <git@el-tramo.be> | 2011-02-18 17:59:47 (GMT) |
---|---|---|
committer | Remko Tronçon <git@el-tramo.be> | 2011-02-18 18:10:35 (GMT) |
commit | 2f6c2299c28c9bb03ee1437058a4c7071ff2ac3f (patch) | |
tree | 5fb82cd3ea77b82ce0c5fec973e1fee4de1def4b /3rdParty | |
parent | 6d115ace1a038acd1a4354391c552bef84d67e79 (diff) | |
download | swift-contrib-2f6c2299c28c9bb03ee1437058a4c7071ff2ac3f.zip swift-contrib-2f6c2299c28c9bb03ee1437058a4c7071ff2ac3f.tar.bz2 |
Updated coverage tools.
Updated LCov to 1.9.
Removed obsolete overview script.
Diffstat (limited to '3rdParty')
-rwxr-xr-x | 3rdParty/LCov/gendesc | 7 | ||||
-rwxr-xr-x | 3rdParty/LCov/genhtml | 2151 | ||||
-rwxr-xr-x | 3rdParty/LCov/geninfo | 2488 | ||||
-rwxr-xr-x | 3rdParty/LCov/genpng | 17 | ||||
-rwxr-xr-x | 3rdParty/LCov/lcov | 2120 |
5 files changed, 4992 insertions, 1791 deletions
diff --git a/3rdParty/LCov/gendesc b/3rdParty/LCov/gendesc index e7a8113..522ef69 100755 --- a/3rdParty/LCov/gendesc +++ b/3rdParty/LCov/gendesc @@ -41,7 +41,7 @@ use Getopt::Long; # Constants -our $lcov_version = "LCOV version 1.7"; +our $lcov_version = 'LCOV version 1.9'; our $lcov_url = "http://ltp.sourceforge.net/coverage/lcov.php"; our $tool_name = basename($0); @@ -67,6 +67,9 @@ our $input_filename; $SIG{__WARN__} = \&warn_handler; $SIG{__DIE__} = \&die_handler; +# Prettify version string +$lcov_version =~ s/\$\s*Revision\s*:?\s*(\S+)\s*\$/$1/; + # Parse command line options if (!GetOptions("output-filename=s" => \$output_filename, "version" =>\$version, @@ -169,7 +172,7 @@ sub gen_desc() { chomp($_); - if (/^\s*(\w[\w-]*)(\s*)$/) + if (/^(\w[\w-]*)(\s*)$/) { # Matched test name # Name starts with alphanum or _, continues with diff --git a/3rdParty/LCov/genhtml b/3rdParty/LCov/genhtml index 497363b..d74063a 100755 --- a/3rdParty/LCov/genhtml +++ b/3rdParty/LCov/genhtml @@ -1,6 +1,6 @@ #!/usr/bin/perl -w # -# Copyright (c) International Business Machines Corp., 2002 +# Copyright (c) International Business Machines Corp., 2002,2010 # # 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 @@ -72,7 +72,7 @@ use Digest::MD5 qw(md5_base64); # Global constants our $title = "LCOV - code coverage report"; -our $lcov_version = "LCOV version 1.7"; +our $lcov_version = 'LCOV version 1.9'; our $lcov_url = "http://ltp.sourceforge.net/coverage/lcov.php"; our $tool_name = basename($0); @@ -81,13 +81,17 @@ our $tool_name = basename($0); # 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 line coverage/all coverage types if not specified +our $hi_limit = 90; +our $med_limit = 75; # For function coverage -our $fn_hi_limit = 90; -our $fn_med_limit = 75; +our $fn_hi_limit; +our $fn_med_limit; + +# For branch coverage +our $br_hi_limit; +our $br_med_limit; # Width of overview image our $overview_width = 80; @@ -107,7 +111,49 @@ our $nav_offset = 10; # specifies that offset in lines. our $func_offset = 2; -our $overview_title = "directory"; +our $overview_title = "top level"; + +# Width for line coverage information in the source code view +our $line_field_width = 12; + +# Width for branch coverage information in the source code view +our $br_field_width = 16; + +# Internal Constants + +# Header types +our $HDR_DIR = 0; +our $HDR_FILE = 1; +our $HDR_SOURCE = 2; +our $HDR_TESTDESC = 3; +our $HDR_FUNC = 4; + +# Sort types +our $SORT_FILE = 0; +our $SORT_LINE = 1; +our $SORT_FUNC = 2; +our $SORT_BRANCH = 3; + +# Fileview heading types +our $HEAD_NO_DETAIL = 1; +our $HEAD_DETAIL_HIDDEN = 2; +our $HEAD_DETAIL_SHOWN = 3; + +# Offsets for storing branch coverage data in vectors +our $BR_BLOCK = 0; +our $BR_BRANCH = 1; +our $BR_TAKEN = 2; +our $BR_VEC_ENTRIES = 3; +our $BR_VEC_WIDTH = 32; + +# Additional offsets used when converting branch coverage data to HTML +our $BR_LEN = 3; +our $BR_OPEN = 4; +our $BR_CLOSE = 5; + +# Branch data combination types +our $BR_SUB = 0; +our $BR_ADD = 1; # Data related prototypes sub print_usage(*); @@ -118,21 +164,20 @@ sub process_file($$$); sub info(@); sub read_info_file($); sub get_info_entry($); -sub set_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 get_affecting_tests($$$); sub combine_info_files($$); sub merge_checksums($$$); sub combine_info_entries($$$); @@ -142,6 +187,17 @@ sub read_config($); sub apply_config($); sub get_html_prolog($); sub get_html_epilog($); +sub write_dir_page($$$$$$$$$$$$$$$$$); +sub classify_rate($$$$); +sub br_taken_add($$); +sub br_taken_sub($$); +sub br_ivec_len($); +sub br_ivec_get($$); +sub br_ivec_push($$$$); +sub combine_brcount($$$); +sub get_br_found_and_hit($); +sub warn_handler($); +sub die_handler($); # HTML related prototypes @@ -151,32 +207,31 @@ 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_description_file($$$$$$$); +sub write_function_table(*$$$$$$$$$$); sub write_html(*$); sub write_html_prolog(*$$); sub write_html_epilog(*$;$); -sub write_header(*$$$$$$$$); +sub write_header(*$$$$$$$$$$); sub write_header_prolog(*$); -sub write_header_line(*$@); +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(*$$$$$$$); +sub write_file_table_prolog(*$@); +sub write_file_table_entry(*$$$@); +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($$$$$$$); sub write_source_prolog(*); -sub write_source_line(*$$$$$); +sub write_source_line(*$$$$$$); sub write_source_epilog(*); sub write_frameset(*$$$); @@ -206,6 +261,8 @@ 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 $br_coverage = 1; # If set, generate branch coverage statistics +our $no_br_coverage; # Disable br_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 @@ -221,8 +278,9 @@ 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 $demangle_cpp = 0; # Demangle C++ function names our @fileview_sortlist; -our @fileview_sortname = ("", "-sort-l", "-sort-f"); +our @fileview_sortname = ("", "-sort-l", "-sort-f", "-sort-b"); our @funcview_sortlist; our @rate_name = ("Lo", "Med", "Hi"); our @rate_png = ("ruby.png", "amber.png", "emerald.png"); @@ -239,6 +297,9 @@ our $tool_dir = dirname($0); # Directory where genhtml tool is installed $SIG{__WARN__} = \&warn_handler; $SIG{__DIE__} = \&die_handler; +# Prettify version string +$lcov_version =~ s/\$\s*Revision\s*:?\s*(\S+)\s*\$/$1/; + # Add current working directory if $tool_dir is not already an absolute path if (! ($tool_dir =~ /^\/(.*)$/)) { @@ -246,7 +307,7 @@ if (! ($tool_dir =~ /^\/(.*)$/)) } # Read configuration file if available -if (-r $ENV{"HOME"}."/.lcovrc") +if (defined($ENV{"HOME"}) && (-r $ENV{"HOME"}."/.lcovrc")) { $config = read_config($ENV{"HOME"}."/.lcovrc"); } @@ -262,6 +323,7 @@ if ($config) "genhtml_css_file" => \$css_filename, "genhtml_hi_limit" => \$hi_limit, "genhtml_med_limit" => \$med_limit, + "genhtml_line_field_width" => \$line_field_width, "genhtml_overview_width" => \$overview_width, "genhtml_nav_resolution" => \$nav_resolution, "genhtml_nav_offset" => \$nav_offset, @@ -278,36 +340,49 @@ if ($config) "genhtml_function_hi_limit" => \$fn_hi_limit, "genhtml_function_med_limit" => \$fn_med_limit, "genhtml_function_coverage" => \$func_coverage, + "genhtml_branch_hi_limit" => \$br_hi_limit, + "genhtml_branch_med_limit" => \$br_med_limit, + "genhtml_branch_coverage" => \$br_coverage, + "genhtml_branch_field_width" => \$br_field_width, "genhtml_sort" => \$sort, }); } +# Copy limit values if not specified +$fn_hi_limit = $hi_limit if (!defined($fn_hi_limit)); +$fn_med_limit = $med_limit if (!defined($fn_med_limit)); +$br_hi_limit = $hi_limit if (!defined($br_hi_limit)); +$br_med_limit = $med_limit if (!defined($br_med_limit)); + # 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, +if (!GetOptions("output-directory|o=s" => \$output_directory, + "title|t=s" => \$test_title, + "description-file|d=s" => \$desc_filename, + "keep-descriptions|k" => \$keep_descriptions, + "css-file|c=s" => \$css_filename, + "baseline-file|b=s" => \$base_filename, + "prefix|p=s" => \$dir_prefix, "num-spaces=i" => \$tab_size, "no-prefix" => \$no_prefix, "no-sourceview" => \$no_sourceview, - "show-details" => \$show_details, - "frames" => \$frames, + "show-details|s" => \$show_details, + "frames|f" => \$frames, "highlight" => \$highlight, "legend" => \$legend, - "quiet" => \$quiet, + "quiet|q" => \$quiet, "help|h|?" => \$help, - "version" => \$version, + "version|v" => \$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, + "branch-coverage" => \$br_coverage, + "no-branch-coverage" => \$no_br_coverage, "sort" => \$sort, "no-sort" => \$no_sort, + "demangle-cpp" => \$demangle_cpp, )) { print(STDERR "Use $tool_name --help to get usage information\n"); @@ -317,6 +392,9 @@ if (!GetOptions("output-directory=s" => \$output_directory, if ($no_func_coverage) { $func_coverage = 0; } + if ($no_br_coverage) { + $br_coverage = 0; + } # Merge sort options if ($no_sort) { @@ -400,16 +478,14 @@ if ($no_prefix && defined($dir_prefix)) $dir_prefix = undef; } +@fileview_sortlist = ($SORT_FILE); +@funcview_sortlist = ($SORT_FILE); + 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); + push(@fileview_sortlist, $SORT_LINE); + push(@fileview_sortlist, $SORT_FUNC) if ($func_coverage); + push(@fileview_sortlist, $SORT_BRANCH) if ($br_coverage); + push(@funcview_sortlist, $SORT_LINE); } if ($frames) @@ -418,6 +494,15 @@ if ($frames) do("$tool_dir/genpng"); } +# Ensure that the c++filt tool is available when using --demangle-cpp +if ($demangle_cpp) +{ + if (system_no_output(3, "c++filt", "--version")) { + die("ERROR: could not find c++filt tool needed for ". + "--demangle-cpp\n"); + } +} + # Make sure output_directory exists, create it if necessary if ($output_directory) { @@ -425,8 +510,7 @@ if ($output_directory) if (! -e _) { - system("mkdir", "-p", $output_directory) - and die("ERROR: cannot create directory $_!\n"); + create_sub_dir($output_directory); } } @@ -467,6 +551,7 @@ Operation: -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 + --(no-)branch-coverage Enable (disable) branch coverage display HTML output: -f, --frames Use HTML frames for source code view @@ -481,6 +566,7 @@ HTML output: --html-extension EXT Use EXT as filename extension for pages --html-gzip Use gzip to compress HTML --(no-)sort Enable (disable) sorted coverage views + --demangle-cpp Demangle C++ function names For more information see: $lcov_url END_OF_USAGE @@ -508,6 +594,50 @@ sub get_rate($$) # +# get_overall_line(found, hit, name_singular, name_plural) +# +# Return a string containing overall information for the specified +# found/hit data. +# + +sub get_overall_line($$$$) +{ + my ($found, $hit, $name_sn, $name_pl) = @_; + my $name; + + return "no data found" if (!defined($found) || $found == 0); + $name = ($found == 1) ? $name_sn : $name_pl; + return sprintf("%.1f%% (%d of %d %s)", $hit * 100 / $found, $hit, + $found, $name); +} + + +# +# print_overall_rate(ln_do, ln_found, ln_hit, fn_do, fn_found, fn_hit, br_do +# br_found, br_hit) +# +# Print overall coverage rates for the specified coverage types. +# + +sub print_overall_rate($$$$$$$$$) +{ + my ($ln_do, $ln_found, $ln_hit, $fn_do, $fn_found, $fn_hit, + $br_do, $br_found, $br_hit) = @_; + + info("Overall coverage rate:\n"); + info(" lines......: %s\n", + get_overall_line($ln_found, $ln_hit, "line", "lines")) + if ($ln_do); + info(" functions..: %s\n", + get_overall_line($fn_found, $fn_hit, "function", "functions")) + if ($fn_do); + info(" branches...: %s\n", + get_overall_line($br_found, $br_hit, "branch", "branches")) + if ($br_do); +} + + +# # gen_html() # # Generate a set of HTML pages from contents of .info file INFO_FILENAME. @@ -527,10 +657,14 @@ sub gen_html() my $lines_hit; my $fn_found; my $fn_hit; + my $br_found; + my $br_hit; my $overall_found = 0; my $overall_hit = 0; my $total_fn_found = 0; my $total_fn_hit = 0; + my $total_br_found = 0; + my $total_br_hit = 0; my $dir_name; my $link_name; my @dir_list; @@ -625,7 +759,8 @@ sub gen_html() # Process each subdirectory and collect overview information foreach $dir_name (@dir_list) { - ($lines_found, $lines_hit, $fn_found, $fn_hit) + ($lines_found, $lines_hit, $fn_found, $fn_hit, + $br_found, $br_hit) = process_dir($dir_name); # Remove prefix if applicable @@ -646,13 +781,16 @@ sub gen_html() } $overview{$dir_name} = [$lines_found, $lines_hit, $fn_found, - $fn_hit, $link_name, + $fn_hit, $br_found, $br_hit, $link_name, get_rate($lines_found, $lines_hit), - get_rate($fn_found, $fn_hit)]; + get_rate($fn_found, $fn_hit), + get_rate($br_found, $br_hit)]; $overall_found += $lines_found; $overall_hit += $lines_hit; $total_fn_found += $fn_found; $total_fn_hit += $fn_hit; + $total_br_found += $br_found; + $total_br_hit += $br_hit; } # Generate overview page @@ -662,8 +800,8 @@ sub gen_html() foreach (@fileview_sortlist) { write_dir_page($fileview_sortname[$_], ".", "", $test_title, undef, $overall_found, $overall_hit, - $total_fn_found, $total_fn_hit, \%overview, - {}, {}, 0, $_); + $total_fn_found, $total_fn_hit, $total_br_found, + $total_br_hit, \%overview, {}, {}, {}, 0, $_); } # Check if there are any test case descriptions to write out @@ -672,37 +810,15 @@ sub gen_html() 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; + $total_fn_found, $total_fn_hit, + $total_br_found, $total_br_hit); } - 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); - - } - } + print_overall_rate(1, $overall_found, $overall_hit, + $func_coverage, $total_fn_found, $total_fn_hit, + $br_coverage, $total_br_found, $total_br_hit); + chdir($cwd); } # @@ -727,11 +843,12 @@ sub html_create($$) } } -sub write_dir_page($$$$$$$$$$$$$$) +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) = @_; + $overall_hit, $total_fn_found, $total_fn_hit, $total_br_found, + $total_br_hit, $overview, $testhash, $testfnchash, $testbrhash, + $view_type, $sort_type) = @_; # Generate directory overview page including details html_create(*HTML_HANDLE, "$rel_dir/index$name.$html_ext"); @@ -741,9 +858,9 @@ sub write_dir_page($$$$$$$$$$$$$$) 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); + $total_fn_hit, $total_br_found, $total_br_hit, $sort_type); write_file_table(*HTML_HANDLE, $base_dir, $overview, $testhash, - $testfnchash, $view_type, $sort_type); + $testfnchash, $testbrhash, $view_type, $sort_type); write_html_epilog(*HTML_HANDLE, $base_dir); close(*HTML_HANDLE); } @@ -765,16 +882,22 @@ sub process_dir($) my $lines_hit; my $fn_found; my $fn_hit; + my $br_found; + my $br_hit; my $overall_found=0; my $overall_hit=0; my $total_fn_found=0; my $total_fn_hit=0; + my $total_br_found = 0; + my $total_br_hit = 0; my $base_name; my $extension; my $testdata; my %testhash; my $testfncdata; my %testfnchash; + my $testbrdata; + my %testbrhash; my @sort_list; local *HTML_HANDLE; @@ -804,8 +927,9 @@ sub process_dir($) my $page_link; my $func_link; - ($lines_found, $lines_hit, $fn_found, $fn_hit, $testdata, - $testfncdata) = process_file($trunc_dir, $rel_dir, $filename); + ($lines_found, $lines_hit, $fn_found, $fn_hit, $br_found, + $br_hit, $testdata, $testfncdata, $testbrdata) = + process_file($trunc_dir, $rel_dir, $filename); $base_name = basename($filename); @@ -819,18 +943,24 @@ sub process_dir($) $page_link = "$base_name.gcov.$html_ext"; } $overview{$base_name} = [$lines_found, $lines_hit, $fn_found, - $fn_hit, $page_link, + $fn_hit, $br_found, $br_hit, + $page_link, get_rate($lines_found, $lines_hit), - get_rate($fn_found, $fn_hit)]; + get_rate($fn_found, $fn_hit), + get_rate($br_found, $br_hit)]; $testhash{$base_name} = $testdata; $testfnchash{$base_name} = $testfncdata; + $testbrhash{$base_name} = $testbrdata; $overall_found += $lines_found; $overall_hit += $lines_hit; $total_fn_found += $fn_found; $total_fn_hit += $fn_hit; + + $total_br_found += $br_found; + $total_br_hit += $br_hit; } # Create sorted pages @@ -839,7 +969,8 @@ sub process_dir($) 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, $_); + $total_br_found, $total_br_hit, \%overview, {}, + {}, {}, 1, $_); if (!$show_details) { next; } @@ -847,12 +978,14 @@ sub process_dir($) 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, $_); + $total_fn_hit, $total_br_found, $total_br_hit, + \%overview, \%testhash, \%testfnchash, + \%testbrhash, 1, $_); } # Calculate resulting line counts - return ($overall_found, $overall_hit, $total_fn_found, $total_fn_hit); + return ($overall_found, $overall_hit, $total_fn_found, $total_fn_hit, + $total_br_found, $total_br_hit); } @@ -913,11 +1046,12 @@ sub get_converted_lines($) } -sub write_function_page($$$$$$$$$$$$$$) +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) = @_; + $lines_found, $lines_hit, $fn_found, $fn_hit, $br_found, $br_hit, + $sumcount, $funcdata, $sumfnccount, $testfncdata, $sumbrcount, + $testbrdata, $sort_type) = @_; my $pagetitle; my $filename; @@ -932,10 +1066,11 @@ sub write_function_page($$$$$$$$$$$$$$) 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); + $fn_found, $fn_hit, $br_found, $br_hit, $sort_type); write_function_table(*HTML_HANDLE, "$base_name.gcov.$html_ext", $sumcount, $funcdata, - $sumfnccount, $testfncdata, $base_name, + $sumfnccount, $testfncdata, $sumbrcount, + $testbrdata, $base_name, $base_dir, $sort_type); write_html_epilog(*HTML_HANDLE, $base_dir, 1); close(*HTML_HANDLE); @@ -962,25 +1097,31 @@ sub process_file($$$) my $checkdata; my $testfncdata; my $sumfnccount; + my $testbrdata; + my $sumbrcount; my $lines_found; my $lines_hit; my $fn_found; my $fn_hit; + my $br_found; + my $br_hit; my $converted; my @source; my $pagetitle; local *HTML_HANDLE; ($testdata, $sumcount, $funcdata, $checkdata, $testfncdata, - $sumfnccount, $lines_found, $lines_hit, $fn_found, $fn_hit) + $sumfnccount, $testbrdata, $sumbrcount, $lines_found, $lines_hit, + $fn_found, $fn_hit, $br_found, $br_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); + return ($lines_found, $lines_hit, $fn_found, $fn_hit, + $br_found, $br_hit, $testdata, $testfncdata, + $testbrdata); } $converted = get_converted_lines($testdata); @@ -990,9 +1131,9 @@ sub process_file($$$) 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); + $fn_found, $fn_hit, $br_found, $br_hit, 0); @source = write_source(*HTML_HANDLE, $filename, $sumcount, $checkdata, - $converted, $funcdata); + $converted, $funcdata, $sumbrcount); write_html_epilog(*HTML_HANDLE, $base_dir, 1); close(*HTML_HANDLE); @@ -1003,17 +1144,20 @@ sub process_file($$$) write_function_page($base_dir, $rel_dir, $trunc_dir, $base_name, $test_title, $lines_found, $lines_hit, - $fn_found, $fn_hit, $sumcount, + $fn_found, $fn_hit, $br_found, + $br_hit, $sumcount, $funcdata, $sumfnccount, - $testfncdata, $_); + $testfncdata, $sumbrcount, + $testbrdata, $_); } } # Additional files are needed in case of frame output if (!$frames) { - return ($lines_found, $lines_hit, - $fn_found, $fn_hit, $testdata); + return ($lines_found, $lines_hit, $fn_found, $fn_hit, + $br_found, $br_hit, $testdata, $testfncdata, + $testbrdata); } # Create overview png file @@ -1033,8 +1177,8 @@ sub process_file($$$) scalar(@source)); close(*HTML_HANDLE); - return ($lines_found, $lines_hit, $fn_found, $fn_hit, $testdata, - $testfncdata); + return ($lines_found, $lines_hit, $fn_found, $fn_hit, $br_found, + $br_hit, $testdata, $testfncdata, $testbrdata); } @@ -1054,16 +1198,22 @@ sub process_file($$$) # "check" -> \%checkdata # "testfnc" -> \%testfncdata # "sumfnc" -> \%sumfnccount +# "testbr" -> \%testbrdata +# "sumbr" -> \%sumbrcount # # %testdata : name of test affecting this file -> \%testcount # %testfncdata: name of test affecting this file -> \%testfnccount +# %testbrdata: name of test affecting this file -> \%testbrcount # # %testcount : line number -> execution count for a single test # %testfnccount: function name -> execution count for a single test +# %testbrcount : line number -> branch coverage data for a single test # %sumcount : line number -> execution count for all tests # %sumfnccount : function name -> execution count for all tests +# %sumbrcount : line number -> branch coverage data for all tests # %funcdata : function name -> line number # %checkdata : line number -> checksum of source code line +# $brdata : vector of items: block, branch, taken # # Note that .info file sections referring to the same file and test name # will automatically be combined by adding all execution counts. @@ -1088,6 +1238,9 @@ sub read_info_file($) my $testfncdata; my $testfnccount; my $sumfnccount; + my $testbrdata; + my $testbrcount; + my $sumbrcount; my $line; # Current line read from .info file my $testname; # Current test name my $filename; # Current filename @@ -1096,6 +1249,8 @@ sub read_info_file($) my $negative; # If set, warn about negative counts my $changed_testname; # If set, warn about changed testname my $line_checksum; # Checksum of current line + my $br_found; + my $br_hit; local *INFO_HANDLE; # Filehandle for .info file info("Reading data file $tracefile\n"); @@ -1146,7 +1301,7 @@ sub read_info_file($) # Switch statement foreach ($line) { - /^TN:([^,]*)/ && do + /^TN:([^,]*)(,diff)?/ && do { # Test name information found $testname = defined($1) ? $1 : ""; @@ -1154,6 +1309,7 @@ sub read_info_file($) { $changed_testname = 1; } + $testname .= $2 if (defined($2)); last; }; @@ -1165,18 +1321,21 @@ sub read_info_file($) $data = $result{$filename}; ($testdata, $sumcount, $funcdata, $checkdata, - $testfncdata, $sumfnccount) = + $testfncdata, $sumfnccount, $testbrdata, + $sumbrcount) = get_info_entry($data); if (defined($testname)) { $testcount = $testdata->{$testname}; $testfnccount = $testfncdata->{$testname}; + $testbrcount = $testbrdata->{$testname}; } else { $testcount = {}; $testfnccount = {}; + $testbrcount = {}; } last; }; @@ -1249,6 +1408,27 @@ sub read_info_file($) } last; }; + + /^BRDA:(\d+),(\d+),(\d+),(\d+|-)/ && do { + # Branch coverage data found + my ($line, $block, $branch, $taken) = + ($1, $2, $3, $4); + + $sumbrcount->{$line} = + br_ivec_push($sumbrcount->{$line}, + $block, $branch, $taken); + + # Add test-specific counts + if (defined($testname)) { + $testbrcount->{$line} = + br_ivec_push( + $testbrcount->{$line}, + $block, $branch, + $taken); + } + last; + }; + /^end_of_record/ && do { # Found end of section marker @@ -1261,12 +1441,16 @@ sub read_info_file($) $testcount; $testfncdata->{$testname} = $testfnccount; + $testbrdata->{$testname} = + $testbrcount; } set_info_entry($data, $testdata, $sumcount, $funcdata, $checkdata, $testfncdata, - $sumfnccount); + $sumfnccount, + $testbrdata, + $sumbrcount); $result{$filename} = $data; last; } @@ -1284,7 +1468,8 @@ sub read_info_file($) $data = $result{$filename}; ($testdata, $sumcount, undef, undef, $testfncdata, - $sumfnccount) = get_info_entry($data); + $sumfnccount, $testbrdata, $sumbrcount) = + get_info_entry($data); # Filter out empty files if (scalar(keys(%{$sumcount})) == 0) @@ -1323,6 +1508,12 @@ sub read_info_file($) } } $data->{"f_hit"} = $hitcount; + + # Get found/hit values for branch data + ($br_found, $br_hit) = get_br_found_and_hit($sumbrcount); + + $data->{"b_found"} = $br_found; + $data->{"b_hit"} = $br_hit; } if (scalar(keys(%result)) == 0) @@ -1362,26 +1553,32 @@ sub get_info_entry($) my $checkdata_ref = $_[0]->{"check"}; my $testfncdata = $_[0]->{"testfnc"}; my $sumfnccount = $_[0]->{"sumfnc"}; + my $testbrdata = $_[0]->{"testbr"}; + my $sumbrcount = $_[0]->{"sumbr"}; my $lines_found = $_[0]->{"found"}; my $lines_hit = $_[0]->{"hit"}; my $fn_found = $_[0]->{"f_found"}; my $fn_hit = $_[0]->{"f_hit"}; + my $br_found = $_[0]->{"b_found"}; + my $br_hit = $_[0]->{"b_hit"}; return ($testdata_ref, $sumcount_ref, $funcdata_ref, $checkdata_ref, - $testfncdata, $sumfnccount, $lines_found, $lines_hit, - $fn_found, $fn_hit); + $testfncdata, $sumfnccount, $testbrdata, $sumbrcount, + $lines_found, $lines_hit, $fn_found, $fn_hit, + $br_found, $br_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]) +# checkdata_ref, testfncdata_ref, sumfcncount_ref, +# testbrdata_ref, sumbrcount_ref[,lines_found, +# lines_hit, f_found, f_hit, $b_found, $b_hit]) # # Update the hash referenced by HASH_REF with the provided data references. # -sub set_info_entry($$$$$$$;$$$$) +sub set_info_entry($$$$$$$$$;$$$$$$) { my $data_ref = $_[0]; @@ -1391,11 +1588,15 @@ sub set_info_entry($$$$$$$;$$$$) $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]; } + $data_ref->{"testbr"} = $_[7]; + $data_ref->{"sumbr"} = $_[8]; + + if (defined($_[9])) { $data_ref->{"found"} = $_[9]; } + if (defined($_[10])) { $data_ref->{"hit"} = $_[10]; } + if (defined($_[11])) { $data_ref->{"f_found"} = $_[11]; } + if (defined($_[12])) { $data_ref->{"f_hit"} = $_[12]; } + if (defined($_[13])) { $data_ref->{"b_found"} = $_[13]; } + if (defined($_[14])) { $data_ref->{"b_hit"} = $_[14]; } } @@ -1503,7 +1704,9 @@ sub merge_func_data($$$) my %result; my $func; - %result = %{$funcdata1}; + if (defined($funcdata1)) { + %result = %{$funcdata1}; + } foreach $func (keys(%{$funcdata2})) { my $line1 = $result{$func}; @@ -1535,7 +1738,9 @@ sub add_fnccount($$) my $fn_hit; my $function; - %result = %{$fnccount1}; + if (defined($fnccount1)) { + %result = %{$fnccount1}; + } foreach $function (keys(%{$fnccount2})) { $result{$function} += $fnccount2->{$function}; } @@ -1589,6 +1794,167 @@ sub add_testfncdata($$) return \%result; } + +# +# brcount_to_db(brcount) +# +# Convert brcount data to the following format: +# +# db: line number -> block hash +# block hash: block number -> branch hash +# branch hash: branch number -> taken value +# + +sub brcount_to_db($) +{ + my ($brcount) = @_; + my $line; + my $db; + + # Add branches from first count to database + foreach $line (keys(%{$brcount})) { + my $brdata = $brcount->{$line}; + my $i; + my $num = br_ivec_len($brdata); + + for ($i = 0; $i < $num; $i++) { + my ($block, $branch, $taken) = br_ivec_get($brdata, $i); + + $db->{$line}->{$block}->{$branch} = $taken; + } + } + + return $db; +} + + +# +# db_to_brcount(db) +# +# Convert branch coverage data back to brcount format. +# + +sub db_to_brcount($) +{ + my ($db) = @_; + my $line; + my $brcount = {}; + my $br_found = 0; + my $br_hit = 0; + + # Convert database back to brcount format + foreach $line (sort({$a <=> $b} keys(%{$db}))) { + my $ldata = $db->{$line}; + my $brdata; + my $block; + + foreach $block (sort({$a <=> $b} keys(%{$ldata}))) { + my $bdata = $ldata->{$block}; + my $branch; + + foreach $branch (sort({$a <=> $b} keys(%{$bdata}))) { + my $taken = $bdata->{$branch}; + + $br_found++; + $br_hit++ if ($taken ne "-" && $taken > 0); + $brdata = br_ivec_push($brdata, $block, + $branch, $taken); + } + } + $brcount->{$line} = $brdata; + } + + return ($brcount, $br_found, $br_hit); +} + + +# +# combine_brcount(brcount1, brcount2, type) +# +# If add is BR_ADD, add branch coverage data and return list (brcount_added, +# br_found, br_hit). If add is BR_SUB, subtract the taken values of brcount2 +# from brcount1 and return (brcount_sub, br_found, br_hit). +# + +sub combine_brcount($$$) +{ + my ($brcount1, $brcount2, $type) = @_; + my $line; + my $block; + my $branch; + my $taken; + my $db; + my $br_found = 0; + my $br_hit = 0; + my $result; + + # Convert branches from first count to database + $db = brcount_to_db($brcount1); + # Combine values from database and second count + foreach $line (keys(%{$brcount2})) { + my $brdata = $brcount2->{$line}; + my $num = br_ivec_len($brdata); + my $i; + + for ($i = 0; $i < $num; $i++) { + ($block, $branch, $taken) = br_ivec_get($brdata, $i); + my $new_taken = $db->{$line}->{$block}->{$branch}; + + if ($type == $BR_ADD) { + $new_taken = br_taken_add($new_taken, $taken); + } elsif ($type == $BR_SUB) { + $new_taken = br_taken_sub($new_taken, $taken); + } + $db->{$line}->{$block}->{$branch} = $new_taken + if (defined($new_taken)); + } + } + # Convert database back to brcount format + ($result, $br_found, $br_hit) = db_to_brcount($db); + + return ($result, $br_found, $br_hit); +} + + +# +# add_testbrdata(testbrdata1, testbrdata2) +# +# Add branch coverage data for several tests. Return reference to +# added_testbrdata. +# + +sub add_testbrdata($$) +{ + my ($testbrdata1, $testbrdata2) = @_; + my %result; + my $testname; + + foreach $testname (keys(%{$testbrdata1})) { + if (defined($testbrdata2->{$testname})) { + my $brcount; + + # Branch coverage data for this testname exists + # in both data sets: add + ($brcount) = combine_brcount($testbrdata1->{$testname}, + $testbrdata2->{$testname}, $BR_ADD); + $result{$testname} = $brcount; + next; + } + # Branch coverage data for this testname is unique to + # data set 1: copy + $result{$testname} = $testbrdata1->{$testname}; + } + + # Add count data for testnames unique to data set 2 + foreach $testname (keys(%{$testbrdata2})) { + if (!defined($result{$testname})) { + $result{$testname} = $testbrdata2->{$testname}; + } + } + return \%result; +} + + # # combine_info_entries(entry_ref1, entry_ref2, filename) # @@ -1605,6 +1971,8 @@ sub combine_info_entries($$$) my $checkdata1; my $testfncdata1; my $sumfnccount1; + my $testbrdata1; + my $sumbrcount1; my $entry2 = $_[1]; # Reference to hash containing second entry my $testdata2; @@ -1613,6 +1981,8 @@ sub combine_info_entries($$$) my $checkdata2; my $testfncdata2; my $sumfnccount2; + my $testbrdata2; + my $sumbrcount2; my %result; # Hash containing combined entry my %result_testdata; @@ -1620,19 +1990,23 @@ sub combine_info_entries($$$) my $result_funcdata; my $result_testfncdata; my $result_sumfnccount; + my $result_testbrdata; + my $result_sumbrcount; my $lines_found; my $lines_hit; my $fn_found; my $fn_hit; + my $br_found; + my $br_hit; my $testname; my $filename = $_[2]; # Retrieve data ($testdata1, $sumcount1, $funcdata1, $checkdata1, $testfncdata1, - $sumfnccount1) = get_info_entry($entry1); + $sumfnccount1, $testbrdata1, $sumbrcount1) = get_info_entry($entry1); ($testdata2, $sumcount2, $funcdata2, $checkdata2, $testfncdata2, - $sumfnccount2) = get_info_entry($entry2); + $sumfnccount2, $testbrdata2, $sumbrcount2) = get_info_entry($entry2); # Merge checksums $checkdata1 = merge_checksums($checkdata1, $checkdata2, $filename); @@ -1645,6 +2019,11 @@ sub combine_info_entries($$$) ($result_sumfnccount, $fn_found, $fn_hit) = add_fnccount($sumfnccount1, $sumfnccount2); + # Combine branch coverage data + $result_testbrdata = add_testbrdata($testbrdata1, $testbrdata2); + ($result_sumbrcount, $br_found, $br_hit) = + combine_brcount($sumbrcount1, $sumbrcount2, $BR_ADD); + # Combine testdata foreach $testname (keys(%{$testdata1})) { @@ -1687,8 +2066,9 @@ sub combine_info_entries($$$) # 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); + $result_sumfnccount, $result_testbrdata, + $result_sumbrcount, $lines_found, $lines_hit, + $fn_found, $fn_hit, $br_found, $br_hit); return(\%result); } @@ -1986,14 +2366,17 @@ sub get_date_string() sub create_sub_dir($) { - system("mkdir", "-p" ,$_[0]) - and die("ERROR: cannot create directory $_!\n"); + my ($dir) = @_; + + system("mkdir", "-p" ,$dir) + and die("ERROR: cannot create directory $dir!\n"); } # # write_description_file(descriptions, overall_found, overall_hit, -# total_fn_found, total_fn_hit) +# total_fn_found, total_fn_hit, total_br_found, +# total_br_hit) # # Write HTML file containing all test case descriptions. DESCRIPTIONS is a # reference to a hash containing a mapping @@ -2003,20 +2386,22 @@ sub create_sub_dir($) # Die on error. # -sub write_description_file($$$$$) +sub write_description_file($$$$$$$) { my %description = %{$_[0]}; my $found = $_[1]; my $hit = $_[2]; my $fn_found = $_[3]; my $fn_hit = $_[4]; + my $br_found = $_[5]; + my $br_hit = $_[6]; 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); + $fn_hit, $br_found, $br_hit, 0); write_test_table_prolog(*HTML_HANDLE, "Test case descriptions - alphabetical list"); @@ -2328,17 +2713,6 @@ sub write_css_file() 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 { @@ -2419,6 +2793,7 @@ sub write_css_file() padding-right: 10px; background-color: #A7FC9D; font-weight: bold; + font-family: sans-serif; } /* Directory view/File view (all): line count entry for files with @@ -2430,16 +2805,7 @@ sub write_css_file() 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; + font-family: sans-serif; } /* Directory view/File view (all): percentage entry for files with @@ -2451,6 +2817,7 @@ sub write_css_file() padding-right: 10px; background-color: #FFEA20; font-weight: bold; + font-family: sans-serif; } /* Directory view/File view (all): line count entry for files with @@ -2462,16 +2829,7 @@ sub write_css_file() 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; + font-family: sans-serif; } /* Directory view/File view (all): percentage entry for files with @@ -2483,6 +2841,7 @@ sub write_css_file() padding-right: 10px; background-color: #FF0000; font-weight: bold; + font-family: sans-serif; } /* Directory view/File view (all): line count entry for files with @@ -2494,53 +2853,28 @@ sub write_css_file() 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; + font-family: sans-serif; } /* File view (all): "show/hide details" link format */ a.detail:link { color: #B8D0FF; + font-size:80%; } /* File view (all): "show/hide details" link - visited format */ a.detail:visited { color: #B8D0FF; + font-size:80%; } /* 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; + font-size:80%; } /* File view (detail): test name entry */ @@ -2549,6 +2883,7 @@ sub write_css_file() text-align: right; padding-right: 10px; background-color: #DAE7FE; + font-family: sans-serif; } /* File view (detail): test percentage entry */ @@ -2558,6 +2893,7 @@ sub write_css_file() padding-left: 10px; padding-right: 10px; background-color: #DAE7FE; + font-family: sans-serif; } /* File view (detail): test lines count entry */ @@ -2567,6 +2903,7 @@ sub write_css_file() padding-left: 10px; padding-right: 10px; background-color: #DAE7FE; + font-family: sans-serif; } /* Test case descriptions: test name format*/ @@ -2605,6 +2942,7 @@ sub write_css_file() padding-right: 10px; background-color: #FF0000; font-weight: bold; + font-family: sans-serif; } /* Source code view: function entry nonzero count*/ @@ -2615,14 +2953,15 @@ sub write_css_file() padding-right: 10px; background-color: #DAE7FE; font-weight: bold; + font-family: sans-serif; } /* Source code view: source code format */ - /* Source code view: source code format */ pre.source { font-family: monospace; white-space: pre; + margin-top: 2px; } /* Source code view: line number format */ @@ -2660,7 +2999,7 @@ sub write_css_file() padding-left: 10px; padding-right: 10px; padding-bottom: 2px; - background-color: #FF0000; + background-color: #FF6230; } /* Source code view (function table): standard link - visited format */ @@ -2678,13 +3017,96 @@ sub write_css_file() background-color: #B5F7AF; } - /* Source code view: format for DiffCov legend */ - span.LegendDiffCov + /* Source code view: format for branches which were executed + * and taken */ + span.branchCov + { + background-color: #CAD7FE; + } + + /* Source code view: format for branches which were executed + * but not taken */ + span.branchNoCov + { + background-color: #FF6230; + } + + /* Source code view: format for branches which were not executed */ + span.branchNoExec + { + background-color: #FF6230; + } + + /* Source code view: format for the source code heading line */ + pre.sourceHeading + { + white-space: pre; + font-family: monospace; + font-weight: bold; + margin: 0px; + } + + /* All views: header legend value for low rate */ + td.headerValueLegL + { + font-family: sans-serif; + text-align: center; + white-space: nowrap; + padding-left: 4px; + padding-right: 2px; + background-color: #FF0000; + font-size: 80%; + } + + /* All views: header legend value for med rate */ + td.headerValueLegM + { + font-family: sans-serif; + text-align: center; + white-space: nowrap; + padding-left: 2px; + padding-right: 2px; + background-color: #FFEA20; + font-size: 80%; + } + + /* All views: header legend value for hi rate */ + td.headerValueLegH { + font-family: sans-serif; text-align: center; + white-space: nowrap; + padding-left: 2px; + padding-right: 4px; + background-color: #A7FC9D; + font-size: 80%; + } + + /* All views except source code view: legend format for low coverage */ + span.coverLegendCovLo + { padding-left: 10px; padding-right: 10px; - background-color: #B5F7AF; + padding-top: 2px; + background-color: #FF0000; + } + + /* All views except source code view: legend format for med coverage */ + span.coverLegendCovMed + { + padding-left: 10px; + padding-right: 10px; + padding-top: 2px; + background-color: #FFEA20; + } + + /* All views except source code view: legend format for hi coverage */ + span.coverLegendCovHi + { + padding-left: 10px; + padding-right: 10px; + padding-top: 2px; + background-color: #A7FC9D; } END_OF_CSS ; @@ -2853,104 +3275,40 @@ END_OF_HTML # -# write_header_line(filehandle, type, additional params..) +# write_header_line(handle, content) # -# Write a header line. +# Write a header line with the specified table contents. # -sub write_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; + my ($handle, @content) = @_; + my $entry; -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; + write_html($handle, " <tr>\n"); + foreach $entry (@content) { + my ($width, $class, $text, $colspan) = @{$entry}; -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 - # ************************************************************* + if (defined($width)) { + $width = " width=\"$width\""; + } else { + $width = ""; + } + if (defined($class)) { + $class = " class=\"$class\""; + } else { + $class = ""; + } + if (defined($colspan)) { + $colspan = " colspan=\"$colspan\""; + } else { + $colspan = ""; + } + $text = "" if (!defined($text)); + write_html($handle, + " <td$width$class$colspan>$text</td>\n"); + } + write_html($handle, " </tr>\n"); } @@ -2965,10 +3323,11 @@ sub write_header_epilog(*$) # ************************************************************* write_html($_[0], <<END_OF_HTML) - <tr><td><img src="$_[1]glass.png" width=3 height=3 alt=""></td></tr> + <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> @@ -2980,84 +3339,82 @@ END_OF_HTML # -# write_file_table_prolog(filehandle, file_heading, lines_heading, func_heading) +# write_file_table_prolog(handle, file_heading, ([heading, num_cols], ...)) # # Write heading for file table. # -sub write_file_table_prolog(*$$$) +sub write_file_table_prolog(*$@) { - # ************************************************************* + my ($handle, $file_heading, @columns) = @_; + my $num_columns = 0; + my $file_width; + my $col; + my $width; - if ($func_coverage) - { - write_html($_[0], <<END_OF_HTML) - <center> - <table width="80%" cellpadding=1 cellspacing=1 border=0> + $width = 20 if (scalar(@columns) == 1); + $width = 10 if (scalar(@columns) == 2); + $width = 8 if (scalar(@columns) > 2); - <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> + foreach $col (@columns) { + my ($heading, $cols) = @{$col}; - <tr> - <td class="tableHead">$_[1]</td> - <td class="tableHead" colspan=3>$_[2]</td> - <td class="tableHead" colspan=2>$_[3]</td> - </tr> + $num_columns += $cols; + } + $file_width = 100 - $num_columns * $width; -END_OF_HTML - ; - } - else - { - write_html($_[0], <<END_OF_HTML) + # Table definition + write_html($handle, <<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> + <td width="$file_width%"><br></td> +END_OF_HTML + # Empty first row + foreach $col (@columns) { + my ($heading, $cols) = @{$col}; + + while ($cols-- > 0) { + write_html($handle, <<END_OF_HTML); + <td width="$width%"></td> +END_OF_HTML + } + } + # Next row + write_html($handle, <<END_OF_HTML); </tr> <tr> - <td class="tableHead">$_[1]</td> - <td class="tableHead" colspan=3>$_[2]</td> + <td class="tableHead">$file_heading</td> +END_OF_HTML + # Heading row + foreach $col (@columns) { + my ($heading, $cols) = @{$col}; + my $colspan = ""; + + $colspan = " colspan=$cols" if ($cols > 1); + write_html($handle, <<END_OF_HTML); + <td class="tableHead"$colspan>$heading</td> +END_OF_HTML + } + write_html($handle, <<END_OF_HTML); </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_file_table_entry(handle, base_dir, filename, page_link, +# ([ found, hit, med_limit, hi_limit, graph ], ..) # # Write an entry of the file table. # -sub write_file_table_entry(*$$$$$$$) +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 ($handle, $base_dir, $filename, $page_link, @entries) = @_; my $file_code; + my $entry; # Add link to source if provided if (defined($page_link) && $page_link ne "") { @@ -3066,158 +3423,88 @@ sub write_file_table_entry(*$$$$$$$) $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) + # First column: filename + write_html($handle, <<END_OF_HTML); <tr> <td class="coverFile">$file_code</td> +END_OF_HTML + # Columns as defined + foreach $entry (@entries) { + my ($found, $hit, $med, $hi, $graph) = @{$entry}; + my $bar_graph; + my $class; + my $rate; + + # Generate bar graph if requested + if ($graph) { + $bar_graph = get_bar_graph_code($base_dir, $found, + $hit); + write_html($handle, <<END_OF_HTML); <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> + } + # Get rate color and text + if ($found == 0) { + $rate = "-"; + $class = "Hi"; + } else { + $rate = sprintf("%.1f %%", $hit * 100 / $found); + $class = $rate_name[classify_rate($found, $hit, + $med, $hi)]; + } + write_html($handle, <<END_OF_HTML); + <td class="coverPer$class">$rate</td> + <td class="coverNum$class">$hit / $found</td> 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 + } + # End of row + write_html($handle, <<END_OF_HTML); </tr> - END_OF_HTML - ; - - # ************************************************************* } # -# write_file_table_detail_entry(filehandle, test_name, -# cover_found, cover_hit, func_found, func_hit) +# write_file_table_detail_entry(filehandle, test_name, ([found, hit], ...)) # # Write entry for detail section in file table. # -sub write_file_table_detail_entry(*$$$$$) +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 = "-"; - } + my ($handle, $test, @entries) = @_; + my $entry; - if ($name =~ /^(.*),diff$/) - { - $name = $1." (converted)"; + if ($test eq "") { + $test = "<span style=\"font-style:italic\"><unnamed></span>"; + } elsif ($test =~ /^(.*),diff$/) { + $test = $1." (converted)"; } - - if ($name eq "") - { - $name = "<span style=\"font-style:italic\"><unnamed></span>"; - } - - # ************************************************************* - - write_html($_[0], <<END_OF_HTML) + # Testname + write_html($handle, <<END_OF_HTML); <tr> - <td class="testName" colspan=2>$name</td> - <td class="testPer">$rate</td> - <td class="testNum">$_[3] / $_[2] lines</td> + <td class="testName" colspan=2>$test</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> + # Test data + foreach $entry (@entries) { + my ($found, $hit) = @{$entry}; + my $rate = "-"; + + if ($found > 0) { + $rate = sprintf("%.1f %%", $hit * 100 / $found); + } + write_html($handle, <<END_OF_HTML); + <td class="testPer">$rate</td> + <td class="testNum">$hit / $found</td> END_OF_HTML - ; - } - write_html($_[0], <<END_OF_HTML) + } + + write_html($handle, <<END_OF_HTML); </tr> END_OF_HTML - ; # ************************************************************* } @@ -3322,6 +3609,17 @@ END_OF_HTML } +sub fmt_centered($$) +{ + my ($width, $text) = @_; + my $w0 = length($text); + my $w1 = int(($width - $w0) / 2); + my $w2 = $width - $w0 - $w1; + + return (" "x$w1).$text.(" "x$w2); +} + + # # write_source_prolog(filehandle) # @@ -3330,6 +3628,15 @@ END_OF_HTML sub write_source_prolog(*) { + my $lineno_heading = " "; + my $branch_heading = ""; + my $line_heading = fmt_centered($line_field_width, "Line data"); + my $source_heading = " Source code"; + + if ($br_coverage) { + $branch_heading = fmt_centered($br_field_width, "Branch data"). + " "; + } # ************************************************************* write_html($_[0], <<END_OF_HTML) @@ -3338,7 +3645,9 @@ sub write_source_prolog(*) <td><br></td> </tr> <tr> - <td><pre class="source"> + <td> +<pre class="sourceHeading">${lineno_heading}${branch_heading}${line_heading} ${source_heading}</pre> +<pre class="source"> END_OF_HTML ; @@ -3347,51 +3656,251 @@ END_OF_HTML # +# get_branch_blocks(brdata) +# +# Group branches that belong to the same basic block. +# +# Returns: [block1, block2, ...] +# block: [branch1, branch2, ...] +# branch: [block_num, branch_num, taken_count, text_length, open, close] +# + +sub get_branch_blocks($) +{ + my ($brdata) = @_; + my $last_block_num; + my $block = []; + my @blocks; + my $i; + my $num = br_ivec_len($brdata); + + # Group branches + for ($i = 0; $i < $num; $i++) { + my ($block_num, $branch, $taken) = br_ivec_get($brdata, $i); + my $br; + + if (defined($last_block_num) && $block_num != $last_block_num) { + push(@blocks, $block); + $block = []; + } + $br = [$block_num, $branch, $taken, 3, 0, 0]; + push(@{$block}, $br); + $last_block_num = $block_num; + } + push(@blocks, $block) if (scalar(@{$block}) > 0); + + # Add braces to first and last branch in group + foreach $block (@blocks) { + $block->[0]->[$BR_OPEN] = 1; + $block->[0]->[$BR_LEN]++; + $block->[scalar(@{$block}) - 1]->[$BR_CLOSE] = 1; + $block->[scalar(@{$block}) - 1]->[$BR_LEN]++; + } + + return @blocks; +} + +# +# get_block_len(block) +# +# Calculate total text length of all branches in a block of branches. +# + +sub get_block_len($) +{ + my ($block) = @_; + my $len = 0; + my $branch; + + foreach $branch (@{$block}) { + $len += $branch->[$BR_LEN]; + } + + return $len; +} + + +# +# get_branch_html(brdata) +# +# Return a list of HTML lines which represent the specified branch coverage +# data in source code view. +# + +sub get_branch_html($) +{ + my ($brdata) = @_; + my @blocks = get_branch_blocks($brdata); + my $block; + my $branch; + my $line_len = 0; + my $line = []; # [branch2|" ", branch|" ", ...] + my @lines; # [line1, line2, ...] + my @result; + + # Distribute blocks to lines + foreach $block (@blocks) { + my $block_len = get_block_len($block); + + # Does this block fit into the current line? + if ($line_len + $block_len <= $br_field_width) { + # Add it + $line_len += $block_len; + push(@{$line}, @{$block}); + next; + } elsif ($block_len <= $br_field_width) { + # It would fit if the line was empty - add it to new + # line + push(@lines, $line); + $line_len = $block_len; + $line = [ @{$block} ]; + next; + } + # Split the block into several lines + foreach $branch (@{$block}) { + if ($line_len + $branch->[$BR_LEN] >= $br_field_width) { + # Start a new line + if (($line_len + 1 <= $br_field_width) && + scalar(@{$line}) > 0 && + !$line->[scalar(@$line) - 1]->[$BR_CLOSE]) { + # Try to align branch symbols to be in + # one # row + push(@{$line}, " "); + } + push(@lines, $line); + $line_len = 0; + $line = []; + } + push(@{$line}, $branch); + $line_len += $branch->[$BR_LEN]; + } + } + push(@lines, $line); + + # Convert to HTML + foreach $line (@lines) { + my $current = ""; + my $current_len = 0; + + foreach $branch (@$line) { + # Skip alignment space + if ($branch eq " ") { + $current .= " "; + $current_len++; + next; + } + + my ($block_num, $br_num, $taken, $len, $open, $close) = + @{$branch}; + my $class; + my $title; + my $text; + + if ($taken eq '-') { + $class = "branchNoExec"; + $text = " # "; + $title = "Branch $br_num was not executed"; + } elsif ($taken == 0) { + $class = "branchNoCov"; + $text = " - "; + $title = "Branch $br_num was not taken"; + } else { + $class = "branchCov"; + $text = " + "; + $title = "Branch $br_num was taken $taken ". + "time"; + $title .= "s" if ($taken > 1); + } + $current .= "[" if ($open); + $current .= "<span class=\"$class\" title=\"$title\">"; + $current .= $text."</span>"; + $current .= "]" if ($close); + $current_len += $len; + } + + # Right-align result text + if ($current_len < $br_field_width) { + $current = (" "x($br_field_width - $current_len)). + $current; + } + push(@result, $current); + } + + return @result; +} + + +# +# format_count(count, width) +# +# Return a right-aligned representation of count that fits in width characters. +# + +sub format_count($$) +{ + my ($count, $width) = @_; + my $result; + my $exp; + + $result = sprintf("%*.0f", $width, $count); + while (length($result) > $width) { + last if ($count < 10); + $exp++; + $count = int($count/10); + $result = sprintf("%*s", $width, ">$count*10^$exp"); + } + return $result; +} + +# # write_source_line(filehandle, line_num, source, hit_count, converted, -# add_anchor) +# brdata, add_anchor) # # Write formatted source code line. Return a line in a format as needed # by gen_png() # -sub write_source_line(*$$$$$) +sub write_source_line(*$$$$$$) { + my ($handle, $line, $source, $count, $converted, $brdata, + $add_anchor) = @_; my $source_format; - my $count; + my $count_format; my $result; my $anchor_start = ""; my $anchor_end = ""; + my $count_field_width = $line_field_width - 1; + my @br_html; + my $html; - if (!(defined$_[3])) - { + # Get branch HTML data for this line + @br_html = get_branch_html($brdata) if ($br_coverage); + + if (!defined($count)) { $result = ""; $source_format = ""; - $count = " "x15; + $count_format = " "x$count_field_width; } - elsif ($_[3] == 0) - { - $result = $_[3]; + elsif ($count == 0) { + $result = $count; $source_format = '<span class="lineNoCov">'; - $count = sprintf("%15d", $_[3]); + $count_format = format_count($count, $count_field_width); } - elsif ($_[4] && defined($highlight)) - { - $result = "*".$_[3]; + elsif ($converted && defined($highlight)) { + $result = "*".$count; $source_format = '<span class="lineDiffCov">'; - $count = sprintf("%15d", $_[3]); + $count_format = format_count($count, $count_field_width); } - else - { - $result = $_[3]; + else { + $result = $count; $source_format = '<span class="lineCov">'; - $count = sprintf("%15d", $_[3]); + $count_format = format_count($count, $count_field_width); } - - $result .= ":".$_[2]; + $result .= ":".$source; # Write out a line number navigation anchor every $nav_resolution # lines if necessary - if ($_[5]) + if ($add_anchor) { $anchor_start = "<a name=\"$_[1]\">"; $anchor_end = "</a>"; @@ -3400,13 +3909,23 @@ sub write_source_line(*$$$$$) # ************************************************************* - write_html($_[0], - $anchor_start. - '<span class="lineNum">'.sprintf("%8d", $_[1]). - " </span>$source_format$count : ". - escape_html($_[2]).($source_format?"</span>":""). - $anchor_end."\n"); - + $html = $anchor_start; + $html .= "<span class=\"lineNum\">".sprintf("%8d", $line)." </span>"; + $html .= shift(@br_html).":" if ($br_coverage); + $html .= "$source_format$count_format : "; + $html .= escape_html($source); + $html .= "</span>" if ($source_format); + $html .= $anchor_end."\n"; + + write_html($handle, $html); + + if ($br_coverage) { + # Add lines for overlong branch information + foreach (@br_html) { + write_html($handle, "<span class=\"lineNum\">". + " </span>$_\n"); + } + } # ************************************************************* return($result); @@ -3460,8 +3979,8 @@ sub write_html_epilog(*$;$) 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> + <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 @@ -3601,28 +4120,6 @@ 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. @@ -3633,20 +4130,16 @@ sub format_rate($$) return $_[0] == 0 ? "-" : sprintf("%.1f", $_[1] * 100 / $_[0])." %"; } -sub get_legend_code($$$) + +sub max($$) { - my ($text, $med, $hi) = @_; - my $result; + my ($a, $b) = @_; - $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; + return $a if ($a > $b); + return $b; } + # # write_header(filehandle, type, trunc_file_name, rel_file_name, lines_found, # lines_hit, funcs_found, funcs_hit, sort_type) @@ -3656,7 +4149,7 @@ EOF # header, test case description header, function view header) # -sub write_header(*$$$$$$$$) +sub write_header(*$$$$$$$$$$) { local *HTML_HANDLE = $_[0]; my $type = $_[1]; @@ -3666,29 +4159,37 @@ sub write_header(*$$$$$$$$) my $lines_hit = $_[5]; my $fn_found = $_[6]; my $fn_hit = $_[7]; - my $sort_type = $_[8]; + my $br_found = $_[8]; + my $br_hit = $_[9]; + my $sort_type = $_[10]; my $base_dir; my $view; my $test; my $base_name; + my $style; + my $rate; + my @row_left; + my @row_right; + my $num_rows; + my $i; $base_name = basename($rel_filename); # Prepare text for "current view" field - if ($type == 0) + if ($type == $HDR_DIR) { # Main overview $base_dir = ""; $view = $overview_title; } - elsif ($type == 1) + elsif ($type == $HDR_FILE) { # 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) + elsif ($type == $HDR_SOURCE || $type == $HDR_FUNC) { # File view my $dir_name = dirname($rel_filename); @@ -3713,14 +4214,16 @@ sub write_header(*$$$$$$$$) # Add function suffix if ($func_coverage) { - if ($type == 2) { + $view .= "<span style=\"font-size: 80%;\">"; + if ($type == $HDR_SOURCE) { $view .= " (source / <a href=\"$base_name.func.$html_ext\">functions</a>)"; - } elsif ($type == 4) { + } elsif ($type == $HDR_FUNC) { $view .= " (<a href=\"$base_name.gcov.$html_ext\">source</a> / functions)"; } + $view .= "</span>"; } } - elsif ($type == 3) + elsif ($type == $HDR_TESTDESC) { # Test description header $base_dir = ""; @@ -3732,84 +4235,126 @@ sub write_header(*$$$$$$$$) $test = escape_html($test_title); # Append link to test description page if available - if (%test_description && ($type != 3)) + if (%test_description && ($type != $HDR_TESTDESC)) { - if ($frames && ($type == 2 || $type == 4)) + if ($frames && ($type == $HDR_SOURCE || $type == $HDR_FUNC)) { # Need to break frameset when clicking this link - $test .= " ( <a href=\"$base_dir". + $test .= " ( <span style=\"font-size:80%;\">". + "<a href=\"$base_dir". "descriptions.$html_ext\" target=\"_parent\">". - "view descriptions</a> )"; + "view descriptions</a></span> )"; } else { - $test .= " ( <a href=\"$base_dir". + $test .= " ( <span style=\"font-size:80%;\">". + "<a href=\"$base_dir". "descriptions.$html_ext\">". - "view descriptions</a> )"; + "view descriptions</a></span> )"; } } # 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)); + + # Left row + push(@row_left, [[ "10%", "headerItem", "Current view:" ], + [ "35%", "headerValue", $view ]]); + push(@row_left, [[undef, "headerItem", "Test:"], + [undef, "headerValue", $test]]); + push(@row_left, [[undef, "headerItem", "Date:"], + [undef, "headerValue", $date]]); + + # Right row + if ($legend && ($type == $HDR_SOURCE || $type == $HDR_FUNC)) { + my $text = <<END_OF_HTML; + Lines: + <span class="coverLegendCov">hit</span> + <span class="coverLegendNoCov">not hit</span> +END_OF_HTML + if ($br_coverage) { + $text .= <<END_OF_HTML; + | Branches: + <span class="coverLegendCov">+</span> taken + <span class="coverLegendNoCov">-</span> not taken + <span class="coverLegendNoCov">#</span> not executed +END_OF_HTML + } + push(@row_left, [[undef, "headerItem", "Legend:"], + [undef, "headerValueLeg", $text]]); + } elsif ($legend && ($type != $HDR_TESTDESC)) { + my $text = <<END_OF_HTML; + Rating: + <span class="coverLegendCovLo" title="Coverage rates below $med_limit % are classified as low">low: < $med_limit %</span> + <span class="coverLegendCovMed" title="Coverage rates between $med_limit % and $hi_limit % are classified as medium">medium: >= $med_limit %</span> + <span class="coverLegendCovHi" title="Coverage rates of $hi_limit % and more are classified as high">high: >= $hi_limit %</span> +END_OF_HTML + push(@row_left, [[undef, "headerItem", "Legend:"], + [undef, "headerValueLeg", $text]]); + } + if ($type == $HDR_TESTDESC) { + push(@row_right, [[ "55%" ]]); } 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"); + push(@row_right, [["15%", undef, undef ], + ["10%", "headerCovTableHead", "Hit" ], + ["10%", "headerCovTableHead", "Total" ], + ["15%", "headerCovTableHead", "Coverage"]]); + } + # Line coverage + $style = $rate_name[classify_rate($lines_found, $lines_hit, + $med_limit, $hi_limit)]; + $rate = format_rate($lines_found, $lines_hit); + push(@row_right, [[undef, "headerItem", "Lines:"], + [undef, "headerCovTableEntry", $lines_hit], + [undef, "headerCovTableEntry", $lines_found], + [undef, "headerCovTableEntry$style", $rate]]) + if ($type != $HDR_TESTDESC); + # Function coverage + if ($func_coverage) { + $style = $rate_name[classify_rate($fn_found, $fn_hit, + $fn_med_limit, $fn_hi_limit)]; + $rate = format_rate($fn_found, $fn_hit); + push(@row_right, [[undef, "headerItem", "Functions:"], + [undef, "headerCovTableEntry", $fn_hit], + [undef, "headerCovTableEntry", $fn_found], + [undef, "headerCovTableEntry$style", $rate]]) + if ($type != $HDR_TESTDESC); + } + # Branch coverage + if ($br_coverage) { + $style = $rate_name[classify_rate($br_found, $br_hit, + $br_med_limit, $br_hi_limit)]; + $rate = format_rate($br_found, $br_hit); + push(@row_right, [[undef, "headerItem", "Branches:"], + [undef, "headerCovTableEntry", $br_hit], + [undef, "headerCovTableEntry", $br_found], + [undef, "headerCovTableEntry$style", $rate]]) + if ($type != $HDR_TESTDESC); + } + + # Print rows + $num_rows = max(scalar(@row_left), scalar(@row_right)); + for ($i = 0; $i < $num_rows; $i++) { + my $left = $row_left[$i]; + my $right = $row_right[$i]; + + if (!defined($left)) { + $left = [[undef, undef, undef], [undef, undef, undef]]; + } + if (!defined($right)) { + $right = []; } + write_header_line(*HTML_HANDLE, @{$left}, + [ $i == 0 ? "5%" : undef, undef, undef], + @{$right}); } + + # Fourth line 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) # @@ -3817,15 +4362,18 @@ sub get_sorted_keys($$) { my ($hash, $type) = @_; - if ($type == 0) { + if ($type == $SORT_FILE) { # Sort by name return sort(keys(%{$hash})); - } elsif ($type == 1) { + } elsif ($type == $SORT_LINE) { # Sort by line coverage - return sort({$hash->{$a}[5] <=> $hash->{$b}[5]} keys(%{$hash})); - } elsif ($type == 2) { + return sort({$hash->{$a}[7] <=> $hash->{$b}[7]} keys(%{$hash})); + } elsif ($type == $SORT_FUNC) { # Sort by function coverage; - return sort({$hash->{$a}[6] <=> $hash->{$b}[6]} keys(%{$hash})); + return sort({$hash->{$a}[8] <=> $hash->{$b}[8]} keys(%{$hash})); + } elsif ($type == $SORT_BRANCH) { + # Sort by br coverage; + return sort({$hash->{$a}[9] <=> $hash->{$b}[9]} keys(%{$hash})); } } @@ -3858,7 +4406,7 @@ sub get_file_code($$$$) my $link; if ($sort_button) { - if ($type == 1) { + if ($type == $HEAD_NO_DETAIL) { $link = "index.$html_ext"; } else { $link = "index-detail.$html_ext"; @@ -3875,12 +4423,12 @@ sub get_line_code($$$$$) my $result = $text; my $sort_link; - if ($type == 1) { + if ($type == $HEAD_NO_DETAIL) { # Just text if ($sort_button) { $sort_link = "index-sort-l.$html_ext"; } - } elsif ($type == 2) { + } elsif ($type == $HEAD_DETAIL_HIDDEN) { # Text + link to detail view $result .= ' ( <a class="detail" href="index-detail'. $fileview_sortname[$sort_type].'.'.$html_ext. @@ -3910,7 +4458,7 @@ sub get_func_code($$$$) my $link; if ($sort_button) { - if ($type == 1) { + if ($type == $HEAD_NO_DETAIL) { $link = "index-sort-f.$html_ext"; } else { $link = "index-detail-sort-f.$html_ext"; @@ -3920,9 +4468,26 @@ sub get_func_code($$$$) return $result; } +sub get_br_code($$$$) +{ + my ($type, $text, $sort_button, $base) = @_; + my $result = $text; + my $link; + + if ($sort_button) { + if ($type == $HEAD_NO_DETAIL) { + $link = "index-sort-b.$html_ext"; + } else { + $link = "index-detail-sort-b.$html_ext"; + } + } + $result .= get_sort_code($link, "Sort by branch coverage", $base); + return $result; +} + # # write_file_table(filehandle, base_dir, overview, testhash, testfnchash, -# fileview, sort_type) +# testbrhash, fileview, sort_type) # # Write a complete file table. OVERVIEW is a reference to a hash containing # the following mapping: @@ -3940,70 +4505,107 @@ sub get_func_code($$$$) # otherwise. # -sub write_file_table(*$$$$$$) +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 $testbrhash = $_[5]; + my $fileview = $_[6]; + my $sort_type = $_[7]; my $filename; my $bar_graph; my $hit; my $found; my $fn_found; my $fn_hit; + my $br_found; + my $br_hit; my $page_link; my $testname; my $testdata; my $testfncdata; - my $testcount; - my $testfnccount; + my $testbrdata; my %affecting_tests; my $line_code = ""; my $func_code; + my $br_code; my $file_code; + my @head_columns; # Determine HTML code for column headings if (($base_dir ne "") && $show_details) { my $detailed = keys(%{$testhash}); - $file_code = get_file_code($detailed ? 2 : 1, + $file_code = get_file_code($detailed ? $HEAD_DETAIL_HIDDEN : + $HEAD_NO_DETAIL, $fileview ? "Filename" : "Directory", - $sort && $sort_type != 0, $base_dir); - $line_code = get_line_code($detailed ? 3 : 2, $sort_type, + $sort && $sort_type != $SORT_FILE, + $base_dir); + $line_code = get_line_code($detailed ? $HEAD_DETAIL_SHOWN : + $HEAD_DETAIL_HIDDEN, + $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); + $sort && $sort_type != $SORT_LINE, + $base_dir); + $func_code = get_func_code($detailed ? $HEAD_DETAIL_HIDDEN : + $HEAD_NO_DETAIL, + "Functions", + $sort && $sort_type != $SORT_FUNC, + $base_dir); + $br_code = get_br_code($detailed ? $HEAD_DETAIL_HIDDEN : + $HEAD_NO_DETAIL, + "Branches", + $sort && $sort_type != $SORT_BRANCH, + $base_dir); } else { - $file_code = get_file_code(1, + $file_code = get_file_code($HEAD_NO_DETAIL, $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); + $sort && $sort_type != $SORT_FILE, + $base_dir); + $line_code = get_line_code($HEAD_NO_DETAIL, $sort_type, "Line Coverage", + $sort && $sort_type != $SORT_LINE, + $base_dir); + $func_code = get_func_code($HEAD_NO_DETAIL, "Functions", + $sort && $sort_type != $SORT_FUNC, + $base_dir); + $br_code = get_br_code($HEAD_NO_DETAIL, "Branches", + $sort && $sort_type != $SORT_BRANCH, + $base_dir); + } + push(@head_columns, [ $line_code, 3 ]); + push(@head_columns, [ $func_code, 2]) if ($func_coverage); + push(@head_columns, [ $br_code, 2]) if ($br_coverage); + + write_file_table_prolog(*HTML_HANDLE, $file_code, @head_columns); 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); + my @columns; + ($found, $hit, $fn_found, $fn_hit, $br_found, $br_hit, + $page_link) = @{$overview->{$filename}}; + + # Line coverage + push(@columns, [$found, $hit, $med_limit, $hi_limit, 1]); + # Function coverage + if ($func_coverage) { + push(@columns, [$fn_found, $fn_hit, $fn_med_limit, + $fn_hi_limit, 0]); + } + # Branch coverage + if ($br_coverage) { + push(@columns, [$br_found, $br_hit, $br_med_limit, + $br_hi_limit, 0]); + } + write_file_table_entry(*HTML_HANDLE, $base_dir, $filename, + $page_link, @columns); $testdata = $testhash->{$filename}; $testfncdata = $testfnchash->{$filename}; - - write_file_table_entry(*HTML_HANDLE, $filename, $bar_graph, - $found, $hit, $fn_found, $fn_hit, - $page_link); + $testbrdata = $testbrhash->{$filename}; # Check whether we should write test specific coverage # as well @@ -4011,18 +4613,15 @@ sub write_file_table(*$$$$$$) # Filter out those tests that actually affect this file %affecting_tests = %{ get_affecting_tests($testdata, - $testfncdata) }; + $testfncdata, $testbrdata) }; # 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) = + my @results; + ($found, $hit, $fn_found, $fn_hit, $br_found, $br_hit) = split(",", $affecting_tests{$testname}); # Insert link to description of available @@ -4033,8 +4632,11 @@ sub write_file_table(*$$$$$$) "$testname</a>"; } + push(@results, [$found, $hit]); + push(@results, [$fn_found, $fn_hit]) if ($func_coverage); + push(@results, [$br_found, $br_hit]) if ($br_coverage); write_file_table_detail_entry(*HTML_HANDLE, $testname, - $found, $hit, $fn_found, $fn_hit); + @results); } } @@ -4095,39 +4697,224 @@ sub get_func_found_and_hit($) # -# get_affecting_tests(testdata, testfncdata) +# br_taken_to_num(taken) +# +# Convert a branch taken value .info format to number format. +# + +sub br_taken_to_num($) +{ + my ($taken) = @_; + + return 0 if ($taken eq '-'); + return $taken + 1; +} + + +# +# br_num_to_taken(taken) +# +# Convert a branch taken value in number format to .info format. +# + +sub br_num_to_taken($) +{ + my ($taken) = @_; + + return '-' if ($taken == 0); + return $taken - 1; +} + + +# +# br_taken_add(taken1, taken2) +# +# Return the result of taken1 + taken2 for 'branch taken' values. +# + +sub br_taken_add($$) +{ + my ($t1, $t2) = @_; + + return $t1 if (!defined($t2)); + return $t2 if (!defined($t1)); + return $t1 if ($t2 eq '-'); + return $t2 if ($t1 eq '-'); + return $t1 + $t2; +} + + +# +# br_taken_sub(taken1, taken2) +# +# Return the result of taken1 - taken2 for 'branch taken' values. Return 0 +# if the result would become negative. +# + +sub br_taken_sub($$) +{ + my ($t1, $t2) = @_; + + return $t1 if (!defined($t2)); + return undef if (!defined($t1)); + return $t1 if ($t1 eq '-'); + return $t1 if ($t2 eq '-'); + return 0 if $t2 > $t1; + return $t1 - $t2; +} + + +# +# br_ivec_len(vector) +# +# Return the number of entries in the branch coverage vector. +# + +sub br_ivec_len($) +{ + my ($vec) = @_; + + return 0 if (!defined($vec)); + return (length($vec) * 8 / $BR_VEC_WIDTH) / $BR_VEC_ENTRIES; +} + + +# +# br_ivec_get(vector, number) +# +# Return an entry from the branch coverage vector. +# + +sub br_ivec_get($$) +{ + my ($vec, $num) = @_; + my $block; + my $branch; + my $taken; + my $offset = $num * $BR_VEC_ENTRIES; + + # Retrieve data from vector + $block = vec($vec, $offset + $BR_BLOCK, $BR_VEC_WIDTH); + $branch = vec($vec, $offset + $BR_BRANCH, $BR_VEC_WIDTH); + $taken = vec($vec, $offset + $BR_TAKEN, $BR_VEC_WIDTH); + + # Decode taken value from an integer + $taken = br_num_to_taken($taken); + + return ($block, $branch, $taken); +} + + +# +# br_ivec_push(vector, block, branch, taken) +# +# Add an entry to the branch coverage vector. If an entry with the same +# branch ID already exists, add the corresponding taken values. +# + +sub br_ivec_push($$$$) +{ + my ($vec, $block, $branch, $taken) = @_; + my $offset; + my $num = br_ivec_len($vec); + my $i; + + $vec = "" if (!defined($vec)); + + # Check if branch already exists in vector + for ($i = 0; $i < $num; $i++) { + my ($v_block, $v_branch, $v_taken) = br_ivec_get($vec, $i); + + next if ($v_block != $block || $v_branch != $branch); + + # Add taken counts + $taken = br_taken_add($taken, $v_taken); + last; + } + + $offset = $i * $BR_VEC_ENTRIES; + $taken = br_taken_to_num($taken); + + # Add to vector + vec($vec, $offset + $BR_BLOCK, $BR_VEC_WIDTH) = $block; + vec($vec, $offset + $BR_BRANCH, $BR_VEC_WIDTH) = $branch; + vec($vec, $offset + $BR_TAKEN, $BR_VEC_WIDTH) = $taken; + + return $vec; +} + + +# +# get_br_found_and_hit(sumbrcount) +# +# Return (br_found, br_hit) for sumbrcount +# + +sub get_br_found_and_hit($) +{ + my ($sumbrcount) = @_; + my $line; + my $br_found = 0; + my $br_hit = 0; + + foreach $line (keys(%{$sumbrcount})) { + my $brdata = $sumbrcount->{$line}; + my $i; + my $num = br_ivec_len($brdata); + + for ($i = 0; $i < $num; $i++) { + my $taken; + + (undef, undef, $taken) = br_ivec_get($brdata, $i); + + $br_found++; + $br_hit++ if ($taken ne "-" && $taken > 0); + } + } + + return ($br_found, $br_hit); +} + + +# +# get_affecting_tests(testdata, testfncdata, testbrdata) # # 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($$) +sub get_affecting_tests($$$) { - my $testdata = $_[0]; - my $testfncdata = $_[1]; + my ($testdata, $testfncdata, $testbrdata) = @_; my $testname; my $testcount; my $testfnccount; + my $testbrcount; my %result; my $found; my $hit; my $fn_found; my $fn_hit; + my $br_found; + my $br_hit; foreach $testname (keys(%{$testdata})) { # Get (line number -> count) hash for this test case $testcount = $testdata->{$testname}; $testfnccount = $testfncdata->{$testname}; + $testbrcount = $testbrdata->{$testname}; # Calculate sum ($found, $hit) = get_found_and_hit($testcount); ($fn_found, $fn_hit) = get_func_found_and_hit($testfnccount); + ($br_found, $br_hit) = get_br_found_and_hit($testbrcount); if ($hit>0) { - $result{$testname} = "$found,$hit,$fn_found,$fn_hit"; + $result{$testname} = "$found,$hit,$fn_found,$fn_hit,". + "$br_found,$br_hit"; } } @@ -4149,7 +4936,7 @@ sub get_hash_reverse($) # # write_source(filehandle, source_filename, count_data, checksum_data, -# converted_data, func_data) +# converted_data, func_data, sumbrcount) # # Write an HTML view of a source code file. Returns a list containing # data as needed by gen_png(). @@ -4157,7 +4944,7 @@ sub get_hash_reverse($) # Die on error. # -sub write_source($$$$$$) +sub write_source($$$$$$$) { local *HTML_HANDLE = $_[0]; local *SOURCE_HANDLE; @@ -4168,6 +4955,7 @@ sub write_source($$$$$$) my $checkdata = $_[3]; my $converted = $_[4]; my $funcdata = $_[5]; + my $sumbrcount = $_[6]; my $datafunc = get_hash_reverse($funcdata); my $add_anchor; @@ -4185,6 +4973,9 @@ sub write_source($$$$$$) { chomp($_); + # Also remove CR from line-end + s/\015$//; + # Source code matches coverage data? if (defined($checkdata->{$line_number}) && ($checkdata->{$line_number} ne md5_base64($_))) @@ -4211,7 +5002,7 @@ sub write_source($$$$$$) write_source_line(HTML_HANDLE, $line_number, $_, $count_data{$line_number}, $converted->{$line_number}, - $add_anchor)); + $sumbrcount->{$line_number}, $add_anchor)); } close(SOURCE_HANDLE); @@ -4270,7 +5061,8 @@ sub funcview_get_sorted($$$) # # write_function_table(filehandle, source_file, sumcount, funcdata, -# sumfnccount, testfncdata) +# sumfnccount, testfncdata, sumbrcount, testbrdata, +# base_name, base_dir, sort_type) # # Write an HTML table listing all functions in a source file, including # also function call counts and line coverages inside of each function. @@ -4278,7 +5070,7 @@ sub funcview_get_sorted($$$) # Die on error. # -sub write_function_table(*$$$$$$$$) +sub write_function_table(*$$$$$$$$$$) { local *HTML_HANDLE = $_[0]; my $source = $_[1]; @@ -4286,9 +5078,11 @@ sub write_function_table(*$$$$$$$$) my $funcdata = $_[3]; my $sumfncdata = $_[4]; my $testfncdata = $_[5]; - my $name = $_[6]; - my $base = $_[7]; - my $type = $_[8]; + my $sumbrcount = $_[6]; + my $testbrdata = $_[7]; + my $name = $_[8]; + my $base = $_[9]; + my $type = $_[10]; my $func; my $func_code; my $count_code; @@ -4309,11 +5103,23 @@ END_OF_HTML # Get a sorted table foreach $func (funcview_get_sorted($funcdata, $sumfncdata, $type)) { + if (!defined($funcdata->{$func})) + { + next; + } + my $startline = $funcdata->{$func} - $func_offset; - my $name = escape_html($func); + my $name = $func; my $count = $sumfncdata->{$name}; my $countstyle; + # Demangle C++ function names if requested + if ($demangle_cpp) { + $name = `c++filt "$name"`; + chomp($name); + } + # Escape any remaining special characters + $name = escape_html($name); if ($startline < 1) { $startline = 1; } @@ -4402,14 +5208,16 @@ sub subtract_counts($$) sub subtract_fnccounts($$) { - my %data = %{$_[0]}; - my %base = %{$_[1]}; + my %data; + my %base; my $func; my $data_count; my $base_count; my $fn_hit = 0; my $fn_found = 0; + %data = %{$_[0]} if (defined($_[0])); + %base = %{$_[1]} if (defined($_[1])); foreach $func (keys(%data)) { $fn_found++; $data_count = $data{$func}; @@ -4452,18 +5260,24 @@ sub apply_baseline($$) my $data_funcdata; my $data_checkdata; my $data_testfncdata; + my $data_testbrdata; my $data_count; my $data_testfnccount; + my $data_testbrcount; my $base; my $base_checkdata; my $base_sumfnccount; + my $base_sumbrcount; my $base_count; my $sumcount; my $sumfnccount; + my $sumbrcount; my $found; my $hit; my $fn_found; my $fn_hit; + my $br_found; + my $br_hit; foreach $filename (keys(%data_hash)) { @@ -4479,9 +5293,11 @@ sub apply_baseline($$) # Get set entries for data and baseline ($data_testdata, undef, $data_funcdata, $data_checkdata, - $data_testfncdata) = get_info_entry($data); + $data_testfncdata, undef, $data_testbrdata) = + get_info_entry($data); (undef, $base_count, undef, $base_checkdata, undef, - $base_sumfnccount) = get_info_entry($base); + $base_sumfnccount, undef, $base_sumbrcount) = + get_info_entry($base); # Check for compatible checksums merge_checksums($data_checkdata, $base_checkdata, $filename); @@ -4489,6 +5305,7 @@ sub apply_baseline($$) # sumcount has to be calculated anew $sumcount = {}; $sumfnccount = {}; + $sumbrcount = {}; # For each test case, subtract test specific counts foreach $testname (keys(%{$data_testdata})) @@ -4496,12 +5313,17 @@ sub apply_baseline($$) # Get counts of both data and baseline $data_count = $data_testdata->{$testname}; $data_testfnccount = $data_testfncdata->{$testname}; + $data_testbrcount = $data_testbrdata->{$testname}; ($data_count, undef, $hit) = subtract_counts($data_count, $base_count); ($data_testfnccount) = subtract_fnccounts($data_testfnccount, $base_sumfnccount); + ($data_testbrcount) = + combine_brcount($data_testbrcount, + $base_sumbrcount, $BR_SUB); + # Check whether this test case did hit any line at all if ($hit > 0) @@ -4510,6 +5332,8 @@ sub apply_baseline($$) $data_testdata->{$testname} = $data_count; $data_testfncdata->{$testname} = $data_testfnccount; + $data_testbrdata->{$testname} = + $data_testbrcount; } else { @@ -4517,19 +5341,24 @@ sub apply_baseline($$) # file delete($data_testdata->{$testname}); delete($data_testfncdata->{$testname}); + delete($data_testbrdata->{$testname}); } # Add counts to sum of counts ($sumcount, $found, $hit) = add_counts($sumcount, $data_count); ($sumfnccount, $fn_found, $fn_hit) = - add_fnccounts($sumfnccount, $data_testfnccount); + add_fnccount($sumfnccount, $data_testfnccount); + ($sumbrcount, $br_found, $br_hit) = + combine_brcount($sumbrcount, $data_testbrcount, + $BR_ADD); } # Write back resulting entry set_info_entry($data, $data_testdata, $sumcount, $data_funcdata, $data_checkdata, $data_testfncdata, $sumfnccount, - $found, $hit, $fn_found, $fn_hit); + $data_testbrdata, $sumbrcount, $found, $hit, + $fn_found, $fn_hit, $br_found, $br_hit); $data_hash{$filename} = $data; } diff --git a/3rdParty/LCov/geninfo b/3rdParty/LCov/geninfo index 055641b..dcb1a67 100755 --- a/3rdParty/LCov/geninfo +++ b/3rdParty/LCov/geninfo @@ -1,6 +1,6 @@ #!/usr/bin/perl -w # -# Copyright (c) International Business Machines Corp., 2002,2007 +# Copyright (c) International Business Machines Corp., 2002,2010 # # 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 @@ -51,12 +51,14 @@ use strict; use File::Basename; +use File::Spec::Functions qw /abs2rel catdir file_name_is_absolute splitdir + splitpath/; use Getopt::Long; use Digest::MD5 qw(md5_base64); # Constants -our $lcov_version = "LCOV version 1.7"; +our $lcov_version = 'LCOV version 1.9'; our $lcov_url = "http://ltp.sourceforge.net/coverage/lcov.php"; our $gcov_tool = "gcov"; our $tool_name = basename($0); @@ -72,34 +74,75 @@ our $COMPAT_HAMMER = "hammer"; our $ERROR_GCOV = 0; our $ERROR_SOURCE = 1; +our $ERROR_GRAPH = 2; + +our $EXCL_START = "LCOV_EXCL_START"; +our $EXCL_STOP = "LCOV_EXCL_STOP"; +our $EXCL_LINE = "LCOV_EXCL_LINE"; + +our $BR_LINE = 0; +our $BR_BLOCK = 1; +our $BR_BRANCH = 2; +our $BR_TAKEN = 3; +our $BR_VEC_ENTRIES = 4; +our $BR_VEC_WIDTH = 32; + +our $UNNAMED_BLOCK = 9999; # Prototypes sub print_usage(*); sub gen_info($); -sub process_dafile($); +sub process_dafile($$); sub match_filename($@); sub solve_ambiguous_match($$$); sub split_filename($); sub solve_relative_path($$); -sub get_dir($); sub read_gcov_header($); sub read_gcov_file($); -sub read_bb_file($$); -sub read_string(*$); -sub read_gcno_file($$); -sub read_gcno_string(*$); -sub read_hammer_bbg_file($$); -sub read_hammer_bbg_string(*$); -sub unpack_int32($$); sub info(@); sub get_gcov_version(); sub system_no_output($@); sub read_config($); sub apply_config($); -sub gen_initial_info($); -sub process_graphfile($); +sub get_exclusion_data($); +sub apply_exclusion_data($$); +sub process_graphfile($$); +sub filter_fn_name($); sub warn_handler($); sub die_handler($); +sub graph_error($$); +sub graph_expect($); +sub graph_read(*$;$); +sub graph_skip(*$;$); +sub sort_uniq(@); +sub sort_uniq_lex(@); +sub graph_cleanup($); +sub graph_find_base($); +sub graph_from_bb($$$); +sub graph_add_order($$$); +sub read_bb_word(*;$); +sub read_bb_value(*;$); +sub read_bb_string(*$); +sub read_bb($$); +sub read_bbg_word(*;$); +sub read_bbg_value(*;$); +sub read_bbg_string(*); +sub read_bbg_lines_record(*$$$$$$); +sub read_bbg($$); +sub read_gcno_word(*;$); +sub read_gcno_value(*$;$); +sub read_gcno_string(*$); +sub read_gcno_lines_record(*$$$$$$$); +sub read_gcno_function_record(*$$$$); +sub read_gcno($$); +sub get_gcov_capabilities(); +sub get_overall_line($$$$); +sub print_overall_rate($$$$$$$$$); +sub br_gvec_len($); +sub br_gvec_get($$); +sub debug($); +sub int_handler(); + # Global variables our $gcov_version; @@ -115,7 +158,6 @@ our $version; our $follow; our $checksum; our $no_checksum; -our $preserve_paths; our $compat_libtool; our $no_compat_libtool; our $adjust_testname; @@ -127,6 +169,11 @@ our @ignore; # List of errors to ignore (array) our $initial; our $no_recursion = 0; our $maxdepth; +our $no_markers = 0; +our $opt_derive_func_data = 0; +our $debug = 0; +our $gcov_caps; +our @gcov_options; our $cwd = `pwd`; chomp($cwd); @@ -141,8 +188,14 @@ $SIG{"INT"} = \&int_handler; $SIG{__WARN__} = \&warn_handler; $SIG{__DIE__} = \&die_handler; +# Prettify version string +$lcov_version =~ s/\$\s*Revision\s*:?\s*(\S+)\s*\$/$1/; + +# Set LANG so that gcov output will be in a unified format +$ENV{"LANG"} = "C"; + # Read configuration file if available -if (-r $ENV{"HOME"}."/.lcovrc") +if (defined($ENV{"HOME"}) && (-r $ENV{"HOME"}."/.lcovrc")) { $config = read_config($ENV{"HOME"}."/.lcovrc"); } @@ -170,21 +223,24 @@ if ($config) } # Parse command line options -if (!GetOptions("test-name=s" => \$test_name, - "output-filename=s" => \$output_filename, +if (!GetOptions("test-name|t=s" => \$test_name, + "output-filename|o=s" => \$output_filename, "checksum" => \$checksum, "no-checksum" => \$no_checksum, - "base-directory=s" => \$base_directory, - "version" =>\$version, - "quiet" => \$quiet, - "help|?" => \$help, - "follow" => \$follow, + "base-directory|b=s" => \$base_directory, + "version|v" =>\$version, + "quiet|q" => \$quiet, + "help|h|?" => \$help, + "follow|f" => \$follow, "compat-libtool" => \$compat_libtool, "no-compat-libtool" => \$no_compat_libtool, "gcov-tool=s" => \$gcov_tool, "ignore-errors=s" => \@ignore_errors, "initial|i" => \$initial, "no-recursion" => \$no_recursion, + "no-markers" => \$no_markers, + "derive-func-data" => \$opt_derive_func_data, + "debug" => \$debug, )) { print(STDERR "Use $tool_name --help to get usage information\n"); @@ -323,6 +379,7 @@ if (@ignore_errors) { /^gcov$/ && do { $ignore[$ERROR_GCOV] = 1; next; } ; /^source$/ && do { $ignore[$ERROR_SOURCE] = 1; next; }; + /^graph$/ && do { $ignore[$ERROR_GRAPH] = 1; next; }; die("ERROR: unknown argument for --ignore-errors: $_\n"); } } @@ -353,11 +410,12 @@ else $graph_file_extension = ".gcno"; } -# Check for availability of --preserve-paths option of gcov -if (`$gcov_tool --help` =~ /--preserve-paths/) -{ - $preserve_paths = "--preserve-paths"; -} +# Determine gcov options +$gcov_caps = get_gcov_capabilities(); +push(@gcov_options, "-b") if ($gcov_caps->{'branch-probabilities'}); +push(@gcov_options, "-c") if ($gcov_caps->{'branch-counts'}); +push(@gcov_options, "-a") if ($gcov_caps->{'all-blocks'}); +push(@gcov_options, "-p") if ($gcov_caps->{'preserve-paths'}); # Check output filename if (defined($output_filename) && ($output_filename ne "-")) @@ -378,19 +436,13 @@ if (defined($output_filename) && ($output_filename ne "-")) } # Do something -if ($initial) -{ - foreach (@data_directory) - { - gen_initial_info($_); - } +foreach my $entry (@data_directory) { + gen_info($entry); } -else -{ - foreach (@data_directory) - { - gen_info($_); - } + +if ($initial) { + warn("Note: --initial does not generate branch coverage ". + "data\n"); } info("Finished .info-file creation\n"); @@ -426,15 +478,51 @@ sequentially. --(no-)checksum Enable (disable) line checksumming --(no-)compat-libtool Enable (disable) libtool compatibility mode --gcov-tool TOOL Specify gcov tool location - --ignore-errors ERROR Continue after ERROR (gcov, source) - --no-recursion Exlude subdirectories from processing + --ignore-errors ERROR Continue after ERROR (gcov, source, graph) + --no-recursion Exclude subdirectories from processing --function-coverage Capture function call counts + --no-markers Ignore exclusion markers in source code + --derive-func-data Generate function data from line data For more information see: $lcov_url END_OF_USAGE ; } +# +# get_common_prefix(min_dir, filenames) +# +# Return the longest path prefix shared by all filenames. MIN_DIR specifies +# the minimum number of directories that a filename may have after removing +# the prefix. +# + +sub get_common_prefix($@) +{ + my ($min_dir, @files) = @_; + my $file; + my @prefix; + my $i; + + foreach $file (@files) { + my ($v, $d, $f) = splitpath($file); + my @comp = splitdir($d); + + if (!@prefix) { + @prefix = @comp; + next; + } + for ($i = 0; $i < scalar(@comp) && $i < scalar(@prefix); $i++) { + if ($comp[$i] ne $prefix[$i] || + ((scalar(@comp) - ($i + 1)) <= $min_dir)) { + delete(@prefix[$i..scalar(@prefix)]); + last; + } + } + } + + return catdir(@prefix); +} # # gen_info(directory) @@ -473,52 +561,166 @@ sub gen_info($) { my $directory = $_[0]; my @file_list; + my $file; + my $prefix; + my $type; + my $ext; + + if ($initial) { + $type = "graph"; + $ext = $graph_file_extension; + } else { + $type = "data"; + $ext = $data_file_extension; + } if (-d $directory) { - info("Scanning $directory for $data_file_extension ". - "files ...\n"); + info("Scanning $directory for $ext files ...\n"); - @file_list = `find "$directory" $maxdepth $follow -name \\*$data_file_extension -type f 2>/dev/null`; + @file_list = `find "$directory" $maxdepth $follow -name \\*$ext -type f 2>/dev/null`; chomp(@file_list); - @file_list or die("ERROR: no $data_file_extension files found ". - "in $directory!\n"); - info("Found %d data files in %s\n", $#file_list+1, $directory); + @file_list or + die("ERROR: no $ext files found in $directory!\n"); + $prefix = get_common_prefix(1, @file_list); + info("Found %d %s files in %s\n", $#file_list+1, $type, + $directory); } else { @file_list = ($directory); + $prefix = ""; } # Process all files in list - foreach (@file_list) { process_dafile($_); } + foreach $file (@file_list) { + # Process file + if ($initial) { + process_graphfile($file, $prefix); + } else { + process_dafile($file, $prefix); + } + } } +sub derive_data($$$) +{ + my ($contentdata, $funcdata, $bbdata) = @_; + my @gcov_content = @{$contentdata}; + my @gcov_functions = @{$funcdata}; + my %fn_count; + my %ln_fn; + my $line; + my $maxline; + my %fn_name; + my $fn; + my $count; + + if (!defined($bbdata)) { + return @gcov_functions; + } + + # First add existing function data + while (@gcov_functions) { + $count = shift(@gcov_functions); + $fn = shift(@gcov_functions); + + $fn_count{$fn} = $count; + } + + # Convert line coverage data to function data + foreach $fn (keys(%{$bbdata})) { + my $line_data = $bbdata->{$fn}; + my $line; + + if ($fn eq "") { + next; + } + # Find the lowest line count for this function + $count = 0; + foreach $line (@$line_data) { + my $lcount = $gcov_content[ ( $line - 1 ) * 3 + 1 ]; + + if (($lcount > 0) && + (($count == 0) || ($lcount < $count))) { + $count = $lcount; + } + } + $fn_count{$fn} = $count; + } + + + # Check if we got data for all functions + foreach $fn (keys(%fn_name)) { + if ($fn eq "") { + next; + } + if (defined($fn_count{$fn})) { + next; + } + warn("WARNING: no derived data found for function $fn\n"); + } + + # Convert hash to list in @gcov_functions format + foreach $fn (sort(keys(%fn_count))) { + push(@gcov_functions, $fn_count{$fn}, $fn); + } + + return @gcov_functions; +} + # -# process_dafile(da_filename) +# get_filenames(directory, pattern) # -# Create a .info file for a single data file. +# Return a list of filenames found in directory which match the specified +# pattern. # # Die on error. # -sub process_dafile($) +sub get_filenames($$) { - info("Processing %s\n", $_[0]); + my ($dirname, $pattern) = @_; + my @result; + my $directory; + local *DIR; + opendir(DIR, $dirname) or + die("ERROR: cannot read directory $dirname\n"); + while ($directory = readdir(DIR)) { + push(@result, $directory) if ($directory =~ /$pattern/); + } + closedir(DIR); + + return @result; +} + +# +# process_dafile(da_filename, dir) +# +# Create a .info file for a single data file. +# +# Die on error. +# + +sub process_dafile($$) +{ + my ($file, $dir) = @_; my $da_filename; # Name of data file to process my $da_dir; # Directory of data file my $source_dir; # Directory of source file my $da_basename; # data filename without ".da/.gcda" extension my $bb_filename; # Name of respective graph file - my %bb_content; # Contents of graph file + my $bb_basename; # Basename of the original graph file + my $graph; # Contents of graph file + my $instr; # Contents of graph file part 2 my $gcov_error; # Error code of gcov tool my $object_dir; # Directory containing all object files my $source_filename; # Name of a source code file my $gcov_file; # Name of a .gcov file my @gcov_content; # Content of a .gcov file - my @gcov_branches; # Branch content of a .gcov file + my $gcov_branches; # Branch content of a .gcov file my @gcov_functions; # Function calls of a .gcov file my @gcov_list; # List of generated .gcov files my $line_number; # Line number count @@ -526,19 +728,23 @@ sub process_dafile($) my $lines_found; # Number of instrumented lines found my $funcs_hit; # Number of instrumented functions hit my $funcs_found; # Number of instrumented functions found + my $br_hit; + my $br_found; my $source; # gcov source header information my $object; # gcov object header information my @matches; # List of absolute paths matching filename my @unprocessed; # List of unprocessed source code files my $base_dir; # Base directory for current file + my @tmp_links; # Temporary links to be cleaned up my @result; my $index; my $da_renamed; # If data file is to be renamed local *INFO_HANDLE; + info("Processing %s\n", abs2rel($file, $dir)); # Get path to data file in absolute and normalized form (begins with /, # contains no more ../ or ./) - $da_filename = solve_relative_path($cwd, $_[0]); + $da_filename = solve_relative_path($cwd, $file); # Get directory and basename of data file ($da_dir, $da_basename) = split_filename($da_filename); @@ -577,7 +783,8 @@ sub process_dafile($) } # Construct name of graph file - $bb_filename = $da_dir."/".$da_basename.$graph_file_extension; + $bb_basename = $da_basename.$graph_file_extension; + $bb_filename = "$da_dir/$bb_basename"; # Find out the real location of graph file in case we're just looking at # a link @@ -603,17 +810,16 @@ sub process_dafile($) { if (defined($compatibility) && $compatibility eq $COMPAT_HAMMER) { - %bb_content = read_hammer_bbg_file($bb_filename, - $base_dir); + ($instr, $graph) = read_bbg($bb_filename, $base_dir); } else { - %bb_content = read_bb_file($bb_filename, $base_dir); + ($instr, $graph) = read_bb($bb_filename, $base_dir); } } else { - %bb_content = read_gcno_file($bb_filename, $base_dir); + ($instr, $graph) = read_gcno($bb_filename, $base_dir); } # Set $object_dir to real location of object files. This may differ @@ -630,6 +836,17 @@ sub process_dafile($) "$object_dir/$da_basename$data_file_extension") and die ("ERROR: cannot create link $object_dir/". "$da_basename$data_file_extension!\n"); + push(@tmp_links, + "$object_dir/$da_basename$data_file_extension"); + # Need to create link to graph file if basename of link + # and file are different (CONFIG_MODVERSION compat) + if ((basename($bb_filename) ne $bb_basename) && + (! -e "$object_dir/$bb_basename")) { + symlink($bb_filename, "$object_dir/$bb_basename") or + warn("WARNING: cannot create link ". + "$object_dir/$bb_basename\n"); + push(@tmp_links, "$object_dir/$bb_basename"); + } } # Change to directory containing data files and apply GCOV @@ -644,19 +861,8 @@ sub process_dafile($) } # Execute gcov command and suppress standard output - if ($preserve_paths) - { - $gcov_error = system_no_output(1, $gcov_tool, $da_filename, - "-o", $object_dir, - "--preserve-paths", - "-b"); - } - else - { - $gcov_error = system_no_output(1, $gcov_tool, $da_filename, - "-o", $object_dir, - "-b"); - } + $gcov_error = system_no_output(1, $gcov_tool, $da_filename, + "-o", $object_dir, @gcov_options); if ($da_renamed) { @@ -664,10 +870,9 @@ sub process_dafile($) and die ("ERROR: cannot rename $da_filename.ori"); } - # Clean up link - if ($object_dir ne $da_dir) - { - unlink($object_dir."/".$da_basename.$data_file_extension); + # Clean up temporary links + foreach (@tmp_links) { + unlink($_); } if ($gcov_error) @@ -681,7 +886,7 @@ sub process_dafile($) } # Collect data from resulting .gcov files and create .info file - @gcov_list = glob("*.gcov"); + @gcov_list = get_filenames('.', '\.gcov$'); # Check for files if (!@gcov_list) @@ -717,9 +922,12 @@ sub process_dafile($) # Traverse the list of generated .gcov files and combine them into a # single .info file - @unprocessed = keys(%bb_content); - foreach $gcov_file (@gcov_list) + @unprocessed = keys(%{$instr}); + foreach $gcov_file (sort(@gcov_list)) { + my $i; + my $num; + ($source, $object) = read_gcov_header($gcov_file); if (defined($source)) @@ -742,7 +950,7 @@ sub process_dafile($) } @matches = match_filename(defined($source) ? $source : - $gcov_file, keys(%bb_content)); + $gcov_file, keys(%{$instr})); # Skip files that are not mentioned in the graph file if (!@matches) @@ -756,8 +964,14 @@ sub process_dafile($) # Read in contents of gcov file @result = read_gcov_file($gcov_file); + if (!defined($result[0])) { + warn("WARNING: skipping unreadable file ". + $gcov_file."\n"); + unlink($gcov_file); + next; + } @gcov_content = @{$result[0]}; - @gcov_branches = @{$result[1]}; + $gcov_branches = $result[1]; @gcov_functions = @{$result[2]}; # Skip empty files @@ -793,19 +1007,48 @@ sub process_dafile($) # Write absolute path of source file printf(INFO_HANDLE "SF:%s\n", $source_filename); + # If requested, derive function coverage data from + # line coverage data of the first line of a function + if ($opt_derive_func_data) { + @gcov_functions = + derive_data(\@gcov_content, \@gcov_functions, + $graph->{$source_filename}); + } + # Write function-related information - if (defined($bb_content{$source_filename})) + if (defined($graph->{$source_filename})) { - foreach (split(",",$bb_content{$source_filename})) - { - my ($fn, $line) = split("=", $_); + my $fn_data = $graph->{$source_filename}; + my $fn; + foreach $fn (sort + {$fn_data->{$a}->[0] <=> $fn_data->{$b}->[0]} + keys(%{$fn_data})) { + my $ln_data = $fn_data->{$fn}; + my $line = $ln_data->[0]; + + # Skip empty function if ($fn eq "") { next; } + # Remove excluded functions + if (!$no_markers) { + my $gfn; + my $found = 0; + + foreach $gfn (@gcov_functions) { + if ($gfn eq $fn) { + $found = 1; + last; + } + } + if (!$found) { + next; + } + } # Normalize function name - $fn =~ s/\W/_/g; + $fn = filter_fn_name($fn); print(INFO_HANDLE "FN:$line,$fn\n"); } @@ -820,18 +1063,42 @@ sub process_dafile($) $funcs_hit = 0; while (@gcov_functions) { - printf(INFO_HANDLE "FNDA:%s,%s\n", - $gcov_functions[0], - $gcov_functions[1]); - $funcs_found++; - $funcs_hit++ if $gcov_functions[0]; - splice(@gcov_functions,0,2); + my $count = shift(@gcov_functions); + my $fn = shift(@gcov_functions); + + $fn = filter_fn_name($fn); + printf(INFO_HANDLE "FNDA:$count,$fn\n"); + $funcs_found++; + $funcs_hit++ if ($count > 0); } if ($funcs_found > 0) { printf(INFO_HANDLE "FNF:%s\n", $funcs_found); printf(INFO_HANDLE "FNH:%s\n", $funcs_hit); } + # Write coverage information for each instrumented branch: + # + # BRDA:<line number>,<block number>,<branch number>,<taken> + # + # where 'taken' is the number of times the branch was taken + # or '-' if the block to which the branch belongs was never + # executed + $br_found = 0; + $br_hit = 0; + $num = br_gvec_len($gcov_branches); + for ($i = 0; $i < $num; $i++) { + my ($line, $block, $branch, $taken) = + br_gvec_get($gcov_branches, $i); + + print(INFO_HANDLE "BRDA:$line,$block,$branch,$taken\n"); + $br_found++; + $br_hit++ if ($taken ne '-' && $taken > 0); + } + if ($br_found > 0) { + printf(INFO_HANDLE "BRF:%s\n", $br_found); + printf(INFO_HANDLE "BRH:%s\n", $br_hit); + } + # Reset line counters $line_number = 0; $lines_found = 0; @@ -862,27 +1129,6 @@ sub process_dafile($) splice(@gcov_content,0,3); } - #-- - #-- BA: <code-line>, <branch-coverage> - #-- - #-- print one BA line for every branch of a - #-- conditional. <branch-coverage> values - #-- are: - #-- 0 - not executed - #-- 1 - executed but not taken - #-- 2 - executed and taken - #-- - while (@gcov_branches) - { - if ($gcov_branches[0]) - { - printf(INFO_HANDLE "BA:%s,%s\n", - $gcov_branches[0], - $gcov_branches[1]); - } - splice(@gcov_branches,0,2); - } - # Write line statistics and section separator printf(INFO_HANDLE "LF:%s\n", $lines_found); printf(INFO_HANDLE "LH:%s\n", $lines_hit); @@ -958,28 +1204,42 @@ sub solve_relative_path($$) sub match_filename($@) { - my $filename = shift; - my @list = @_; + my ($filename, @list) = @_; + my ($vol, $dir, $file) = splitpath($filename); + my @comp = splitdir($dir); + my $comps = scalar(@comp); + my $entry; my @result; - $filename =~ s/^(.*).gcov$/$1/; - - if ($filename =~ /^\/(.*)$/) - { - $filename = "$1"; - } +entry: + foreach $entry (@list) { + my ($evol, $edir, $efile) = splitpath($entry); + my @ecomp; + my $ecomps; + my $i; - foreach (@list) - { - if (/\/\Q$filename\E(.*)$/ && $1 eq "") - { - @result = (@result, $_); + # Filename component must match + if ($efile ne $file) { + next; } + # Check directory components last to first for match + @ecomp = splitdir($edir); + $ecomps = scalar(@ecomp); + if ($ecomps < $comps) { + next; + } + for ($i = 0; $i < $comps; $i++) { + if ($comp[$comps - $i - 1] ne + $ecomp[$ecomps - $i - 1]) { + next entry; + } + } + push(@result, $entry), } + return @result; } - # # solve_ambiguous_match(rel_filename, matches_ref, gcov_content_ref) # @@ -1014,6 +1274,9 @@ sub solve_ambiguous_match($$$) { chomp; + # Also remove CR from line-end + s/\015$//; + if ($_ ne @$content[$index]) { $no_match = 1; @@ -1052,21 +1315,6 @@ sub split_filename($) # -# get_dir(filename); -# -# Return the directory component of a given FILENAME. -# - -sub get_dir($) -{ - my @components = split("/", $_[0]); - pop(@components); - - return join("/", @components); -} - - -# # read_gcov_header(gcov_filename) # # Parse file GCOV_FILENAME and return a list containing the following @@ -1102,6 +1350,9 @@ sub read_gcov_header($) { chomp($_); + # Also remove CR from line-end + s/\015$//; + if (/^\s+-:\s+0:Source:(.*)$/) { # Source: header entry @@ -1125,6 +1376,84 @@ sub read_gcov_header($) # +# br_gvec_len(vector) +# +# Return the number of entries in the branch coverage vector. +# + +sub br_gvec_len($) +{ + my ($vec) = @_; + + return 0 if (!defined($vec)); + return (length($vec) * 8 / $BR_VEC_WIDTH) / $BR_VEC_ENTRIES; +} + + +# +# br_gvec_get(vector, number) +# +# Return an entry from the branch coverage vector. +# + +sub br_gvec_get($$) +{ + my ($vec, $num) = @_; + my $line; + my $block; + my $branch; + my $taken; + my $offset = $num * $BR_VEC_ENTRIES; + + # Retrieve data from vector + $line = vec($vec, $offset + $BR_LINE, $BR_VEC_WIDTH); + $block = vec($vec, $offset + $BR_BLOCK, $BR_VEC_WIDTH); + $branch = vec($vec, $offset + $BR_BRANCH, $BR_VEC_WIDTH); + $taken = vec($vec, $offset + $BR_TAKEN, $BR_VEC_WIDTH); + + # Decode taken value from an integer + if ($taken == 0) { + $taken = "-"; + } else { + $taken--; + } + + return ($line, $block, $branch, $taken); +} + + +# +# br_gvec_push(vector, line, block, branch, taken) +# +# Add an entry to the branch coverage vector. +# + +sub br_gvec_push($$$$$) +{ + my ($vec, $line, $block, $branch, $taken) = @_; + my $offset; + + $vec = "" if (!defined($vec)); + $offset = br_gvec_len($vec) * $BR_VEC_ENTRIES; + + # Encode taken value into an integer + if ($taken eq "-") { + $taken = 0; + } else { + $taken++; + } + + # Add to vector + vec($vec, $offset + $BR_LINE, $BR_VEC_WIDTH) = $line; + vec($vec, $offset + $BR_BLOCK, $BR_VEC_WIDTH) = $block; + vec($vec, $offset + $BR_BRANCH, $BR_VEC_WIDTH) = $branch; + vec($vec, $offset + $BR_TAKEN, $BR_VEC_WIDTH) = $taken; + + return $vec; +} + + +# # read_gcov_file(gcov_filename) # # Parse file GCOV_FILENAME (.gcov file format) and return the list: @@ -1137,8 +1466,8 @@ sub read_gcov_header($) # $result[($line_number-1)*3+1] = execution count for line $line_number # $result[($line_number-1)*3+2] = source code text for line $line_number # -# gcov_branch is a list of 2 elements -# (linenumber, branch result) for each branch +# gcov_branch is a vector of 4 4-byte long elements for each branch: +# line number, block number, branch number, count + 1 or 0 # # gcov_func is a list of 2 elements # (number of calls, function name) for each function @@ -1150,13 +1479,23 @@ sub read_gcov_file($) { my $filename = $_[0]; my @result = (); - my @branches = (); + my $branches = ""; my @functions = (); my $number; + my $exclude_flag = 0; + my $exclude_line = 0; + my $last_block = $UNNAMED_BLOCK; + my $last_line = 0; local *INPUT; - open(INPUT, $filename) - or die("ERROR: cannot read $filename!\n"); + if (!open(INPUT, $filename)) { + if ($ignore_errors[$ERROR_GCOV]) + { + warn("WARNING: cannot read $filename!\n"); + return (undef, undef, undef); + } + die("ERROR: cannot read $filename!\n"); + } if ($gcov_version < $GCOV_VERSION_3_3_0) { @@ -1165,29 +1504,17 @@ sub read_gcov_file($) { chomp($_); - if (/^\t\t(.*)$/) - { - # Uninstrumented line - push(@result, 0); - push(@result, 0); - push(@result, $1); - } - elsif (/^branch/) - { - # Branch execution data - push(@branches, scalar(@result) / 3); - if (/^branch \d+ never executed$/) - { - push(@branches, 0); - } - elsif (/^branch \d+ taken = 0%/) - { - push(@branches, 1); - } - else - { - push(@branches, 2); - } + # Also remove CR from line-end + s/\015$//; + + if (/^branch\s+(\d+)\s+taken\s+=\s+(\d+)/) { + next if ($exclude_line); + $branches = br_gvec_push($branches, $last_line, + $last_block, $1, $2); + } elsif (/^branch\s+(\d+)\s+never\s+executed/) { + next if ($exclude_line); + $branches = br_gvec_push($branches, $last_line, + $last_block, $1, '-'); } elsif (/^call/ || /^function/) { @@ -1195,15 +1522,43 @@ sub read_gcov_file($) } else { + $last_line++; + # Check for exclusion markers + if (!$no_markers) { + if (/$EXCL_STOP/) { + $exclude_flag = 0; + } elsif (/$EXCL_START/) { + $exclude_flag = 1; + } + if (/$EXCL_LINE/ || $exclude_flag) { + $exclude_line = 1; + } else { + $exclude_line = 0; + } + } # Source code execution data + if (/^\t\t(.*)$/) + { + # Uninstrumented line + push(@result, 0); + push(@result, 0); + push(@result, $1); + next; + } $number = (split(" ",substr($_, 0, 16)))[0]; # Check for zero count which is indicated # by ###### if ($number eq "######") { $number = 0; } - push(@result, 1); - push(@result, $number); + if ($exclude_line) { + # Register uninstrumented line instead + push(@result, 0); + push(@result, 0); + } else { + push(@result, 1); + push(@result, $number); + } push(@result, substr($_, 16)); } } @@ -1215,25 +1570,28 @@ sub read_gcov_file($) { chomp($_); - if (/^branch\s+\d+\s+(\S+)\s+(\S+)/) - { - # Branch execution data - push(@branches, scalar(@result) / 3); - if ($1 eq "never") - { - push(@branches, 0); - } - elsif ($2 eq "0%") - { - push(@branches, 1); - } - else - { - push(@branches, 2); - } + # Also remove CR from line-end + s/\015$//; + + if (/^\s*(\d+|\$+):\s*(\d+)-block\s+(\d+)\s*$/) { + # Block information - used to group related + # branches + $last_line = $2; + $last_block = $3; + } elsif (/^branch\s+(\d+)\s+taken\s+(\d+)/) { + next if ($exclude_line); + $branches = br_gvec_push($branches, $last_line, + $last_block, $1, $2); + } elsif (/^branch\s+(\d+)\s+never\s+executed/) { + next if ($exclude_line); + $branches = br_gvec_push($branches, $last_line, + $last_block, $1, '-'); } elsif (/^function\s+(\S+)\s+called\s+(\d+)/) { + if ($exclude_line) { + next; + } push(@functions, $2, $1); } elsif (/^call/) @@ -1242,580 +1600,59 @@ sub read_gcov_file($) } elsif (/^\s*([^:]+):\s*([^:]+):(.*)$/) { + my ($count, $line, $code) = ($1, $2, $3); + + $last_line = $line; + $last_block = $UNNAMED_BLOCK; + # Check for exclusion markers + if (!$no_markers) { + if (/$EXCL_STOP/) { + $exclude_flag = 0; + } elsif (/$EXCL_START/) { + $exclude_flag = 1; + } + if (/$EXCL_LINE/ || $exclude_flag) { + $exclude_line = 1; + } else { + $exclude_line = 0; + } + } # <exec count>:<line number>:<source code> - if ($2 eq "0") + if ($line eq "0") { # Extra data } - elsif ($1 eq "-") + elsif ($count eq "-") { # Uninstrumented line push(@result, 0); push(@result, 0); - push(@result, $3); + push(@result, $code); } else { - # Source code execution data - $number = $1; - - # Check for zero count - if ($number eq "#####") { $number = 0; } - - push(@result, 1); - push(@result, $number); - push(@result, $3); - } - } - } - } - - close(INPUT); - return(\@result, \@branches, \@functions); -} - - -# -# read_bb_file(bb_filename, base_dir) -# -# Read .bb file BB_FILENAME and return a hash containing the following -# mapping: -# -# filename -> comma-separated list of pairs (function name=starting -# line number) to indicate the starting line of a function or -# =name to indicate an instrumented line -# -# for each entry in the .bb file. Filenames are absolute, i.e. relative -# filenames are prefixed with BASE_DIR. -# -# Die on error. -# - -sub read_bb_file($$) -{ - my $bb_filename = $_[0]; - my $base_dir = $_[1]; - my %result; - my $filename; - my $function_name; - my $minus_one = sprintf("%d", 0x80000001); - my $minus_two = sprintf("%d", 0x80000002); - my $value; - my $packed_word; - local *INPUT; - - open(INPUT, $bb_filename) - or die("ERROR: cannot read $bb_filename!\n"); - - binmode(INPUT); - - # Read data in words of 4 bytes - while (read(INPUT, $packed_word, 4) == 4) - { - # Decode integer in intel byteorder - $value = unpack_int32($packed_word, 0); - - # Note: the .bb file format is documented in GCC info pages - if ($value == $minus_one) - { - # Filename follows - $filename = read_string(*INPUT, $minus_one) - or die("ERROR: incomplete filename in ". - "$bb_filename!\n"); - - # Make path absolute - $filename = solve_relative_path($base_dir, $filename); - - # Insert into hash if not yet present. - # This is necessary because functions declared as - # "inline" are not listed as actual functions in - # .bb files - if (!$result{$filename}) - { - $result{$filename}=""; - } - } - elsif ($value == $minus_two) - { - # Function name follows - $function_name = read_string(*INPUT, $minus_two) - or die("ERROR: incomplete function ". - "name in $bb_filename!\n"); - $function_name =~ s/\W/_/g; - } - elsif ($value > 0) - { - if (defined($filename)) - { - $result{$filename} .= - ($result{$filename} ? "," : ""). - "=$value"; - } - else - { - warn("WARNING: unassigned line". - " number in .bb file ". - "$bb_filename\n"); - } - if ($function_name) - { - # Got a full entry filename, funcname, lineno - # Add to resulting hash - - $result{$filename}.= - ($result{$filename} ? "," : ""). - join("=",($function_name,$value)); - undef($function_name); - } - } - } - close(INPUT); - - if (!scalar(keys(%result))) - { - die("ERROR: no data found in $bb_filename!\n"); - } - return %result; -} - - -# -# read_string(handle, delimiter); -# -# Read and return a string in 4-byte chunks from HANDLE until DELIMITER -# is found. -# -# Return empty string on error. -# - -sub read_string(*$) -{ - my $HANDLE = $_[0]; - my $delimiter = $_[1]; - my $string = ""; - my $packed_word; - my $value; - - while (read($HANDLE,$packed_word,4) == 4) - { - $value = unpack_int32($packed_word, 0); - - if ($value == $delimiter) - { - # Remove trailing nil bytes - $/="\0"; - while (chomp($string)) {}; - $/="\n"; - return($string); - } - - $string = $string.$packed_word; - } - return(""); -} - - -# -# read_gcno_file(bb_filename, base_dir) -# -# Read .gcno file BB_FILENAME and return a hash containing the following -# mapping: -# -# filename -> comma-separated list of pairs (function name=starting -# line number) to indicate the starting line of a function or -# =name to indicate an instrumented line -# -# for each entry in the .gcno file. Filenames are absolute, i.e. relative -# filenames are prefixed with BASE_DIR. -# -# Die on error. -# - -sub read_gcno_file($$) -{ - my $gcno_filename = $_[0]; - my $base_dir = $_[1]; - my %result; - my $filename; - my $function_name; - my $lineno; - my $length; - my $value; - my $endianness; - my $blocks; - my $packed_word; - my $string; - local *INPUT; - - open(INPUT, $gcno_filename) - or die("ERROR: cannot read $gcno_filename!\n"); - - binmode(INPUT); - - read(INPUT, $packed_word, 4) == 4 - or die("ERROR: Invalid gcno file format\n"); - - $value = unpack_int32($packed_word, 0); - $endianness = !($value == $GCNO_FILE_MAGIC); - - unpack_int32($packed_word, $endianness) == $GCNO_FILE_MAGIC - or die("ERROR: gcno file magic does not match\n"); - - seek(INPUT, 8, 1); - - # Read data in words of 4 bytes - while (read(INPUT, $packed_word, 4) == 4) - { - # Decode integer in intel byteorder - $value = unpack_int32($packed_word, $endianness); - - if ($value == $GCNO_FUNCTION_TAG) - { - # skip length, ident and checksum - seek(INPUT, 12, 1); - (undef, $function_name) = - read_gcno_string(*INPUT, $endianness); - $function_name =~ s/\W/_/g; - (undef, $filename) = - read_gcno_string(*INPUT, $endianness); - $filename = solve_relative_path($base_dir, $filename); - - read(INPUT, $packed_word, 4); - $lineno = unpack_int32($packed_word, $endianness); - - $result{$filename}.= - ($result{$filename} ? "," : ""). - join("=",($function_name,$lineno)); - } - elsif ($value == $GCNO_LINES_TAG) - { - # Check for names of files containing inlined code - # included in this file - read(INPUT, $packed_word, 4); - $length = unpack_int32($packed_word, $endianness); - if ($length > 0) - { - # Block number - read(INPUT, $packed_word, 4); - $length--; - } - while ($length > 0) - { - read(INPUT, $packed_word, 4); - $lineno = unpack_int32($packed_word, - $endianness); - $length--; - if ($lineno != 0) - { - if (defined($filename)) - { - $result{$filename} .= - ($result{$filename} ? "," : ""). - "=$lineno"; + if ($exclude_line) { + push(@result, 0); + push(@result, 0); + } else { + # Check for zero count + if ($count eq "#####") { + $count = 0; + } + push(@result, 1); + push(@result, $count); } - else - { - warn("WARNING: unassigned line". - " number in .gcno file ". - "$gcno_filename\n"); - } - next; + push(@result, $code); } - last if ($length == 0); - ($blocks, $string) = - read_gcno_string(*INPUT, $endianness); - if (defined($string)) - { - $filename = $string; - } - if ($blocks > 1) - { - $filename = solve_relative_path( - $base_dir, $filename); - if (!defined($result{$filename})) - { - $result{$filename} = ""; - } - } - $length -= $blocks; } } - else - { - read(INPUT, $packed_word, 4); - $length = unpack_int32($packed_word, $endianness); - seek(INPUT, 4 * $length, 1); - } - } - close(INPUT); - - if (!scalar(keys(%result))) - { - die("ERROR: no data found in $gcno_filename!\n"); - } - return %result; -} - - -# -# read_gcno_string(handle, endianness); -# -# Read a string in 4-byte chunks from HANDLE. -# -# Return (number of 4-byte chunks read, string). -# - -sub read_gcno_string(*$) -{ - my $handle = $_[0]; - my $endianness = $_[1]; - my $number_of_blocks = 0; - my $string = ""; - my $packed_word; - - read($handle, $packed_word, 4) == 4 - or die("ERROR: reading string\n"); - - $number_of_blocks = unpack_int32($packed_word, $endianness); - - if ($number_of_blocks == 0) - { - return (1, undef); - } - - if (read($handle, $packed_word, 4 * $number_of_blocks) != - 4 * $number_of_blocks) - { - my $msg = "invalid string size ".(4 * $number_of_blocks)." in ". - "gcno file at position ".tell($handle)."\n"; - if ($ignore[$ERROR_SOURCE]) - { - warn("WARNING: $msg"); - return (1, undef); - } - else - { - die("ERROR: $msg"); - } } - $string = $string . $packed_word; - - # Remove trailing nil bytes - $/="\0"; - while (chomp($string)) {}; - $/="\n"; - - return(1 + $number_of_blocks, $string); -} - - -# -# read_hammer_bbg_file(bb_filename, base_dir) -# -# Read .bbg file BB_FILENAME and return a hash containing the following -# mapping: -# -# filename -> comma-separated list of pairs (function name=starting -# line number) to indicate the starting line of a function or -# =name to indicate an instrumented line -# -# for each entry in the .bbg file. Filenames are absolute, i.e. relative -# filenames are prefixed with BASE_DIR. -# -# Die on error. -# - -sub read_hammer_bbg_file($$) -{ - my $bbg_filename = $_[0]; - my $base_dir = $_[1]; - my %result; - my $filename; - my $function_name; - my $first_line; - my $lineno; - my $length; - my $value; - my $endianness; - my $blocks; - my $packed_word; - local *INPUT; - - open(INPUT, $bbg_filename) - or die("ERROR: cannot read $bbg_filename!\n"); - - binmode(INPUT); - - # Read magic - read(INPUT, $packed_word, 4) == 4 - or die("ERROR: invalid bbg file format\n"); - - $endianness = 1; - - unpack_int32($packed_word, $endianness) == $BBG_FILE_MAGIC - or die("ERROR: bbg file magic does not match\n"); - - # Skip version - seek(INPUT, 4, 1); - - # Read data in words of 4 bytes - while (read(INPUT, $packed_word, 4) == 4) - { - # Get record tag - $value = unpack_int32($packed_word, $endianness); - - # Get record length - read(INPUT, $packed_word, 4); - $length = unpack_int32($packed_word, $endianness); - - if ($value == $GCNO_FUNCTION_TAG) - { - # Get function name - ($value, $function_name) = - read_hammer_bbg_string(*INPUT, $endianness); - $function_name =~ s/\W/_/g; - $filename = undef; - $first_line = undef; - - seek(INPUT, $length - $value * 4, 1); - } - elsif ($value == $GCNO_LINES_TAG) - { - # Get linenumber and filename - # Skip block number - seek(INPUT, 4, 1); - $length -= 4; - - while ($length > 0) - { - read(INPUT, $packed_word, 4); - $lineno = unpack_int32($packed_word, - $endianness); - $length -= 4; - if ($lineno != 0) - { - if (!defined($first_line)) - { - $first_line = $lineno; - } - if (defined($filename)) - { - $result{$filename} .= - ($result{$filename} ? "," : ""). - "=$lineno"; - } - else - { - warn("WARNING: unassigned line". - " number in .bbg file ". - "$bbg_filename\n"); - } - next; - } - ($blocks, $value) = - read_hammer_bbg_string( - *INPUT, $endianness); - # Add all filenames to result list - if (defined($value)) - { - $value = solve_relative_path( - $base_dir, $value); - if (!defined($result{$value})) - { - $result{$value} = undef; - } - if (!defined($filename)) - { - $filename = $value; - } - } - $length -= $blocks * 4; - - # Got a complete data set? - if (defined($filename) && - defined($first_line) && - defined($function_name)) - { - # Add it to our result hash - if (defined($result{$filename})) - { - $result{$filename} .= - ",$function_name=$first_line"; - } - else - { - $result{$filename} = - "$function_name=$first_line"; - } - $function_name = undef; - $filename = undef; - $first_line = undef; - } - } - } - else - { - # Skip other records - seek(INPUT, $length, 1); - } - } close(INPUT); - - if (!scalar(keys(%result))) - { - die("ERROR: no data found in $bbg_filename!\n"); - } - return %result; -} - - -# -# read_hammer_bbg_string(handle, endianness); -# -# Read a string in 4-byte chunks from HANDLE. -# -# Return (number of 4-byte chunks read, string). -# - -sub read_hammer_bbg_string(*$) -{ - my $handle = $_[0]; - my $endianness = $_[1]; - my $length = 0; - my $string = ""; - my $packed_word; - my $pad; - - read($handle, $packed_word, 4) == 4 - or die("ERROR: reading string\n"); - - $length = unpack_int32($packed_word, $endianness); - $pad = 4 - $length % 4; - - if ($length == 0) - { - return (1, undef); + if ($exclude_flag) { + warn("WARNING: unterminated exclusion section in $filename\n"); } - - read($handle, $string, $length) == - $length or die("ERROR: reading string\n"); - seek($handle, $pad, 1); - - return(1 + ($length + $pad) / 4, $string); -} - -# -# unpack_int32(word, endianness) -# -# Interpret 4-byte binary string WORD as signed 32 bit integer in -# endian encoding defined by ENDIANNESS (0=little, 1=big) and return its -# value. -# - -sub unpack_int32($$) -{ - return sprintf("%d", unpack($_[1] ? "N" : "V",$_[0])); + return(\@result, $branches, \@functions); } @@ -2013,43 +1850,171 @@ sub apply_config($) } -sub gen_initial_info($) +# +# get_exclusion_data(filename) +# +# Scan specified source code file for exclusion markers and return +# linenumber -> 1 +# for all lines which should be excluded. +# + +sub get_exclusion_data($) { - my $directory = $_[0]; - my @file_list; + my ($filename) = @_; + my %list; + my $flag = 0; + local *HANDLE; - if (-d $directory) - { - info("Scanning $directory for $graph_file_extension ". - "files ...\n"); + if (!open(HANDLE, "<$filename")) { + warn("WARNING: could not open $filename\n"); + return undef; + } + while (<HANDLE>) { + if (/$EXCL_STOP/) { + $flag = 0; + } elsif (/$EXCL_START/) { + $flag = 1; + } + if (/$EXCL_LINE/ || $flag) { + $list{$.} = 1; + } + } + close(HANDLE); - @file_list = `find "$directory" $maxdepth $follow -name \\*$graph_file_extension -type f 2>/dev/null`; - chomp(@file_list); - @file_list or die("ERROR: no $graph_file_extension files ". - "found in $directory!\n"); - info("Found %d graph files in %s\n", $#file_list+1, $directory); + if ($flag) { + warn("WARNING: unterminated exclusion section in $filename\n"); } - else - { - @file_list = ($directory); + + return \%list; +} + + +# +# apply_exclusion_data(instr, graph) +# +# Remove lines from instr and graph data structures which are marked +# for exclusion in the source code file. +# +# Return adjusted (instr, graph). +# +# graph : file name -> function data +# function data : function name -> line data +# line data : [ line1, line2, ... ] +# +# instr : filename -> line data +# line data : [ line1, line2, ... ] +# + +sub apply_exclusion_data($$) +{ + my ($instr, $graph) = @_; + my $filename; + my %excl_data; + my $excl_read_failed = 0; + + # Collect exclusion marker data + foreach $filename (sort_uniq_lex(keys(%{$graph}), keys(%{$instr}))) { + my $excl = get_exclusion_data($filename); + + # Skip and note if file could not be read + if (!defined($excl)) { + $excl_read_failed = 1; + next; + } + + # Add to collection if there are markers + $excl_data{$filename} = $excl if (keys(%{$excl}) > 0); } - # Process all files in list - foreach (@file_list) { process_graphfile($_); } + # Warn if not all source files could be read + if ($excl_read_failed) { + warn("WARNING: some exclusion markers may be ignored\n"); + } + + # Skip if no markers were found + return ($instr, $graph) if (keys(%excl_data) == 0); + + # Apply exclusion marker data to graph + foreach $filename (keys(%excl_data)) { + my $function_data = $graph->{$filename}; + my $excl = $excl_data{$filename}; + my $function; + + next if (!defined($function_data)); + + foreach $function (keys(%{$function_data})) { + my $line_data = $function_data->{$function}; + my $line; + my @new_data; + + # To be consistent with exclusion parser in non-initial + # case we need to remove a function if the first line + # was excluded + if ($excl->{$line_data->[0]}) { + delete($function_data->{$function}); + next; + } + # Copy only lines which are not excluded + foreach $line (@{$line_data}) { + push(@new_data, $line) if (!$excl->{$line}); + } + + # Store modified list + if (scalar(@new_data) > 0) { + $function_data->{$function} = \@new_data; + } else { + # All of this function was excluded + delete($function_data->{$function}); + } + } + + # Check if all functions of this file were excluded + if (keys(%{$function_data}) == 0) { + delete($graph->{$filename}); + } + } + + # Apply exclusion marker data to instr + foreach $filename (keys(%excl_data)) { + my $line_data = $instr->{$filename}; + my $excl = $excl_data{$filename}; + my $line; + my @new_data; + + next if (!defined($line_data)); + + # Copy only lines which are not excluded + foreach $line (@{$line_data}) { + push(@new_data, $line) if (!$excl->{$line}); + } + + # Store modified list + if (scalar(@new_data) > 0) { + $instr->{$filename} = \@new_data; + } else { + # All of this file was excluded + delete($instr->{$filename}); + } + } + + return ($instr, $graph); } -sub process_graphfile($) + +sub process_graphfile($$) { - my $graph_filename = $_[0]; + my ($file, $dir) = @_; + my $graph_filename = $file; my $graph_dir; my $graph_basename; my $source_dir; my $base_dir; - my %graph_data; + my $graph; + my $instr; my $filename; local *INFO_HANDLE; - info("Processing $_[0]\n"); + info("Processing %s\n", abs2rel($file, $dir)); # Get path to data file in absolute and normalized form (begins with /, # contains no more ../ or ./) @@ -2079,17 +2044,21 @@ sub process_graphfile($) { if (defined($compatibility) && $compatibility eq $COMPAT_HAMMER) { - %graph_data = read_hammer_bbg_file($graph_filename, - $base_dir); + ($instr, $graph) = read_bbg($graph_filename, $base_dir); } else { - %graph_data = read_bb_file($graph_filename, $base_dir); + ($instr, $graph) = read_bb($graph_filename, $base_dir); } } else { - %graph_data = read_gcno_file($graph_filename, $base_dir); + ($instr, $graph) = read_gcno($graph_filename, $base_dir); + } + + if (!$no_markers) { + # Apply exclusion marker data to graph file data + ($instr, $graph) = apply_exclusion_data($instr, $graph); } # Check whether we're writing to a single file @@ -2116,45 +2085,45 @@ sub process_graphfile($) # Write test name printf(INFO_HANDLE "TN:%s\n", $test_name); - foreach $filename (keys(%graph_data)) + foreach $filename (sort(keys(%{$instr}))) { - my %lines; - my $count = 0; - my @functions; + my $funcdata = $graph->{$filename}; + my $line; + my $linedata; print(INFO_HANDLE "SF:$filename\n"); - # Write function related data - foreach (split(",",$graph_data{$filename})) - { - my ($fn, $line) = split("=", $_); + if (defined($funcdata)) { + my @functions = sort {$funcdata->{$a}->[0] <=> + $funcdata->{$b}->[0]} + keys(%{$funcdata}); + my $func; - if ($fn eq "") - { - $lines{$line} = ""; - next; - } - - # Normalize function name - $fn =~ s/\W/_/g; + # Gather list of instrumented lines and functions + foreach $func (@functions) { + $linedata = $funcdata->{$func}; - print(INFO_HANDLE "FN:$line,$fn\n"); - push(@functions, $fn); - } - foreach (@functions) { - print(INFO_HANDLE "FNDA:$_,0\n"); + # Print function name and starting line + print(INFO_HANDLE "FN:".$linedata->[0]. + ",".filter_fn_name($func)."\n"); + } + # Print zero function coverage data + foreach $func (@functions) { + print(INFO_HANDLE "FNDA:0,". + filter_fn_name($func)."\n"); + } + # Print function summary + print(INFO_HANDLE "FNF:".scalar(@functions)."\n"); + print(INFO_HANDLE "FNH:0\n"); } - print(INFO_HANDLE "FNF:".scalar(@functions)."\n"); - print(INFO_HANDLE "FNH:0\n"); - - # Write line related data - foreach (sort {$a <=> $b } keys(%lines)) - { - print(INFO_HANDLE "DA:$_,0\n"); - $count++; + # Print zero line coverage data + foreach $line (@{$instr->{$filename}}) { + print(INFO_HANDLE "DA:$line,0\n"); } + # Print line summary + print(INFO_HANDLE "LF:".scalar(@{$instr->{$filename}})."\n"); print(INFO_HANDLE "LH:0\n"); - print(INFO_HANDLE "LF:$count\n"); + print(INFO_HANDLE "end_of_record\n"); } if (!($output_filename && ($output_filename eq "-"))) @@ -2163,6 +2132,16 @@ sub process_graphfile($) } } +sub filter_fn_name($) +{ + my ($fn) = @_; + + # Remove characters used internally as function name delimiters + $fn =~ s/[,=]/_/g; + + return $fn; +} + sub warn_handler($) { my ($msg) = @_; @@ -2176,3 +2155,914 @@ sub die_handler($) die("$tool_name: $msg"); } + + +# +# graph_error(filename, message) +# +# Print message about error in graph file. If ignore_graph_error is set, return. +# Otherwise abort. +# + +sub graph_error($$) +{ + my ($filename, $msg) = @_; + + if ($ignore[$ERROR_GRAPH]) { + warn("WARNING: $filename: $msg - skipping\n"); + return; + } + die("ERROR: $filename: $msg\n"); +} + +# +# graph_expect(description) +# +# If debug is set to a non-zero value, print the specified description of what +# is expected to be read next from the graph file. +# + +sub graph_expect($) +{ + my ($msg) = @_; + + if (!$debug || !defined($msg)) { + return; + } + + print(STDERR "DEBUG: expecting $msg\n"); +} + +# +# graph_read(handle, bytes[, description]) +# +# Read and return the specified number of bytes from handle. Return undef +# if the number of bytes could not be read. +# + +sub graph_read(*$;$) +{ + my ($handle, $length, $desc) = @_; + my $data; + my $result; + + graph_expect($desc); + $result = read($handle, $data, $length); + if ($debug) { + my $ascii = ""; + my $hex = ""; + my $i; + + print(STDERR "DEBUG: read($length)=$result: "); + for ($i = 0; $i < length($data); $i++) { + my $c = substr($data, $i, 1);; + my $n = ord($c); + + $hex .= sprintf("%02x ", $n); + if ($n >= 32 && $n <= 127) { + $ascii .= $c; + } else { + $ascii .= "."; + } + } + print(STDERR "$hex |$ascii|"); + print(STDERR "\n"); + } + if ($result != $length) { + return undef; + } + return $data; +} + +# +# graph_skip(handle, bytes[, description]) +# +# Read and discard the specified number of bytes from handle. Return non-zero +# if bytes could be read, zero otherwise. +# + +sub graph_skip(*$;$) +{ + my ($handle, $length, $desc) = @_; + + if (defined(graph_read($handle, $length, $desc))) { + return 1; + } + return 0; +} + +# +# sort_uniq(list) +# +# Return list in numerically ascending order and without duplicate entries. +# + +sub sort_uniq(@) +{ + my (@list) = @_; + my %hash; + + foreach (@list) { + $hash{$_} = 1; + } + return sort { $a <=> $b } keys(%hash); +} + +# +# sort_uniq_lex(list) +# +# Return list in lexically ascending order and without duplicate entries. +# + +sub sort_uniq_lex(@) +{ + my (@list) = @_; + my %hash; + + foreach (@list) { + $hash{$_} = 1; + } + return sort keys(%hash); +} + +# +# graph_cleanup(graph) +# +# Remove entries for functions with no lines. Remove duplicate line numbers. +# Sort list of line numbers numerically ascending. +# + +sub graph_cleanup($) +{ + my ($graph) = @_; + my $filename; + + foreach $filename (keys(%{$graph})) { + my $per_file = $graph->{$filename}; + my $function; + + foreach $function (keys(%{$per_file})) { + my $lines = $per_file->{$function}; + + if (scalar(@$lines) == 0) { + # Remove empty function + delete($per_file->{$function}); + next; + } + # Normalize list + $per_file->{$function} = [ sort_uniq(@$lines) ]; + } + if (scalar(keys(%{$per_file})) == 0) { + # Remove empty file + delete($graph->{$filename}); + } + } +} + +# +# graph_find_base(bb) +# +# Try to identify the filename which is the base source file for the +# specified bb data. +# + +sub graph_find_base($) +{ + my ($bb) = @_; + my %file_count; + my $basefile; + my $file; + my $func; + my $filedata; + my $count; + my $num; + + # Identify base name for this bb data. + foreach $func (keys(%{$bb})) { + $filedata = $bb->{$func}; + + foreach $file (keys(%{$filedata})) { + $count = $file_count{$file}; + + # Count file occurrence + $file_count{$file} = defined($count) ? $count + 1 : 1; + } + } + $count = 0; + $num = 0; + foreach $file (keys(%file_count)) { + if ($file_count{$file} > $count) { + # The file that contains code for the most functions + # is likely the base file + $count = $file_count{$file}; + $num = 1; + $basefile = $file; + } elsif ($file_count{$file} == $count) { + # If more than one file could be the basefile, we + # don't have a basefile + $basefile = undef; + } + } + + return $basefile; +} + +# +# graph_from_bb(bb, fileorder, bb_filename) +# +# Convert data from bb to the graph format and list of instrumented lines. +# Returns (instr, graph). +# +# bb : function name -> file data +# : undef -> file order +# file data : filename -> line data +# line data : [ line1, line2, ... ] +# +# file order : function name -> [ filename1, filename2, ... ] +# +# graph : file name -> function data +# function data : function name -> line data +# line data : [ line1, line2, ... ] +# +# instr : filename -> line data +# line data : [ line1, line2, ... ] +# + +sub graph_from_bb($$$) +{ + my ($bb, $fileorder, $bb_filename) = @_; + my $graph = {}; + my $instr = {}; + my $basefile; + my $file; + my $func; + my $filedata; + my $linedata; + my $order; + + $basefile = graph_find_base($bb); + # Create graph structure + foreach $func (keys(%{$bb})) { + $filedata = $bb->{$func}; + $order = $fileorder->{$func}; + + # Account for lines in functions + if (defined($basefile) && defined($filedata->{$basefile})) { + # If the basefile contributes to this function, + # account this function to the basefile. + $graph->{$basefile}->{$func} = $filedata->{$basefile}; + } else { + # If the basefile does not contribute to this function, + # account this function to the first file contributing + # lines. + $graph->{$order->[0]}->{$func} = + $filedata->{$order->[0]}; + } + + foreach $file (keys(%{$filedata})) { + # Account for instrumented lines + $linedata = $filedata->{$file}; + push(@{$instr->{$file}}, @$linedata); + } + } + # Clean up array of instrumented lines + foreach $file (keys(%{$instr})) { + $instr->{$file} = [ sort_uniq(@{$instr->{$file}}) ]; + } + + return ($instr, $graph); +} + +# +# graph_add_order(fileorder, function, filename) +# +# Add an entry for filename to the fileorder data set for function. +# + +sub graph_add_order($$$) +{ + my ($fileorder, $function, $filename) = @_; + my $item; + my $list; + + $list = $fileorder->{$function}; + foreach $item (@$list) { + if ($item eq $filename) { + return; + } + } + push(@$list, $filename); + $fileorder->{$function} = $list; +} +# +# read_bb_word(handle[, description]) +# +# Read and return a word in .bb format from handle. +# + +sub read_bb_word(*;$) +{ + my ($handle, $desc) = @_; + + return graph_read($handle, 4, $desc); +} + +# +# read_bb_value(handle[, description]) +# +# Read a word in .bb format from handle and return the word and its integer +# value. +# + +sub read_bb_value(*;$) +{ + my ($handle, $desc) = @_; + my $word; + + $word = read_bb_word($handle, $desc); + return undef if (!defined($word)); + + return ($word, unpack("V", $word)); +} + +# +# read_bb_string(handle, delimiter) +# +# Read and return a string in .bb format from handle up to the specified +# delimiter value. +# + +sub read_bb_string(*$) +{ + my ($handle, $delimiter) = @_; + my $word; + my $value; + my $string = ""; + + graph_expect("string"); + do { + ($word, $value) = read_bb_value($handle, "string or delimiter"); + return undef if (!defined($value)); + if ($value != $delimiter) { + $string .= $word; + } + } while ($value != $delimiter); + $string =~ s/\0//g; + + return $string; +} + +# +# read_bb(filename, base_dir) +# +# Read the contents of the specified .bb file and return (instr, graph), where: +# +# instr : filename -> line data +# line data : [ line1, line2, ... ] +# +# graph : filename -> file_data +# file_data : function name -> line_data +# line_data : [ line1, line2, ... ] +# +# Relative filenames are converted to absolute form using base_dir as +# base directory. See the gcov info pages of gcc 2.95 for a description of +# the .bb file format. +# + +sub read_bb($$) +{ + my ($bb_filename, $base) = @_; + my $minus_one = 0x80000001; + my $minus_two = 0x80000002; + my $value; + my $filename; + my $function; + my $bb = {}; + my $fileorder = {}; + my $instr; + my $graph; + local *HANDLE; + + open(HANDLE, "<$bb_filename") or goto open_error; + binmode(HANDLE); + while (!eof(HANDLE)) { + $value = read_bb_value(*HANDLE, "data word"); + goto incomplete if (!defined($value)); + if ($value == $minus_one) { + # Source file name + graph_expect("filename"); + $filename = read_bb_string(*HANDLE, $minus_one); + goto incomplete if (!defined($filename)); + if ($filename ne "") { + $filename = solve_relative_path($base, + $filename); + } + } elsif ($value == $minus_two) { + # Function name + graph_expect("function name"); + $function = read_bb_string(*HANDLE, $minus_two); + goto incomplete if (!defined($function)); + } elsif ($value > 0) { + # Line number + if (!defined($filename) || !defined($function)) { + warn("WARNING: unassigned line number ". + "$value\n"); + next; + } + push(@{$bb->{$function}->{$filename}}, $value); + graph_add_order($fileorder, $function, $filename); + } + } + close(HANDLE); + ($instr, $graph) = graph_from_bb($bb, $fileorder, $bb_filename); + graph_cleanup($graph); + + return ($instr, $graph); + +open_error: + graph_error($bb_filename, "could not open file"); + return undef; +incomplete: + graph_error($bb_filename, "reached unexpected end of file"); + return undef; +} + +# +# read_bbg_word(handle[, description]) +# +# Read and return a word in .bbg format. +# + +sub read_bbg_word(*;$) +{ + my ($handle, $desc) = @_; + + return graph_read($handle, 4, $desc); +} + +# +# read_bbg_value(handle[, description]) +# +# Read a word in .bbg format from handle and return its integer value. +# + +sub read_bbg_value(*;$) +{ + my ($handle, $desc) = @_; + my $word; + + $word = read_bbg_word($handle, $desc); + return undef if (!defined($word)); + + return unpack("N", $word); +} + +# +# read_bbg_string(handle) +# +# Read and return a string in .bbg format. +# + +sub read_bbg_string(*) +{ + my ($handle, $desc) = @_; + my $length; + my $string; + + graph_expect("string"); + # Read string length + $length = read_bbg_value($handle, "string length"); + return undef if (!defined($length)); + if ($length == 0) { + return ""; + } + # Read string + $string = graph_read($handle, $length, "string"); + return undef if (!defined($string)); + # Skip padding + graph_skip($handle, 4 - $length % 4, "string padding") or return undef; + + return $string; +} + +# +# read_bbg_lines_record(handle, bbg_filename, bb, fileorder, filename, +# function, base) +# +# Read a bbg format lines record from handle and add the relevant data to +# bb and fileorder. Return filename on success, undef on error. +# + +sub read_bbg_lines_record(*$$$$$$) +{ + my ($handle, $bbg_filename, $bb, $fileorder, $filename, $function, + $base) = @_; + my $string; + my $lineno; + + graph_expect("lines record"); + # Skip basic block index + graph_skip($handle, 4, "basic block index") or return undef; + while (1) { + # Read line number + $lineno = read_bbg_value($handle, "line number"); + return undef if (!defined($lineno)); + if ($lineno == 0) { + # Got a marker for a new filename + graph_expect("filename"); + $string = read_bbg_string($handle); + return undef if (!defined($string)); + # Check for end of record + if ($string eq "") { + return $filename; + } + $filename = solve_relative_path($base, $string); + next; + } + # Got an actual line number + if (!defined($filename)) { + warn("WARNING: unassigned line number in ". + "$bbg_filename\n"); + next; + } + push(@{$bb->{$function}->{$filename}}, $lineno); + graph_add_order($fileorder, $function, $filename); + } +} + +# +# read_bbg(filename, base_dir) +# +# Read the contents of the specified .bbg file and return the following mapping: +# graph: filename -> file_data +# file_data: function name -> line_data +# line_data: [ line1, line2, ... ] +# +# Relative filenames are converted to absolute form using base_dir as +# base directory. See the gcov-io.h file in the SLES 9 gcc 3.3.3 source code +# for a description of the .bbg format. +# + +sub read_bbg($$) +{ + my ($bbg_filename, $base) = @_; + my $file_magic = 0x67626267; + my $tag_function = 0x01000000; + my $tag_lines = 0x01450000; + my $word; + my $tag; + my $length; + my $function; + my $filename; + my $bb = {}; + my $fileorder = {}; + my $instr; + my $graph; + local *HANDLE; + + open(HANDLE, "<$bbg_filename") or goto open_error; + binmode(HANDLE); + # Read magic + $word = read_bbg_value(*HANDLE, "file magic"); + goto incomplete if (!defined($word)); + # Check magic + if ($word != $file_magic) { + goto magic_error; + } + # Skip version + graph_skip(*HANDLE, 4, "version") or goto incomplete; + while (!eof(HANDLE)) { + # Read record tag + $tag = read_bbg_value(*HANDLE, "record tag"); + goto incomplete if (!defined($tag)); + # Read record length + $length = read_bbg_value(*HANDLE, "record length"); + goto incomplete if (!defined($tag)); + if ($tag == $tag_function) { + graph_expect("function record"); + # Read function name + graph_expect("function name"); + $function = read_bbg_string(*HANDLE); + goto incomplete if (!defined($function)); + $filename = undef; + # Skip function checksum + graph_skip(*HANDLE, 4, "function checksum") + or goto incomplete; + } elsif ($tag == $tag_lines) { + # Read lines record + $filename = read_bbg_lines_record(HANDLE, $bbg_filename, + $bb, $fileorder, $filename, + $function, $base); + goto incomplete if (!defined($filename)); + } else { + # Skip record contents + graph_skip(*HANDLE, $length, "unhandled record") + or goto incomplete; + } + } + close(HANDLE); + ($instr, $graph) = graph_from_bb($bb, $fileorder, $bbg_filename); + graph_cleanup($graph); + + return ($instr, $graph); + +open_error: + graph_error($bbg_filename, "could not open file"); + return undef; +incomplete: + graph_error($bbg_filename, "reached unexpected end of file"); + return undef; +magic_error: + graph_error($bbg_filename, "found unrecognized bbg file magic"); + return undef; +} + +# +# read_gcno_word(handle[, description]) +# +# Read and return a word in .gcno format. +# + +sub read_gcno_word(*;$) +{ + my ($handle, $desc) = @_; + + return graph_read($handle, 4, $desc); +} + +# +# read_gcno_value(handle, big_endian[, description]) +# +# Read a word in .gcno format from handle and return its integer value +# according to the specified endianness. +# + +sub read_gcno_value(*$;$) +{ + my ($handle, $big_endian, $desc) = @_; + my $word; + + $word = read_gcno_word($handle, $desc); + return undef if (!defined($word)); + if ($big_endian) { + return unpack("N", $word); + } else { + return unpack("V", $word); + } +} + +# +# read_gcno_string(handle, big_endian) +# +# Read and return a string in .gcno format. +# + +sub read_gcno_string(*$) +{ + my ($handle, $big_endian) = @_; + my $length; + my $string; + + graph_expect("string"); + # Read string length + $length = read_gcno_value($handle, $big_endian, "string length"); + return undef if (!defined($length)); + if ($length == 0) { + return ""; + } + $length *= 4; + # Read string + $string = graph_read($handle, $length, "string and padding"); + return undef if (!defined($string)); + $string =~ s/\0//g; + + return $string; +} + +# +# read_gcno_lines_record(handle, gcno_filename, bb, fileorder, filename, +# function, base, big_endian) +# +# Read a gcno format lines record from handle and add the relevant data to +# bb and fileorder. Return filename on success, undef on error. +# + +sub read_gcno_lines_record(*$$$$$$$) +{ + my ($handle, $gcno_filename, $bb, $fileorder, $filename, $function, + $base, $big_endian) = @_; + my $string; + my $lineno; + + graph_expect("lines record"); + # Skip basic block index + graph_skip($handle, 4, "basic block index") or return undef; + while (1) { + # Read line number + $lineno = read_gcno_value($handle, $big_endian, "line number"); + return undef if (!defined($lineno)); + if ($lineno == 0) { + # Got a marker for a new filename + graph_expect("filename"); + $string = read_gcno_string($handle, $big_endian); + return undef if (!defined($string)); + # Check for end of record + if ($string eq "") { + return $filename; + } + $filename = solve_relative_path($base, $string); + next; + } + # Got an actual line number + if (!defined($filename)) { + warn("WARNING: unassigned line number in ". + "$gcno_filename\n"); + next; + } + # Add to list + push(@{$bb->{$function}->{$filename}}, $lineno); + graph_add_order($fileorder, $function, $filename); + } +} + +# +# read_gcno_function_record(handle, graph, base, big_endian) +# +# Read a gcno format function record from handle and add the relevant data +# to graph. Return (filename, function) on success, undef on error. +# + +sub read_gcno_function_record(*$$$$) +{ + my ($handle, $bb, $fileorder, $base, $big_endian) = @_; + my $filename; + my $function; + my $lineno; + my $lines; + + graph_expect("function record"); + # Skip ident and checksum + graph_skip($handle, 8, "function ident and checksum") or return undef; + # Read function name + graph_expect("function name"); + $function = read_gcno_string($handle, $big_endian); + return undef if (!defined($function)); + # Read filename + graph_expect("filename"); + $filename = read_gcno_string($handle, $big_endian); + return undef if (!defined($filename)); + $filename = solve_relative_path($base, $filename); + # Read first line number + $lineno = read_gcno_value($handle, $big_endian, "initial line number"); + return undef if (!defined($lineno)); + # Add to list + push(@{$bb->{$function}->{$filename}}, $lineno); + graph_add_order($fileorder, $function, $filename); + + return ($filename, $function); +} + +# +# read_gcno(filename, base_dir) +# +# Read the contents of the specified .gcno file and return the following +# mapping: +# graph: filename -> file_data +# file_data: function name -> line_data +# line_data: [ line1, line2, ... ] +# +# Relative filenames are converted to absolute form using base_dir as +# base directory. See the gcov-io.h file in the gcc 3.3 source code +# for a description of the .gcno format. +# + +sub read_gcno($$) +{ + my ($gcno_filename, $base) = @_; + my $file_magic = 0x67636e6f; + my $tag_function = 0x01000000; + my $tag_lines = 0x01450000; + my $big_endian; + my $word; + my $tag; + my $length; + my $filename; + my $function; + my $bb = {}; + my $fileorder = {}; + my $instr; + my $graph; + local *HANDLE; + + open(HANDLE, "<$gcno_filename") or goto open_error; + binmode(HANDLE); + # Read magic + $word = read_gcno_word(*HANDLE, "file magic"); + goto incomplete if (!defined($word)); + # Determine file endianness + if (unpack("N", $word) == $file_magic) { + $big_endian = 1; + } elsif (unpack("V", $word) == $file_magic) { + $big_endian = 0; + } else { + goto magic_error; + } + # Skip version and stamp + graph_skip(*HANDLE, 8, "version and stamp") or goto incomplete; + while (!eof(HANDLE)) { + my $next_pos; + my $curr_pos; + + # Read record tag + $tag = read_gcno_value(*HANDLE, $big_endian, "record tag"); + goto incomplete if (!defined($tag)); + # Read record length + $length = read_gcno_value(*HANDLE, $big_endian, + "record length"); + goto incomplete if (!defined($length)); + # Convert length to bytes + $length *= 4; + # Calculate start of next record + $next_pos = tell(HANDLE); + goto tell_error if ($next_pos == -1); + $next_pos += $length; + # Process record + if ($tag == $tag_function) { + ($filename, $function) = read_gcno_function_record( + *HANDLE, $bb, $fileorder, $base, $big_endian); + goto incomplete if (!defined($function)); + } elsif ($tag == $tag_lines) { + # Read lines record + $filename = read_gcno_lines_record(*HANDLE, + $gcno_filename, $bb, $fileorder, + $filename, $function, $base, + $big_endian); + goto incomplete if (!defined($filename)); + } else { + # Skip record contents + graph_skip(*HANDLE, $length, "unhandled record") + or goto incomplete; + } + # Ensure that we are at the start of the next record + $curr_pos = tell(HANDLE); + goto tell_error if ($curr_pos == -1); + next if ($curr_pos == $next_pos); + goto record_error if ($curr_pos > $next_pos); + graph_skip(*HANDLE, $next_pos - $curr_pos, + "unhandled record content") + or goto incomplete; + } + close(HANDLE); + ($instr, $graph) = graph_from_bb($bb, $fileorder, $gcno_filename); + graph_cleanup($graph); + + return ($instr, $graph); + +open_error: + graph_error($gcno_filename, "could not open file"); + return undef; +incomplete: + graph_error($gcno_filename, "reached unexpected end of file"); + return undef; +magic_error: + graph_error($gcno_filename, "found unrecognized gcno file magic"); + return undef; +tell_error: + graph_error($gcno_filename, "could not determine file position"); + return undef; +record_error: + graph_error($gcno_filename, "found unrecognized record format"); + return undef; +} + +sub debug($) +{ + my ($msg) = @_; + + return if (!$debug); + print(STDERR "DEBUG: $msg"); +} + +# +# get_gcov_capabilities +# +# Determine the list of available gcov options. +# + +sub get_gcov_capabilities() +{ + my $help = `$gcov_tool --help`; + my %capabilities; + + foreach (split(/\n/, $help)) { + next if (!/--(\S+)/); + next if ($1 eq 'help'); + next if ($1 eq 'version'); + next if ($1 eq 'object-directory'); + + $capabilities{$1} = 1; + debug("gcov has capability '$1'\n"); + } + + return \%capabilities; +} diff --git a/3rdParty/LCov/genpng b/3rdParty/LCov/genpng index b4d90c2..7fe9dfe 100755 --- a/3rdParty/LCov/genpng +++ b/3rdParty/LCov/genpng @@ -35,7 +35,7 @@ use Getopt::Long; # Constants -our $lcov_version = "LCOV version 1.7"; +our $lcov_version = 'LCOV version 1.9'; our $lcov_url = "http://ltp.sourceforge.net/coverage/lcov.php"; our $tool_name = basename($0); @@ -45,14 +45,17 @@ sub gen_png($$$@); sub check_and_load_module($); sub genpng_print_usage(*); sub genpng_process_file($$$$); -sub warn_handler($); -sub die_handler($); +sub genpng_warn_handler($); +sub genpng_die_handler($); # # Code entry point # +# Prettify version string +$lcov_version =~ s/\$\s*Revision\s*:?\s*(\S+)\s*\$/$1/; + # Check whether required module GD.pm is installed if (check_and_load_module("GD")) { @@ -75,8 +78,8 @@ if (!caller) my $help; my $version; - $SIG{__WARN__} = \&warn_handler; - $SIG{__DIE__} = \&die_handler; + $SIG{__WARN__} = \&genpng_warn_handler; + $SIG{__DIE__} = \&genpng_die_handler; # Parse command line options if (!GetOptions("tab-size=i" => \$tab_size, @@ -366,14 +369,14 @@ sub gen_png($$$@) close(PNG_HANDLE); } -sub warn_handler($) +sub genpng_warn_handler($) { my ($msg) = @_; warn("$tool_name: $msg"); } -sub die_handler($) +sub genpng_die_handler($) { my ($msg) = @_; diff --git a/3rdParty/LCov/lcov b/3rdParty/LCov/lcov index 6304d75..4e392ff 100755 --- a/3rdParty/LCov/lcov +++ b/3rdParty/LCov/lcov @@ -1,6 +1,6 @@ #!/usr/bin/perl -w # -# Copyright (c) International Business Machines Corp., 2002,2007 +# Copyright (c) International Business Machines Corp., 2002,2010 # # 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 @@ -60,36 +60,43 @@ # use strict; -use File::Basename; +use File::Basename; +use File::Path; +use File::Find; +use File::Temp qw /tempdir/; +use File::Spec::Functions qw /abs2rel canonpath catdir catfile catpath + file_name_is_absolute rootdir splitdir splitpath/; use Getopt::Long; +use Cwd qw /abs_path getcwd/; # Global constants -our $lcov_version = "LCOV version 1.7"; +our $lcov_version = 'LCOV version 1.9'; our $lcov_url = "http://ltp.sourceforge.net/coverage/lcov.php"; our $tool_name = basename($0); -# Names of the GCOV kernel module -our @gcovmod = ("gcov-prof", "gcov-proc"); - # Directory containing gcov kernel files -our $gcov_dir = "/proc/gcov"; - -# The location of the insmod tool -our $insmod_tool = "/sbin/insmod"; - -# The location of the modprobe tool -our $modprobe_tool = "/sbin/modprobe"; - -# The location of the rmmod tool -our $rmmod_tool = "/sbin/rmmod"; +our $gcov_dir; # Where to create temporary directories -our $tmp_dir = "/tmp"; +our $tmp_dir; + +# Internal constants +our $GKV_PROC = 0; # gcov-kernel data in /proc via external patch +our $GKV_SYS = 1; # gcov-kernel data in /sys via vanilla 2.6.31+ +our @GKV_NAME = ( "external", "upstream" ); +our $pkg_gkv_file = ".gcov_kernel_version"; +our $pkg_build_file = ".build_directory"; -# How to prefix a temporary directory name -our $tmp_prefix = "tmpdir"; +our $BR_BLOCK = 0; +our $BR_BRANCH = 1; +our $BR_TAKEN = 2; +our $BR_VEC_ENTRIES = 3; +our $BR_VEC_WIDTH = 32; +# Branch data combination types +our $BR_SUB = 0; +our $BR_ADD = 1; # Prototypes sub print_usage(*); @@ -98,10 +105,12 @@ sub userspace_reset(); sub userspace_capture(); sub kernel_reset(); sub kernel_capture(); +sub kernel_capture_initial(); +sub package_capture(); sub add_traces(); sub read_info_file($); sub get_info_entry($); -sub set_info_entry($$$$$$$;$$$$); +sub set_info_entry($$$$$$$$$;$$$$$$); sub add_counts($$); sub merge_checksums($$$); sub combine_info_entries($$$); @@ -117,13 +126,19 @@ sub system_no_output($@); sub read_config($); sub apply_config($); sub info(@); -sub unload_module($); -sub check_and_load_kernel_module(); sub create_temp_dir(); sub transform_pattern($); sub warn_handler($); sub die_handler($); - +sub abort_handler($); +sub temp_cleanup(); +sub setup_gkv(); +sub get_overall_line($$$$); +sub print_overall_rate($$$$$$$$$); +sub lcov_geninfo(@); +sub create_package($$$;$); +sub get_func_found_and_hit($); +sub br_ivec_get($$); # Global variables & initialization our @directory; # Specifies where to get coverage data from @@ -142,7 +157,6 @@ our $help; # Help option flag our $version; # Version option flag our $convert_filenames; # If set, convert filenames when applying diff our $strip; # If set, strip leading directories when applying diff -our $need_unload; # If set, unload gcov kernel module our $temp_dir_name; # Name of temporary directory our $cwd = `pwd`; # Current working directory our $to_file; # If set, indicates that output is written to a file @@ -157,10 +171,27 @@ our $gcov_tool; our $ignore_errors; our $initial; our $no_recursion = 0; +our $to_package; +our $from_package; our $maxdepth; +our $no_markers; our $config; # Configuration file contents chomp($cwd); our $tool_dir = dirname($0); # Directory where genhtml tool is installed +our @temp_dirs; +our $gcov_gkv; # gcov kernel support version found on machine +our $opt_derive_func_data; +our $opt_debug; +our $opt_list_full_path; +our $opt_no_list_full_path; +our $opt_list_width = 80; +our $opt_list_truncate_max = 20; +our $ln_overall_found; +our $ln_overall_hit; +our $fn_overall_found; +our $fn_overall_hit; +our $br_overall_found; +our $br_overall_hit; # @@ -169,6 +200,11 @@ our $tool_dir = dirname($0); # Directory where genhtml tool is installed $SIG{__WARN__} = \&warn_handler; $SIG{__DIE__} = \&die_handler; +$SIG{'INT'} = \&abort_handler; +$SIG{'QUIT'} = \&abort_handler; + +# Prettify version string +$lcov_version =~ s/\$\s*Revision\s*:?\s*(\S+)\s*\$/$1/; # Add current working directory if $tool_dir is not already an absolute path if (! ($tool_dir =~ /^\/(.*)$/)) @@ -177,7 +213,7 @@ if (! ($tool_dir =~ /^\/(.*)$/)) } # Read configuration file if available -if (-r $ENV{"HOME"}."/.lcovrc") +if (defined($ENV{"HOME"}) && (-r $ENV{"HOME"}."/.lcovrc")) { $config = read_config($ENV{"HOME"}."/.lcovrc"); } @@ -191,32 +227,33 @@ if ($config) # Copy configuration file values to variables apply_config({ "lcov_gcov_dir" => \$gcov_dir, - "lcov_insmod_tool" => \$insmod_tool, - "lcov_modprobe_tool" => \$modprobe_tool, - "lcov_rmmod_tool" => \$rmmod_tool, - "lcov_tmp_dir" => \$tmp_dir}); + "lcov_tmp_dir" => \$tmp_dir, + "lcov_list_full_path" => \$opt_list_full_path, + "lcov_list_width" => \$opt_list_width, + "lcov_list_truncate_max"=> \$opt_list_truncate_max, + }); } # Parse command line options if (!GetOptions("directory|d|di=s" => \@directory, - "add-tracefile=s" => \@add_tracefile, - "list=s" => \$list, - "kernel-directory=s" => \@kernel_directory, - "extract=s" => \$extract, - "remove=s" => \$remove, + "add-tracefile|a=s" => \@add_tracefile, + "list|l=s" => \$list, + "kernel-directory|k=s" => \@kernel_directory, + "extract|e=s" => \$extract, + "remove|r=s" => \$remove, "diff=s" => \$diff, "convert-filenames" => \$convert_filenames, "strip=i" => \$strip, "capture|c" => \$capture, - "output-file=s" => \$output_filename, - "test-name=s" => \$test_name, - "zerocounters" => \$reset, - "quiet" => \$quiet, - "help|?" => \$help, - "version" => \$version, - "follow" => \$follow, + "output-file|o=s" => \$output_filename, + "test-name|t=s" => \$test_name, + "zerocounters|z" => \$reset, + "quiet|q" => \$quiet, + "help|h|?" => \$help, + "version|v" => \$version, + "follow|f" => \$follow, "path=s" => \$diff_path, - "base-directory=s" => \$base_directory, + "base-directory|b=s" => \$base_directory, "checksum" => \$checksum, "no-checksum" => \$no_checksum, "compat-libtool" => \$compat_libtool, @@ -224,7 +261,14 @@ if (!GetOptions("directory|d|di=s" => \@directory, "gcov-tool=s" => \$gcov_tool, "ignore-errors=s" => \$ignore_errors, "initial|i" => \$initial, - "no-recursion" => \$no_recursion + "no-recursion" => \$no_recursion, + "to-package=s" => \$to_package, + "from-package=s" => \$from_package, + "no-markers" => \$no_markers, + "derive-func-data" => \$opt_derive_func_data, + "debug" => \$opt_debug, + "list-full-path" => \$opt_list_full_path, + "no-list-full-path" => \$opt_no_list_full_path, )) { print(STDERR "Use $tool_name --help to get usage information\n"); @@ -244,6 +288,12 @@ else $compat_libtool = ($no_compat_libtool ? 0 : 1); $no_compat_libtool = undef; } + + if (defined($opt_no_list_full_path)) + { + $opt_list_full_path = ($opt_no_list_full_path ? 0 : 1); + $opt_no_list_full_path = undef; + } } # Check for help option @@ -260,6 +310,12 @@ if ($version) exit(0); } +# Check list width option +if ($opt_list_width <= 40) { + die("ERROR: lcov_list_width parameter out of range (needs to be ". + "larger than 40)\n"); +} + # Normalize --path text $diff_path =~ s/\/$//; @@ -287,7 +343,7 @@ check_options(); # Only --extract, --remove and --diff allow unnamed parameters if (@ARGV && !($extract || $remove || $diff)) { - die("Extra parameter found\n". + die("Extra parameter found: '".join(" ", @ARGV)."'\n". "Use $tool_name --help to get usage information\n"); } @@ -302,13 +358,10 @@ if ($capture) $output_filename = "-"; } } -else -{ - if ($initial) - { - die("Option --initial is only valid when capturing data (-c)\n". - "Use $tool_name --help to get usage information\n"); - } + +# Determine kernel directory for gcov data +if (!$from_package && !@directory && ($capture || $reset)) { + ($gcov_gkv, $gcov_dir) = setup_gkv(); } # Check for requested functionality @@ -326,27 +379,40 @@ if ($reset) } elsif ($capture) { - # Differentiate between user space and kernel - if (@directory) - { + # Capture source can be user space, kernel or package + if ($from_package) { + package_capture(); + } elsif (@directory) { userspace_capture(); - } - else - { - kernel_capture(); + } else { + if ($initial) { + if (defined($to_package)) { + die("ERROR: --initial cannot be used together ". + "with --to-package\n"); + } + kernel_capture_initial(); + } else { + kernel_capture(); + } } } elsif (@add_tracefile) { - add_traces(); + ($ln_overall_found, $ln_overall_hit, + $fn_overall_found, $fn_overall_hit, + $br_overall_found, $br_overall_hit) = add_traces(); } elsif ($remove) { - remove(); + ($ln_overall_found, $ln_overall_hit, + $fn_overall_found, $fn_overall_hit, + $br_overall_found, $br_overall_hit) = remove(); } elsif ($extract) { - extract(); + ($ln_overall_found, $ln_overall_hit, + $fn_overall_found, $fn_overall_hit, + $br_overall_found, $br_overall_hit) = extract(); } elsif ($list) { @@ -359,10 +425,20 @@ elsif ($diff) die("ERROR: option --diff requires one additional argument!\n". "Use $tool_name --help to get usage information\n"); } - diff(); + ($ln_overall_found, $ln_overall_hit, + $fn_overall_found, $fn_overall_hit, + $br_overall_found, $br_overall_hit) = diff(); } -info("Done.\n"); +temp_cleanup(); + +if (defined($ln_overall_found)) { + print_overall_rate(1, $ln_overall_found, $ln_overall_hit, + 1, $fn_overall_found, $fn_overall_hit, + 1, $br_overall_found, $br_overall_hit); +} else { + info("Done.\n") if (!$list && !$capture); +} exit(0); # @@ -410,8 +486,13 @@ Options: --(no-)checksum Enable (disable) line checksumming --(no-)compat-libtool Enable (disable) libtool compatibility mode --gcov-tool TOOL Specify gcov tool location - --ignore-errors ERRORS Continue after ERRORS (gcov, source) - --no-recursion Exlude subdirectories from processing + --ignore-errors ERRORS Continue after ERRORS (gcov, source, graph) + --no-recursion Exclude subdirectories from processing + --to-package FILENAME Store unprocessed coverage data in FILENAME + --from-package FILENAME Capture from unprocessed data in FILENAME + --no-markers Ignore exclusion markers in source code + --derive-func-data Generate function data from line data + --list-full-path Print full path during a list operation For more information see: $lcov_url END_OF_USAGE @@ -483,19 +564,197 @@ sub userspace_reset() # # userspace_capture() # -# Capture coverage data found in DIRECTORY and write it to OUTPUT_FILENAME -# if specified, otherwise to STDOUT. +# Capture coverage data found in DIRECTORY and write it to a package (if +# TO_PACKAGE specified) or to OUTPUT_FILENAME or STDOUT. # # Die on error. # sub userspace_capture() { + my $dir; + my $build; + + if (!defined($to_package)) { + lcov_geninfo(@directory); + return; + } + if (scalar(@directory) != 1) { + die("ERROR: -d may be specified only once with --to-package\n"); + } + $dir = $directory[0]; + if (defined($base_directory)) { + $build = $base_directory; + } else { + $build = $dir; + } + create_package($to_package, $dir, $build); +} + + +# +# kernel_reset() +# +# Reset kernel coverage. +# +# Die on error. +# + +sub kernel_reset() +{ + local *HANDLE; + my $reset_file; + + info("Resetting kernel execution counters\n"); + if (-e "$gcov_dir/vmlinux") { + $reset_file = "$gcov_dir/vmlinux"; + } elsif (-e "$gcov_dir/reset") { + $reset_file = "$gcov_dir/reset"; + } else { + die("ERROR: no reset control found in $gcov_dir\n"); + } + open(HANDLE, ">$reset_file") or + die("ERROR: cannot write to $reset_file!\n"); + print(HANDLE "0"); + close(HANDLE); +} + + +# +# lcov_copy_single(from, to) +# +# Copy single regular file FROM to TO without checking its size. This is +# required to work with special files generated by the kernel +# seq_file-interface. +# +# +sub lcov_copy_single($$) +{ + my ($from, $to) = @_; + my $content; + local $/; + local *HANDLE; + + open(HANDLE, "<$from") or die("ERROR: cannot read $from: $!\n"); + $content = <HANDLE>; + close(HANDLE); + open(HANDLE, ">$to") or die("ERROR: cannot write $from: $!\n"); + if (defined($content)) { + print(HANDLE $content); + } + close(HANDLE); +} + +# +# lcov_find(dir, function, data[, extension, ...)]) +# +# Search DIR for files and directories whose name matches PATTERN and run +# FUNCTION for each match. If not pattern is specified, match all names. +# +# FUNCTION has the following prototype: +# function(dir, relative_name, data) +# +# Where: +# dir: the base directory for this search +# relative_name: the name relative to the base directory of this entry +# data: the DATA variable passed to lcov_find +# +sub lcov_find($$$;@) +{ + my ($dir, $fn, $data, @pattern) = @_; + my $result; + my $_fn = sub { + my $filename = $File::Find::name; + + if (defined($result)) { + return; + } + $filename = abs2rel($filename, $dir); + foreach (@pattern) { + if ($filename =~ /$_/) { + goto ok; + } + } + return; + ok: + $result = &$fn($dir, $filename, $data); + }; + if (scalar(@pattern) == 0) { + @pattern = ".*"; + } + find( { wanted => $_fn, no_chdir => 1 }, $dir); + + return $result; +} + +# +# lcov_copy_fn(from, rel, to) +# +# Copy directories, files and links from/rel to to/rel. +# + +sub lcov_copy_fn($$$) +{ + my ($from, $rel, $to) = @_; + my $absfrom = canonpath(catfile($from, $rel)); + my $absto = canonpath(catfile($to, $rel)); + + if (-d) { + if (! -d $absto) { + mkpath($absto) or + die("ERROR: cannot create directory $absto\n"); + chmod(0700, $absto); + } + } elsif (-l) { + # Copy symbolic link + my $link = readlink($absfrom); + + if (!defined($link)) { + die("ERROR: cannot read link $absfrom: $!\n"); + } + symlink($link, $absto) or + die("ERROR: cannot create link $absto: $!\n"); + } else { + lcov_copy_single($absfrom, $absto); + chmod(0600, $absto); + } + return undef; +} + +# +# lcov_copy(from, to, subdirs) +# +# Copy all specified SUBDIRS and files from directory FROM to directory TO. For +# regular files, copy file contents without checking its size. This is required +# to work with seq_file-generated files. +# + +sub lcov_copy($$;@) +{ + my ($from, $to, @subdirs) = @_; + my @pattern; + + foreach (@subdirs) { + push(@pattern, "^$_"); + } + lcov_find($from, \&lcov_copy_fn, $to, @pattern); +} + +# +# lcov_geninfo(directory) +# +# Call geninfo for the specified directory and with the parameters specified +# at the command line. +# + +sub lcov_geninfo(@) +{ + my (@dir) = @_; my @param; - my $file_list = join(" ", @directory); - info("Capturing coverage data from $file_list\n"); - @param = ("$tool_dir/geninfo", @directory); + # Capture data + info("Capturing coverage data from ".join(" ", @dir)."\n"); + @param = ("$tool_dir/geninfo", @dir); if ($output_filename) { @param = (@param, "--output-filename", $output_filename); @@ -547,172 +806,443 @@ sub userspace_capture() { @param = (@param, "--initial"); } - if ($no_recursion) + if ($no_markers) { - @param = (@param, "--no-recursion"); + @param = (@param, "--no-markers"); } - - system(@param); - exit($? >> 8); + if ($opt_derive_func_data) + { + @param = (@param, "--derive-func-data"); + } + if ($opt_debug) + { + @param = (@param, "--debug"); + } + system(@param) and exit($? >> 8); } - # -# kernel_reset() +# read_file(filename) # -# Reset kernel coverage. -# -# Die on error. +# Return the contents of the file defined by filename. # -sub kernel_reset() +sub read_file($) { + my ($filename) = @_; + my $content; + local $\; local *HANDLE; - check_and_load_kernel_module(); - info("Resetting kernel execution counters\n"); - open(HANDLE, ">$gcov_dir/vmlinux") or - die("ERROR: cannot write to $gcov_dir/vmlinux!\n"); - print(HANDLE "0"); + open(HANDLE, "<$filename") || return undef; + $content = <HANDLE>; close(HANDLE); - # Unload module if we loaded it in the first place - if ($need_unload) - { - unload_module($need_unload); - } + return $content; } +# +# get_package(package_file) +# +# Unpack unprocessed coverage data files from package_file to a temporary +# directory and return directory name, build directory and gcov kernel version +# as found in package. +# + +sub get_package($) +{ + my ($file) = @_; + my $dir = create_temp_dir(); + my $gkv; + my $build; + my $cwd = getcwd(); + my $count; + local *HANDLE; + + info("Reading package $file:\n"); + info(" data directory .......: $dir\n"); + $file = abs_path($file); + chdir($dir); + open(HANDLE, "tar xvfz $file 2>/dev/null|") + or die("ERROR: could not process package $file\n"); + while (<HANDLE>) { + if (/\.da$/ || /\.gcda$/) { + $count++; + } + } + close(HANDLE); + $build = read_file("$dir/$pkg_build_file"); + if (defined($build)) { + info(" build directory ......: $build\n"); + } + $gkv = read_file("$dir/$pkg_gkv_file"); + if (defined($gkv)) { + $gkv = int($gkv); + if ($gkv != $GKV_PROC && $gkv != $GKV_SYS) { + die("ERROR: unsupported gcov kernel version found ". + "($gkv)\n"); + } + info(" content type .........: kernel data\n"); + info(" gcov kernel version ..: %s\n", $GKV_NAME[$gkv]); + } else { + info(" content type .........: application data\n"); + } + info(" data files ...........: $count\n"); + chdir($cwd); + + return ($dir, $build, $gkv); +} # -# kernel_capture() +# write_file(filename, $content) # -# Capture kernel coverage data and write it to OUTPUT_FILENAME if specified, -# otherwise stdout. +# Create a file named filename and write the specified content to it. # -sub kernel_capture() +sub write_file($$) { - my @param; + my ($filename, $content) = @_; + local *HANDLE; - check_and_load_kernel_module(); + open(HANDLE, ">$filename") || return 0; + print(HANDLE $content); + close(HANDLE) || return 0; - # Make sure the temporary directory is removed upon script termination - END - { - if ($temp_dir_name) - { - stat($temp_dir_name); - if (-r _) - { - info("Removing temporary directory ". - "$temp_dir_name\n"); - - # Remove temporary directory - system("rm", "-rf", $temp_dir_name) - and warn("WARNING: cannot remove ". - "temporary directory ". - "$temp_dir_name!\n"); - } + return 1; +} + +# count_package_data(filename) +# +# Count the number of coverage data files in the specified package file. +# + +sub count_package_data($) +{ + my ($filename) = @_; + local *HANDLE; + my $count = 0; + + open(HANDLE, "tar tfz $filename|") or return undef; + while (<HANDLE>) { + if (/\.da$/ || /\.gcda$/) { + $count++; } } + close(HANDLE); + return $count; +} - # Get temporary directory - $temp_dir_name = create_temp_dir(); - - info("Copying kernel data to temporary directory $temp_dir_name\n"); +# +# create_package(package_file, source_directory, build_directory[, +# kernel_gcov_version]) +# +# Store unprocessed coverage data files from source_directory to package_file. +# - if (!@kernel_directory) - { - # Copy files from gcov kernel directory - system("cp", "-dr", $gcov_dir, $temp_dir_name) - and die("ERROR: cannot copy files from $gcov_dir!\n"); +sub create_package($$$;$) +{ + my ($file, $dir, $build, $gkv) = @_; + my $cwd = getcwd(); + + # Print information about the package + info("Creating package $file:\n"); + info(" data directory .......: $dir\n"); + + # Handle build directory + if (defined($build)) { + info(" build directory ......: $build\n"); + write_file("$dir/$pkg_build_file", $build) + or die("ERROR: could not write to ". + "$dir/$pkg_build_file\n"); } - else - { - # Prefix list of kernel sub-directories with the gcov kernel - # directory - @kernel_directory = map("$gcov_dir/$_", @kernel_directory); - # Copy files from gcov kernel directory - system("cp", "-dr", @kernel_directory, $temp_dir_name) - and die("ERROR: cannot copy files from ". - join(" ", @kernel_directory)."!\n"); + # Handle gcov kernel version data + if (defined($gkv)) { + info(" content type .........: kernel data\n"); + info(" gcov kernel version ..: %s\n", $GKV_NAME[$gkv]); + write_file("$dir/$pkg_gkv_file", $gkv) + or die("ERROR: could not write to ". + "$dir/$pkg_gkv_file\n"); + } else { + info(" content type .........: application data\n"); } - # Make directories writable - system("find", $temp_dir_name, "-type", "d", "-exec", "chmod", "u+w", - "{}", ";") - and die("ERROR: cannot modify access rights for ". - "$temp_dir_name!\n"); + # Create package + $file = abs_path($file); + chdir($dir); + system("tar cfz $file .") + and die("ERROR: could not create package $file\n"); - # Make files writable - system("find", $temp_dir_name, "-type", "f", "-exec", "chmod", "u+w", - "{}", ";") - and die("ERROR: cannot modify access rights for ". - "$temp_dir_name!\n"); + # Remove temporary files + unlink("$dir/$pkg_build_file"); + unlink("$dir/$pkg_gkv_file"); - # Capture data - info("Capturing coverage data from $temp_dir_name\n"); - @param = ("$tool_dir/geninfo", $temp_dir_name); - if ($output_filename) - { - @param = (@param, "--output-filename", $output_filename); + # Show number of data files + if (!$quiet) { + my $count = count_package_data($file); + + if (defined($count)) { + info(" data files ...........: $count\n"); + } } - if ($test_name) - { - @param = (@param, "--test-name", $test_name); + chdir($cwd); +} + +sub find_link_fn($$$) +{ + my ($from, $rel, $filename) = @_; + my $absfile = catfile($from, $rel, $filename); + + if (-l $absfile) { + return $absfile; } - if ($follow) - { - @param = (@param, "--follow"); + return undef; +} + +# +# get_base(dir) +# +# Return (BASE, OBJ), where +# - BASE: is the path to the kernel base directory relative to dir +# - OBJ: is the absolute path to the kernel build directory +# + +sub get_base($) +{ + my ($dir) = @_; + my $marker = "kernel/gcov/base.gcno"; + my $markerfile; + my $sys; + my $obj; + my $link; + + $markerfile = lcov_find($dir, \&find_link_fn, $marker); + if (!defined($markerfile)) { + return (undef, undef); } - if ($quiet) - { - @param = (@param, "--quiet"); + + # sys base is parent of parent of markerfile. + $sys = abs2rel(dirname(dirname(dirname($markerfile))), $dir); + + # obj base is parent of parent of markerfile link target. + $link = readlink($markerfile); + if (!defined($link)) { + die("ERROR: could not read $markerfile\n"); } - if (defined($checksum)) - { - if ($checksum) - { - @param = (@param, "--checksum"); + $obj = dirname(dirname(dirname($link))); + + return ($sys, $obj); +} + +# +# apply_base_dir(data_dir, base_dir, build_dir, @directories) +# +# Make entries in @directories relative to data_dir. +# + +sub apply_base_dir($$$@) +{ + my ($data, $base, $build, @dirs) = @_; + my $dir; + my @result; + + foreach $dir (@dirs) { + # Is directory path relative to data directory? + if (-d catdir($data, $dir)) { + push(@result, $dir); + next; } - else - { - @param = (@param, "--no-checksum"); + # Relative to the auto-detected base-directory? + if (defined($base)) { + if (-d catdir($data, $base, $dir)) { + push(@result, catdir($base, $dir)); + next; + } + } + # Relative to the specified base-directory? + if (defined($base_directory)) { + if (file_name_is_absolute($base_directory)) { + $base = abs2rel($base_directory, rootdir()); + } else { + $base = $base_directory; + } + if (-d catdir($data, $base, $dir)) { + push(@result, catdir($base, $dir)); + next; + } } + # Relative to the build directory? + if (defined($build)) { + if (file_name_is_absolute($build)) { + $base = abs2rel($build, rootdir()); + } else { + $base = $build; + } + if (-d catdir($data, $base, $dir)) { + push(@result, catdir($base, $dir)); + next; + } + } + die("ERROR: subdirectory $dir not found\n". + "Please use -b to specify the correct directory\n"); } - if ($base_directory) - { - @param = (@param, "--base-directory", $base_directory); + return @result; +} + +# +# copy_gcov_dir(dir, [@subdirectories]) +# +# Create a temporary directory and copy all or, if specified, only some +# subdirectories from dir to that directory. Return the name of the temporary +# directory. +# + +sub copy_gcov_dir($;@) +{ + my ($data, @dirs) = @_; + my $tempdir = create_temp_dir(); + + info("Copying data to temporary directory $tempdir\n"); + lcov_copy($data, $tempdir, @dirs); + + return $tempdir; +} + +# +# kernel_capture_initial +# +# Capture initial kernel coverage data, i.e. create a coverage data file from +# static graph files which contains zero coverage data for all instrumented +# lines. +# + +sub kernel_capture_initial() +{ + my $build; + my $source; + my @params; + + if (defined($base_directory)) { + $build = $base_directory; + $source = "specified"; + } else { + (undef, $build) = get_base($gcov_dir); + if (!defined($build)) { + die("ERROR: could not auto-detect build directory.\n". + "Please use -b to specify the build directory\n"); + } + $source = "auto-detected"; } - if ($no_compat_libtool) - { - @param = (@param, "--no-compat-libtool"); + info("Using $build as kernel build directory ($source)\n"); + # Build directory needs to be passed to geninfo + $base_directory = $build; + if (@kernel_directory) { + foreach my $dir (@kernel_directory) { + push(@params, "$build/$dir"); + } + } else { + push(@params, $build); } - elsif ($compat_libtool) - { - @param = (@param, "--compat-libtool"); + lcov_geninfo(@params); +} + +# +# kernel_capture_from_dir(directory, gcov_kernel_version, build) +# +# Perform the actual kernel coverage capturing from the specified directory +# assuming that the data was copied from the specified gcov kernel version. +# + +sub kernel_capture_from_dir($$$) +{ + my ($dir, $gkv, $build) = @_; + + # Create package or coverage file + if (defined($to_package)) { + create_package($to_package, $dir, $build, $gkv); + } else { + # Build directory needs to be passed to geninfo + $base_directory = $build; + lcov_geninfo($dir); } - if ($gcov_tool) - { - @param = (@param, "--gcov-tool", $gcov_tool); +} + +# +# adjust_kernel_dir(dir, build) +# +# Adjust directories specified with -k so that they point to the directory +# relative to DIR. Return the build directory if specified or the auto- +# detected build-directory. +# + +sub adjust_kernel_dir($$) +{ + my ($dir, $build) = @_; + my ($sys_base, $build_auto) = get_base($dir); + + if (!defined($build)) { + $build = $build_auto; } - if ($ignore_errors) - { - @param = (@param, "--ignore-errors", $ignore_errors); + if (!defined($build)) { + die("ERROR: could not auto-detect build directory.\n". + "Please use -b to specify the build directory\n"); } - if ($initial) - { - @param = (@param, "--initial"); + # Make @kernel_directory relative to sysfs base + if (@kernel_directory) { + @kernel_directory = apply_base_dir($dir, $sys_base, $build, + @kernel_directory); } - system(@param) and exit($? >> 8); + return $build; +} +sub kernel_capture() +{ + my $data_dir; + my $build = $base_directory; - # Unload module if we loaded it in the first place - if ($need_unload) - { - unload_module($need_unload); + if ($gcov_gkv == $GKV_SYS) { + $build = adjust_kernel_dir($gcov_dir, $build); + } + $data_dir = copy_gcov_dir($gcov_dir, @kernel_directory); + kernel_capture_from_dir($data_dir, $gcov_gkv, $build); +} + +# +# package_capture() +# +# Capture coverage data from a package of unprocessed coverage data files +# as generated by lcov --to-package. +# + +sub package_capture() +{ + my $dir; + my $build; + my $gkv; + + ($dir, $build, $gkv) = get_package($from_package); + + # Check for build directory + if (defined($base_directory)) { + if (defined($build)) { + info("Using build directory specified by -b.\n"); + } + $build = $base_directory; + } + + # Do the actual capture + if (defined($gkv)) { + if ($gkv == $GKV_SYS) { + $build = adjust_kernel_dir($dir, $build); + } + if (@kernel_directory) { + $dir = copy_gcov_dir($dir, @kernel_directory); + } + kernel_capture_from_dir($dir, $gkv, $build); + } else { + # Build directory needs to be passed to geninfo + $base_directory = $build; + lcov_geninfo($dir); } } @@ -731,11 +1261,11 @@ sub info(@) # Print info string if ($to_file) { - print(@_) + printf(@_) } else { - # Don't interfer with the .info output to STDOUT + # Don't interfere with the .info output to STDOUT printf(STDERR @_); } } @@ -743,133 +1273,209 @@ sub info(@) # -# Check if the gcov kernel module is loaded. If it is, exit, if not, try -# to load it. +# create_temp_dir() +# +# Create a temporary directory and return its path. # # Die on error. # -sub check_and_load_kernel_module() +sub create_temp_dir() { - my $module_name; - - # Is it loaded already? - stat("$gcov_dir"); - if (-r _) { return(); } + my $dir; - info("Loading required gcov kernel module.\n"); - - # Do we have access to the insmod tool? - stat($insmod_tool); - if (!-x _) - { - die("ERROR: need insmod tool ($insmod_tool) to access kernel ". - "coverage data!\n"); + if (defined($tmp_dir)) { + $dir = tempdir(DIR => $tmp_dir, CLEANUP => 1); + } else { + $dir = tempdir(CLEANUP => 1); } - # Do we have access to the modprobe tool? - stat($modprobe_tool); - if (!-x _) - { - die("ERROR: need modprobe tool ($modprobe_tool) to access ". - "kernel coverage data!\n"); + if (!defined($dir)) { + die("ERROR: cannot create temporary directory\n"); } + push(@temp_dirs, $dir); - # Try some possibilities of where the gcov kernel module may be found - foreach $module_name (@gcovmod) - { - # Try to load module from system wide module directory - # /lib/modules - if (system_no_output(3, $modprobe_tool, $module_name) == 0) - { - # Succeeded - $need_unload = $module_name; - return(); - } + return $dir; +} - # Try to load linux 2.5/2.6 module from tool directory - if (system_no_output(3, $insmod_tool, - "$tool_dir/$module_name.ko") == 0) - { - # Succeeded - $need_unload = $module_name; - return(); - } - # Try to load linux 2.4 module from tool directory - if (system_no_output(3, $insmod_tool, - "$tool_dir/$module_name.o") == 0) - { - # Succeeded - $need_unload = $module_name; - return(); - } - } +# +# br_taken_to_num(taken) +# +# Convert a branch taken value .info format to number format. +# - # Hm, loading failed - maybe we aren't root? - if ($> != 0) - { - die("ERROR: need root access to load kernel module!\n"); - } +sub br_taken_to_num($) +{ + my ($taken) = @_; - die("ERROR: cannot load required gcov kernel module!\n"); + return 0 if ($taken eq '-'); + return $taken + 1; } # -# unload_module() +# br_num_to_taken(taken) # -# Unload the gcov kernel module. +# Convert a branch taken value in number format to .info format. # -sub unload_module($) +sub br_num_to_taken($) { - my $module = $_[0]; + my ($taken) = @_; - info("Unloading kernel module $module\n"); + return '-' if ($taken == 0); + return $taken - 1; +} - # Do we have access to the rmmod tool? - stat($rmmod_tool); - if (!-x _) - { - warn("WARNING: cannot execute rmmod tool at $rmmod_tool - ". - "gcov module still loaded!\n"); + +# +# br_taken_add(taken1, taken2) +# +# Return the result of taken1 + taken2 for 'branch taken' values. +# + +sub br_taken_add($$) +{ + my ($t1, $t2) = @_; + + return $t1 if (!defined($t2)); + return $t2 if (!defined($t1)); + return $t1 if ($t2 eq '-'); + return $t2 if ($t1 eq '-'); + return $t1 + $t2; +} + + +# +# br_taken_sub(taken1, taken2) +# +# Return the result of taken1 - taken2 for 'branch taken' values. Return 0 +# if the result would become negative. +# + +sub br_taken_sub($$) +{ + my ($t1, $t2) = @_; + + return $t1 if (!defined($t2)); + return undef if (!defined($t1)); + return $t1 if ($t1 eq '-'); + return $t1 if ($t2 eq '-'); + return 0 if $t2 > $t1; + return $t1 - $t2; +} + + +# +# +# br_ivec_len(vector) +# +# Return the number of entries in the branch coverage vector. +# + +sub br_ivec_len($) +{ + my ($vec) = @_; + + return 0 if (!defined($vec)); + return (length($vec) * 8 / $BR_VEC_WIDTH) / $BR_VEC_ENTRIES; +} + + +# +# br_ivec_push(vector, block, branch, taken) +# +# Add an entry to the branch coverage vector. If an entry with the same +# branch ID already exists, add the corresponding taken values. +# + +sub br_ivec_push($$$$) +{ + my ($vec, $block, $branch, $taken) = @_; + my $offset; + my $num = br_ivec_len($vec); + my $i; + + $vec = "" if (!defined($vec)); + + # Check if branch already exists in vector + for ($i = 0; $i < $num; $i++) { + my ($v_block, $v_branch, $v_taken) = br_ivec_get($vec, $i); + + next if ($v_block != $block || $v_branch != $branch); + + # Add taken counts + $taken = br_taken_add($taken, $v_taken); + last; } - # Unload gcov kernel module - system_no_output(1, $rmmod_tool, $module) - and warn("WARNING: cannot unload gcov kernel module ". - "$module!\n"); + $offset = $i * $BR_VEC_ENTRIES; + $taken = br_taken_to_num($taken); + + # Add to vector + vec($vec, $offset + $BR_BLOCK, $BR_VEC_WIDTH) = $block; + vec($vec, $offset + $BR_BRANCH, $BR_VEC_WIDTH) = $branch; + vec($vec, $offset + $BR_TAKEN, $BR_VEC_WIDTH) = $taken; + + return $vec; } # -# create_temp_dir() +# br_ivec_get(vector, number) # -# Create a temporary directory and return its path. +# Return an entry from the branch coverage vector. # -# Die on error. + +sub br_ivec_get($$) +{ + my ($vec, $num) = @_; + my $block; + my $branch; + my $taken; + my $offset = $num * $BR_VEC_ENTRIES; + + # Retrieve data from vector + $block = vec($vec, $offset + $BR_BLOCK, $BR_VEC_WIDTH); + $branch = vec($vec, $offset + $BR_BRANCH, $BR_VEC_WIDTH); + $taken = vec($vec, $offset + $BR_TAKEN, $BR_VEC_WIDTH); + + # Decode taken value from an integer + $taken = br_num_to_taken($taken); + + return ($block, $branch, $taken); +} + + +# +# get_br_found_and_hit(brcount) +# +# Return (br_found, br_hit) for brcount # -sub create_temp_dir() +sub get_br_found_and_hit($) { - my $dirname; - my $number = sprintf("%d", rand(1000)); + my ($brcount) = @_; + my $line; + my $br_found = 0; + my $br_hit = 0; - # Endless loops are evil - while ($number++ < 1000) - { - $dirname = "$tmp_dir/$tmp_prefix$number"; - stat($dirname); - if (-e _) { next; } + foreach $line (keys(%{$brcount})) { + my $brdata = $brcount->{$line}; + my $i; + my $num = br_ivec_len($brdata); - mkdir($dirname) - or die("ERROR: cannot create temporary directory ". - "$dirname!\n"); + for ($i = 0; $i < $num; $i++) { + my $taken; - return($dirname); + (undef, undef, $taken) = br_ivec_get($brdata, $i); + + $br_found++; + $br_hit++ if ($taken ne "-" && $taken > 0); + } } - die("ERROR: cannot create temporary directory in $tmp_dir!\n"); + return ($br_found, $br_hit); } @@ -889,16 +1495,22 @@ sub create_temp_dir() # "check" -> \%checkdata # "testfnc" -> \%testfncdata # "sumfnc" -> \%sumfnccount +# "testbr" -> \%testbrdata +# "sumbr" -> \%sumbrcount # # %testdata : name of test affecting this file -> \%testcount # %testfncdata: name of test affecting this file -> \%testfnccount +# %testbrdata: name of test affecting this file -> \%testbrcount # # %testcount : line number -> execution count for a single test # %testfnccount: function name -> execution count for a single test +# %testbrcount : line number -> branch coverage data for a single test # %sumcount : line number -> execution count for all tests # %sumfnccount : function name -> execution count for all tests +# %sumbrcount : line number -> branch coverage data for all tests # %funcdata : function name -> line number # %checkdata : line number -> checksum of source code line +# $brdata : vector of items: block, branch, taken # # Note that .info file sections referring to the same file and test name # will automatically be combined by adding all execution counts. @@ -923,6 +1535,9 @@ sub read_info_file($) my $testfncdata; my $testfnccount; my $sumfnccount; + my $testbrdata; + my $testbrcount; + my $sumbrcount; my $line; # Current line read from .info file my $testname; # Current test name my $filename; # Current filename @@ -981,7 +1596,7 @@ sub read_info_file($) # Switch statement foreach ($line) { - /^TN:([^,]*)/ && do + /^TN:([^,]*)(,diff)?/ && do { # Test name information found $testname = defined($1) ? $1 : ""; @@ -989,6 +1604,7 @@ sub read_info_file($) { $changed_testname = 1; } + $testname .= $2 if (defined($2)); last; }; @@ -1000,18 +1616,21 @@ sub read_info_file($) $data = $result{$filename}; ($testdata, $sumcount, $funcdata, $checkdata, - $testfncdata, $sumfnccount) = + $testfncdata, $sumfnccount, $testbrdata, + $sumbrcount) = get_info_entry($data); if (defined($testname)) { $testcount = $testdata->{$testname}; $testfnccount = $testfncdata->{$testname}; + $testbrcount = $testbrdata->{$testname}; } else { $testcount = {}; $testfnccount = {}; + $testbrcount = {}; } last; }; @@ -1084,6 +1703,27 @@ sub read_info_file($) } last; }; + + /^BRDA:(\d+),(\d+),(\d+),(\d+|-)/ && do { + # Branch coverage data found + my ($line, $block, $branch, $taken) = + ($1, $2, $3, $4); + + $sumbrcount->{$line} = + br_ivec_push($sumbrcount->{$line}, + $block, $branch, $taken); + + # Add test-specific counts + if (defined($testname)) { + $testbrcount->{$line} = + br_ivec_push( + $testbrcount->{$line}, + $block, $branch, + $taken); + } + last; + }; + /^end_of_record/ && do { # Found end of section marker @@ -1096,12 +1736,16 @@ sub read_info_file($) $testcount; $testfncdata->{$testname} = $testfnccount; + $testbrdata->{$testname} = + $testbrcount; } set_info_entry($data, $testdata, $sumcount, $funcdata, $checkdata, $testfncdata, - $sumfnccount); + $sumfnccount, + $testbrdata, + $sumbrcount); $result{$filename} = $data; last; } @@ -1119,7 +1763,8 @@ sub read_info_file($) $data = $result{$filename}; ($testdata, $sumcount, undef, undef, $testfncdata, - $sumfnccount) = get_info_entry($data); + $sumfnccount, $testbrdata, $sumbrcount) = + get_info_entry($data); # Filter out empty files if (scalar(keys(%{$sumcount})) == 0) @@ -1158,6 +1803,14 @@ sub read_info_file($) } } $data->{"f_hit"} = $hitcount; + + # Get found/hit values for branch data + { + my ($br_found, $br_hit) = get_br_found_and_hit($sumbrcount); + + $data->{"b_found"} = $br_found; + $data->{"b_hit"} = $br_hit; + } } if (scalar(keys(%result)) == 0) @@ -1185,8 +1838,9 @@ sub read_info_file($) # 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) +# ref, testfncdata hash ref, sumfnccount hash ref, testbrdata hash ref, +# sumbrcount hash ref, lines found, lines hit, functions found, +# functions hit, branches found, branches hit) # sub get_info_entry($) @@ -1197,26 +1851,32 @@ sub get_info_entry($) my $checkdata_ref = $_[0]->{"check"}; my $testfncdata = $_[0]->{"testfnc"}; my $sumfnccount = $_[0]->{"sumfnc"}; + my $testbrdata = $_[0]->{"testbr"}; + my $sumbrcount = $_[0]->{"sumbr"}; my $lines_found = $_[0]->{"found"}; my $lines_hit = $_[0]->{"hit"}; my $f_found = $_[0]->{"f_found"}; my $f_hit = $_[0]->{"f_hit"}; + my $br_found = $_[0]->{"b_found"}; + my $br_hit = $_[0]->{"b_hit"}; return ($testdata_ref, $sumcount_ref, $funcdata_ref, $checkdata_ref, - $testfncdata, $sumfnccount, $lines_found, $lines_hit, - $f_found, $f_hit); + $testfncdata, $sumfnccount, $testbrdata, $sumbrcount, + $lines_found, $lines_hit, $f_found, $f_hit, + $br_found, $br_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]) +# checkdata_ref, testfncdata_ref, sumfcncount_ref, +# testbrdata_ref, sumbrcount_ref[,lines_found, +# lines_hit, f_found, f_hit, $b_found, $b_hit]) # # Update the hash referenced by HASH_REF with the provided data references. # -sub set_info_entry($$$$$$$;$$$$) +sub set_info_entry($$$$$$$$$;$$$$$$) { my $data_ref = $_[0]; @@ -1226,11 +1886,15 @@ sub set_info_entry($$$$$$$;$$$$) $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]; } + $data_ref->{"testbr"} = $_[7]; + $data_ref->{"sumbr"} = $_[8]; + + if (defined($_[9])) { $data_ref->{"found"} = $_[9]; } + if (defined($_[10])) { $data_ref->{"hit"} = $_[10]; } + if (defined($_[11])) { $data_ref->{"f_found"} = $_[11]; } + if (defined($_[12])) { $data_ref->{"f_hit"} = $_[12]; } + if (defined($_[13])) { $data_ref->{"b_found"} = $_[13]; } + if (defined($_[14])) { $data_ref->{"b_hit"} = $_[14]; } } @@ -1338,7 +2002,9 @@ sub merge_func_data($$$) my %result; my $func; - %result = %{$funcdata1}; + if (defined($funcdata1)) { + %result = %{$funcdata1}; + } foreach $func (keys(%{$funcdata2})) { my $line1 = $result{$func}; @@ -1370,7 +2036,9 @@ sub add_fnccount($$) my $f_hit; my $function; - %result = %{$fnccount1}; + if (defined($fnccount1)) { + %result = %{$fnccount1}; + } foreach $function (keys(%{$fnccount2})) { $result{$function} += $fnccount2->{$function}; } @@ -1424,6 +2092,167 @@ sub add_testfncdata($$) return \%result; } + +# +# brcount_to_db(brcount) +# +# Convert brcount data to the following format: +# +# db: line number -> block hash +# block hash: block number -> branch hash +# branch hash: branch number -> taken value +# + +sub brcount_to_db($) +{ + my ($brcount) = @_; + my $line; + my $db; + + # Add branches from first count to database + foreach $line (keys(%{$brcount})) { + my $brdata = $brcount->{$line}; + my $i; + my $num = br_ivec_len($brdata); + + for ($i = 0; $i < $num; $i++) { + my ($block, $branch, $taken) = br_ivec_get($brdata, $i); + + $db->{$line}->{$block}->{$branch} = $taken; + } + } + + return $db; +} + + +# +# db_to_brcount(db) +# +# Convert branch coverage data back to brcount format. +# + +sub db_to_brcount($) +{ + my ($db) = @_; + my $line; + my $brcount = {}; + my $br_found = 0; + my $br_hit = 0; + + # Convert database back to brcount format + foreach $line (sort({$a <=> $b} keys(%{$db}))) { + my $ldata = $db->{$line}; + my $brdata; + my $block; + + foreach $block (sort({$a <=> $b} keys(%{$ldata}))) { + my $bdata = $ldata->{$block}; + my $branch; + + foreach $branch (sort({$a <=> $b} keys(%{$bdata}))) { + my $taken = $bdata->{$branch}; + + $br_found++; + $br_hit++ if ($taken ne "-" && $taken > 0); + $brdata = br_ivec_push($brdata, $block, + $branch, $taken); + } + } + $brcount->{$line} = $brdata; + } + + return ($brcount, $br_found, $br_hit); +} + + +# combine_brcount(brcount1, brcount2, type) +# +# If add is BR_ADD, add branch coverage data and return list (brcount_added, +# br_found, br_hit). If add is BR_SUB, subtract the taken values of brcount2 +# from brcount1 and return (brcount_sub, br_found, br_hit). +# + +sub combine_brcount($$$) +{ + my ($brcount1, $brcount2, $type) = @_; + my $line; + my $block; + my $branch; + my $taken; + my $db; + my $br_found = 0; + my $br_hit = 0; + my $result; + + # Convert branches from first count to database + $db = brcount_to_db($brcount1); + # Combine values from database and second count + foreach $line (keys(%{$brcount2})) { + my $brdata = $brcount2->{$line}; + my $num = br_ivec_len($brdata); + my $i; + + for ($i = 0; $i < $num; $i++) { + ($block, $branch, $taken) = br_ivec_get($brdata, $i); + my $new_taken = $db->{$line}->{$block}->{$branch}; + + if ($type == $BR_ADD) { + $new_taken = br_taken_add($new_taken, $taken); + } elsif ($type == $BR_SUB) { + $new_taken = br_taken_sub($new_taken, $taken); + } + $db->{$line}->{$block}->{$branch} = $new_taken + if (defined($new_taken)); + } + } + # Convert database back to brcount format + ($result, $br_found, $br_hit) = db_to_brcount($db); + + return ($result, $br_found, $br_hit); +} + + +# +# add_testbrdata(testbrdata1, testbrdata2) +# +# Add branch coverage data for several tests. Return reference to +# added_testbrdata. +# + +sub add_testbrdata($$) +{ + my ($testbrdata1, $testbrdata2) = @_; + my %result; + my $testname; + + foreach $testname (keys(%{$testbrdata1})) { + if (defined($testbrdata2->{$testname})) { + my $brcount; + + # Branch coverage data for this testname exists + # in both data sets: add + ($brcount) = combine_brcount( + $testbrdata1->{$testname}, + $testbrdata2->{$testname}, $BR_ADD); + $result{$testname} = $brcount; + next; + } + # Branch coverage data for this testname is unique to + # data set 1: copy + $result{$testname} = $testbrdata1->{$testname}; + } + + # Add count data for testnames unique to data set 2 + foreach $testname (keys(%{$testbrdata2})) { + if (!defined($result{$testname})) { + $result{$testname} = $testbrdata2->{$testname}; + } + } + return \%result; +} + + # # combine_info_entries(entry_ref1, entry_ref2, filename) # @@ -1440,6 +2269,8 @@ sub combine_info_entries($$$) my $checkdata1; my $testfncdata1; my $sumfnccount1; + my $testbrdata1; + my $sumbrcount1; my $entry2 = $_[1]; # Reference to hash containing second entry my $testdata2; @@ -1448,6 +2279,8 @@ sub combine_info_entries($$$) my $checkdata2; my $testfncdata2; my $sumfnccount2; + my $testbrdata2; + my $sumbrcount2; my %result; # Hash containing combined entry my %result_testdata; @@ -1455,19 +2288,23 @@ sub combine_info_entries($$$) my $result_funcdata; my $result_testfncdata; my $result_sumfnccount; + my $result_testbrdata; + my $result_sumbrcount; my $lines_found; my $lines_hit; my $f_found; my $f_hit; + my $br_found; + my $br_hit; my $testname; my $filename = $_[2]; # Retrieve data ($testdata1, $sumcount1, $funcdata1, $checkdata1, $testfncdata1, - $sumfnccount1) = get_info_entry($entry1); + $sumfnccount1, $testbrdata1, $sumbrcount1) = get_info_entry($entry1); ($testdata2, $sumcount2, $funcdata2, $checkdata2, $testfncdata2, - $sumfnccount2) = get_info_entry($entry2); + $sumfnccount2, $testbrdata2, $sumbrcount2) = get_info_entry($entry2); # Merge checksums $checkdata1 = merge_checksums($checkdata1, $checkdata2, $filename); @@ -1479,7 +2316,12 @@ sub combine_info_entries($$$) $result_testfncdata = add_testfncdata($testfncdata1, $testfncdata2); ($result_sumfnccount, $f_found, $f_hit) = add_fnccount($sumfnccount1, $sumfnccount2); - + + # Combine branch coverage data + $result_testbrdata = add_testbrdata($testbrdata1, $testbrdata2); + ($result_sumbrcount, $br_found, $br_hit) = + combine_brcount($sumbrcount1, $sumbrcount2, $BR_ADD); + # Combine testdata foreach $testname (keys(%{$testdata1})) { @@ -1522,8 +2364,9 @@ sub combine_info_entries($$$) # Store result set_info_entry(\%result, \%result_testdata, $result_sumcount, $result_funcdata, $checkdata1, $result_testfncdata, - $result_sumfnccount, $lines_found, $lines_hit, - $f_found, $f_hit); + $result_sumfnccount, $result_testbrdata, + $result_sumbrcount, $lines_found, $lines_hit, + $f_found, $f_hit, $br_found, $br_hit); return(\%result); } @@ -1573,6 +2416,7 @@ sub add_traces() my $total_trace; my $current_trace; my $tracefile; + my @result; local *INFO_HANDLE; info("Combining tracefiles.\n"); @@ -1597,13 +2441,15 @@ sub add_traces() info("Writing data to $output_filename\n"); open(INFO_HANDLE, ">$output_filename") or die("ERROR: cannot write to $output_filename!\n"); - write_info_file(*INFO_HANDLE, $total_trace); + @result = write_info_file(*INFO_HANDLE, $total_trace); close(*INFO_HANDLE); } else { - write_info_file(*STDOUT, $total_trace); + @result = write_info_file(*STDOUT, $total_trace); } + + return @result; } @@ -1623,25 +2469,48 @@ sub write_info_file(*$) my $checkdata; my $testfncdata; my $sumfnccount; + my $testbrdata; + my $sumbrcount; my $testname; my $line; my $func; my $testcount; my $testfnccount; + my $testbrcount; my $found; my $hit; my $f_found; my $f_hit; - - foreach $source_file (keys(%data)) + my $br_found; + my $br_hit; + my $ln_total_found = 0; + my $ln_total_hit = 0; + my $fn_total_found = 0; + my $fn_total_hit = 0; + my $br_total_found = 0; + my $br_total_hit = 0; + + foreach $source_file (sort(keys(%data))) { $entry = $data{$source_file}; ($testdata, $sumcount, $funcdata, $checkdata, $testfncdata, - $sumfnccount) = get_info_entry($entry); - foreach $testname (keys(%{$testdata})) + $sumfnccount, $testbrdata, $sumbrcount, $found, $hit, + $f_found, $f_hit, $br_found, $br_hit) = + get_info_entry($entry); + + # Add to totals + $ln_total_found += $found; + $ln_total_hit += $hit; + $fn_total_found += $f_found; + $fn_total_hit += $f_hit; + $br_total_found += $br_found; + $br_total_hit += $br_hit; + + foreach $testname (sort(keys(%{$testdata}))) { $testcount = $testdata->{$testname}; $testfnccount = $testfncdata->{$testname}; + $testbrcount = $testbrdata->{$testname}; $found = 0; $hit = 0; @@ -1666,6 +2535,31 @@ sub write_info_file(*$) print(INFO_HANDLE "FNF:$f_found\n"); print(INFO_HANDLE "FNH:$f_hit\n"); + # Write branch related data + $br_found = 0; + $br_hit = 0; + foreach $line (sort({$a <=> $b} + keys(%{$testbrcount}))) { + my $brdata = $testbrcount->{$line}; + my $num = br_ivec_len($brdata); + my $i; + + for ($i = 0; $i < $num; $i++) { + my ($block, $branch, $taken) = + br_ivec_get($brdata, $i); + + print(INFO_HANDLE "BRDA:$line,$block,". + "$branch,$taken\n"); + $br_found++; + $br_hit++ if ($taken ne '-' && + $taken > 0); + } + } + if ($br_found > 0) { + print(INFO_HANDLE "BRF:$br_found\n"); + print(INFO_HANDLE "BRH:$br_hit\n"); + } + # Write line related data foreach $line (sort({$a <=> $b} keys(%{$testcount}))) { @@ -1686,6 +2580,9 @@ sub write_info_file(*$) print(INFO_HANDLE "end_of_record\n"); } } + + return ($ln_total_found, $ln_total_hit, $fn_total_found, $fn_total_hit, + $br_total_found, $br_total_hit); } @@ -1739,6 +2636,7 @@ sub extract() my $pattern; my @pattern_list; my $extracted = 0; + my @result; local *INFO_HANDLE; # Need perlreg expressions instead of shell pattern @@ -1773,13 +2671,15 @@ sub extract() info("Writing data to $output_filename\n"); open(INFO_HANDLE, ">$output_filename") or die("ERROR: cannot write to $output_filename!\n"); - write_info_file(*INFO_HANDLE, $data); + @result = write_info_file(*INFO_HANDLE, $data); close(*INFO_HANDLE); } else { - write_info_file(*STDOUT, $data); + @result = write_info_file(*STDOUT, $data); } + + return @result; } @@ -1795,6 +2695,7 @@ sub remove() my $pattern; my @pattern_list; my $removed = 0; + my @result; local *INFO_HANDLE; # Need perlreg expressions instead of shell pattern @@ -1826,16 +2727,126 @@ sub remove() info("Writing data to $output_filename\n"); open(INFO_HANDLE, ">$output_filename") or die("ERROR: cannot write to $output_filename!\n"); - write_info_file(*INFO_HANDLE, $data); + @result = write_info_file(*INFO_HANDLE, $data); close(*INFO_HANDLE); } else { - write_info_file(*STDOUT, $data); + @result = write_info_file(*STDOUT, $data); } + + return @result; +} + + +# get_prefix(max_width, max_percentage_too_long, path_list) +# +# Return a path prefix that satisfies the following requirements: +# - is shared by more paths in path_list than any other prefix +# - the percentage of paths which would exceed the given max_width length +# after applying the prefix does not exceed max_percentage_too_long +# +# If multiple prefixes satisfy all requirements, the longest prefix is +# returned. Return an empty string if no prefix could be found. + +sub get_prefix($$@) +{ + my ($max_width, $max_long, @path_list) = @_; + my $path; + my $ENTRY_NUM = 0; + my $ENTRY_LONG = 1; + my %prefix; + + # Build prefix hash + foreach $path (@path_list) { + my ($v, $d, $f) = splitpath($path); + my @dirs = splitdir($d); + my $p_len = length($path); + my $i; + + # Remove trailing '/' + pop(@dirs) if ($dirs[scalar(@dirs) - 1] eq ''); + for ($i = 0; $i < scalar(@dirs); $i++) { + my $subpath = catpath($v, catdir(@dirs[0..$i]), ''); + my $entry = $prefix{$subpath}; + + $entry = [ 0, 0 ] if (!defined($entry)); + $entry->[$ENTRY_NUM]++; + if (($p_len - length($subpath) - 1) > $max_width) { + $entry->[$ENTRY_LONG]++; + } + $prefix{$subpath} = $entry; + } + } + # Find suitable prefix (sort descending by two keys: 1. number of + # entries covered by a prefix, 2. length of prefix) + foreach $path (sort {($prefix{$a}->[$ENTRY_NUM] == + $prefix{$b}->[$ENTRY_NUM]) ? + length($b) <=> length($a) : + $prefix{$b}->[$ENTRY_NUM] <=> + $prefix{$a}->[$ENTRY_NUM]} + keys(%prefix)) { + my ($num, $long) = @{$prefix{$path}}; + + # Check for additional requirement: number of filenames + # that would be too long may not exceed a certain percentage + if ($long <= $num * $max_long / 100) { + return $path; + } + } + + return ""; +} + + +# +# shorten_filename(filename, width) +# +# Truncate filename if it is longer than width characters. +# + +sub shorten_filename($$) +{ + my ($filename, $width) = @_; + my $l = length($filename); + my $s; + my $e; + + return $filename if ($l <= $width); + $e = int(($width - 3) / 2); + $s = $width - 3 - $e; + + return substr($filename, 0, $s).'...'.substr($filename, $l - $e); } +sub shorten_number($$) +{ + my ($number, $width) = @_; + my $result = sprintf("%*d", $width, $number); + + return $result if (length($result) <= $width); + $number = $number / 1000; + return $result if (length($result) <= $width); + $result = sprintf("%*dk", $width - 1, $number); + return $result if (length($result) <= $width); + $number = $number / 1000; + $result = sprintf("%*dM", $width - 1, $number); + return $result if (length($result) <= $width); + return '#'; +} + +sub shorten_rate($$) +{ + my ($rate, $width) = @_; + my $result = sprintf("%*.1f%%", $width - 3, $rate); + + return $result if (length($result) <= $width); + $result = sprintf("%*d%%", $width - 1, $rate); + return $result if (length($result) <= $width); + return "#"; +} + # # list() # @@ -1847,17 +2858,278 @@ sub list() my $found; my $hit; my $entry; - - info("Listing contents of $list:\n"); - - # List all files + my $fn_found; + my $fn_hit; + my $br_found; + my $br_hit; + my $total_found = 0; + my $total_hit = 0; + my $fn_total_found = 0; + my $fn_total_hit = 0; + my $br_total_found = 0; + my $br_total_hit = 0; + my $prefix; + my $strlen = length("Filename"); + my $format; + my $heading1; + my $heading2; + my @footer; + my $barlen; + my $rate; + my $fnrate; + my $brrate; + my $lastpath; + my $F_LN_NUM = 0; + my $F_LN_RATE = 1; + my $F_FN_NUM = 2; + my $F_FN_RATE = 3; + my $F_BR_NUM = 4; + my $F_BR_RATE = 5; + my @fwidth_narrow = (5, 5, 3, 5, 4, 5); + my @fwidth_wide = (6, 5, 5, 5, 6, 5); + my @fwidth = @fwidth_wide; + my $w; + my $max_width = $opt_list_width; + my $max_long = $opt_list_truncate_max; + my $fwidth_narrow_length; + my $fwidth_wide_length; + my $got_prefix = 0; + my $root_prefix = 0; + + # Calculate total width of narrow fields + $fwidth_narrow_length = 0; + foreach $w (@fwidth_narrow) { + $fwidth_narrow_length += $w + 1; + } + # Calculate total width of wide fields + $fwidth_wide_length = 0; + foreach $w (@fwidth_wide) { + $fwidth_wide_length += $w + 1; + } + # Get common file path prefix + $prefix = get_prefix($max_width - $fwidth_narrow_length, $max_long, + keys(%{$data})); + $root_prefix = 1 if ($prefix eq rootdir()); + $got_prefix = 1 if (length($prefix) > 0); + $prefix =~ s/\/$//; + # Get longest filename length + foreach $filename (keys(%{$data})) { + if (!$opt_list_full_path) { + if (!$got_prefix || !$root_prefix && + !($filename =~ s/^\Q$prefix\/\E//)) { + my ($v, $d, $f) = splitpath($filename); + + $filename = $f; + } + } + # Determine maximum length of entries + if (length($filename) > $strlen) { + $strlen = length($filename) + } + } + if (!$opt_list_full_path) { + my $blanks; + + $w = $fwidth_wide_length; + # Check if all columns fit into max_width characters + if ($strlen + $fwidth_wide_length > $max_width) { + # Use narrow fields + @fwidth = @fwidth_narrow; + $w = $fwidth_narrow_length; + if (($strlen + $fwidth_narrow_length) > $max_width) { + # Truncate filenames at max width + $strlen = $max_width - $fwidth_narrow_length; + } + } + # Add some blanks between filename and fields if possible + $blanks = int($strlen * 0.5); + $blanks = 4 if ($blanks < 4); + $blanks = 8 if ($blanks > 8); + if (($strlen + $w + $blanks) < $max_width) { + $strlen += $blanks; + } else { + $strlen = $max_width - $w; + } + } + # Filename + $w = $strlen; + $format = "%-${w}s|"; + $heading1 = sprintf("%*s|", $w, ""); + $heading2 = sprintf("%-*s|", $w, "Filename"); + $barlen = $w + 1; + # Line coverage rate + $w = $fwidth[$F_LN_RATE]; + $format .= "%${w}s "; + $heading1 .= sprintf("%-*s |", $w + $fwidth[$F_LN_NUM], + "Lines"); + $heading2 .= sprintf("%-*s ", $w, "Rate"); + $barlen += $w + 1; + # Number of lines + $w = $fwidth[$F_LN_NUM]; + $format .= "%${w}s|"; + $heading2 .= sprintf("%*s|", $w, "Num"); + $barlen += $w + 1; + # Function coverage rate + $w = $fwidth[$F_FN_RATE]; + $format .= "%${w}s "; + $heading1 .= sprintf("%-*s|", $w + $fwidth[$F_FN_NUM] + 1, + "Functions"); + $heading2 .= sprintf("%-*s ", $w, "Rate"); + $barlen += $w + 1; + # Number of functions + $w = $fwidth[$F_FN_NUM]; + $format .= "%${w}s|"; + $heading2 .= sprintf("%*s|", $w, "Num"); + $barlen += $w + 1; + # Branch coverage rate + $w = $fwidth[$F_BR_RATE]; + $format .= "%${w}s "; + $heading1 .= sprintf("%-*s", $w + $fwidth[$F_BR_NUM] + 1, + "Branches"); + $heading2 .= sprintf("%-*s ", $w, "Rate"); + $barlen += $w + 1; + # Number of branches + $w = $fwidth[$F_BR_NUM]; + $format .= "%${w}s"; + $heading2 .= sprintf("%*s", $w, "Num"); + $barlen += $w; + # Line end + $format .= "\n"; + $heading1 .= "\n"; + $heading2 .= "\n"; + + # Print heading + print($heading1); + print($heading2); + print(("="x$barlen)."\n"); + + # Print per file information foreach $filename (sort(keys(%{$data}))) { + my @file_data; + my $print_filename = $filename; + $entry = $data->{$filename}; - (undef, undef, undef, undef, undef, undef, $found, $hit) = + if (!$opt_list_full_path) { + my $p; + + $print_filename = $filename; + if (!$got_prefix || !$root_prefix && + !($print_filename =~ s/^\Q$prefix\/\E//)) { + my ($v, $d, $f) = splitpath($filename); + + $p = catpath($v, $d, ""); + $p =~ s/\/$//; + $print_filename = $f; + } else { + $p = $prefix; + } + + if (!defined($lastpath) || $lastpath ne $p) { + print("\n") if (defined($lastpath)); + $lastpath = $p; + print("[$lastpath/]\n") if (!$root_prefix); + } + $print_filename = shorten_filename($print_filename, + $strlen); + } + + (undef, undef, undef, undef, undef, undef, undef, undef, + $found, $hit, $fn_found, $fn_hit, $br_found, $br_hit) = get_info_entry($entry); - printf("$filename: $hit of $found lines hit\n"); + + # Assume zero count if there is no function data for this file + if (!defined($fn_found) || !defined($fn_hit)) { + $fn_found = 0; + $fn_hit = 0; + } + # Assume zero count if there is no branch data for this file + if (!defined($br_found) || !defined($br_hit)) { + $br_found = 0; + $br_hit = 0; + } + + # Add line coverage totals + $total_found += $found; + $total_hit += $hit; + # Add function coverage totals + $fn_total_found += $fn_found; + $fn_total_hit += $fn_hit; + # Add branch coverage totals + $br_total_found += $br_found; + $br_total_hit += $br_hit; + + # Determine line coverage rate for this file + if ($found == 0) { + $rate = "-"; + } else { + $rate = shorten_rate(100 * $hit / $found, + $fwidth[$F_LN_RATE]); + } + # Determine function coverage rate for this file + if (!defined($fn_found) || $fn_found == 0) { + $fnrate = "-"; + } else { + $fnrate = shorten_rate(100 * $fn_hit / $fn_found, + $fwidth[$F_FN_RATE]); + } + # Determine branch coverage rate for this file + if (!defined($br_found) || $br_found == 0) { + $brrate = "-"; + } else { + $brrate = shorten_rate(100 * $br_hit / $br_found, + $fwidth[$F_BR_RATE]); + } + + # Assemble line parameters + push(@file_data, $print_filename); + push(@file_data, $rate); + push(@file_data, shorten_number($found, $fwidth[$F_LN_NUM])); + push(@file_data, $fnrate); + push(@file_data, shorten_number($fn_found, $fwidth[$F_FN_NUM])); + push(@file_data, $brrate); + push(@file_data, shorten_number($br_found, $fwidth[$F_BR_NUM])); + + # Print assembled line + printf($format, @file_data); } + + # Determine total line coverage rate + if ($total_found == 0) { + $rate = "-"; + } else { + $rate = shorten_rate(100 * $total_hit / $total_found, + $fwidth[$F_LN_RATE]); + } + # Determine total function coverage rate + if ($fn_total_found == 0) { + $fnrate = "-"; + } else { + $fnrate = shorten_rate(100 * $fn_total_hit / $fn_total_found, + $fwidth[$F_FN_RATE]); + } + # Determine total branch coverage rate + if ($br_total_found == 0) { + $brrate = "-"; + } else { + $brrate = shorten_rate(100 * $br_total_hit / $br_total_found, + $fwidth[$F_BR_RATE]); + } + + # Print separator + print(("="x$barlen)."\n"); + + # Assemble line parameters + push(@footer, sprintf("%*s", $strlen, "Total:")); + push(@footer, $rate); + push(@footer, shorten_number($total_found, $fwidth[$F_LN_NUM])); + push(@footer, $fnrate); + push(@footer, shorten_number($fn_total_found, $fwidth[$F_FN_NUM])); + push(@footer, $brrate); + push(@footer, shorten_number($br_total_found, $fwidth[$F_BR_NUM])); + + # Print assembled line + printf($format, @footer); } @@ -2155,6 +3427,28 @@ sub apply_diff($$) # +# apply_diff_to_brcount(brcount, linedata) +# +# Adjust line numbers of branch coverage data according to linedata. +# + +sub apply_diff_to_brcount($$) +{ + my ($brcount, $linedata) = @_; + my $db; + + # Convert brcount to db format + $db = brcount_to_db($brcount); + # Apply diff to db format + $db = apply_diff($db, $linedata); + # Convert db format back to brcount format + ($brcount) = db_to_brcount($db); + + return $brcount; +} + + +# # get_hash_max(hash_ref) # # Return the highest integer key from hash. @@ -2235,10 +3529,16 @@ sub get_line_hash($$$) my $old_depth; my $new_depth; + # Remove trailing slash from diff path + $diff_path =~ s/\/$//; foreach (keys(%{$diff_data})) { + my $sep = ""; + + $sep = '/' if (!/^\//); + # Try to match diff filename with filename - if ($filename =~ /^\Q$diff_path\E\/$_$/) + if ($filename =~ /^\Q$diff_path$sep$_\E$/) { if ($diff_name) { @@ -2432,12 +3732,17 @@ sub diff() my $checkdata; my $testfncdata; my $sumfnccount; + my $testbrdata; + my $sumbrcount; my $found; my $hit; my $f_found; my $f_hit; + my $br_found; + my $br_hit; my $converted = 0; my $unchanged = 0; + my @result; local *INFO_HANDLE; ($diff_data, $path_data) = read_diff($ARGV[0]); @@ -2468,17 +3773,24 @@ sub diff() info("Converting $filename\n"); $entry = $trace_data->{$filename}; ($testdata, $sumcount, $funcdata, $checkdata, $testfncdata, - $sumfnccount) = get_info_entry($entry); + $sumfnccount, $testbrdata, $sumbrcount) = + get_info_entry($entry); # Convert test data foreach $testname (keys(%{$testdata})) { + # Adjust line numbers of line coverage data $testdata->{$testname} = apply_diff($testdata->{$testname}, $line_hash); + # Adjust line numbers of branch coverage data + $testbrdata->{$testname} = + apply_diff_to_brcount($testbrdata->{$testname}, + $line_hash); # Remove empty sets of test data if (scalar(keys(%{$testdata->{$testname}})) == 0) { delete($testdata->{$testname}); delete($testfncdata->{$testname}); + delete($testbrdata->{$testname}); } } # Rename test data to indicate conversion @@ -2502,6 +3814,12 @@ sub diff() $testfncdata->{$testname}, $testfncdata->{$testname.",diff"}); delete($testfncdata->{$testname.",diff"}); + # Add branch counts + ($testbrdata->{$testname}) = combine_brcount( + $testbrdata->{$testname}, + $testbrdata->{$testname.",diff"}, + $BR_ADD); + delete($testbrdata->{$testname.",diff"}); } # Move test data to new testname $testdata->{$testname.",diff"} = $testdata->{$testname}; @@ -2510,16 +3828,24 @@ sub diff() $testfncdata->{$testname.",diff"} = $testfncdata->{$testname}; delete($testfncdata->{$testname}); + # Move branch count data to new testname + $testbrdata->{$testname.",diff"} = + $testbrdata->{$testname}; + delete($testbrdata->{$testname}); } # Convert summary of test data $sumcount = apply_diff($sumcount, $line_hash); # Convert function data $funcdata = apply_diff_to_funcdata($funcdata, $line_hash); + # Convert branch coverage data + $sumbrcount = apply_diff_to_brcount($sumbrcount, $line_hash); + # Update found/hit numbers # Convert checksum data $checkdata = apply_diff($checkdata, $line_hash); # Convert function call count data adjust_fncdata($funcdata, $testfncdata, $sumfnccount); ($f_found, $f_hit) = get_func_found_and_hit($sumfnccount); + ($br_found, $br_hit) = get_br_found_and_hit($sumbrcount); # Update found/hit numbers $found = 0; $hit = 0; @@ -2536,7 +3862,8 @@ sub diff() # Store converted entry set_info_entry($entry, $testdata, $sumcount, $funcdata, $checkdata, $testfncdata, $sumfnccount, - $found, $hit, $f_found, $f_hit); + $testbrdata, $sumbrcount, $found, $hit, + $f_found, $f_hit, $br_found, $br_hit); } else { @@ -2561,13 +3888,15 @@ sub diff() info("Writing data to $output_filename\n"); open(INFO_HANDLE, ">$output_filename") or die("ERROR: cannot write to $output_filename!\n"); - write_info_file(*INFO_HANDLE, $trace_data); + @result = write_info_file(*INFO_HANDLE, $trace_data); close(*INFO_HANDLE); } else { - write_info_file(*STDOUT, $trace_data); + @result = write_info_file(*STDOUT, $trace_data); } + + return @result; } @@ -2688,6 +4017,7 @@ sub warn_handler($) { my ($msg) = @_; + temp_cleanup(); warn("$tool_name: $msg"); } @@ -2695,5 +4025,151 @@ sub die_handler($) { my ($msg) = @_; + temp_cleanup(); die("$tool_name: $msg"); } + +sub abort_handler($) +{ + temp_cleanup(); + exit(1); +} + +sub temp_cleanup() +{ + if (@temp_dirs) { + info("Removing temporary directories.\n"); + foreach (@temp_dirs) { + rmtree($_); + } + @temp_dirs = (); + } +} + +sub setup_gkv_sys() +{ + system_no_output(3, "mount", "-t", "debugfs", "nodev", + "/sys/kernel/debug"); +} + +sub setup_gkv_proc() +{ + if (system_no_output(3, "modprobe", "gcov_proc")) { + system_no_output(3, "modprobe", "gcov_prof"); + } +} + +sub check_gkv_sys($) +{ + my ($dir) = @_; + + if (-e "$dir/reset") { + return 1; + } + return 0; +} + +sub check_gkv_proc($) +{ + my ($dir) = @_; + + if (-e "$dir/vmlinux") { + return 1; + } + return 0; +} + +sub setup_gkv() +{ + my $dir; + my $sys_dir = "/sys/kernel/debug/gcov"; + my $proc_dir = "/proc/gcov"; + my @todo; + + if (!defined($gcov_dir)) { + info("Auto-detecting gcov kernel support.\n"); + @todo = ( "cs", "cp", "ss", "cs", "sp", "cp" ); + } elsif ($gcov_dir =~ /proc/) { + info("Checking gcov kernel support at $gcov_dir ". + "(user-specified).\n"); + @todo = ( "cp", "sp", "cp", "cs", "ss", "cs"); + } else { + info("Checking gcov kernel support at $gcov_dir ". + "(user-specified).\n"); + @todo = ( "cs", "ss", "cs", "cp", "sp", "cp", ); + } + foreach (@todo) { + if ($_ eq "cs") { + # Check /sys + $dir = defined($gcov_dir) ? $gcov_dir : $sys_dir; + if (check_gkv_sys($dir)) { + info("Found ".$GKV_NAME[$GKV_SYS]." gcov ". + "kernel support at $dir\n"); + return ($GKV_SYS, $dir); + } + } elsif ($_ eq "cp") { + # Check /proc + $dir = defined($gcov_dir) ? $gcov_dir : $proc_dir; + if (check_gkv_proc($dir)) { + info("Found ".$GKV_NAME[$GKV_PROC]." gcov ". + "kernel support at $dir\n"); + return ($GKV_PROC, $dir); + } + } elsif ($_ eq "ss") { + # Setup /sys + setup_gkv_sys(); + } elsif ($_ eq "sp") { + # Setup /proc + setup_gkv_proc(); + } + } + if (defined($gcov_dir)) { + die("ERROR: could not find gcov kernel data at $gcov_dir\n"); + } else { + die("ERROR: no gcov kernel data found\n"); + } +} + + +# +# get_overall_line(found, hit, name_singular, name_plural) +# +# Return a string containing overall information for the specified +# found/hit data. +# + +sub get_overall_line($$$$) +{ + my ($found, $hit, $name_sn, $name_pl) = @_; + my $name; + + return "no data found" if (!defined($found) || $found == 0); + $name = ($found == 1) ? $name_sn : $name_pl; + return sprintf("%.1f%% (%d of %d %s)", $hit * 100 / $found, $hit, + $found, $name); +} + + +# +# print_overall_rate(ln_do, ln_found, ln_hit, fn_do, fn_found, fn_hit, br_do +# br_found, br_hit) +# +# Print overall coverage rates for the specified coverage types. +# + +sub print_overall_rate($$$$$$$$$) +{ + my ($ln_do, $ln_found, $ln_hit, $fn_do, $fn_found, $fn_hit, + $br_do, $br_found, $br_hit) = @_; + + info("Overall coverage rate:\n"); + info(" lines......: %s\n", + get_overall_line($ln_found, $ln_hit, "line", "lines")) + if ($ln_do); + info(" functions..: %s\n", + get_overall_line($fn_found, $fn_hit, "function", "functions")) + if ($fn_do); + info(" branches...: %s\n", + get_overall_line($br_found, $br_hit, "branch", "branches")) + if ($br_do); +} |