diff options
author | Remko Tronçon <git@el-tramo.be> | 2011-02-18 17:59:47 (GMT) |
---|---|---|
committer | Remko Tronçon <git@el-tramo.be> | 2011-02-18 18:10:35 (GMT) |
commit | 2f6c2299c28c9bb03ee1437058a4c7071ff2ac3f (patch) | |
tree | 5fb82cd3ea77b82ce0c5fec973e1fee4de1def4b /3rdParty/LCov/lcov | |
parent | 6d115ace1a038acd1a4354391c552bef84d67e79 (diff) | |
download | swift-contrib-2f6c2299c28c9bb03ee1437058a4c7071ff2ac3f.zip swift-contrib-2f6c2299c28c9bb03ee1437058a4c7071ff2ac3f.tar.bz2 |
Updated coverage tools.
Updated LCov to 1.9.
Removed obsolete overview script.
Diffstat (limited to '3rdParty/LCov/lcov')
-rwxr-xr-x | 3rdParty/LCov/lcov | 2120 |
1 files changed, 1798 insertions, 322 deletions
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); +} |