mysql-test/lib/mtr_coverage.pl (182 lines of code) (raw):
use strict;
# Extract coverage option
#
# Arguments:
# $option coverage option
# $delim delimiter for splitting the string
# $option_error error to be printed in case of failure
sub coverage_extract_option {
my ($option, $delim, $option_error) = @_;
# check for sanity of option which should be of the format --option=value
if (length($option) == 0) {
print "**** ERROR **** ",
"Invalid coverage option specified for $option_error\n";
exit(1);
}
# split the string on delimiter '='
my @option_arr = split(/$delim/, $option);
$option=$option_arr[$#option_arr];
return $option;
}
# Prepare to generate coverage data
#
# Arguments:
# $build_dir build directory
# $base_dir basedir, normally the home directory
# $scope coverage option which is of the form:
# * full : complete code coverage
# * diff : coverage of the git diff HEAD
# * diff:<commit_hash> : coverage of git diff commit_hash
# $src_path directory path for coverage source files
# $llvm_path directory for llvm coverage binaries
# $format format for coverage report which is of the form:
# * text : text format
# * html : html format
# $src_filter src filter directories
sub coverage_prepare($$$) {
my ($build_dir, $base_dir, $scope, $src_path, $llvm_path, $format,
$src_filter) = @_;
print "Purging coverage information from '$base_dir'...\n";
system("find $base_dir -name \"code\*.profraw\" | xargs rm");
my $scope = coverage_extract_option($scope, "=", "coverage-scope");
my $commit_hash = "HEAD"; # default commit hash is 'HEAD'
# if the coverage scope is "--full" then extract the git commithash
if ($scope =~ m/^diff/) {
my $invalid_commit_hash = 0; # is this commit hash valid?
# if the coverage scope is of the form 'diff:<commit_hash>'
if ($scope =~ /^diff:/) {
$commit_hash = coverage_extract_option($scope, ":",
"coverage-scope");
# sanity check for commit hash
if (length($commit_hash) == 0) {
$invalid_commit_hash = 1;
}
}
# if the coverage scope is of the form '--diff'
elsif ($scope ne "diff") {
$invalid_commit_hash = 1;
}
if ($invalid_commit_hash) {
print "**** ERROR **** ",
"Invalid coverage scope diff option: $scope\n";
exit(1);
}
}
# make sure that the coverage scope is "--full"
elsif ($scope ne "full") {
print "**** ERROR **** ", "Invalid coverage scope: $scope\n";
exit(1);
}
# Update the scope of the coverage
if ($scope eq "full") {
$_[2] = $scope;
}
else {
$_[2] = $commit_hash;
}
# extract directory for coverage source files
$src_path = coverage_extract_option($src_path, "=", "coverage-src-path")."/";
# Update the coverage src path
$_[3] = $src_path;
$llvm_path = coverage_extract_option($llvm_path, "=", "coverage-llvm-path");
# append "/" at the end of the llvm_path
if (length($llvm_path) > 0 && ! ($llvm_path =~ m/\/$/) ) {
$llvm_path .= "/";
}
# Update the coverage llvm path
$_[4] = $llvm_path;
# extract format for coverage report
$format = coverage_extract_option($format, "=", "coverage-format");
# sanity check for coverage format
if ( ! ( ($format eq "text") || ($format eq "html")) ) {
print "**** ERROR **** ", "Invalid coverage-format option: $format\n";
exit(1);
}
$_[5] = $format;
# process "--coverage-src-filter" arguments
$src_filter =~ s/^\s+//g;
my @filter_arr = split(/ /, $src_filter);
my $final_src_filter="";
my $f;
foreach $f (@filter_arr) {
my $filter_path = coverage_extract_option($f, "=", "coverage-src-filter");
if (length($final_src_filter) > 0) {
$final_src_filter .= ",";
}
$final_src_filter .= $filter_path;
}
# Update the coverage src filter
$_[6] = $final_src_filter;
# create the directory to store the generated coverage files
my $mkdir_cmd = "$build_dir/coverage_files";
mkpath($mkdir_cmd);
}
# Get the files modified by a git diff
#
# Arguments:
# $src_dir directory for coverage source files
# $commit_hash git commit hash
sub coverage_get_diff_files ($$) {
my ($src_dir, $commit_hash) = @_;
# command to extract files modified by a git commit hash
my $cmd = "git diff --name-only $commit_hash"."^ $commit_hash";
open(PIPE, "$cmd|");
my $commit_hash_files; # concatenated list of files
while(<PIPE>) {
chomp;
if (/\.h$/ or /\.cc$/) {
$commit_hash_files .= $src_dir.$_." ";
}
}
return $commit_hash_files;
}
# Merge coverage profile files
#
# Arguments:
# $merge_com command for merging the coverage profile files
# $results_dir directory that contains coverage results
sub coverage_merge_prof_files($$) {
my ($merge_com, $result_dir) = @_;
my $err_file = "$result_dir/err";
my $merge_com_err = "$merge_com 2>$err_file";
# If the merge command fails due to corrupted profile files
# then repeat merging the files by deleting the corrupt files
while (1) {
system($merge_com_err);
open(FP, "<", $err_file) or dir $!;
my $got_error = 0; # did we see an error
my $line;
while($line = <FP>) {
$got_error = 1;
chomp($line);
my @files_list = split(":", $line);
my $corrupt_file = $files_list[1];
$corrupt_file =~ s/ //g;
# delete the corrupt file
rmtree("$corrupt_file");
}
# there is no error, bail out
if ($got_error == 0) {
last;
}
}
}
# Collect coverage information
#
# Arguments:
# $build_dir build directory
# $base_dir basedir, normally the home directory
# $binary_path path to mysqld binary
# $scope coverage option which is of the form:
# * full : complete code coverage
# * HEAD : coverage of the git diff HEAD
# * <commit_hash> : coverage of git diff commit_hash
# $src_path directory path for coverage source files
# $llvm_path directory for llvm coverage binaries
# $format format for coverage report which is of the form:
# * text : text format
# * html : html format
# $src_filter src filter directories
# $cov_comand coverage command issued (used for logging)
sub coverage_collect ($$$) {
my ($build_dir, $base_dir, $binary_path, $scope, $src_path, $llvm_path,
$format, $src_filter, $cov_command) = @_;
my $files_modified=""; # list of files modified concatenated into one string
if ($scope ne "full") {
$files_modified = coverage_get_diff_files($src_path, $scope);
}
print "Generating coverage information ";
if (length($files_modified) > 0) {
print "for git commit hash $scope";
if ($scope eq "HEAD") {
# command to extract git commit hash of 'HEAD'
my $cmd = "git rev-parse $scope";
open(PIPE, "$cmd|");
my $head_commit_hash = <PIPE>;
chomp($head_commit_hash);
print " ($head_commit_hash)";
}
}
elsif (length($src_filter) > 0) {
my @dir_paths = $src_filter =~ /,/g;
my $num_dirs = @dir_paths+1;
my $dir_str = "directory";
if ($num_dirs > 1) {
$dir_str = "directories";
}
print "for source files in $dir_str: $src_filter";
}
else {
print "for complete source code ";
}
print " ...\n";
# Create directory to store the coverage results
my $result_dir = "$build_dir/reports/".time();
mkpath($result_dir);
# Recreate the 'last' directory to point to the latest coverage
# results directory
my $last_dir = "$build_dir/reports/last";
rmtree($last_dir);
symlink($result_dir, $last_dir);
# Log the command used for generating coverage in command.log
open(FP, ">$result_dir/command.log");
print FP "$cov_command\n";
close(FP);
# Merge coverage reports using command
# llvm-prof merge --output=file.profdata <list of code*.profraw>
my $merge_cov = $llvm_path."llvm-profdata merge --output=";
$merge_cov .= $result_dir."/combined.profdata ";
$merge_cov .= "`find $build_dir -name \"code*.profraw\"`";
coverage_merge_prof_files($merge_cov, $result_dir);
# Generate coverage report using command
# llvm-cov show <binary_path> --instr-profile=file.profdata \
# --format <text|html> --output-dir=<output_dir>
my $generate_cov
= $llvm_path."llvm-cov show $binary_path -instr-profile=";
$generate_cov .= $result_dir."/combined.profdata ";
# Add the list of files modified if the coverage report is for
# a specific git diff
if (length($files_modified) > 0) {
$generate_cov .= $files_modified;
} elsif (length($src_filter) > 0) {
# if files modified is empty then apply the coverage-src-filter
my @filter_paths = split(/,/, $src_filter);
my $single_filter;
foreach $single_filter (@filter_paths) {
my $find_cmd
= "`find $src_path/$single_filter -name \"*.cc\" -o -name \"*.h\"";
$find_cmd .= " -o -name \"*.c\" -o -name \"*.ic\"`";
$generate_cov .= " $find_cmd ";
}
}
$generate_cov .= "--format $format"; # coverage report format
$generate_cov .= " --output-dir=$result_dir 2>/dev/null";
system($generate_cov);
# Delete profdata file
print "Purging coverage related files...\n";
my $rm_profdata = $result_dir."/combined.profdata";
rmtree($rm_profdata);
my $rm_coverage_files = "$build_dir/coverage_files";
rmtree($rm_coverage_files);
system("find $base_dir -name \"code\*.profraw\" | xargs rm");
print "Completed generating coverage information in ",
"$format format.\n";
print "Coverage results directory: $build_dir/reports/last\n";
}
1;