Index: third_party/lcov/bin/lcov |
diff --git a/third_party/lcov/bin/lcov b/third_party/lcov/bin/lcov |
index 6304d7510970f50399aa4b71379a9c44ef9314e2..1a7f525fb2e66fcf1a0e53296a0e99ddbb43cd27 100755 |
--- a/third_party/lcov/bin/lcov |
+++ b/third_party/lcov/bin/lcov |
@@ -1,6 +1,6 @@ |
#!/usr/bin/perl -w |
# |
-# Copyright (c) International Business Machines Corp., 2002,2007 |
+# Copyright (c) International Business Machines Corp., 2002,2012 |
# |
# 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.10'; |
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,21 @@ 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($$); |
+sub summary(); |
+sub rate($$;$$$); |
# Global variables & initialization |
our @directory; # Specifies where to get coverage data from |
@@ -142,7 +159,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 |
@@ -154,13 +170,38 @@ our $no_checksum; # If set, don't calculate a checksum for each line |
our $compat_libtool; # If set, indicates that libtool mode is to be enabled |
our $no_compat_libtool; # If set, indicates that libtool mode is to be disabled |
our $gcov_tool; |
-our $ignore_errors; |
+our @opt_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 $opt_external; |
+our $opt_no_external; |
+our $opt_config_file; |
+our %opt_rc; |
+our @opt_summary; |
+our $opt_compat; |
+our $ln_overall_found; |
+our $ln_overall_hit; |
+our $fn_overall_found; |
+our $fn_overall_hit; |
+our $br_overall_found; |
+our $br_overall_hit; |
+our $func_coverage = 1; |
+our $br_coverage = 0; |
# |
@@ -169,6 +210,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 =~ /^\/(.*)$/)) |
@@ -176,8 +222,16 @@ if (! ($tool_dir =~ /^\/(.*)$/)) |
$tool_dir = "$cwd/$tool_dir"; |
} |
+# Check command line for a configuration file name |
+Getopt::Long::Configure("pass_through", "no_auto_abbrev"); |
+GetOptions("config-file=s" => \$opt_config_file, |
+ "rc=s%" => \%opt_rc); |
+Getopt::Long::Configure("default"); |
+ |
# Read configuration file if available |
-if (-r $ENV{"HOME"}."/.lcovrc") |
+if (defined($opt_config_file)) { |
+ $config = read_config($opt_config_file); |
+} elsif (defined($ENV{"HOME"}) && (-r $ENV{"HOME"}."/.lcovrc")) |
{ |
$config = read_config($ENV{"HOME"}."/.lcovrc"); |
} |
@@ -186,45 +240,61 @@ elsif (-r "/etc/lcovrc") |
$config = read_config("/etc/lcovrc"); |
} |
-if ($config) |
+if ($config || %opt_rc) |
{ |
- # Copy configuration file values to variables |
+ # Copy configuration file and --rc 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, |
+ "lcov_branch_coverage" => \$br_coverage, |
+ "lcov_function_coverage"=> \$func_coverage, |
+ }); |
} |
# 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, |
"no-compat-libtool" => \$no_compat_libtool, |
"gcov-tool=s" => \$gcov_tool, |
- "ignore-errors=s" => \$ignore_errors, |
+ "ignore-errors=s" => \@opt_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, |
+ "external" => \$opt_external, |
+ "no-external" => \$opt_no_external, |
+ "summary=s" => \@opt_summary, |
+ "compat=s" => \$opt_compat, |
+ "config-file=s" => \$opt_config_file, |
+ "rc=s%" => \%opt_rc, |
)) |
{ |
print(STDERR "Use $tool_name --help to get usage information\n"); |
@@ -244,6 +314,17 @@ 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; |
+ } |
+ |
+ if (defined($opt_no_external)) { |
+ $opt_external = 0; |
+ $opt_no_external = undef; |
+ } |
} |
# Check for help option |
@@ -260,6 +341,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/\/$//; |
@@ -285,9 +372,9 @@ else |
check_options(); |
# Only --extract, --remove and --diff allow unnamed parameters |
-if (@ARGV && !($extract || $remove || $diff)) |
+if (@ARGV && !($extract || $remove || $diff || @opt_summary)) |
{ |
- die("Extra parameter found\n". |
+ die("Extra parameter found: '".join(" ", @ARGV)."'\n". |
"Use $tool_name --help to get usage information\n"); |
} |
@@ -302,13 +389,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 +410,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 +456,26 @@ 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(); |
+} |
+elsif (@opt_summary) |
+{ |
+ ($ln_overall_found, $ln_overall_hit, |
+ $fn_overall_found, $fn_overall_hit, |
+ $br_overall_found, $br_overall_hit) = summary(); |
} |
-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); |
# |
@@ -395,6 +508,7 @@ Operation: |
-r, --remove FILE PATTERN Remove files matching PATTERN from FILE |
-l, --list FILE List contents of tracefile FILE |
--diff FILE DIFF Transform tracefile FILE according to DIFF |
+ --summary FILE Show summary coverage data for tracefiles |
Options: |
-i, --initial Capture initial zero coverage data |
@@ -410,8 +524,17 @@ 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 |
+ --(no-)external Include (ignore) data for external files |
+ --config-file FILENAME Specify configuration file location |
+ --rc SETTING=VALUE Override configuration file setting |
+ --compat MODE=on|off|auto Set compat MODE (libtool, hammer, split_crc) |
For more information see: $lcov_url |
END_OF_USAGE |
@@ -437,17 +560,18 @@ sub check_options() |
$remove && $i++; |
$list && $i++; |
$diff && $i++; |
+ @opt_summary && $i++; |
if ($i == 0) |
{ |
- die("Need one of the options -z, -c, -a, -e, -r, -l or ". |
- "--diff\n". |
+ die("Need one of options -z, -c, -a, -e, -r, -l, ". |
+ "--diff or --summary\n". |
"Use $tool_name --help to get usage information\n"); |
} |
elsif ($i > 1) |
{ |
- die("ERROR: only one of -z, -c, -a, -e, -r, -l or ". |
- "--diff allowed!\n". |
+ die("ERROR: only one of -z, -c, -a, -e, -r, -l, ". |
+ "--diff or --summary allowed!\n". |
"Use $tool_name --help to get usage information\n"); |
} |
} |
@@ -483,19 +607,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); |
@@ -539,180 +841,470 @@ sub userspace_capture() |
{ |
@param = (@param, "--gcov-tool", $gcov_tool); |
} |
- if ($ignore_errors) |
- { |
- @param = (@param, "--ignore-errors", $ignore_errors); |
+ foreach (@opt_ignore_errors) { |
+ @param = (@param, "--ignore-errors", $_); |
+ } |
+ if ($no_recursion) { |
+ @param = (@param, "--no-recursion"); |
} |
if ($initial) |
{ |
@param = (@param, "--initial"); |
} |
- if ($no_recursion) |
+ if ($no_markers) |
{ |
- @param = (@param, "--no-recursion"); |
+ @param = (@param, "--no-markers"); |
+ } |
+ if ($opt_derive_func_data) |
+ { |
+ @param = (@param, "--derive-func-data"); |
+ } |
+ if ($opt_debug) |
+ { |
+ @param = (@param, "--debug"); |
+ } |
+ if (defined($opt_external) && $opt_external) |
+ { |
+ @param = (@param, "--external"); |
+ } |
+ if (defined($opt_external) && !$opt_external) |
+ { |
+ @param = (@param, "--no-external"); |
+ } |
+ if (defined($opt_compat)) { |
+ @param = (@param, "--compat", $opt_compat); |
+ } |
+ if (%opt_rc) { |
+ foreach my $key (keys(%opt_rc)) { |
+ @param = (@param, "--rc", "$key=".$opt_rc{$key}); |
+ } |
} |
- system(@param); |
- exit($? >> 8); |
+ system(@param) and exit($? >> 8); |
} |
- |
-# |
-# kernel_reset() |
# |
-# Reset kernel coverage. |
+# read_file(filename) |
# |
-# 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 +1323,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 +1335,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; |
+} |
+ |
+ |
+# |
+# 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); |
} |
# |
-# create_temp_dir() |
-# |
-# Create a temporary directory and return its path. |
+# get_br_found_and_hit(brcount) |
# |
-# Die on error. |
+# 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); |
+ |
+ for ($i = 0; $i < $num; $i++) { |
+ my $taken; |
- mkdir($dirname) |
- or die("ERROR: cannot create temporary directory ". |
- "$dirname!\n"); |
+ (undef, undef, $taken) = br_ivec_get($brdata, $i); |
- return($dirname); |
+ $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 +1557,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 +1597,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 |
@@ -961,14 +1638,14 @@ sub read_info_file($) |
"compressed file $_[0]!\n"); |
# Open compressed file |
- open(INFO_HANDLE, "gunzip -c $_[0]|") |
+ open(INFO_HANDLE, "-|", "gunzip -c '$_[0]'") |
or die("ERROR: cannot start gunzip to decompress ". |
"file $_[0]!\n"); |
} |
else |
{ |
# Open decompressed file |
- open(INFO_HANDLE, $_[0]) |
+ open(INFO_HANDLE, "<", $_[0]) |
or die("ERROR: cannot read file $_[0]!\n"); |
} |
@@ -981,7 +1658,7 @@ sub read_info_file($) |
# Switch statement |
foreach ($line) |
{ |
- /^TN:([^,]*)/ && do |
+ /^TN:([^,]*)(,diff)?/ && do |
{ |
# Test name information found |
$testname = defined($1) ? $1 : ""; |
@@ -989,6 +1666,7 @@ sub read_info_file($) |
{ |
$changed_testname = 1; |
} |
+ $testname .= $2 if (defined($2)); |
last; |
}; |
@@ -1000,18 +1678,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; |
}; |
@@ -1055,6 +1736,8 @@ sub read_info_file($) |
/^FN:(\d+),([^,]+)/ && do |
{ |
+ last if (!$func_coverage); |
+ |
# Function data found, add to structure |
$funcdata->{$2} = $1; |
@@ -1073,6 +1756,8 @@ sub read_info_file($) |
/^FNDA:(\d+),([^,]+)/ && do |
{ |
+ last if (!$func_coverage); |
+ |
# Function call count found, add to structure |
# Add summary counts |
$sumfnccount->{$2} += $1; |
@@ -1084,6 +1769,28 @@ sub read_info_file($) |
} |
last; |
}; |
+ |
+ /^BRDA:(\d+),(\d+),(\d+),(\d+|-)/ && do { |
+ # Branch coverage data found |
+ my ($line, $block, $branch, $taken) = |
+ ($1, $2, $3, $4); |
+ |
+ last if (!$br_coverage); |
+ $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 +1803,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 +1830,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 +1870,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 +1905,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 +1918,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 +1953,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 +2069,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 +2103,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 +2159,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 +2336,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 +2346,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 +2355,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 +2383,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 +2431,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 +2483,7 @@ sub add_traces() |
my $total_trace; |
my $current_trace; |
my $tracefile; |
+ my @result; |
local *INFO_HANDLE; |
info("Combining tracefiles.\n"); |
@@ -1595,15 +2506,17 @@ sub add_traces() |
if ($to_file) |
{ |
info("Writing data to $output_filename\n"); |
- open(INFO_HANDLE, ">$output_filename") |
+ 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 +2536,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 +2602,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,13 +2647,16 @@ 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); |
} |
# |
# transform_pattern(pattern) |
# |
-# Transform shell wildcard expression to equivalent PERL regular expression. |
+# Transform shell wildcard expression to equivalent Perl regular expression. |
# Return transformed pattern. |
# |
@@ -1739,6 +2703,7 @@ sub extract() |
my $pattern; |
my @pattern_list; |
my $extracted = 0; |
+ my @result; |
local *INFO_HANDLE; |
# Need perlreg expressions instead of shell pattern |
@@ -1771,15 +2736,17 @@ sub extract() |
{ |
info("Extracted $extracted files\n"); |
info("Writing data to $output_filename\n"); |
- open(INFO_HANDLE, ">$output_filename") |
+ 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 +2762,7 @@ sub remove() |
my $pattern; |
my @pattern_list; |
my $removed = 0; |
+ my @result; |
local *INFO_HANDLE; |
# Need perlreg expressions instead of shell pattern |
@@ -1824,17 +2792,127 @@ sub remove() |
{ |
info("Deleted $removed files\n"); |
info("Writing data to $output_filename\n"); |
- open(INFO_HANDLE, ">$output_filename") |
+ 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 ($hit, $found, $width) = @_; |
+ my $result = rate($hit, $found, "%", 1, $width); |
+ |
+ return $result if (length($result) <= $width); |
+ $result = rate($hit, $found, "%", 0, $width); |
+ return $result if (length($result) <= $width); |
+ return "#"; |
+} |
# |
# list() |
@@ -1847,17 +2925,250 @@ 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 |
+ $rate = shorten_rate($hit, $found, $fwidth[$F_LN_RATE]); |
+ # Determine function coverage rate for this file |
+ $fnrate = shorten_rate($fn_hit, $fn_found, $fwidth[$F_FN_RATE]); |
+ # Determine branch coverage rate for this file |
+ $brrate = shorten_rate($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 |
+ $rate = shorten_rate($total_hit, $total_found, $fwidth[$F_LN_RATE]); |
+ # Determine total function coverage rate |
+ $fnrate = shorten_rate($fn_total_hit, $fn_total_found, |
+ $fwidth[$F_FN_RATE]); |
+ # Determine total branch coverage rate |
+ $brrate = shorten_rate($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); |
} |
@@ -1981,14 +3292,14 @@ sub read_diff($) |
"compressed file $diff_file!\n"); |
# Open compressed file |
- open(HANDLE, "gunzip -c $diff_file|") |
+ open(HANDLE, "-|", "gunzip -c '$diff_file'") |
or die("ERROR: cannot start gunzip to decompress ". |
"file $_[0]!\n"); |
} |
else |
{ |
# Open decompressed file |
- open(HANDLE, $diff_file) |
+ open(HANDLE, "<", $diff_file) |
or die("ERROR: cannot read file $_[0]!\n"); |
} |
@@ -2155,6 +3466,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 +3568,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 +3771,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 +3812,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 +3853,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 +3867,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 +3901,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 |
{ |
@@ -2559,17 +3925,72 @@ sub diff() |
if ($to_file) |
{ |
info("Writing data to $output_filename\n"); |
- open(INFO_HANDLE, ">$output_filename") |
+ 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; |
} |
+# |
+# summary() |
+# |
+ |
+sub summary() |
+{ |
+ my $filename; |
+ my $current; |
+ my $total; |
+ my $ln_total_found; |
+ my $ln_total_hit; |
+ my $fn_total_found; |
+ my $fn_total_hit; |
+ my $br_total_found; |
+ my $br_total_hit; |
+ |
+ # Read and combine trace files |
+ foreach $filename (@opt_summary) { |
+ $current = read_info_file($filename); |
+ if (!defined($total)) { |
+ $total = $current; |
+ } else { |
+ $total = combine_info_files($total, $current); |
+ } |
+ } |
+ # Calculate coverage data |
+ foreach $filename (keys(%{$total})) |
+ { |
+ my $entry = $total->{$filename}; |
+ my $ln_found; |
+ my $ln_hit; |
+ my $fn_found; |
+ my $fn_hit; |
+ my $br_found; |
+ my $br_hit; |
+ |
+ (undef, undef, undef, undef, undef, undef, undef, undef, |
+ $ln_found, $ln_hit, $fn_found, $fn_hit, $br_found, |
+ $br_hit) = get_info_entry($entry); |
+ |
+ # Add to totals |
+ $ln_total_found += $ln_found; |
+ $ln_total_hit += $ln_hit; |
+ $fn_total_found += $fn_found; |
+ $fn_total_hit += $fn_hit; |
+ $br_total_found += $br_found; |
+ $br_total_hit += $br_hit; |
+ } |
+ |
+ |
+ return ($ln_total_found, $ln_total_hit, $fn_total_found, $fn_total_hit, |
+ $br_total_found, $br_total_hit); |
+} |
# |
# system_no_output(mode, parameters) |
@@ -2591,12 +4012,12 @@ sub system_no_output($@) |
local *OLD_STDOUT; |
# Save old stdout and stderr handles |
- ($mode & 1) && open(OLD_STDOUT, ">>&STDOUT"); |
- ($mode & 2) && open(OLD_STDERR, ">>&STDERR"); |
+ ($mode & 1) && open(OLD_STDOUT, ">>&", "STDOUT"); |
+ ($mode & 2) && open(OLD_STDERR, ">>&", "STDERR"); |
# Redirect to /dev/null |
- ($mode & 1) && open(STDOUT, ">/dev/null"); |
- ($mode & 2) && open(STDERR, ">/dev/null"); |
+ ($mode & 1) && open(STDOUT, ">", "/dev/null"); |
+ ($mode & 2) && open(STDERR, ">", "/dev/null"); |
system(@_); |
$result = $?; |
@@ -2606,8 +4027,8 @@ sub system_no_output($@) |
($mode & 2) && close(STDERR); |
# Restore old handles |
- ($mode & 1) && open(STDOUT, ">>&OLD_STDOUT"); |
- ($mode & 2) && open(STDERR, ">>&OLD_STDERR"); |
+ ($mode & 1) && open(STDOUT, ">>&", "OLD_STDOUT"); |
+ ($mode & 2) && open(STDERR, ">>&", "OLD_STDERR"); |
return $result; |
} |
@@ -2628,7 +4049,7 @@ sub read_config($) |
my $value; |
local *HANDLE; |
- if (!open(HANDLE, "<$filename")) |
+ if (!open(HANDLE, "<", $filename)) |
{ |
warn("WARNING: cannot read configuration file $filename\n"); |
return undef; |
@@ -2667,8 +4088,8 @@ sub read_config($) |
# key_string => var_ref |
# |
# where KEY_STRING is a keyword and VAR_REF is a reference to an associated |
-# variable. If the global configuration hash CONFIG contains a value for |
-# keyword KEY_STRING, VAR_REF will be assigned the value for that keyword. |
+# variable. If the global configuration hashes CONFIG or OPT_RC contain a value |
+# for keyword KEY_STRING, VAR_REF will be assigned the value for that keyword. |
# |
sub apply_config($) |
@@ -2677,8 +4098,9 @@ sub apply_config($) |
foreach (keys(%{$ref})) |
{ |
- if (defined($config->{$_})) |
- { |
+ if (defined($opt_rc{$_})) { |
+ ${$ref->{$_}} = $opt_rc{$_}; |
+ } elsif (defined($config->{$_})) { |
${$ref->{$_}} = $config->{$_}; |
} |
} |
@@ -2688,6 +4110,7 @@ sub warn_handler($) |
{ |
my ($msg) = @_; |
+ temp_cleanup(); |
warn("$tool_name: $msg"); |
} |
@@ -2695,5 +4118,186 @@ 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 rate($hit, $found, "% ($hit of $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("Summary 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); |
+} |
+ |
+ |
+# |
+# rate(hit, found[, suffix, precision, width]) |
+# |
+# Return the coverage rate [0..100] for HIT and FOUND values. 0 is only |
+# returned when HIT is 0. 100 is only returned when HIT equals FOUND. |
+# PRECISION specifies the precision of the result. SUFFIX defines a |
+# string that is appended to the result if FOUND is non-zero. Spaces |
+# are added to the start of the resulting string until it is at least WIDTH |
+# characters wide. |
+# |
+ |
+sub rate($$;$$$) |
+{ |
+ my ($hit, $found, $suffix, $precision, $width) = @_; |
+ my $rate; |
+ |
+ # Assign defaults if necessary |
+ $precision = 1 if (!defined($precision)); |
+ $suffix = "" if (!defined($suffix)); |
+ $width = 0 if (!defined($width)); |
+ |
+ return sprintf("%*s", $width, "-") if (!defined($found) || $found == 0); |
+ $rate = sprintf("%.*f", $precision, $hit * 100 / $found); |
+ |
+ # Adjust rates if necessary |
+ if ($rate == 0 && $hit > 0) { |
+ $rate = sprintf("%.*f", $precision, 1 / 10 ** $precision); |
+ } elsif ($rate == 100 && $hit != $found) { |
+ $rate = sprintf("%.*f", $precision, 100 - 1 / 10 ** $precision); |
+ } |
+ |
+ return sprintf("%*s", $width, $rate.$suffix); |
+} |