summaryrefslogtreecommitdiffstats
path: root/third_party/lcov/bin/mcov
blob: e6d344665f15674f6576ff19a5520ef2ac8bea66 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
#!/usr/bin/perl -w
# 
# mcov: script to convert gcov data to lcov format on Mac.
#
# Based on lcov (http://ltp.sourceforge.net/coverage/lcov.php)
# Written by ajeya at google dot com.
#
# usage:
# mcov --directory <base directory> --output <output file> --verbose <level>
#
 
use strict;

use Cwd;
use File::Basename;
use File::Find;
use File::Spec::Functions;
use Getopt::Long;

# function prototypes
sub process_dafile(@);
sub canonical_path(@);
sub split_filename(@);
sub read_gcov_header(@);
sub read_gcov_file(@);

# scalars with default values
my $directory = Cwd::abs_path(Cwd::getcwd);
my $data_file_extension = ".gcda";
my $output_filename = "output.lcov";
my $gcov_tool  = "/usr/bin/gcov";
my $verbosity = 0;

# TODO(ajeya): GetOptions doesn't handle case where the script is called with
# no arguments. This needs to be fixed.
my $result = GetOptions("directory|d=s" => \$directory,
                         "output|o=s" => \$output_filename,
                         "verbose" => \$verbosity);                        
if (!$result) {
  print "Usage: $0  --directory <base directory> --output <output file>";
  print " [--verbose <level>]\n";
  exit(1);
}

# convert the directory path to absolute path.
$directory = Cwd::abs_path($directory);

# convert the output file path to absolute path.
$output_filename = Cwd::abs_path($output_filename);

# Output expected args for buildbot debugging assistance.
my $cwd = getcwd();
print "mcov: after abs_pathing\n";
print "mcov: getcwd() = $cwd\n";
print "mcov: directory for data files is $directory\n";
print "mcov: output filename is $output_filename\n";

# Sanity check; die if path is wrong.
# We don't check for output_filename because... we create it.
if (! -d $directory) {
  print "mcov: Bad args passed; exiting with error.\n";
  exit(1);
}

# open file for output
open(INFO_HANDLE, ">$output_filename");

my @file_list;  # scalar to hold the list of all gcda files.
if (-d $directory) {
  printf("Scanning $directory for $data_file_extension files ...\n");
  find(sub {
         my $file = $_;
         if ($file =~ m/\Q$data_file_extension\E$/i) {
           push(@file_list, Cwd::abs_path($file));
         }},
       $directory);
  printf("Found %d data files in %s\n", $#file_list + 1, $directory);
}

# Process all files in list
foreach my $file (@file_list) {
  process_dafile($file);
}
close(INFO_HANDLE);

# Remove the misc gcov files that are created.
my @gcov_list = glob("*.gcov");
foreach my $gcov_file (@gcov_list) {
  unlink($gcov_file);
}

exit(0);

# end of script

# process_dafile:
# argument(s): a file path with gcda extension
# returns: void 
# This method calls gcov to generate the coverage data and write the output in
# lcov format to the output file.
sub process_dafile(@) {
  my ($filename) = @_;
  print("Processing $filename ...\n");

  my $da_filename;   # Name of data file to process
  my $base_name;     # data filename without ".da/.gcda" extension
  my $gcov_error;    # Error code of gcov tool
  my $object_dir;    # Directory containing all object files
  my $gcov_file;     # Name of a .gcov file
  my @gcov_data;     # Contents of a .gcov file
  my @gcov_list;     # List of generated .gcov files
  my $base_dir;      # Base directory for current da file
  local *OLD_STDOUT; # Handle to store STDOUT temporarily
  
  # Get directory and basename of data file
  ($base_dir, $base_name) = split_filename(canonical_path($filename));

  # Check for writable $base_dir (gcov will try to write files there)
  if (!-w $base_dir) {
    print("ERROR: cannot write to directory $base_dir\n");
    return;
  }

  # Construct name of graph file
  $da_filename = File::Spec::Functions::catfile($base_dir,
                                                join(".", $base_name, "gcno"));

  # Ignore empty graph file (e.g. source file with no statement)
  if (-z $da_filename) {
    warn("WARNING: empty $da_filename (skipped)\n");
    return;
  }
    
  # Set $object_dir to real location of object files. This may differ
  # from $base_dir if the graph file is just a link to the "real" object
  # file location.
  $object_dir = dirname($da_filename);
  
  # Save the current STDOUT to OLD_STDOUT and set STDOUT to /dev/null to mute
  # standard output.
  if (!$verbosity) {
    open(OLD_STDOUT, ">>&STDOUT");
    open(STDOUT, ">/dev/null");
  }
  
  # run gcov utility with the supplied gcno file and object directory.
  $gcov_error = system($gcov_tool, $da_filename, "-o", $object_dir);
  
  # Restore STDOUT if we changed it before.
  if (!$verbosity) {
    open(STDOUT, ">>&OLD_STDOUT");
  }

  if ($gcov_error) {
    warn("WARNING: GCOV failed for $da_filename!\n");
    return;
  }

  # Collect data from resulting .gcov files and create .info file
  @gcov_list = glob("*.gcov");
  # Check for files
  if (!scalar(@gcov_list)) {
    warn("WARNING: gcov did not create any files for $da_filename!\n");
  }
  
  foreach $gcov_file (@gcov_list) {
    my $source_filename = read_gcov_header($gcov_file);
    
    if (!defined($source_filename)) {
      next;
    }
    
    $source_filename = canonical_path($source_filename);
    
    # Read in contents of gcov file
    @gcov_data = read_gcov_file($gcov_file);
    
    # Skip empty files
    if (!scalar(@gcov_data)) {
      warn("WARNING: skipping empty file $gcov_file\n");
      unlink($gcov_file);
      next;
    }
        
    print(INFO_HANDLE "SF:", Cwd::abs_path($source_filename), "\n");

    # Write coverage information for each instrumented line
    # Note: @gcov_content contains a list of (flag, count, source)
    # tuple for each source code line
    while (@gcov_data) {
      # Check for instrumented line 
      if ($gcov_data[0]) {
        print(INFO_HANDLE "DA:", $gcov_data[3], ",", $gcov_data[1], "\n");
      }
      # Remove already processed data from array
      splice(@gcov_data,0,4);  
    }
    print(INFO_HANDLE "end_of_record\n");
    
    # Remove .gcov file after processing
    unlink($gcov_file);
  } #end for_each
}

# canonical_path:
# argument(s): any file path
# returns: the file path as a string
# 
# clean up the file path being passed.
sub canonical_path(@) {
  my ($filename) = @_;
  return (File::Spec::Functions::canonpath($filename));
}

# split_filename:
# argument(s): any file path
# returns: an array with the path components
# 
# splits the file path into path and filename (with no extension).
sub split_filename(@){
  my ($filename) = @_;
  my ($base, $path, $ext) = File::Basename::fileparse($filename, '\.[^\.]*');
  return ($path, $base);
}

# read_gcov_header:
# argument(s): path to gcov file
# returns: an array the contens of the gcov header.
# 
# reads the gcov file and returns the parsed contents of a gcov header as an 
# array.
sub read_gcov_header(@) {
  my ($filename) = @_;
  my $source;
  local *INPUT;

  if (!open(INPUT, $filename)) {
    warn("WARNING: cannot read $filename!\n");
    return (undef,undef);
  }
  
  my @lines = <INPUT>;
  foreach my $line (@lines) {
    chomp($line);
    # check for lines with source string. 
    if ($line =~ /^\s+-:\s+0:Source:(.*)$/) {
      # Source: header entry
      $source = $1;
    } else {
      last;
    }
  }
  close(INPUT);
  return $source;
}

# read_gcov_file:
# argument(s): path to gcov file
# returns: an array with the contents of the gcov file.
# 
# reads the gcov file and returns the parsed contents of a gcov file
# as an array.
sub read_gcov_file(@) {
  my ($filename) = @_;
  my @result = ();
  my $number;
  local *INPUT;

  if (!open(INPUT, $filename)) {
    warn("WARNING: cannot read $filename!\n");
    return @result;
  }
  
  # Parse gcov output and populate the array
  my @lines = <INPUT>;
  foreach my $line (@lines) {
    chomp($line);
    if ($line =~ /^\s*([^:]+):\s*(\d+?):(.*)$/) {
      # <exec count>:<line number>:<source code>
      
      if ($1 eq "-") {
        # Uninstrumented line
        push(@result, 0);
        push(@result, 0);
        push(@result, $3);
        push(@result, $2);
      } elsif ($2 eq "0") {
        #ignore comments and other header info
      } else {
        # Source code execution data
        $number = $1;
        # Check for zero count
        if ($number eq "#####") {
          $number = 0;
        }
        push(@result, 1);
        push(@result, $number);
        push(@result, $3);
        push(@result, $2);
      }  
    }
  }
  close(INPUT);
  return @result;
}