OLD | NEW |
(Empty) | |
| 1 #!/usr/bin/perl -w |
| 2 # |
| 3 # mcov: script to convert gcov data to lcov format on Mac. |
| 4 # |
| 5 # Based on lcov (http://ltp.sourceforge.net/coverage/lcov.php) |
| 6 # Written by ajeya at google dot com. |
| 7 # |
| 8 # usage: |
| 9 # mcov --directory <base directory> --output <output file> --verbose <level> |
| 10 # |
| 11 |
| 12 use strict; |
| 13 |
| 14 use Cwd; |
| 15 use File::Basename; |
| 16 use File::Find; |
| 17 use File::Spec::Functions; |
| 18 use Getopt::Long; |
| 19 |
| 20 # function prototypes |
| 21 sub process_dafile(@); |
| 22 sub canonical_path(@); |
| 23 sub split_filename(@); |
| 24 sub read_gcov_header(@); |
| 25 sub read_gcov_file(@); |
| 26 |
| 27 # scalars with default values |
| 28 my $directory = Cwd::abs_path(Cwd::getcwd); |
| 29 my $data_file_extension = ".gcda"; |
| 30 my $output_filename = "output.lcov"; |
| 31 my $gcov_tool = "/usr/bin/gcov"; |
| 32 my $verbosity = 0; |
| 33 |
| 34 # TODO(ajeya): GetOptions doesn't handle case where the script is called with |
| 35 # no arguments. This needs to be fixed. |
| 36 my $result = GetOptions("directory|d=s" => \$directory, |
| 37 "output|o=s" => \$output_filename, |
| 38 "verbose" => \$verbosity); |
| 39 if (!$result) { |
| 40 print "Usage: $0 --directory <base directory> --output <output file>"; |
| 41 print " [--verbose <level>]\n"; |
| 42 exit(1); |
| 43 } |
| 44 |
| 45 # convert the directory path to absolute path. |
| 46 $directory = Cwd::abs_path($directory); |
| 47 |
| 48 # convert the output file path to absolute path. |
| 49 $output_filename = Cwd::abs_path($output_filename); |
| 50 |
| 51 # open file for output |
| 52 open(INFO_HANDLE, ">$output_filename"); |
| 53 |
| 54 my @file_list; # scalar to hold the list of all gcda files. |
| 55 if (-d $directory) { |
| 56 printf("Scanning $directory for $data_file_extension files ...\n"); |
| 57 find(sub { |
| 58 my $file = $_; |
| 59 if ($file =~ m/\Q$data_file_extension\E$/i) { |
| 60 push(@file_list, Cwd::abs_path($file)); |
| 61 }}, |
| 62 $directory); |
| 63 printf("Found %d data files in %s\n", $#file_list + 1, $directory); |
| 64 } |
| 65 |
| 66 # Process all files in list |
| 67 foreach my $file (@file_list) { |
| 68 process_dafile($file); |
| 69 } |
| 70 close(INFO_HANDLE); |
| 71 |
| 72 # Remove the misc gcov files that are created. |
| 73 my @gcov_list = glob("*.gcov"); |
| 74 foreach my $gcov_file (@gcov_list) { |
| 75 unlink($gcov_file); |
| 76 } |
| 77 |
| 78 exit(0); |
| 79 |
| 80 # end of script |
| 81 |
| 82 # process_dafile: |
| 83 # argument(s): a file path with gcda extension |
| 84 # returns: void |
| 85 # This method calls gcov to generate the coverage data and write the output in |
| 86 # lcov format to the output file. |
| 87 sub process_dafile(@) { |
| 88 my ($filename) = @_; |
| 89 print("Processing $filename ...\n"); |
| 90 |
| 91 my $da_filename; # Name of data file to process |
| 92 my $base_name; # data filename without ".da/.gcda" extension |
| 93 my $gcov_error; # Error code of gcov tool |
| 94 my $object_dir; # Directory containing all object files |
| 95 my $gcov_file; # Name of a .gcov file |
| 96 my @gcov_data; # Contents of a .gcov file |
| 97 my @gcov_list; # List of generated .gcov files |
| 98 my $base_dir; # Base directory for current da file |
| 99 local *OLD_STDOUT; # Handle to store STDOUT temporarily |
| 100 |
| 101 # Get directory and basename of data file |
| 102 ($base_dir, $base_name) = split_filename(canonical_path($filename)); |
| 103 |
| 104 # Check for writable $base_dir (gcov will try to write files there) |
| 105 if (!-w $base_dir) { |
| 106 print("ERROR: cannot write to directory $base_dir\n"); |
| 107 return; |
| 108 } |
| 109 |
| 110 # Construct name of graph file |
| 111 $da_filename = File::Spec::Functions::catfile($base_dir, |
| 112 join(".", $base_name, "gcno")); |
| 113 |
| 114 # Ignore empty graph file (e.g. source file with no statement) |
| 115 if (-z $da_filename) { |
| 116 warn("WARNING: empty $da_filename (skipped)\n"); |
| 117 return; |
| 118 } |
| 119 |
| 120 # Set $object_dir to real location of object files. This may differ |
| 121 # from $base_dir if the graph file is just a link to the "real" object |
| 122 # file location. |
| 123 $object_dir = dirname($da_filename); |
| 124 |
| 125 # Save the current STDOUT to OLD_STDOUT and set STDOUT to /dev/null to mute |
| 126 # standard output. |
| 127 if (!$verbosity) { |
| 128 open(OLD_STDOUT, ">>&STDOUT"); |
| 129 open(STDOUT, ">/dev/null"); |
| 130 } |
| 131 |
| 132 # run gcov utility with the supplied gcno file and object directory. |
| 133 $gcov_error = system($gcov_tool, $da_filename, "-o", $object_dir); |
| 134 |
| 135 # Restore STDOUT if we changed it before. |
| 136 if (!$verbosity) { |
| 137 open(STDOUT, ">>&OLD_STDOUT"); |
| 138 } |
| 139 |
| 140 if ($gcov_error) { |
| 141 warn("WARNING: GCOV failed for $da_filename!\n"); |
| 142 return; |
| 143 } |
| 144 |
| 145 # Collect data from resulting .gcov files and create .info file |
| 146 @gcov_list = glob("*.gcov"); |
| 147 # Check for files |
| 148 if (!scalar(@gcov_list)) { |
| 149 warn("WARNING: gcov did not create any files for $da_filename!\n"); |
| 150 } |
| 151 |
| 152 foreach $gcov_file (@gcov_list) { |
| 153 my $source_filename = read_gcov_header($gcov_file); |
| 154 |
| 155 if (!defined($source_filename)) { |
| 156 next; |
| 157 } |
| 158 |
| 159 $source_filename = canonical_path($source_filename); |
| 160 |
| 161 # Read in contents of gcov file |
| 162 @gcov_data = read_gcov_file($gcov_file); |
| 163 |
| 164 # Skip empty files |
| 165 if (!scalar(@gcov_data)) { |
| 166 warn("WARNING: skipping empty file $gcov_file\n"); |
| 167 unlink($gcov_file); |
| 168 next; |
| 169 } |
| 170 |
| 171 print(INFO_HANDLE "SF:", Cwd::abs_path($source_filename), "\n"); |
| 172 |
| 173 # Write coverage information for each instrumented line |
| 174 # Note: @gcov_content contains a list of (flag, count, source) |
| 175 # tuple for each source code line |
| 176 while (@gcov_data) { |
| 177 # Check for instrumented line |
| 178 if ($gcov_data[0]) { |
| 179 print(INFO_HANDLE "DA:", $gcov_data[3], ",", $gcov_data[1], "\n"); |
| 180 } |
| 181 # Remove already processed data from array |
| 182 splice(@gcov_data,0,4); |
| 183 } |
| 184 print(INFO_HANDLE "end_of_record\n"); |
| 185 |
| 186 # Remove .gcov file after processing |
| 187 unlink($gcov_file); |
| 188 } #end for_each |
| 189 } |
| 190 |
| 191 # canonical_path: |
| 192 # argument(s): any file path |
| 193 # returns: the file path as a string |
| 194 # |
| 195 # clean up the file path being passed. |
| 196 sub canonical_path(@) { |
| 197 my ($filename) = @_; |
| 198 return (File::Spec::Functions::canonpath($filename)); |
| 199 } |
| 200 |
| 201 # split_filename: |
| 202 # argument(s): any file path |
| 203 # returns: an array with the path components |
| 204 # |
| 205 # splits the file path into path and filename (with no extension). |
| 206 sub split_filename(@){ |
| 207 my ($filename) = @_; |
| 208 my ($base, $path, $ext) = File::Basename::fileparse($filename, '\.[^\.]*'); |
| 209 return ($path, $base); |
| 210 } |
| 211 |
| 212 # read_gcov_header: |
| 213 # argument(s): path to gcov file |
| 214 # returns: an array the contens of the gcov header. |
| 215 # |
| 216 # reads the gcov file and returns the parsed contents of a gcov header as an |
| 217 # array. |
| 218 sub read_gcov_header(@) { |
| 219 my ($filename) = @_; |
| 220 my $source; |
| 221 local *INPUT; |
| 222 |
| 223 if (!open(INPUT, $filename)) { |
| 224 warn("WARNING: cannot read $filename!\n"); |
| 225 return (undef,undef); |
| 226 } |
| 227 |
| 228 my @lines = <INPUT>; |
| 229 foreach my $line (@lines) { |
| 230 chomp($line); |
| 231 # check for lines with source string. |
| 232 if ($line =~ /^\s+-:\s+0:Source:(.*)$/) { |
| 233 # Source: header entry |
| 234 $source = $1; |
| 235 } else { |
| 236 last; |
| 237 } |
| 238 } |
| 239 close(INPUT); |
| 240 return $source; |
| 241 } |
| 242 |
| 243 # read_gcov_file: |
| 244 # argument(s): path to gcov file |
| 245 # returns: an array with the contents of the gcov file. |
| 246 # |
| 247 # reads the gcov file and returns the parsed contents of a gcov file |
| 248 # as an array. |
| 249 sub read_gcov_file(@) { |
| 250 my ($filename) = @_; |
| 251 my @result = (); |
| 252 my $number; |
| 253 local *INPUT; |
| 254 |
| 255 ## TODO(ajeya): Exit more gracefully here. |
| 256 open(INPUT, $filename) or die("ERROR: cannot read $filename!\n"); |
| 257 |
| 258 # Parse gcov output and populate the array |
| 259 my @lines = <INPUT>; |
| 260 foreach my $line (@lines) { |
| 261 chomp($line); |
| 262 if ($line =~ /^\s*([^:]+):\s*(\d+?):(.*)$/) { |
| 263 # <exec count>:<line number>:<source code> |
| 264 |
| 265 if ($1 eq "-") { |
| 266 # Uninstrumented line |
| 267 push(@result, 0); |
| 268 push(@result, 0); |
| 269 push(@result, $3); |
| 270 push(@result, $2); |
| 271 } elsif ($2 eq "0") { |
| 272 #ignore comments and other header info |
| 273 } else { |
| 274 # Source code execution data |
| 275 $number = $1; |
| 276 # Check for zero count |
| 277 if ($number eq "#####") { |
| 278 $number = 0; |
| 279 } |
| 280 push(@result, 1); |
| 281 push(@result, $number); |
| 282 push(@result, $3); |
| 283 push(@result, $2); |
| 284 } |
| 285 } |
| 286 } |
| 287 close(INPUT); |
| 288 return @result; |
| 289 } |
OLD | NEW |