diff options
| -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 | ||||
| -rwxr-xr-x | BuildTools/Coverage/GenerateCoverageResults.sh | 2 | ||||
| -rwxr-xr-x | BuildTools/Coverage/GenerateOverview.py | 63 | 
7 files changed, 4993 insertions, 1855 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); +} diff --git a/BuildTools/Coverage/GenerateCoverageResults.sh b/BuildTools/Coverage/GenerateCoverageResults.sh index 111dc07..3f7accb 100755 --- a/BuildTools/Coverage/GenerateCoverageResults.sh +++ b/BuildTools/Coverage/GenerateCoverageResults.sh @@ -29,7 +29,7 @@ $SCRIPT_DIR/FilterLCovData.py $OUTPUT_DIR/all.info  # Generate HTML  $LCOVDIR/gendesc -o $OUTPUT_DIR/descriptions $SCRIPT_DIR/descriptions.txt -$LCOVDIR/genhtml --no-function-coverage --title "Swift Coverage" --output-directory $OUTPUT_DIR $OUTPUT_DIR/all.info +$LCOVDIR/genhtml --prefix $PWD --no-function-coverage --title "Swift Coverage" --output-directory $OUTPUT_DIR $OUTPUT_DIR/all.info  # Generate summary  $SCRIPT_DIR/GenerateSummary.py $OUTPUT_DIR/all.info $OUTPUT_DIR/summary diff --git a/BuildTools/Coverage/GenerateOverview.py b/BuildTools/Coverage/GenerateOverview.py deleted file mode 100755 index 8928afd..0000000 --- a/BuildTools/Coverage/GenerateOverview.py +++ /dev/null @@ -1,63 +0,0 @@ -#!/usr/bin/env python - -import sys, os.path - -assert(len(sys.argv) == 4) - -resultsDir = sys.argv[1] -summaryFile = sys.argv[2] -overviewFile = sys.argv[3] - -results = [] -for dir in os.listdir(resultsDir) : -  summary = os.path.join(resultsDir, dir, summaryFile) -  if os.path.exists(summary) : -    file = open(summary) -    lines = file.readlines() -    if len(lines) == 0 or len(lines[0].split("/")) != 2 : -      continue -    coveredCount = int(lines[0].split("/")[0]) -    totalCount = int(lines[0].split("/")[1]) -    results.append((dir,coveredCount,totalCount)) - -# Compute coverage chart URL -chartparams = ["chs=320x240", "cht=lc", "chtt=Coverage (Relative)", "chxt=y", "chxl=0:|50%|80%|100%|", "chxp=0,50,80,100"] -chartdata = [] -for (url,covered,total) in results : -  chartdata.append(str(100*covered/total)) -chartparams.append("chd=t:" + ",".join(chartdata)) -coverageChartURL = "http://chart.apis.google.com/chart?" + '&'.join(chartparams) - -# Compute the maximum of lines over time -maximumNumberOfLines = 0 -for (url,covered,total) in results : -  maximumNumberOfLines = max(maximumNumberOfLines,total) - -# Compute code chart URL -chartparams = ["chs=320x240", "cht=lc", "chtt=Coverage (Absolute)", "chxt=y", "chxl=0:|" + str(maximumNumberOfLines) + "|", "chxp=0,100", "chm=b,FF0000,0,1,0|b,80C65A,1,2,0", "chco=00000000,00000000,00000000"] -coveredLinesData = [] -totalLinesData = [] -nullLinesData = [] -for (url,covered,total) in results : -  coveredLinesData.append(str(100*covered/maximumNumberOfLines)) -  totalLinesData.append(str(100*total/maximumNumberOfLines)) -  nullLinesData.append("0") -chartparams.append("chd=t:" + ",".join(totalLinesData) + "|" + ",".join(coveredLinesData) + "|" + ",".join(nullLinesData)) -codeChartURL = "http://chart.apis.google.com/chart?" + '&'.join(chartparams) - - -# Output page -output = open(os.path.join(resultsDir,overviewFile), 'w') -output.write("<img src=\"%(url)s\"s/>" % {'url' : coverageChartURL}) -output.write("<img src=\"%(url)s\"s/>" % {'url' : codeChartURL}) -output.write("<ul>\n") -for (url,covered,total) in results : -  output.write("<li><a href='%(url)s/index.html'>%(url)s</a> %(percentage)s%% (%(covered)s/%(total)s)</li>\n" % { -      'url' : url,  -      'percentage' : 100*covered / total,  -      'covered' : covered,  -      'total' : total  -    }) -output.write("</ul>") -output.close() - | 
 Swift
 Swift