diff options
Diffstat (limited to 'third_party/lcov/bin/mcov')
-rwxr-xr-x | third_party/lcov/bin/mcov | 289 |
1 files changed, 289 insertions, 0 deletions
diff --git a/third_party/lcov/bin/mcov b/third_party/lcov/bin/mcov new file mode 100755 index 0000000..b04324b --- /dev/null +++ b/third_party/lcov/bin/mcov @@ -0,0 +1,289 @@ +#!/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); + +# 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; + + ## TODO(ajeya): Exit more gracefully here. + open(INPUT, $filename) or die("ERROR: cannot read $filename!\n"); + + # 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; +} |