Skip to content

Commit

Permalink
Initial version for local commits (Percona-QA#376)
Browse files Browse the repository at this point in the history
* PS-8622 Generate a coverage report for files changed locally but not committ
* PS-8642 Support out-of-source builds for differential coverage reports
* Changes to support GCA process
  • Loading branch information
saikumar-vs authored Feb 28, 2023
1 parent f58edb4 commit 79cd2d3
Showing 1 changed file with 97 additions and 45 deletions.
142 changes: 97 additions & 45 deletions Coverage/dgcov.pl
Original file line number Diff line number Diff line change
Expand Up @@ -4,57 +4,102 @@
use warnings;
use Getopt::Long;
use File::Find;
use Cwd 'realpath';
use File::Basename;
use Carp;

# Args
my $help;
my $verbose;
my $sandbox;
my $source=undef;
my $uncommitted;

# Main
my $changedlines = 0;

my $options = GetOptions
(
"verbose" => \$verbose,
"help" => \$help,
"verbose" => \$verbose,
"help" => \$help,
"uncommitted" => \$uncommitted,
"source=s" => \$source,
);


if (not $options) {
print "Use --help for usage\n";
exit 1;
}

usage() if $help;

# Variables

my $file_regexp= qr/\.(c|cc|cpp|h|hpp|i|ic)$/;
my $filemap = {};
my $nosourcemap = {};
my $uncommitt_filemap = {};
my $committ_filemap = {};
my $nosource = {};
my %missing_files;
my $uncovered = 0;
my $instrumented = 0;
my @revisions;
my ($revid1, $revid2);
my $git = "git";

# Main

print("----- Start of report ----- \n");

# Find source location to run the scrip from.
$sandbox = findSource($sandbox);
$source = findSource($source);

# Staged changes only.
# Staged/Working changes only.
if($uncommitted) {
$uncommitt_filemap = find_uncommitted_changes();
my $cmd = "$git status -s -uno $source";
find_changes($cmd);
foreach my $file (keys %$filemap) {
$uncommitt_filemap->{$file} = get_diff_lines($file);
}
} else {
# Add revisions present in this snapshot only.
my $cmd = "$git rev-list HEAD ^origin --first-parent --topo-order";
print "Running: $cmd\n"
if $verbose;
for $_ (`$cmd`) {
chomp($_);
push @revisions, $_;
print("Added revision $_\n")
if $verbose;
}
if ($#revisions < 0) {
print("No local revisions in $source\n");
print("----- End of dgcov.pl report ----- \n");
exit 0;
}
# Find revisions included in the list of revisions.
$revid1= $revisions[0];
$revid2= $revisions[$#revisions];
my $ncmd = "$git diff ";
$ncmd.= "$revid2~..$revid1 ";
$ncmd.= "--name-status --oneline --diff-filter=\"AM\" --pretty=\"%H\" ";
find_changes($ncmd);
foreach my $file (keys %$filemap) {
$committ_filemap->{$file} = get_diff_lines($file);
}
}

for my $file (sort keys %$filemap) {

my $lines = [ ];

$lines = get_cov_lines($uncommitt_filemap->{$file})
if $uncommitt_filemap->{$file};
if (exists $uncommitt_filemap->{$file} and $uncommitted);
$lines = get_cov_lines($committ_filemap->{$file})
if (exists $committ_filemap->{$file} and (not $uncommitted) );

# All lines in revision(s) changed.
$changedlines = $changedlines + scalar @$lines;
next unless @$lines;
my $gcov_file = findGcov($file);

if (defined $gcov_file) {
print "Using Gcov file $gcov_file for $file\n";
my $gcov_dir = dirname($gcov_file);
Expand All @@ -66,7 +111,9 @@
next;
}

my ($cov, $lineno, $code, $full);
my $header = undef;

my $printer = sub {
unless($header) {
print("\nFile: $file\n", '-' x 79, "\n");
Expand All @@ -75,7 +122,6 @@
print($_[0]);
};

my ($cov, $lineno, $code, $full);
while(<FH>) {
next if /^function /; # Skip function summaries.
next if (/^-------/) or (/^_ZN*/); # TODO :: Handle embedded constructor calls.
Expand All @@ -88,7 +134,7 @@
$uncovered++;
$instrumented++;
$printer->("|$full");
} elsif ($lineno eq $_ and $cov =~ /^[ \t]*\d+$/ ) {
} elsif ($lineno eq $_ and $cov =~ /^[ \t]*[0-9]+$/ ) {
$instrumented++;
}
}
Expand All @@ -104,22 +150,24 @@
printf("Line Coverage is %.2f%% of modified code.\n\n", (($instrumented-$uncovered)/$instrumented * 100));
}
print("----- End of report ----- \n");

exit 0;

# Subroutines

sub findGcov {
my ($fname) = @_;
# Seperate dir and filename.
# Seperate dir and filename
my $dir = dirname($fname);
my @dir = split('/', $dir);
# Special case
my @cmake_dir = ("sql_main","sql_dd","sql_gis");
@dir = (@dir, @cmake_dir) if ( grep(/^sql$/, @dir) );
@dir = reverse @dir;
my $file = basename($fname);

my @found;
print "Looking for gcov files for $fname\n" if $verbose;
my $gcov_file = "$file.gcda";
my $gcov_file = "$file.gcno";
find(sub {
if ($_ eq $gcov_file && -e $gcov_file) {
push @found, $File::Find::dir."/".$file.".gcov";
Expand All @@ -138,15 +186,18 @@ sub findGcov {
foreach my $clue (@dir) {
$clue = "$clue.dir";
foreach my $file (@found) {
my $parent = dirname($file);
my $gparent = dirname($parent);
if ($gparent =~ m/$clue/) {
my $fpath = dirname($file);
my @parents = split('/',$fpath);
foreach my $parent (@parents) {
if ($parent =~ m/^$clue$/) {
$gcf = $file;
last;
}
}
# Skip searching once our clue is usefull
last if defined $gcf;
}
last if defined $gcf;
}
# If we cant find based on clue, we'll just pick the first one
if (not defined $gcf && ($#found >= 0)) {
Expand All @@ -165,13 +216,13 @@ sub findGcov {
}

sub findSource {
my ($Sandbox) = @_;
if (defined $Sandbox) {
my $root = "$Sandbox\/.git";
my ($Source) = @_;
if (defined $Source) {
my $root = "$Source\/.git";
if (!-d $root) {
croak "Failed to find git root, this tool must be run within a git working tree";
} else {
return $Sandbox;
return $Source;
}
} else {
if (-e "CMakeCache.txt") {
Expand All @@ -190,46 +241,45 @@ sub findSource {
croak "No source directory found";
}

sub find_uncommitted_changes {
my $cmd;
$cmd = "$git status -s -uno $sandbox";
sub find_changes {
my ($cmd) = @_;
print "Running: $cmd\n"
if $verbose;
open GIT_STAT, "$cmd |"
open GIT_CMD, "$cmd |"
or croak "Failed to spawn '$cmd': $!: $?\n";
while(<GIT_STAT>) {
while(<GIT_CMD>) {
next unless /(A|M)\s+(.*)$/;
my $file = $2;
$file = realpath($file) if $uncommitted;
$file = $source."/".$file if not $uncommitted;
next if ($file =~ /unittest/);
if ($file =~ $file_regexp)
{
printf "File %s added in revision(s) list\n", $file
if $verbose;
$filemap->{$file} = 1;
printf "Added file %s\n", $file
if $verbose and (not exists $filemap->{$file});
$filemap->{$file} = 1 if (not exists $filemap->{$file});
}
else
{
printf "Skipping non source file %s\n", $file
if $verbose;
$nosource->{$file}= 1;
if $verbose;
$nosourcemap->{$file} = 1;
}
}
close GIT_STAT
close GIT_CMD
or croak "Command '$cmd' failed: $!: $?\n";

my $uncom_filemap = {};
foreach my $file (keys %$filemap) {
$uncom_filemap->{$file} = get_diff_changes($file);
}
return $uncom_filemap;
}

sub get_diff_changes {
sub get_diff_lines {
my ($file) = @_;
my $modified = [ ];
my $cmd = "$git diff -U0 $sandbox\/$file";
my $cmd = "$git diff -U0 ";
if (not $uncommitted) {
$cmd .= "$revid2~..$revid1 ";
}
$cmd .= "$file";
print "Running: $cmd\n"
if $verbose;
if $verbose;
open GIT_DIFF, "$cmd |"
or croak "Failed to spawn '$cmd': $!: $?\n";
while(<GIT_DIFF>) {
Expand Down Expand Up @@ -268,7 +318,8 @@ sub get_cov_lines {
my $type = shift @$elements;
if($type eq 'm') {
my ($from, $to) = @$elements;
push @$new_lines, ($from .. $to);
$to=$to-1 if ($from!=$to);
push @$new_lines, ($from..$to);
} else {
croak "Unable to get coverage info lines";
}
Expand All @@ -284,7 +335,8 @@ sub usage {
--help Display script usage information.
--verbose Show command outputs by setting verbosity with 1.
--uncommitted Changes only in staging area that are not being committed.
--uncommitted Changes only in working area that havent being committed.
--source Git Soruce/Root directory location.
The dgcov program runs on gcov files for Code Coverage Analysis, and reports
missing coverage only for those lines that are changed by the specified revision(s).
Expand Down

0 comments on commit 79cd2d3

Please sign in to comment.