3 # Copyright (c) International Business Machines Corp., 2002,2007
5 # This program is free software; you can redistribute it and/or modify
6 # it under the terms of the GNU General Public License as published by
7 # the Free Software Foundation; either version 2 of the License, or (at
8 # your option) any later version.
10 # This program is distributed in the hope that it will be useful, but
11 # WITHOUT ANY WARRANTY; without even the implied warranty of
12 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
13 # General Public License for more details.
15 # You should have received a copy of the GNU General Public License
16 # along with this program; if not, write to the Free Software
17 # Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
22 # This is a wrapper script which provides a single interface for accessing
27 # 2002-08-29 created by Peter Oberparleiter <Peter.Oberparleiter@de.ibm.com>
29 # 2002-09-05 / Peter Oberparleiter: implemented --kernel-directory +
30 # multiple directories
31 # 2002-10-16 / Peter Oberparleiter: implemented --add-tracefile option
32 # 2002-10-17 / Peter Oberparleiter: implemented --extract option
33 # 2002-11-04 / Peter Oberparleiter: implemented --list option
34 # 2003-03-07 / Paul Larson: Changed to make it work with the latest gcov
35 # kernel patch. This will break it with older gcov-kernel
36 # patches unless you change the value of $gcovmod in this script
37 # 2003-04-07 / Peter Oberparleiter: fixed bug which resulted in an error
38 # when trying to combine .info files containing data without
40 # 2003-04-10 / Peter Oberparleiter: extended Paul's change so that LCOV
41 # works both with the new and the old gcov-kernel patch
42 # 2003-04-10 / Peter Oberparleiter: added $gcov_dir constant in anticipation
43 # of a possible move of the gcov kernel directory to another
44 # file system in a future version of the gcov-kernel patch
45 # 2003-04-15 / Paul Larson: make info write to STDERR, not STDOUT
46 # 2003-04-15 / Paul Larson: added --remove option
47 # 2003-04-30 / Peter Oberparleiter: renamed --reset to --zerocounters
48 # to remove naming ambiguity with --remove
49 # 2003-04-30 / Peter Oberparleiter: adjusted help text to include --remove
50 # 2003-06-27 / Peter Oberparleiter: implemented --diff
51 # 2003-07-03 / Peter Oberparleiter: added line checksum support, added
53 # 2003-12-11 / Laurent Deniel: added --follow option
54 # 2004-03-29 / Peter Oberparleiter: modified --diff option to better cope with
55 # ambiguous patch file entries, modified --capture option to use
56 # modprobe before insmod (needed for 2.6)
57 # 2004-03-30 / Peter Oberparleiter: added --path option
58 # 2004-08-09 / Peter Oberparleiter: added configuration file support
59 # 2008-08-13 / Peter Oberparleiter: added function coverage support
68 our $lcov_version = "LCOV version 1.7";
69 our $lcov_url = "http://ltp.sourceforge.net/coverage/lcov.php";
70 our $tool_name = basename
($0);
72 # Names of the GCOV kernel module
73 our @gcovmod = ("gcov-prof", "gcov-proc");
75 # Directory containing gcov kernel files
76 our $gcov_dir = "/proc/gcov";
78 # The location of the insmod tool
79 our $insmod_tool = "/sbin/insmod";
81 # The location of the modprobe tool
82 our $modprobe_tool = "/sbin/modprobe";
84 # The location of the rmmod tool
85 our $rmmod_tool = "/sbin/rmmod";
87 # Where to create temporary directories
88 our $tmp_dir = "/tmp";
90 # How to prefix a temporary directory name
91 our $tmp_prefix = "tmpdir";
97 sub userspace_reset
();
98 sub userspace_capture
();
100 sub kernel_capture
();
102 sub read_info_file
($);
103 sub get_info_entry
($);
104 sub set_info_entry
($$$$$$$;$$$$);
106 sub merge_checksums
($$$);
107 sub combine_info_entries
($$$);
108 sub combine_info_files
($$);
109 sub write_info_file
(*$);
113 sub get_common_filename
($$);
116 sub system_no_output
($@
);
120 sub unload_module
($);
121 sub check_and_load_kernel_module
();
122 sub create_temp_dir
();
123 sub transform_pattern
($);
128 # Global variables & initialization
129 our @directory; # Specifies where to get coverage data from
130 our @kernel_directory; # If set, captures only from specified kernel subdirs
131 our @add_tracefile; # If set, reads in and combines all files in list
132 our $list; # If set, list contents of tracefile
133 our $extract; # If set, extracts parts of tracefile
134 our $remove; # If set, removes parts of tracefile
135 our $diff; # If set, modifies tracefile according to diff
136 our $reset; # If set, reset all coverage data to zero
137 our $capture; # If set, capture data
138 our $output_filename; # Name for file to write coverage data to
139 our $test_name = ""; # Test case name
140 our $quiet = ""; # If set, suppress information messages
141 our $help; # Help option flag
142 our $version; # Version option flag
143 our $convert_filenames; # If set, convert filenames when applying diff
144 our $strip; # If set, strip leading directories when applying diff
145 our $need_unload; # If set, unload gcov kernel module
146 our $temp_dir_name; # Name of temporary directory
147 our $cwd = `pwd`; # Current working directory
148 our $to_file; # If set, indicates that output is written to a file
149 our $follow; # If set, indicates that find shall follow links
150 our $diff_path = ""; # Path removed from tracefile when applying diff
151 our $base_directory; # Base directory (cwd of gcc during compilation)
152 our $checksum; # If set, calculate a checksum for each line
153 our $no_checksum; # If set, don't calculate a checksum for each line
154 our $compat_libtool; # If set, indicates that libtool mode is to be enabled
155 our $no_compat_libtool; # If set, indicates that libtool mode is to be disabled
159 our $no_recursion = 0;
161 our $config; # Configuration file contents
163 our $tool_dir = dirname
($0); # Directory where genhtml tool is installed
170 $SIG{__WARN__
} = \
&warn_handler
;
171 $SIG{__DIE__
} = \
&die_handler
;
173 # Add current working directory if $tool_dir is not already an absolute path
174 if (! ($tool_dir =~ /^\/(.*)$/))
176 $tool_dir = "$cwd/$tool_dir";
179 # Read configuration file if available
180 if (-r
$ENV{"HOME"}."/.lcovrc")
182 $config = read_config
($ENV{"HOME"}."/.lcovrc");
184 elsif (-r
"/etc/lcovrc")
186 $config = read_config
("/etc/lcovrc");
191 # Copy configuration file values to variables
193 "lcov_gcov_dir" => \
$gcov_dir,
194 "lcov_insmod_tool" => \
$insmod_tool,
195 "lcov_modprobe_tool" => \
$modprobe_tool,
196 "lcov_rmmod_tool" => \
$rmmod_tool,
197 "lcov_tmp_dir" => \
$tmp_dir});
200 # Parse command line options
201 if (!GetOptions
("directory|d|di=s" => \
@directory,
202 "add-tracefile=s" => \
@add_tracefile,
204 "kernel-directory=s" => \
@kernel_directory,
205 "extract=s" => \
$extract,
206 "remove=s" => \
$remove,
208 "convert-filenames" => \
$convert_filenames,
209 "strip=i" => \
$strip,
210 "capture|c" => \
$capture,
211 "output-file=s" => \
$output_filename,
212 "test-name=s" => \
$test_name,
213 "zerocounters" => \
$reset,
216 "version" => \
$version,
217 "follow" => \
$follow,
218 "path=s" => \
$diff_path,
219 "base-directory=s" => \
$base_directory,
220 "checksum" => \
$checksum,
221 "no-checksum" => \
$no_checksum,
222 "compat-libtool" => \
$compat_libtool,
223 "no-compat-libtool" => \
$no_compat_libtool,
224 "gcov-tool=s" => \
$gcov_tool,
225 "ignore-errors=s" => \
$ignore_errors,
226 "initial|i" => \
$initial,
227 "no-recursion" => \
$no_recursion
230 print(STDERR
"Use $tool_name --help to get usage information\n");
236 if (defined($no_checksum))
238 $checksum = ($no_checksum ?
0 : 1);
239 $no_checksum = undef;
242 if (defined($no_compat_libtool))
244 $compat_libtool = ($no_compat_libtool ?
0 : 1);
245 $no_compat_libtool = undef;
249 # Check for help option
252 print_usage
(*STDOUT
);
256 # Check for version option
259 print("$tool_name: $lcov_version\n");
263 # Normalize --path text
264 $diff_path =~ s/\/$//;
277 $maxdepth = "-maxdepth 1";
284 # Check for valid options
287 # Only --extract, --remove and --diff allow unnamed parameters
288 if (@ARGV && !($extract || $remove || $diff))
290 die("Extra parameter found\n".
291 "Use $tool_name --help to get usage information\n");
294 # Check for output filename
295 $to_file = ($output_filename && ($output_filename ne "-"));
301 # Option that tells geninfo to write to stdout
302 $output_filename = "-";
309 die("Option --initial is only valid when capturing data (-c)\n".
310 "Use $tool_name --help to get usage information\n");
314 # Check for requested functionality
317 # Differentiate between user space and kernel reset
329 # Differentiate between user space and kernel
339 elsif (@add_tracefile)
357 if (scalar(@ARGV) != 1)
359 die("ERROR: option --diff requires one additional argument!\n".
360 "Use $tool_name --help to get usage information\n");
369 # print_usage(handle)
371 # Print usage information.
376 local *HANDLE
= $_[0];
378 print(HANDLE
<<END_OF_USAGE);
379 Usage: $tool_name [OPTIONS]
381 Use lcov to collect coverage data from either the currently running Linux
382 kernel or from a user space application. Specify the --directory option to
383 get coverage data for a user space program.
386 -h, --help Print this help, then exit
387 -v, --version Print version number, then exit
388 -q, --quiet Do not print progress messages
391 -z, --zerocounters Reset all execution counts to zero
392 -c, --capture Capture coverage data
393 -a, --add-tracefile FILE Add contents of tracefiles
394 -e, --extract FILE PATTERN Extract files matching PATTERN from FILE
395 -r, --remove FILE PATTERN Remove files matching PATTERN from FILE
396 -l, --list FILE List contents of tracefile FILE
397 --diff FILE DIFF Transform tracefile FILE according to DIFF
400 -i, --initial Capture initial zero coverage data
401 -t, --test-name NAME Specify test name to be stored with data
402 -o, --output-file FILENAME Write data to FILENAME instead of stdout
403 -d, --directory DIR Use .da files in DIR instead of kernel
404 -f, --follow Follow links when searching .da files
405 -k, --kernel-directory KDIR Capture kernel coverage data only from KDIR
406 -b, --base-directory DIR Use DIR as base directory for relative paths
407 --convert-filenames Convert filenames when applying diff
408 --strip DEPTH Strip initial DEPTH directory levels in diff
409 --path PATH Strip PATH from tracefile when applying diff
410 --(no-)checksum Enable (disable) line checksumming
411 --(no-)compat-libtool Enable (disable) libtool compatibility mode
412 --gcov-tool TOOL Specify gcov tool location
413 --ignore-errors ERRORS Continue after ERRORS (gcov, source)
414 --no-recursion Exlude subdirectories from processing
416 For more information see: $lcov_url
425 # Check for valid combination of command line options. Die on error.
432 # Count occurrence of mutually exclusive options
435 @add_tracefile && $i++;
443 die("Need one of the options -z, -c, -a, -e, -r, -l or ".
445 "Use $tool_name --help to get usage information\n");
449 die("ERROR: only one of -z, -c, -a, -e, -r, -l or ".
451 "Use $tool_name --help to get usage information\n");
459 # Reset coverage data found in DIRECTORY by deleting all contained .da files.
464 sub userspace_reset()
469 foreach $current_dir (@directory)
471 info("Deleting all .da files in $current_dir".
472 ($no_recursion?"\n":" and subdirectories\n"));
473 @file_list = `find "$current_dir" $maxdepth $follow -name \\*\\.da -o -name \\*\\.gcda -type f 2>/dev/null`;
477 unlink($_) or die("ERROR: cannot remove file $_!\n");
484 # userspace_capture()
486 # Capture coverage data found in DIRECTORY and write it to OUTPUT_FILENAME
487 # if specified, otherwise to STDOUT.
492 sub userspace_capture()
495 my $file_list = join(" ", @directory);
497 info("Capturing coverage data from $file_list\n");
498 @param = ("$tool_dir/geninfo", @directory);
499 if ($output_filename)
501 @param = (@param, "--output-filename", $output_filename);
505 @param = (@param, "--test-name", $test_name);
509 @param = (@param, "--follow");
513 @param = (@param, "--quiet");
515 if (defined($checksum))
519 @param = (@param, "--checksum");
523 @param = (@param, "--no-checksum");
528 @param = (@param, "--base-directory", $base_directory);
530 if ($no_compat_libtool)
532 @param = (@param, "--no-compat-libtool");
534 elsif ($compat_libtool)
536 @param = (@param, "--compat-libtool");
540 @param = (@param, "--gcov-tool", $gcov_tool);
544 @param = (@param, "--ignore-errors", $ignore_errors);
548 @param = (@param, "--initial");
552 @param = (@param, "--no-recursion");
563 # Reset kernel coverage.
571 check_and_load_kernel_module();
573 info("Resetting kernel execution counters\n");
574 open(HANDLE, ">$gcov_dir/vmlinux") or
575 die("ERROR: cannot write to $gcov_dir/vmlinux!\n");
579 # Unload module if we loaded it in the first place
582 unload_module($need_unload);
590 # Capture kernel coverage data and write it to OUTPUT_FILENAME if specified,
598 check_and_load_kernel_module();
600 # Make sure the temporary directory is removed upon script termination
605 stat($temp_dir_name);
608 info
("Removing temporary directory ".
611 # Remove temporary directory
612 system("rm", "-rf", $temp_dir_name)
613 and warn("WARNING: cannot remove ".
614 "temporary directory ".
615 "$temp_dir_name!\n");
620 # Get temporary directory
621 $temp_dir_name = create_temp_dir
();
623 info
("Copying kernel data to temporary directory $temp_dir_name\n");
625 if (!@kernel_directory)
627 # Copy files from gcov kernel directory
628 system("cp", "-dr", $gcov_dir, $temp_dir_name)
629 and die("ERROR: cannot copy files from $gcov_dir!\n");
633 # Prefix list of kernel sub-directories with the gcov kernel
635 @kernel_directory = map("$gcov_dir/$_", @kernel_directory);
637 # Copy files from gcov kernel directory
638 system("cp", "-dr", @kernel_directory, $temp_dir_name)
639 and die("ERROR: cannot copy files from ".
640 join(" ", @kernel_directory)."!\n");
643 # Make directories writable
644 system("find", $temp_dir_name, "-type", "d", "-exec", "chmod", "u+w",
646 and die("ERROR: cannot modify access rights for ".
647 "$temp_dir_name!\n");
649 # Make files writable
650 system("find", $temp_dir_name, "-type", "f", "-exec", "chmod", "u+w",
652 and die("ERROR: cannot modify access rights for ".
653 "$temp_dir_name!\n");
656 info
("Capturing coverage data from $temp_dir_name\n");
657 @param = ("$tool_dir/geninfo", $temp_dir_name);
658 if ($output_filename)
660 @param = (@param, "--output-filename", $output_filename);
664 @param = (@param, "--test-name", $test_name);
668 @param = (@param, "--follow");
672 @param = (@param, "--quiet");
674 if (defined($checksum))
678 @param = (@param, "--checksum");
682 @param = (@param, "--no-checksum");
687 @param = (@param, "--base-directory", $base_directory);
689 if ($no_compat_libtool)
691 @param = (@param, "--no-compat-libtool");
693 elsif ($compat_libtool)
695 @param = (@param, "--compat-libtool");
699 @param = (@param, "--gcov-tool", $gcov_tool);
703 @param = (@param, "--ignore-errors", $ignore_errors);
707 @param = (@param, "--initial");
709 system(@param) and exit($?
>> 8);
712 # Unload module if we loaded it in the first place
715 unload_module
($need_unload);
721 # info(printf_parameter)
723 # Use printf to write PRINTF_PARAMETER to stdout only when the $quiet flag
738 # Don't interfer with the .info output to STDOUT
746 # Check if the gcov kernel module is loaded. If it is, exit, if not, try
752 sub check_and_load_kernel_module
()
756 # Is it loaded already?
758 if (-r _
) { return(); }
760 info
("Loading required gcov kernel module.\n");
762 # Do we have access to the insmod tool?
766 die("ERROR: need insmod tool ($insmod_tool) to access kernel ".
769 # Do we have access to the modprobe tool?
770 stat($modprobe_tool);
773 die("ERROR: need modprobe tool ($modprobe_tool) to access ".
774 "kernel coverage data!\n");
777 # Try some possibilities of where the gcov kernel module may be found
778 foreach $module_name (@gcovmod)
780 # Try to load module from system wide module directory
782 if (system_no_output
(3, $modprobe_tool, $module_name) == 0)
785 $need_unload = $module_name;
789 # Try to load linux 2.5/2.6 module from tool directory
790 if (system_no_output
(3, $insmod_tool,
791 "$tool_dir/$module_name.ko") == 0)
794 $need_unload = $module_name;
798 # Try to load linux 2.4 module from tool directory
799 if (system_no_output
(3, $insmod_tool,
800 "$tool_dir/$module_name.o") == 0)
803 $need_unload = $module_name;
808 # Hm, loading failed - maybe we aren't root?
811 die("ERROR: need root access to load kernel module!\n");
814 die("ERROR: cannot load required gcov kernel module!\n");
821 # Unload the gcov kernel module.
828 info
("Unloading kernel module $module\n");
830 # Do we have access to the rmmod tool?
834 warn("WARNING: cannot execute rmmod tool at $rmmod_tool - ".
835 "gcov module still loaded!\n");
838 # Unload gcov kernel module
839 system_no_output
(1, $rmmod_tool, $module)
840 and warn("WARNING: cannot unload gcov kernel module ".
848 # Create a temporary directory and return its path.
853 sub create_temp_dir
()
856 my $number = sprintf("%d", rand(1000));
858 # Endless loops are evil
859 while ($number++ < 1000)
861 $dirname = "$tmp_dir/$tmp_prefix$number";
866 or die("ERROR: cannot create temporary directory ".
872 die("ERROR: cannot create temporary directory in $tmp_dir!\n");
877 # read_info_file(info_filename)
879 # Read in the contents of the .info file specified by INFO_FILENAME. Data will
880 # be returned as a reference to a hash containing the following mappings:
882 # %result: for each filename found in file -> \%data
884 # %data: "test" -> \%testdata
885 # "sum" -> \%sumcount
886 # "func" -> \%funcdata
887 # "found" -> $lines_found (number of instrumented lines found in file)
888 # "hit" -> $lines_hit (number of executed lines in file)
889 # "check" -> \%checkdata
890 # "testfnc" -> \%testfncdata
891 # "sumfnc" -> \%sumfnccount
893 # %testdata : name of test affecting this file -> \%testcount
894 # %testfncdata: name of test affecting this file -> \%testfnccount
896 # %testcount : line number -> execution count for a single test
897 # %testfnccount: function name -> execution count for a single test
898 # %sumcount : line number -> execution count for all tests
899 # %sumfnccount : function name -> execution count for all tests
900 # %funcdata : function name -> line number
901 # %checkdata : line number -> checksum of source code line
903 # Note that .info file sections referring to the same file and test name
904 # will automatically be combined by adding all execution counts.
906 # Note that if INFO_FILENAME ends with ".gz", it is assumed that the file
907 # is compressed using GZIP. If available, GUNZIP will be used to decompress
913 sub read_info_file
($)
915 my $tracefile = $_[0]; # Name of tracefile
916 my %result; # Resulting hash: file -> data
917 my $data; # Data handle for current entry
926 my $line; # Current line read from .info file
927 my $testname; # Current test name
928 my $filename; # Current filename
929 my $hitcount; # Count for lines hit
930 my $count; # Execution count of current line
931 my $negative; # If set, warn about negative counts
932 my $changed_testname; # If set, warn about changed testname
933 my $line_checksum; # Checksum of current line
934 local *INFO_HANDLE
; # Filehandle for .info file
936 info
("Reading tracefile $tracefile\n");
938 # Check if file exists and is readable
942 die("ERROR: cannot read file $_[0]!\n");
945 # Check if this is really a plain file
948 die("ERROR: not a plain file: $_[0]!\n");
951 # Check for .gz extension
952 if ($_[0] =~ /\.gz$/)
954 # Check for availability of GZIP tool
955 system_no_output
(1, "gunzip" ,"-h")
956 and die("ERROR: gunzip command not available!\n");
958 # Check integrity of compressed file
959 system_no_output
(1, "gunzip", "-t", $_[0])
960 and die("ERROR: integrity check failed for ".
961 "compressed file $_[0]!\n");
963 # Open compressed file
964 open(INFO_HANDLE
, "gunzip -c $_[0]|")
965 or die("ERROR: cannot start gunzip to decompress ".
970 # Open decompressed file
971 open(INFO_HANDLE
, $_[0])
972 or die("ERROR: cannot read file $_[0]!\n");
976 while (<INFO_HANDLE
>)
986 # Test name information found
987 $testname = defined($1) ?
$1 : "";
988 if ($testname =~ s/\W/_/g)
990 $changed_testname = 1;
997 # Filename information found
998 # Retrieve data for new entry
1001 $data = $result{$filename};
1002 ($testdata, $sumcount, $funcdata, $checkdata,
1003 $testfncdata, $sumfnccount) =
1004 get_info_entry
($data);
1006 if (defined($testname))
1008 $testcount = $testdata->{$testname};
1009 $testfnccount = $testfncdata->{$testname};
1019 /^DA:(\d+),(-?\d+)(,[^,\s]+)?/ && do
1021 # Fix negative counts
1022 $count = $2 < 0 ?
0 : $2;
1027 # Execution count found, add to structure
1028 # Add summary counts
1029 $sumcount->{$1} += $count;
1031 # Add test-specific counts
1032 if (defined($testname))
1034 $testcount->{$1} += $count;
1037 # Store line checksum if available
1040 $line_checksum = substr($3, 1);
1042 # Does it match a previous definition
1043 if (defined($checkdata->{$1}) &&
1044 ($checkdata->{$1} ne
1047 die("ERROR: checksum mismatch ".
1048 "at $filename:$1\n");
1051 $checkdata->{$1} = $line_checksum;
1056 /^FN:(\d+),([^,]+)/ && do
1058 # Function data found, add to structure
1059 $funcdata->{$2} = $1;
1061 # Also initialize function call data
1062 if (!defined($sumfnccount->{$2})) {
1063 $sumfnccount->{$2} = 0;
1065 if (defined($testname))
1067 if (!defined($testfnccount->{$2})) {
1068 $testfnccount->{$2} = 0;
1074 /^FNDA:(\d+),([^,]+)/ && do
1076 # Function call count found, add to structure
1077 # Add summary counts
1078 $sumfnccount->{$2} += $1;
1080 # Add test-specific counts
1081 if (defined($testname))
1083 $testfnccount->{$2} += $1;
1087 /^end_of_record/ && do
1089 # Found end of section marker
1092 # Store current section data
1093 if (defined($testname))
1095 $testdata->{$testname} =
1097 $testfncdata->{$testname} =
1101 set_info_entry
($data, $testdata,
1102 $sumcount, $funcdata,
1103 $checkdata, $testfncdata,
1105 $result{$filename} = $data;
1116 # Calculate hit and found values for lines and functions of each file
1117 foreach $filename (keys(%result))
1119 $data = $result{$filename};
1121 ($testdata, $sumcount, undef, undef, $testfncdata,
1122 $sumfnccount) = get_info_entry
($data);
1124 # Filter out empty files
1125 if (scalar(keys(%{$sumcount})) == 0)
1127 delete($result{$filename});
1130 # Filter out empty test cases
1131 foreach $testname (keys(%{$testdata}))
1133 if (!defined($testdata->{$testname}) ||
1134 scalar(keys(%{$testdata->{$testname}})) == 0)
1136 delete($testdata->{$testname});
1137 delete($testfncdata->{$testname});
1141 $data->{"found"} = scalar(keys(%{$sumcount}));
1144 foreach (keys(%{$sumcount}))
1146 if ($sumcount->{$_} > 0) { $hitcount++; }
1149 $data->{"hit"} = $hitcount;
1151 # Get found/hit values for function call data
1152 $data->{"f_found"} = scalar(keys(%{$sumfnccount}));
1155 foreach (keys(%{$sumfnccount})) {
1156 if ($sumfnccount->{$_} > 0) {
1160 $data->{"f_hit"} = $hitcount;
1163 if (scalar(keys(%result)) == 0)
1165 die("ERROR: no valid records found in tracefile $tracefile\n");
1169 warn("WARNING: negative counts found in tracefile ".
1172 if ($changed_testname)
1174 warn("WARNING: invalid characters removed from testname in ".
1175 "tracefile $tracefile\n");
1183 # get_info_entry(hash_ref)
1185 # Retrieve data from an entry of the structure generated by read_info_file().
1186 # Return a list of references to hashes:
1187 # (test data hash ref, sum count hash ref, funcdata hash ref, checkdata hash
1188 # ref, testfncdata hash ref, sumfnccount hash ref, lines found, lines hit,
1189 # functions found, functions hit)
1192 sub get_info_entry
($)
1194 my $testdata_ref = $_[0]->{"test"};
1195 my $sumcount_ref = $_[0]->{"sum"};
1196 my $funcdata_ref = $_[0]->{"func"};
1197 my $checkdata_ref = $_[0]->{"check"};
1198 my $testfncdata = $_[0]->{"testfnc"};
1199 my $sumfnccount = $_[0]->{"sumfnc"};
1200 my $lines_found = $_[0]->{"found"};
1201 my $lines_hit = $_[0]->{"hit"};
1202 my $f_found = $_[0]->{"f_found"};
1203 my $f_hit = $_[0]->{"f_hit"};
1205 return ($testdata_ref, $sumcount_ref, $funcdata_ref, $checkdata_ref,
1206 $testfncdata, $sumfnccount, $lines_found, $lines_hit,
1212 # set_info_entry(hash_ref, testdata_ref, sumcount_ref, funcdata_ref,
1213 # checkdata_ref, testfncdata_ref, sumfcncount_ref[,lines_found,
1214 # lines_hit, f_found, f_hit])
1216 # Update the hash referenced by HASH_REF with the provided data references.
1219 sub set_info_entry
($$$$$$$;$$$$)
1221 my $data_ref = $_[0];
1223 $data_ref->{"test"} = $_[1];
1224 $data_ref->{"sum"} = $_[2];
1225 $data_ref->{"func"} = $_[3];
1226 $data_ref->{"check"} = $_[4];
1227 $data_ref->{"testfnc"} = $_[5];
1228 $data_ref->{"sumfnc"} = $_[6];
1230 if (defined($_[7])) { $data_ref->{"found"} = $_[7]; }
1231 if (defined($_[8])) { $data_ref->{"hit"} = $_[8]; }
1232 if (defined($_[9])) { $data_ref->{"f_found"} = $_[9]; }
1233 if (defined($_[10])) { $data_ref->{"f_hit"} = $_[10]; }
1238 # add_counts(data1_ref, data2_ref)
1240 # DATA1_REF and DATA2_REF are references to hashes containing a mapping
1242 # line number -> execution count
1244 # Return a list (RESULT_REF, LINES_FOUND, LINES_HIT) where RESULT_REF
1245 # is a reference to a hash containing the combined mapping in which
1246 # execution counts are added.
1251 my %data1 = %{$_[0]}; # Hash 1
1252 my %data2 = %{$_[1]}; # Hash 2
1253 my %result; # Resulting hash
1254 my $line; # Current line iteration scalar
1255 my $data1_count; # Count of line in hash1
1256 my $data2_count; # Count of line in hash2
1257 my $found = 0; # Total number of lines found
1258 my $hit = 0; # Number of lines with a count > 0
1260 foreach $line (keys(%data1))
1262 $data1_count = $data1{$line};
1263 $data2_count = $data2{$line};
1265 # Add counts if present in both hashes
1266 if (defined($data2_count)) { $data1_count += $data2_count; }
1268 # Store sum in %result
1269 $result{$line} = $data1_count;
1272 if ($data1_count > 0) { $hit++; }
1275 # Add lines unique to data2
1276 foreach $line (keys(%data2))
1278 # Skip lines already in data1
1279 if (defined($data1{$line})) { next; }
1281 # Copy count from data2
1282 $result{$line} = $data2{$line};
1285 if ($result{$line} > 0) { $hit++; }
1288 return (\
%result, $found, $hit);
1293 # merge_checksums(ref1, ref2, filename)
1295 # REF1 and REF2 are references to hashes containing a mapping
1297 # line number -> checksum
1299 # Merge checksum lists defined in REF1 and REF2 and return reference to
1300 # resulting hash. Die if a checksum for a line is defined in both hashes
1301 # but does not match.
1304 sub merge_checksums
($$$)
1308 my $filename = $_[2];
1312 foreach $line (keys(%{$ref1}))
1314 if (defined($ref2->{$line}) &&
1315 ($ref1->{$line} ne $ref2->{$line}))
1317 die("ERROR: checksum mismatch at $filename:$line\n");
1319 $result{$line} = $ref1->{$line};
1322 foreach $line (keys(%{$ref2}))
1324 $result{$line} = $ref2->{$line};
1332 # merge_func_data(funcdata1, funcdata2, filename)
1335 sub merge_func_data
($$$)
1337 my ($funcdata1, $funcdata2, $filename) = @_;
1341 %result = %{$funcdata1};
1343 foreach $func (keys(%{$funcdata2})) {
1344 my $line1 = $result{$func};
1345 my $line2 = $funcdata2->{$func};
1347 if (defined($line1) && ($line1 != $line2)) {
1348 warn("WARNING: function data mismatch at ".
1349 "$filename:$line2\n");
1352 $result{$func} = $line2;
1360 # add_fnccount(fnccount1, fnccount2)
1362 # Add function call count data. Return list (fnccount_added, f_found, f_hit)
1365 sub add_fnccount
($$)
1367 my ($fnccount1, $fnccount2) = @_;
1373 %result = %{$fnccount1};
1374 foreach $function (keys(%{$fnccount2})) {
1375 $result{$function} += $fnccount2->{$function};
1377 $f_found = scalar(keys(%result));
1379 foreach $function (keys(%result)) {
1380 if ($result{$function} > 0) {
1385 return (\
%result, $f_found, $f_hit);
1389 # add_testfncdata(testfncdata1, testfncdata2)
1391 # Add function call count data for several tests. Return reference to
1392 # added_testfncdata.
1395 sub add_testfncdata
($$)
1397 my ($testfncdata1, $testfncdata2) = @_;
1401 foreach $testname (keys(%{$testfncdata1})) {
1402 if (defined($testfncdata2->{$testname})) {
1405 # Function call count data for this testname exists
1406 # in both data sets: merge
1407 ($fnccount) = add_fnccount
(
1408 $testfncdata1->{$testname},
1409 $testfncdata2->{$testname});
1410 $result{$testname} = $fnccount;
1413 # Function call count data for this testname is unique to
1415 $result{$testname} = $testfncdata1->{$testname};
1418 # Add count data for testnames unique to data set 2
1419 foreach $testname (keys(%{$testfncdata2})) {
1420 if (!defined($result{$testname})) {
1421 $result{$testname} = $testfncdata2->{$testname};
1428 # combine_info_entries(entry_ref1, entry_ref2, filename)
1430 # Combine .info data entry hashes referenced by ENTRY_REF1 and ENTRY_REF2.
1431 # Return reference to resulting hash.
1434 sub combine_info_entries
($$$)
1436 my $entry1 = $_[0]; # Reference to hash containing first entry
1444 my $entry2 = $_[1]; # Reference to hash containing second entry
1452 my %result; # Hash containing combined entry
1453 my %result_testdata;
1454 my $result_sumcount = {};
1455 my $result_funcdata;
1456 my $result_testfncdata;
1457 my $result_sumfnccount;
1464 my $filename = $_[2];
1467 ($testdata1, $sumcount1, $funcdata1, $checkdata1, $testfncdata1,
1468 $sumfnccount1) = get_info_entry
($entry1);
1469 ($testdata2, $sumcount2, $funcdata2, $checkdata2, $testfncdata2,
1470 $sumfnccount2) = get_info_entry
($entry2);
1473 $checkdata1 = merge_checksums
($checkdata1, $checkdata2, $filename);
1476 $result_funcdata = merge_func_data
($funcdata1, $funcdata2, $filename);
1478 # Combine function call count data
1479 $result_testfncdata = add_testfncdata
($testfncdata1, $testfncdata2);
1480 ($result_sumfnccount, $f_found, $f_hit) =
1481 add_fnccount
($sumfnccount1, $sumfnccount2);
1484 foreach $testname (keys(%{$testdata1}))
1486 if (defined($testdata2->{$testname}))
1488 # testname is present in both entries, requires
1490 ($result_testdata{$testname}) =
1491 add_counts
($testdata1->{$testname},
1492 $testdata2->{$testname});
1496 # testname only present in entry1, add to result
1497 $result_testdata{$testname} = $testdata1->{$testname};
1500 # update sum count hash
1501 ($result_sumcount, $lines_found, $lines_hit) =
1502 add_counts
($result_sumcount,
1503 $result_testdata{$testname});
1506 foreach $testname (keys(%{$testdata2}))
1508 # Skip testnames already covered by previous iteration
1509 if (defined($testdata1->{$testname})) { next; }
1511 # testname only present in entry2, add to result hash
1512 $result_testdata{$testname} = $testdata2->{$testname};
1514 # update sum count hash
1515 ($result_sumcount, $lines_found, $lines_hit) =
1516 add_counts
($result_sumcount,
1517 $result_testdata{$testname});
1520 # Calculate resulting sumcount
1523 set_info_entry
(\
%result, \
%result_testdata, $result_sumcount,
1524 $result_funcdata, $checkdata1, $result_testfncdata,
1525 $result_sumfnccount, $lines_found, $lines_hit,
1533 # combine_info_files(info_ref1, info_ref2)
1535 # Combine .info data in hashes referenced by INFO_REF1 and INFO_REF2. Return
1536 # reference to resulting hash.
1539 sub combine_info_files
($$)
1541 my %hash1 = %{$_[0]};
1542 my %hash2 = %{$_[1]};
1545 foreach $filename (keys(%hash2))
1547 if ($hash1{$filename})
1549 # Entry already exists in hash1, combine them
1551 combine_info_entries
($hash1{$filename},
1557 # Entry is unique in both hashes, simply add to
1559 $hash1{$filename} = $hash2{$filename};
1578 info
("Combining tracefiles.\n");
1580 foreach $tracefile (@add_tracefile)
1582 $current_trace = read_info_file
($tracefile);
1585 $total_trace = combine_info_files
($total_trace,
1590 $total_trace = $current_trace;
1594 # Write combined data
1597 info
("Writing data to $output_filename\n");
1598 open(INFO_HANDLE
, ">$output_filename")
1599 or die("ERROR: cannot write to $output_filename!\n");
1600 write_info_file
(*INFO_HANDLE
, $total_trace);
1601 close(*INFO_HANDLE
);
1605 write_info_file
(*STDOUT
, $total_trace);
1611 # write_info_file(filehandle, data)
1614 sub write_info_file
(*$)
1616 local *INFO_HANDLE
= $_[0];
1617 my %data = %{$_[1]};
1636 foreach $source_file (keys(%data))
1638 $entry = $data{$source_file};
1639 ($testdata, $sumcount, $funcdata, $checkdata, $testfncdata,
1640 $sumfnccount) = get_info_entry
($entry);
1641 foreach $testname (keys(%{$testdata}))
1643 $testcount = $testdata->{$testname};
1644 $testfnccount = $testfncdata->{$testname};
1648 print(INFO_HANDLE
"TN:$testname\n");
1649 print(INFO_HANDLE
"SF:$source_file\n");
1651 # Write function related data
1653 sort({$funcdata->{$a} <=> $funcdata->{$b}}
1654 keys(%{$funcdata})))
1656 print(INFO_HANDLE
"FN:".$funcdata->{$func}.
1659 foreach $func (keys(%{$testfnccount})) {
1660 print(INFO_HANDLE
"FNDA:".
1661 $testfnccount->{$func}.
1664 ($f_found, $f_hit) =
1665 get_func_found_and_hit
($testfnccount);
1666 print(INFO_HANDLE
"FNF:$f_found\n");
1667 print(INFO_HANDLE
"FNH:$f_hit\n");
1669 # Write line related data
1670 foreach $line (sort({$a <=> $b} keys(%{$testcount})))
1672 print(INFO_HANDLE
"DA:$line,".
1673 $testcount->{$line}.
1674 (defined($checkdata->{$line}) &&
1676 ",".$checkdata->{$line} : "")."\n");
1678 if ($testcount->{$line} > 0)
1684 print(INFO_HANDLE
"LF:$found\n");
1685 print(INFO_HANDLE
"LH:$hit\n");
1686 print(INFO_HANDLE
"end_of_record\n");
1693 # transform_pattern(pattern)
1695 # Transform shell wildcard expression to equivalent PERL regular expression.
1696 # Return transformed pattern.
1699 sub transform_pattern
($)
1701 my $pattern = $_[0];
1703 # Escape special chars
1705 $pattern =~ s/\\/\\\\/g;
1706 $pattern =~ s/\//\\\
//g;
1707 $pattern =~ s/\^/\\\^/g;
1708 $pattern =~ s/\$/\\\$/g;
1709 $pattern =~ s/\(/\\\(/g;
1710 $pattern =~ s/\)/\\\)/g;
1711 $pattern =~ s/\[/\\\[/g;
1712 $pattern =~ s/\]/\\\]/g;
1713 $pattern =~ s/\{/\\\{/g;
1714 $pattern =~ s/\}/\\\}/g;
1715 $pattern =~ s/\./\\\./g;
1716 $pattern =~ s/\,/\\\,/g;
1717 $pattern =~ s/\|/\\\|/g;
1718 $pattern =~ s/\+/\\\+/g;
1719 $pattern =~ s/\!/\\\!/g;
1721 # Transform ? => (.) and * => (.*)
1723 $pattern =~ s/\*/\(\.\*\)/g;
1724 $pattern =~ s/\?/\(\.\)/g;
1736 my $data = read_info_file
($extract);
1744 # Need perlreg expressions instead of shell pattern
1745 @pattern_list = map({ transform_pattern
($_); } @ARGV);
1747 # Filter out files which do not match any pattern
1748 foreach $filename (sort(keys(%{$data})))
1752 foreach $pattern (@pattern_list)
1754 $keep ||= ($filename =~ (/^$pattern$/));
1760 delete($data->{$filename});
1764 info
("Extracting $filename\n"),
1769 # Write extracted data
1772 info
("Extracted $extracted files\n");
1773 info
("Writing data to $output_filename\n");
1774 open(INFO_HANDLE
, ">$output_filename")
1775 or die("ERROR: cannot write to $output_filename!\n");
1776 write_info_file
(*INFO_HANDLE
, $data);
1777 close(*INFO_HANDLE
);
1781 write_info_file
(*STDOUT
, $data);
1792 my $data = read_info_file
($remove);
1800 # Need perlreg expressions instead of shell pattern
1801 @pattern_list = map({ transform_pattern
($_); } @ARGV);
1803 # Filter out files that match the pattern
1804 foreach $filename (sort(keys(%{$data})))
1808 foreach $pattern (@pattern_list)
1810 $match_found ||= ($filename =~ (/$pattern$/));
1816 delete($data->{$filename});
1817 info
("Removing $filename\n"),
1825 info
("Deleted $removed files\n");
1826 info
("Writing data to $output_filename\n");
1827 open(INFO_HANDLE
, ">$output_filename")
1828 or die("ERROR: cannot write to $output_filename!\n");
1829 write_info_file
(*INFO_HANDLE
, $data);
1830 close(*INFO_HANDLE
);
1834 write_info_file
(*STDOUT
, $data);
1845 my $data = read_info_file
($list);
1851 info
("Listing contents of $list:\n");
1854 foreach $filename (sort(keys(%{$data})))
1856 $entry = $data->{$filename};
1857 (undef, undef, undef, undef, undef, undef, $found, $hit) =
1858 get_info_entry
($entry);
1859 printf("$filename: $hit of $found lines hit\n");
1865 # get_common_filename(filename1, filename2)
1867 # Check for filename components which are common to FILENAME1 and FILENAME2.
1868 # Upon success, return
1870 # (common, path1, path2)
1872 # or 'undef' in case there are no such parts.
1875 sub get_common_filename
($$)
1877 my @list1 = split("/", $_[0]);
1878 my @list2 = split("/", $_[1]);
1881 # Work in reverse order, i.e. beginning with the filename itself
1882 while (@list1 && @list2 && ($list1[$#list1] eq $list2[$#list2]))
1884 unshift(@result, pop(@list1));
1888 # Did we find any similarities?
1889 if (scalar(@result) > 0)
1891 return (join("/", @result), join("/", @list1),
1902 # strip_directories($path, $depth)
1904 # Remove DEPTH leading directory levels from PATH.
1907 sub strip_directories
($$)
1909 my $filename = $_[0];
1913 if (!defined($depth) || ($depth < 1))
1917 for ($i = 0; $i < $depth; $i++)
1919 $filename =~ s/^[^\/]*\/+(.*)$/$1/;
1926 # read_diff(filename)
1928 # Read diff output from FILENAME to memory. The diff file has to follow the
1929 # format generated by 'diff -u'. Returns a list of hash references:
1931 # (mapping, path mapping)
1933 # mapping: filename -> reference to line hash
1934 # line hash: line number in new file -> corresponding line number in old file
1936 # path mapping: filename -> old filename
1938 # Die in case of error.
1943 my $diff_file = $_[0]; # Name of diff file
1944 my %diff; # Resulting mapping filename -> line hash
1945 my %paths; # Resulting mapping old path -> new path
1946 my $mapping; # Reference to current line hash
1947 my $line; # Contents of current line
1948 my $num_old; # Current line number in old file
1949 my $num_new; # Current line number in new file
1950 my $file_old; # Name of old file in diff section
1951 my $file_new; # Name of new file in diff section
1952 my $filename; # Name of common filename of diff section
1953 my $in_block = 0; # Non-zero while we are inside a diff block
1954 local *HANDLE
; # File handle for reading the diff file
1956 info
("Reading diff $diff_file\n");
1958 # Check if file exists and is readable
1962 die("ERROR: cannot read file $diff_file!\n");
1965 # Check if this is really a plain file
1968 die("ERROR: not a plain file: $diff_file!\n");
1971 # Check for .gz extension
1972 if ($diff_file =~ /\.gz$/)
1974 # Check for availability of GZIP tool
1975 system_no_output
(1, "gunzip", "-h")
1976 and die("ERROR: gunzip command not available!\n");
1978 # Check integrity of compressed file
1979 system_no_output
(1, "gunzip", "-t", $diff_file)
1980 and die("ERROR: integrity check failed for ".
1981 "compressed file $diff_file!\n");
1983 # Open compressed file
1984 open(HANDLE
, "gunzip -c $diff_file|")
1985 or die("ERROR: cannot start gunzip to decompress ".
1990 # Open decompressed file
1991 open(HANDLE
, $diff_file)
1992 or die("ERROR: cannot read file $_[0]!\n");
1995 # Parse diff file line by line
2003 # Filename of old file:
2004 # --- <filename> <date>
2007 $file_old = strip_directories
($1, $strip);
2010 # Filename of new file:
2011 # +++ <filename> <date>
2012 /^\+\+\+ (\S+)/ && do
2014 # Add last file to resulting hash
2018 $diff{$filename} = $mapping;
2019 $mapping = \
%new_hash;
2021 $file_new = strip_directories
($1, $strip);
2022 $filename = $file_old;
2023 $paths{$filename} = $file_new;
2028 # Start of diff block:
2029 # @@ -old_start,old_num, +new_start,new_num @@
2030 /^\@\@\s+-(\d+),(\d+)\s+\+(\d+),(\d+)\s+\@\@$/ && do
2033 while ($num_old < $1)
2035 $mapping->{$num_new} = $num_old;
2042 # <line starts with blank>
2049 $mapping->{$num_new} = $num_old;
2054 # Line as seen in old file
2055 # <line starts with '-'>
2065 # Line as seen in new file
2066 # <line starts with '+'>
2083 $mapping->{$num_new} = $num_old;
2093 # Add final diff file section to resulting hash
2096 $diff{$filename} = $mapping;
2101 die("ERROR: no valid diff data found in $diff_file!\n".
2102 "Make sure to use 'diff -u' when generating the diff ".
2105 return (\
%diff, \
%paths);
2110 # apply_diff($count_data, $line_hash)
2112 # Transform count data using a mapping of lines:
2114 # $count_data: reference to hash: line number -> data
2115 # $line_hash: reference to hash: line number new -> line number old
2117 # Return a reference to transformed count data.
2122 my $count_data = $_[0]; # Reference to data hash: line -> hash
2123 my $line_hash = $_[1]; # Reference to line hash: new line -> old line
2124 my %result; # Resulting hash
2125 my $last_new = 0; # Last new line number found in line hash
2126 my $last_old = 0; # Last old line number found in line hash
2128 # Iterate all new line numbers found in the diff
2129 foreach (sort({$a <=> $b} keys(%{$line_hash})))
2132 $last_old = $line_hash->{$last_new};
2134 # Is there data associated with the corresponding old line?
2135 if (defined($count_data->{$line_hash->{$_}}))
2137 # Copy data to new hash with a new line number
2138 $result{$_} = $count_data->{$line_hash->{$_}};
2141 # Transform all other lines which come after the last diff entry
2142 foreach (sort({$a <=> $b} keys(%{$count_data})))
2144 if ($_ <= $last_old)
2146 # Skip lines which were covered by line hash
2149 # Copy data to new hash with an offset
2150 $result{$_ + ($last_new - $last_old)} = $count_data->{$_};
2158 # get_hash_max(hash_ref)
2160 # Return the highest integer key from hash.
2168 foreach (keys(%{$hash})) {
2169 if (!defined($max)) {
2171 } elsif ($hash->{$_} > $max) {
2178 sub get_hash_reverse
($)
2183 foreach (keys(%{$hash})) {
2184 $result{$hash->{$_}} = $_;
2191 # apply_diff_to_funcdata(funcdata, line_hash)
2194 sub apply_diff_to_funcdata
($$)
2196 my ($funcdata, $linedata) = @_;
2197 my $last_new = get_hash_max
($linedata);
2198 my $last_old = $linedata->{$last_new};
2201 my $line_diff = get_hash_reverse
($linedata);
2203 foreach $func (keys(%{$funcdata})) {
2204 my $line = $funcdata->{$func};
2206 if (defined($line_diff->{$line})) {
2207 $result{$func} = $line_diff->{$line};
2208 } elsif ($line > $last_old) {
2209 $result{$func} = $line + $last_new - $last_old;
2218 # get_line_hash($filename, $diff_data, $path_data)
2220 # Find line hash in DIFF_DATA which matches FILENAME. On success, return list
2221 # line hash. or undef in case of no match. Die if more than one line hashes in
2225 sub get_line_hash
($$$)
2227 my $filename = $_[0];
2228 my $diff_data = $_[1];
2229 my $path_data = $_[2];
2238 foreach (keys(%{$diff_data}))
2240 # Try to match diff filename with filename
2241 if ($filename =~ /^\Q$diff_path\E\/$_$/)
2245 # Two files match, choose the more specific one
2246 # (the one with more path components)
2247 $old_depth = ($diff_name =~ tr/\///);
2248 $new_depth = (tr/\///);
2249 if ($old_depth == $new_depth)
2251 die("ERROR: diff file contains ".
2252 "ambiguous entries for ".
2255 elsif ($new_depth > $old_depth)
2268 # Get converted path
2269 if ($filename =~ /^(.*)$diff_name$/)
2271 ($common, $old_path, $new_path) =
2272 get_common_filename
($filename,
2273 $1.$path_data->{$diff_name});
2275 return ($diff_data->{$diff_name}, $old_path, $new_path);
2285 # convert_paths(trace_data, path_conversion_data)
2287 # Rename all paths in TRACE_DATA which show up in PATH_CONVERSION_DATA.
2290 sub convert_paths
($$)
2292 my $trace_data = $_[0];
2293 my $path_conversion_data = $_[1];
2297 if (scalar(keys(%{$path_conversion_data})) == 0)
2299 info
("No path conversion data available.\n");
2303 # Expand path conversion list
2304 foreach $filename (keys(%{$path_conversion_data}))
2306 $new_path = $path_conversion_data->{$filename};
2307 while (($filename =~ s/^(.*)\/[^\/]+$/$1/) &&
2308 ($new_path =~ s/^(.*)\/[^\/]+$/$1/) &&
2309 ($filename ne $new_path))
2311 $path_conversion_data->{$filename} = $new_path;
2316 FILENAME
: foreach $filename (keys(%{$trace_data}))
2318 # Find a path in our conversion table that matches, starting
2319 # with the longest path
2320 foreach (sort({length($b) <=> length($a)}
2321 keys(%{$path_conversion_data})))
2323 # Is this path a prefix of our filename?
2324 if (!($filename =~ /^$_(.*)$/))
2328 $new_path = $path_conversion_data->{$_}.$1;
2330 # Make sure not to overwrite an existing entry under
2332 if ($trace_data->{$new_path})
2334 # Need to combine entries
2335 $trace_data->{$new_path} =
2336 combine_info_entries
(
2337 $trace_data->{$filename},
2338 $trace_data->{$new_path},
2343 # Simply rename entry
2344 $trace_data->{$new_path} =
2345 $trace_data->{$filename};
2347 delete($trace_data->{$filename});
2350 info
("No conversion available for filename $filename\n");
2355 # sub adjust_fncdata(funcdata, testfncdata, sumfnccount)
2357 # Remove function call count data from testfncdata and sumfnccount which
2358 # is no longer present in funcdata.
2361 sub adjust_fncdata
($$$)
2363 my ($funcdata, $testfncdata, $sumfnccount) = @_;
2369 # Remove count data in testfncdata for functions which are no longer
2371 foreach $testname (%{$testfncdata}) {
2372 my $fnccount = $testfncdata->{$testname};
2374 foreach $func (%{$fnccount}) {
2375 if (!defined($funcdata->{$func})) {
2376 delete($fnccount->{$func});
2380 # Remove count data in sumfnccount for functions which are no longer
2382 foreach $func (%{$sumfnccount}) {
2383 if (!defined($funcdata->{$func})) {
2384 delete($sumfnccount->{$func});
2390 # get_func_found_and_hit(sumfnccount)
2392 # Return (f_found, f_hit) for sumfnccount
2395 sub get_func_found_and_hit
($)
2397 my ($sumfnccount) = @_;
2402 $f_found = scalar(keys(%{$sumfnccount}));
2404 foreach $function (keys(%{$sumfnccount})) {
2405 if ($sumfnccount->{$function} > 0) {
2409 return ($f_found, $f_hit);
2418 my $trace_data = read_info_file
($diff);
2423 my %path_conversion_data;
2443 ($diff_data, $path_data) = read_diff
($ARGV[0]);
2445 foreach $filename (sort(keys(%{$trace_data})))
2447 # Find a diff section corresponding to this file
2448 ($line_hash, $old_path, $new_path) =
2449 get_line_hash
($filename, $diff_data, $path_data);
2452 # There's no diff section for this file
2457 if ($old_path && $new_path && ($old_path ne $new_path))
2459 $path_conversion_data{$old_path} = $new_path;
2461 # Check for deleted files
2462 if (scalar(keys(%{$line_hash})) == 0)
2464 info
("Removing $filename\n");
2465 delete($trace_data->{$filename});
2468 info
("Converting $filename\n");
2469 $entry = $trace_data->{$filename};
2470 ($testdata, $sumcount, $funcdata, $checkdata, $testfncdata,
2471 $sumfnccount) = get_info_entry
($entry);
2473 foreach $testname (keys(%{$testdata}))
2475 $testdata->{$testname} =
2476 apply_diff
($testdata->{$testname}, $line_hash);
2477 # Remove empty sets of test data
2478 if (scalar(keys(%{$testdata->{$testname}})) == 0)
2480 delete($testdata->{$testname});
2481 delete($testfncdata->{$testname});
2484 # Rename test data to indicate conversion
2485 foreach $testname (keys(%{$testdata}))
2487 # Skip testnames which already contain an extension
2488 if ($testname =~ /,[^,]+$/)
2492 # Check for name conflict
2493 if (defined($testdata->{$testname.",diff"}))
2496 ($testdata->{$testname}) = add_counts
(
2497 $testdata->{$testname},
2498 $testdata->{$testname.",diff"});
2499 delete($testdata->{$testname.",diff"});
2500 # Add function call counts
2501 ($testfncdata->{$testname}) = add_fnccount
(
2502 $testfncdata->{$testname},
2503 $testfncdata->{$testname.",diff"});
2504 delete($testfncdata->{$testname.",diff"});
2506 # Move test data to new testname
2507 $testdata->{$testname.",diff"} = $testdata->{$testname};
2508 delete($testdata->{$testname});
2509 # Move function call count data to new testname
2510 $testfncdata->{$testname.",diff"} =
2511 $testfncdata->{$testname};
2512 delete($testfncdata->{$testname});
2514 # Convert summary of test data
2515 $sumcount = apply_diff
($sumcount, $line_hash);
2516 # Convert function data
2517 $funcdata = apply_diff_to_funcdata
($funcdata, $line_hash);
2518 # Convert checksum data
2519 $checkdata = apply_diff
($checkdata, $line_hash);
2520 # Convert function call count data
2521 adjust_fncdata
($funcdata, $testfncdata, $sumfnccount);
2522 ($f_found, $f_hit) = get_func_found_and_hit
($sumfnccount);
2523 # Update found/hit numbers
2526 foreach (keys(%{$sumcount}))
2529 if ($sumcount->{$_} > 0)
2536 # Store converted entry
2537 set_info_entry
($entry, $testdata, $sumcount, $funcdata,
2538 $checkdata, $testfncdata, $sumfnccount,
2539 $found, $hit, $f_found, $f_hit);
2543 # Remove empty data set
2544 delete($trace_data->{$filename});
2548 # Convert filenames as well if requested
2549 if ($convert_filenames)
2551 convert_paths
($trace_data, \
%path_conversion_data);
2554 info
("$converted entr".($converted != 1 ?
"ies" : "y")." converted, ".
2555 "$unchanged entr".($unchanged != 1 ?
"ies" : "y")." left ".
2561 info
("Writing data to $output_filename\n");
2562 open(INFO_HANDLE
, ">$output_filename")
2563 or die("ERROR: cannot write to $output_filename!\n");
2564 write_info_file
(*INFO_HANDLE
, $trace_data);
2565 close(*INFO_HANDLE
);
2569 write_info_file
(*STDOUT
, $trace_data);
2575 # system_no_output(mode, parameters)
2577 # Call an external program using PARAMETERS while suppressing depending on
2578 # the value of MODE:
2580 # MODE & 1: suppress STDOUT
2581 # MODE & 2: suppress STDERR
2583 # Return 0 on success, non-zero otherwise.
2586 sub system_no_output
($@
)
2593 # Save old stdout and stderr handles
2594 ($mode & 1) && open(OLD_STDOUT
, ">>&STDOUT");
2595 ($mode & 2) && open(OLD_STDERR
, ">>&STDERR");
2597 # Redirect to /dev/null
2598 ($mode & 1) && open(STDOUT
, ">/dev/null");
2599 ($mode & 2) && open(STDERR
, ">/dev/null");
2604 # Close redirected handles
2605 ($mode & 1) && close(STDOUT
);
2606 ($mode & 2) && close(STDERR
);
2608 # Restore old handles
2609 ($mode & 1) && open(STDOUT
, ">>&OLD_STDOUT");
2610 ($mode & 2) && open(STDERR
, ">>&OLD_STDERR");
2617 # read_config(filename)
2619 # Read configuration file FILENAME and return a reference to a hash containing
2620 # all valid key=value pairs found.
2625 my $filename = $_[0];
2631 if (!open(HANDLE
, "<$filename"))
2633 warn("WARNING: cannot read configuration file $filename\n");
2641 # Remove leading blanks
2643 # Remove trailing blanks
2646 ($key, $value) = split(/\s*=\s*/, $_, 2);
2647 if (defined($key) && defined($value))
2649 $result{$key} = $value;
2653 warn("WARNING: malformed statement in line $. ".
2654 "of configuration file $filename\n");
2665 # REF is a reference to a hash containing the following mapping:
2667 # key_string => var_ref
2669 # where KEY_STRING is a keyword and VAR_REF is a reference to an associated
2670 # variable. If the global configuration hash CONFIG contains a value for
2671 # keyword KEY_STRING, VAR_REF will be assigned the value for that keyword.
2678 foreach (keys(%{$ref}))
2680 if (defined($config->{$_}))
2682 ${$ref->{$_}} = $config->{$_};
2691 warn("$tool_name: $msg");
2698 die("$tool_name: $msg");