By moving the call to Load() up in SearchProvider::Start(), we are giving a chance...
[chromium-blink-merge.git] / third_party / lcov / bin / lcov
blob6304d7510970f50399aa4b71379a9c44ef9314e2
1 #!/usr/bin/perl -w
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
20 # lcov
22 # This is a wrapper script which provides a single interface for accessing
23 # LCOV coverage data.
26 # History:
27 # 2002-08-29 created by Peter Oberparleiter <Peter.Oberparleiter@de.ibm.com>
28 # IBM Lab Boeblingen
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
39 # a test name
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
52 # --no-checksum
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
62 use strict;
63 use File::Basename;
64 use Getopt::Long;
67 # Global constants
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";
94 # Prototypes
95 sub print_usage(*);
96 sub check_options();
97 sub userspace_reset();
98 sub userspace_capture();
99 sub kernel_reset();
100 sub kernel_capture();
101 sub add_traces();
102 sub read_info_file($);
103 sub get_info_entry($);
104 sub set_info_entry($$$$$$$;$$$$);
105 sub add_counts($$);
106 sub merge_checksums($$$);
107 sub combine_info_entries($$$);
108 sub combine_info_files($$);
109 sub write_info_file(*$);
110 sub extract();
111 sub remove();
112 sub list();
113 sub get_common_filename($$);
114 sub read_diff($);
115 sub diff();
116 sub system_no_output($@);
117 sub read_config($);
118 sub apply_config($);
119 sub info(@);
120 sub unload_module($);
121 sub check_and_load_kernel_module();
122 sub create_temp_dir();
123 sub transform_pattern($);
124 sub warn_handler($);
125 sub die_handler($);
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
156 our $gcov_tool;
157 our $ignore_errors;
158 our $initial;
159 our $no_recursion = 0;
160 our $maxdepth;
161 our $config; # Configuration file contents
162 chomp($cwd);
163 our $tool_dir = dirname($0); # Directory where genhtml tool is installed
167 # Code entry point
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");
189 if ($config)
191 # Copy configuration file values to variables
192 apply_config({
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,
203 "list=s" => \$list,
204 "kernel-directory=s" => \@kernel_directory,
205 "extract=s" => \$extract,
206 "remove=s" => \$remove,
207 "diff=s" => \$diff,
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,
214 "quiet" => \$quiet,
215 "help|?" => \$help,
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");
231 exit(1);
233 else
235 # Merge options
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
250 if ($help)
252 print_usage(*STDOUT);
253 exit(0);
256 # Check for version option
257 if ($version)
259 print("$tool_name: $lcov_version\n");
260 exit(0);
263 # Normalize --path text
264 $diff_path =~ s/\/$//;
266 if ($follow)
268 $follow = "-follow";
270 else
272 $follow = "";
275 if ($no_recursion)
277 $maxdepth = "-maxdepth 1";
279 else
281 $maxdepth = "";
284 # Check for valid options
285 check_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 "-"));
297 if ($capture)
299 if (!$to_file)
301 # Option that tells geninfo to write to stdout
302 $output_filename = "-";
305 else
307 if ($initial)
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
315 if ($reset)
317 # Differentiate between user space and kernel reset
318 if (@directory)
320 userspace_reset();
322 else
324 kernel_reset();
327 elsif ($capture)
329 # Differentiate between user space and kernel
330 if (@directory)
332 userspace_capture();
334 else
336 kernel_capture();
339 elsif (@add_tracefile)
341 add_traces();
343 elsif ($remove)
345 remove();
347 elsif ($extract)
349 extract();
351 elsif ($list)
353 list();
355 elsif ($diff)
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");
362 diff();
365 info("Done.\n");
366 exit(0);
369 # print_usage(handle)
371 # Print usage information.
374 sub print_usage(*)
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.
385 Misc:
386 -h, --help Print this help, then exit
387 -v, --version Print version number, then exit
388 -q, --quiet Do not print progress messages
390 Operation:
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
399 Options:
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
417 END_OF_USAGE
423 # check_options()
425 # Check for valid combination of command line options. Die on error.
428 sub check_options()
430 my $i = 0;
432 # Count occurrence of mutually exclusive options
433 $reset && $i++;
434 $capture && $i++;
435 @add_tracefile && $i++;
436 $extract && $i++;
437 $remove && $i++;
438 $list && $i++;
439 $diff && $i++;
441 if ($i == 0)
443 die("Need one of the options -z, -c, -a, -e, -r, -l or ".
444 "--diff\n".
445 "Use $tool_name --help to get usage information\n");
447 elsif ($i > 1)
449 die("ERROR: only one of -z, -c, -a, -e, -r, -l or ".
450 "--diff allowed!\n".
451 "Use $tool_name --help to get usage information\n");
457 # userspace_reset()
459 # Reset coverage data found in DIRECTORY by deleting all contained .da files.
461 # Die on error.
464 sub userspace_reset()
466 my $current_dir;
467 my @file_list;
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`;
474 chomp(@file_list);
475 foreach (@file_list)
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.
489 # Die on error.
492 sub userspace_capture()
494 my @param;
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);
503 if ($test_name)
505 @param = (@param, "--test-name", $test_name);
507 if ($follow)
509 @param = (@param, "--follow");
511 if ($quiet)
513 @param = (@param, "--quiet");
515 if (defined($checksum))
517 if ($checksum)
519 @param = (@param, "--checksum");
521 else
523 @param = (@param, "--no-checksum");
526 if ($base_directory)
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");
538 if ($gcov_tool)
540 @param = (@param, "--gcov-tool", $gcov_tool);
542 if ($ignore_errors)
544 @param = (@param, "--ignore-errors", $ignore_errors);
546 if ($initial)
548 @param = (@param, "--initial");
550 if ($no_recursion)
552 @param = (@param, "--no-recursion");
555 system(@param);
556 exit($? >> 8);
561 # kernel_reset()
563 # Reset kernel coverage.
565 # Die on error.
568 sub kernel_reset()
570 local *HANDLE;
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");
576 print(HANDLE "0");
577 close(HANDLE);
579 # Unload module if we loaded it in the first place
580 if ($need_unload)
582 unload_module($need_unload);
588 # kernel_capture()
590 # Capture kernel coverage data and write it to OUTPUT_FILENAME if specified,
591 # otherwise stdout.
594 sub kernel_capture()
596 my @param;
598 check_and_load_kernel_module();
600 # Make sure the temporary directory is removed upon script termination
603 if ($temp_dir_name)
605 stat($temp_dir_name);
606 if (-r _)
608 info("Removing temporary directory ".
609 "$temp_dir_name\n");
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");
631 else
633 # Prefix list of kernel sub-directories with the gcov kernel
634 # directory
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",
645 "{}", ";")
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",
651 "{}", ";")
652 and die("ERROR: cannot modify access rights for ".
653 "$temp_dir_name!\n");
655 # Capture data
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);
662 if ($test_name)
664 @param = (@param, "--test-name", $test_name);
666 if ($follow)
668 @param = (@param, "--follow");
670 if ($quiet)
672 @param = (@param, "--quiet");
674 if (defined($checksum))
676 if ($checksum)
678 @param = (@param, "--checksum");
680 else
682 @param = (@param, "--no-checksum");
685 if ($base_directory)
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");
697 if ($gcov_tool)
699 @param = (@param, "--gcov-tool", $gcov_tool);
701 if ($ignore_errors)
703 @param = (@param, "--ignore-errors", $ignore_errors);
705 if ($initial)
707 @param = (@param, "--initial");
709 system(@param) and exit($? >> 8);
712 # Unload module if we loaded it in the first place
713 if ($need_unload)
715 unload_module($need_unload);
721 # info(printf_parameter)
723 # Use printf to write PRINTF_PARAMETER to stdout only when the $quiet flag
724 # is not set.
727 sub info(@)
729 if (!$quiet)
731 # Print info string
732 if ($to_file)
734 print(@_)
736 else
738 # Don't interfer with the .info output to STDOUT
739 printf(STDERR @_);
746 # Check if the gcov kernel module is loaded. If it is, exit, if not, try
747 # to load it.
749 # Die on error.
752 sub check_and_load_kernel_module()
754 my $module_name;
756 # Is it loaded already?
757 stat("$gcov_dir");
758 if (-r _) { return(); }
760 info("Loading required gcov kernel module.\n");
762 # Do we have access to the insmod tool?
763 stat($insmod_tool);
764 if (!-x _)
766 die("ERROR: need insmod tool ($insmod_tool) to access kernel ".
767 "coverage data!\n");
769 # Do we have access to the modprobe tool?
770 stat($modprobe_tool);
771 if (!-x _)
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
781 # /lib/modules
782 if (system_no_output(3, $modprobe_tool, $module_name) == 0)
784 # Succeeded
785 $need_unload = $module_name;
786 return();
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)
793 # Succeeded
794 $need_unload = $module_name;
795 return();
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)
802 # Succeeded
803 $need_unload = $module_name;
804 return();
808 # Hm, loading failed - maybe we aren't root?
809 if ($> != 0)
811 die("ERROR: need root access to load kernel module!\n");
814 die("ERROR: cannot load required gcov kernel module!\n");
819 # unload_module()
821 # Unload the gcov kernel module.
824 sub unload_module($)
826 my $module = $_[0];
828 info("Unloading kernel module $module\n");
830 # Do we have access to the rmmod tool?
831 stat($rmmod_tool);
832 if (!-x _)
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 ".
841 "$module!\n");
846 # create_temp_dir()
848 # Create a temporary directory and return its path.
850 # Die on error.
853 sub create_temp_dir()
855 my $dirname;
856 my $number = sprintf("%d", rand(1000));
858 # Endless loops are evil
859 while ($number++ < 1000)
861 $dirname = "$tmp_dir/$tmp_prefix$number";
862 stat($dirname);
863 if (-e _) { next; }
865 mkdir($dirname)
866 or die("ERROR: cannot create temporary directory ".
867 "$dirname!\n");
869 return($dirname);
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
908 # this file.
910 # Die on error.
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
918 my $testdata; # " "
919 my $testcount; # " "
920 my $sumcount; # " "
921 my $funcdata; # " "
922 my $checkdata; # " "
923 my $testfncdata;
924 my $testfnccount;
925 my $sumfnccount;
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
939 stat($_[0]);
940 if (!(-r _))
942 die("ERROR: cannot read file $_[0]!\n");
945 # Check if this is really a plain file
946 if (!(-f _))
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 ".
966 "file $_[0]!\n");
968 else
970 # Open decompressed file
971 open(INFO_HANDLE, $_[0])
972 or die("ERROR: cannot read file $_[0]!\n");
975 $testname = "";
976 while (<INFO_HANDLE>)
978 chomp($_);
979 $line = $_;
981 # Switch statement
982 foreach ($line)
984 /^TN:([^,]*)/ && do
986 # Test name information found
987 $testname = defined($1) ? $1 : "";
988 if ($testname =~ s/\W/_/g)
990 $changed_testname = 1;
992 last;
995 /^[SK]F:(.*)/ && do
997 # Filename information found
998 # Retrieve data for new entry
999 $filename = $1;
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};
1011 else
1013 $testcount = {};
1014 $testfnccount = {};
1016 last;
1019 /^DA:(\d+),(-?\d+)(,[^,\s]+)?/ && do
1021 # Fix negative counts
1022 $count = $2 < 0 ? 0 : $2;
1023 if ($2 < 0)
1025 $negative = 1;
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
1038 if (defined($3))
1040 $line_checksum = substr($3, 1);
1042 # Does it match a previous definition
1043 if (defined($checkdata->{$1}) &&
1044 ($checkdata->{$1} ne
1045 $line_checksum))
1047 die("ERROR: checksum mismatch ".
1048 "at $filename:$1\n");
1051 $checkdata->{$1} = $line_checksum;
1053 last;
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;
1071 last;
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;
1085 last;
1087 /^end_of_record/ && do
1089 # Found end of section marker
1090 if ($filename)
1092 # Store current section data
1093 if (defined($testname))
1095 $testdata->{$testname} =
1096 $testcount;
1097 $testfncdata->{$testname} =
1098 $testfnccount;
1101 set_info_entry($data, $testdata,
1102 $sumcount, $funcdata,
1103 $checkdata, $testfncdata,
1104 $sumfnccount);
1105 $result{$filename} = $data;
1106 last;
1110 # default
1111 last;
1114 close(INFO_HANDLE);
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});
1128 next;
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}));
1142 $hitcount = 0;
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}));
1153 $hitcount = 0;
1155 foreach (keys(%{$sumfnccount})) {
1156 if ($sumfnccount->{$_} > 0) {
1157 $hitcount++;
1160 $data->{"f_hit"} = $hitcount;
1163 if (scalar(keys(%result)) == 0)
1165 die("ERROR: no valid records found in tracefile $tracefile\n");
1167 if ($negative)
1169 warn("WARNING: negative counts found in tracefile ".
1170 "$tracefile\n");
1172 if ($changed_testname)
1174 warn("WARNING: invalid characters removed from testname in ".
1175 "tracefile $tracefile\n");
1178 return(\%result);
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,
1207 $f_found, $f_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.
1249 sub add_counts($$)
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;
1271 $found++;
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};
1284 $found++;
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($$$)
1306 my $ref1 = $_[0];
1307 my $ref2 = $_[1];
1308 my $filename = $_[2];
1309 my %result;
1310 my $line;
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};
1327 return \%result;
1332 # merge_func_data(funcdata1, funcdata2, filename)
1335 sub merge_func_data($$$)
1337 my ($funcdata1, $funcdata2, $filename) = @_;
1338 my %result;
1339 my $func;
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");
1350 next;
1352 $result{$func} = $line2;
1355 return \%result;
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) = @_;
1368 my %result;
1369 my $f_found;
1370 my $f_hit;
1371 my $function;
1373 %result = %{$fnccount1};
1374 foreach $function (keys(%{$fnccount2})) {
1375 $result{$function} += $fnccount2->{$function};
1377 $f_found = scalar(keys(%result));
1378 $f_hit = 0;
1379 foreach $function (keys(%result)) {
1380 if ($result{$function} > 0) {
1381 $f_hit++;
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) = @_;
1398 my %result;
1399 my $testname;
1401 foreach $testname (keys(%{$testfncdata1})) {
1402 if (defined($testfncdata2->{$testname})) {
1403 my $fnccount;
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;
1411 next;
1413 # Function call count data for this testname is unique to
1414 # data set 1: copy
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};
1424 return \%result;
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
1437 my $testdata1;
1438 my $sumcount1;
1439 my $funcdata1;
1440 my $checkdata1;
1441 my $testfncdata1;
1442 my $sumfnccount1;
1444 my $entry2 = $_[1]; # Reference to hash containing second entry
1445 my $testdata2;
1446 my $sumcount2;
1447 my $funcdata2;
1448 my $checkdata2;
1449 my $testfncdata2;
1450 my $sumfnccount2;
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;
1458 my $lines_found;
1459 my $lines_hit;
1460 my $f_found;
1461 my $f_hit;
1463 my $testname;
1464 my $filename = $_[2];
1466 # Retrieve data
1467 ($testdata1, $sumcount1, $funcdata1, $checkdata1, $testfncdata1,
1468 $sumfnccount1) = get_info_entry($entry1);
1469 ($testdata2, $sumcount2, $funcdata2, $checkdata2, $testfncdata2,
1470 $sumfnccount2) = get_info_entry($entry2);
1472 # Merge checksums
1473 $checkdata1 = merge_checksums($checkdata1, $checkdata2, $filename);
1475 # Combine funcdata
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);
1483 # Combine testdata
1484 foreach $testname (keys(%{$testdata1}))
1486 if (defined($testdata2->{$testname}))
1488 # testname is present in both entries, requires
1489 # combination
1490 ($result_testdata{$testname}) =
1491 add_counts($testdata1->{$testname},
1492 $testdata2->{$testname});
1494 else
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
1522 # Store result
1523 set_info_entry(\%result, \%result_testdata, $result_sumcount,
1524 $result_funcdata, $checkdata1, $result_testfncdata,
1525 $result_sumfnccount, $lines_found, $lines_hit,
1526 $f_found, $f_hit);
1528 return(\%result);
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]};
1543 my $filename;
1545 foreach $filename (keys(%hash2))
1547 if ($hash1{$filename})
1549 # Entry already exists in hash1, combine them
1550 $hash1{$filename} =
1551 combine_info_entries($hash1{$filename},
1552 $hash2{$filename},
1553 $filename);
1555 else
1557 # Entry is unique in both hashes, simply add to
1558 # resulting hash
1559 $hash1{$filename} = $hash2{$filename};
1563 return(\%hash1);
1568 # add_traces()
1571 sub add_traces()
1573 my $total_trace;
1574 my $current_trace;
1575 my $tracefile;
1576 local *INFO_HANDLE;
1578 info("Combining tracefiles.\n");
1580 foreach $tracefile (@add_tracefile)
1582 $current_trace = read_info_file($tracefile);
1583 if ($total_trace)
1585 $total_trace = combine_info_files($total_trace,
1586 $current_trace);
1588 else
1590 $total_trace = $current_trace;
1594 # Write combined data
1595 if ($to_file)
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);
1603 else
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]};
1618 my $source_file;
1619 my $entry;
1620 my $testdata;
1621 my $sumcount;
1622 my $funcdata;
1623 my $checkdata;
1624 my $testfncdata;
1625 my $sumfnccount;
1626 my $testname;
1627 my $line;
1628 my $func;
1629 my $testcount;
1630 my $testfnccount;
1631 my $found;
1632 my $hit;
1633 my $f_found;
1634 my $f_hit;
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};
1645 $found = 0;
1646 $hit = 0;
1648 print(INFO_HANDLE "TN:$testname\n");
1649 print(INFO_HANDLE "SF:$source_file\n");
1651 # Write function related data
1652 foreach $func (
1653 sort({$funcdata->{$a} <=> $funcdata->{$b}}
1654 keys(%{$funcdata})))
1656 print(INFO_HANDLE "FN:".$funcdata->{$func}.
1657 ",$func\n");
1659 foreach $func (keys(%{$testfnccount})) {
1660 print(INFO_HANDLE "FNDA:".
1661 $testfnccount->{$func}.
1662 ",$func\n");
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}) &&
1675 $checksum ?
1676 ",".$checkdata->{$line} : "")."\n");
1677 $found++;
1678 if ($testcount->{$line} > 0)
1680 $hit++;
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;
1726 return $pattern;
1731 # extract()
1734 sub extract()
1736 my $data = read_info_file($extract);
1737 my $filename;
1738 my $keep;
1739 my $pattern;
1740 my @pattern_list;
1741 my $extracted = 0;
1742 local *INFO_HANDLE;
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})))
1750 $keep = 0;
1752 foreach $pattern (@pattern_list)
1754 $keep ||= ($filename =~ (/^$pattern$/));
1758 if (!$keep)
1760 delete($data->{$filename});
1762 else
1764 info("Extracting $filename\n"),
1765 $extracted++;
1769 # Write extracted data
1770 if ($to_file)
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);
1779 else
1781 write_info_file(*STDOUT, $data);
1787 # remove()
1790 sub remove()
1792 my $data = read_info_file($remove);
1793 my $filename;
1794 my $match_found;
1795 my $pattern;
1796 my @pattern_list;
1797 my $removed = 0;
1798 local *INFO_HANDLE;
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})))
1806 $match_found = 0;
1808 foreach $pattern (@pattern_list)
1810 $match_found ||= ($filename =~ (/$pattern$/));
1814 if ($match_found)
1816 delete($data->{$filename});
1817 info("Removing $filename\n"),
1818 $removed++;
1822 # Write data
1823 if ($to_file)
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);
1832 else
1834 write_info_file(*STDOUT, $data);
1840 # list()
1843 sub list()
1845 my $data = read_info_file($list);
1846 my $filename;
1847 my $found;
1848 my $hit;
1849 my $entry;
1851 info("Listing contents of $list:\n");
1853 # List all files
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]);
1879 my @result;
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));
1885 pop(@list2);
1888 # Did we find any similarities?
1889 if (scalar(@result) > 0)
1891 return (join("/", @result), join("/", @list1),
1892 join("/", @list2));
1894 else
1896 return undef;
1902 # strip_directories($path, $depth)
1904 # Remove DEPTH leading directory levels from PATH.
1907 sub strip_directories($$)
1909 my $filename = $_[0];
1910 my $depth = $_[1];
1911 my $i;
1913 if (!defined($depth) || ($depth < 1))
1915 return $filename;
1917 for ($i = 0; $i < $depth; $i++)
1919 $filename =~ s/^[^\/]*\/+(.*)$/$1/;
1921 return $filename;
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.
1941 sub read_diff($)
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
1959 stat($diff_file);
1960 if (!(-r _))
1962 die("ERROR: cannot read file $diff_file!\n");
1965 # Check if this is really a plain file
1966 if (!(-f _))
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 ".
1986 "file $_[0]!\n");
1988 else
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
1996 while (<HANDLE>)
1998 chomp($_);
1999 $line = $_;
2001 foreach ($line)
2003 # Filename of old file:
2004 # --- <filename> <date>
2005 /^--- (\S+)/ && do
2007 $file_old = strip_directories($1, $strip);
2008 last;
2010 # Filename of new file:
2011 # +++ <filename> <date>
2012 /^\+\+\+ (\S+)/ && do
2014 # Add last file to resulting hash
2015 if ($filename)
2017 my %new_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;
2024 $num_old = 1;
2025 $num_new = 1;
2026 last;
2028 # Start of diff block:
2029 # @@ -old_start,old_num, +new_start,new_num @@
2030 /^\@\@\s+-(\d+),(\d+)\s+\+(\d+),(\d+)\s+\@\@$/ && do
2032 $in_block = 1;
2033 while ($num_old < $1)
2035 $mapping->{$num_new} = $num_old;
2036 $num_old++;
2037 $num_new++;
2039 last;
2041 # Unchanged line
2042 # <line starts with blank>
2043 /^ / && do
2045 if ($in_block == 0)
2047 last;
2049 $mapping->{$num_new} = $num_old;
2050 $num_old++;
2051 $num_new++;
2052 last;
2054 # Line as seen in old file
2055 # <line starts with '-'>
2056 /^-/ && do
2058 if ($in_block == 0)
2060 last;
2062 $num_old++;
2063 last;
2065 # Line as seen in new file
2066 # <line starts with '+'>
2067 /^\+/ && do
2069 if ($in_block == 0)
2071 last;
2073 $num_new++;
2074 last;
2076 # Empty line
2077 /^$/ && do
2079 if ($in_block == 0)
2081 last;
2083 $mapping->{$num_new} = $num_old;
2084 $num_old++;
2085 $num_new++;
2086 last;
2091 close(HANDLE);
2093 # Add final diff file section to resulting hash
2094 if ($filename)
2096 $diff{$filename} = $mapping;
2099 if (!%diff)
2101 die("ERROR: no valid diff data found in $diff_file!\n".
2102 "Make sure to use 'diff -u' when generating the diff ".
2103 "file.\n");
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.
2120 sub apply_diff($$)
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})))
2131 $last_new = $_;
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
2147 next;
2149 # Copy data to new hash with an offset
2150 $result{$_ + ($last_new - $last_old)} = $count_data->{$_};
2153 return \%result;
2158 # get_hash_max(hash_ref)
2160 # Return the highest integer key from hash.
2163 sub get_hash_max($)
2165 my ($hash) = @_;
2166 my $max;
2168 foreach (keys(%{$hash})) {
2169 if (!defined($max)) {
2170 $max = $_;
2171 } elsif ($hash->{$_} > $max) {
2172 $max = $_;
2175 return $max;
2178 sub get_hash_reverse($)
2180 my ($hash) = @_;
2181 my %result;
2183 foreach (keys(%{$hash})) {
2184 $result{$hash->{$_}} = $_;
2187 return \%result;
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};
2199 my $func;
2200 my %result;
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;
2213 return \%result;
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
2222 # DIFF_DATA match.
2225 sub get_line_hash($$$)
2227 my $filename = $_[0];
2228 my $diff_data = $_[1];
2229 my $path_data = $_[2];
2230 my $conversion;
2231 my $old_path;
2232 my $new_path;
2233 my $diff_name;
2234 my $common;
2235 my $old_depth;
2236 my $new_depth;
2238 foreach (keys(%{$diff_data}))
2240 # Try to match diff filename with filename
2241 if ($filename =~ /^\Q$diff_path\E\/$_$/)
2243 if ($diff_name)
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 ".
2253 "$filename\n");
2255 elsif ($new_depth > $old_depth)
2257 $diff_name = $_;
2260 else
2262 $diff_name = $_;
2266 if ($diff_name)
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);
2277 else
2279 return undef;
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];
2294 my $filename;
2295 my $new_path;
2297 if (scalar(keys(%{$path_conversion_data})) == 0)
2299 info("No path conversion data available.\n");
2300 return;
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;
2315 # Adjust paths
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 =~ /^$_(.*)$/))
2326 next;
2328 $new_path = $path_conversion_data->{$_}.$1;
2330 # Make sure not to overwrite an existing entry under
2331 # that path name
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},
2339 $filename);
2341 else
2343 # Simply rename entry
2344 $trace_data->{$new_path} =
2345 $trace_data->{$filename};
2347 delete($trace_data->{$filename});
2348 next 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) = @_;
2364 my $testname;
2365 my $func;
2366 my $f_found;
2367 my $f_hit;
2369 # Remove count data in testfncdata for functions which are no longer
2370 # in funcdata
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
2381 # in funcdata
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) = @_;
2398 my $function;
2399 my $f_found;
2400 my $f_hit;
2402 $f_found = scalar(keys(%{$sumfnccount}));
2403 $f_hit = 0;
2404 foreach $function (keys(%{$sumfnccount})) {
2405 if ($sumfnccount->{$function} > 0) {
2406 $f_hit++;
2409 return ($f_found, $f_hit);
2413 # diff()
2416 sub diff()
2418 my $trace_data = read_info_file($diff);
2419 my $diff_data;
2420 my $path_data;
2421 my $old_path;
2422 my $new_path;
2423 my %path_conversion_data;
2424 my $filename;
2425 my $line_hash;
2426 my $new_name;
2427 my $entry;
2428 my $testdata;
2429 my $testname;
2430 my $sumcount;
2431 my $funcdata;
2432 my $checkdata;
2433 my $testfncdata;
2434 my $sumfnccount;
2435 my $found;
2436 my $hit;
2437 my $f_found;
2438 my $f_hit;
2439 my $converted = 0;
2440 my $unchanged = 0;
2441 local *INFO_HANDLE;
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);
2450 if (!$line_hash)
2452 # There's no diff section for this file
2453 $unchanged++;
2454 next;
2456 $converted++;
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});
2466 next;
2468 info("Converting $filename\n");
2469 $entry = $trace_data->{$filename};
2470 ($testdata, $sumcount, $funcdata, $checkdata, $testfncdata,
2471 $sumfnccount) = get_info_entry($entry);
2472 # Convert test data
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 =~ /,[^,]+$/)
2490 next;
2492 # Check for name conflict
2493 if (defined($testdata->{$testname.",diff"}))
2495 # Add counts
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
2524 $found = 0;
2525 $hit = 0;
2526 foreach (keys(%{$sumcount}))
2528 $found++;
2529 if ($sumcount->{$_} > 0)
2531 $hit++;
2534 if ($found > 0)
2536 # Store converted entry
2537 set_info_entry($entry, $testdata, $sumcount, $funcdata,
2538 $checkdata, $testfncdata, $sumfnccount,
2539 $found, $hit, $f_found, $f_hit);
2541 else
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 ".
2556 "unchanged.\n");
2558 # Write data
2559 if ($to_file)
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);
2567 else
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($@)
2588 my $mode = shift;
2589 my $result;
2590 local *OLD_STDERR;
2591 local *OLD_STDOUT;
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");
2601 system(@_);
2602 $result = $?;
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");
2612 return $result;
2617 # read_config(filename)
2619 # Read configuration file FILENAME and return a reference to a hash containing
2620 # all valid key=value pairs found.
2623 sub read_config($)
2625 my $filename = $_[0];
2626 my %result;
2627 my $key;
2628 my $value;
2629 local *HANDLE;
2631 if (!open(HANDLE, "<$filename"))
2633 warn("WARNING: cannot read configuration file $filename\n");
2634 return undef;
2636 while (<HANDLE>)
2638 chomp;
2639 # Skip comments
2640 s/#.*//;
2641 # Remove leading blanks
2642 s/^\s+//;
2643 # Remove trailing blanks
2644 s/\s+$//;
2645 next unless length;
2646 ($key, $value) = split(/\s*=\s*/, $_, 2);
2647 if (defined($key) && defined($value))
2649 $result{$key} = $value;
2651 else
2653 warn("WARNING: malformed statement in line $. ".
2654 "of configuration file $filename\n");
2657 close(HANDLE);
2658 return \%result;
2663 # apply_config(REF)
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.
2674 sub apply_config($)
2676 my $ref = $_[0];
2678 foreach (keys(%{$ref}))
2680 if (defined($config->{$_}))
2682 ${$ref->{$_}} = $config->{$_};
2687 sub warn_handler($)
2689 my ($msg) = @_;
2691 warn("$tool_name: $msg");
2694 sub die_handler($)
2696 my ($msg) = @_;
2698 die("$tool_name: $msg");