You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

198 lines
5.4 KiB

  1. #! /usr/bin/perl
  2. # Copyright (C) 2003,2008 MySQL AB
  3. # Copyright (C) 2010,2017 Sergei Golubchik and MariaDB Corporation
  4. #
  5. # This program is free software; you can redistribute it and/or modify
  6. # it under the terms of the GNU General Public License as published by
  7. # the Free Software Foundation; version 2 of the License.
  8. #
  9. # This program is distributed in the hope that it will be useful,
  10. # but WITHOUT ANY WARRANTY; without even the implied warranty of
  11. # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  12. # GNU General Public License for more details.
  13. #
  14. # You should have received a copy of the GNU General Public License
  15. # along with this program; if not, write to the Free Software
  16. # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02111-1301 USA
  17. # Run gcov and report test coverage on only those code lines touched by
  18. # a given list of commits.
  19. use strict;
  20. use warnings;
  21. use Getopt::Long;
  22. use File::Find;
  23. use Cwd qw/realpath/;
  24. my $opt_verbose=0;
  25. my $opt_generate;
  26. my $opt_help;
  27. my $opt_purge;
  28. my $opt_only_gcov;
  29. my $opt_skip_gcov;
  30. my %cov;
  31. my $file_no=0;
  32. GetOptions
  33. ("v|verbose+" => \$opt_verbose,
  34. "h|help" => \$opt_help,
  35. "p|purge" => \$opt_purge,
  36. "g|generate" => \$opt_generate,
  37. "o|only-gcov" => \$opt_only_gcov,
  38. "s|skip-gcov" => \$opt_skip_gcov,
  39. ) or usage();
  40. usage() if $opt_help;
  41. sub logv(@) { print STDERR @_,"\n" if $opt_verbose; }
  42. sub gcov_prefix($) { defined($_[0]) ? $_[0] || '#####' : '-' }
  43. my $root= `git rev-parse --show-toplevel`;
  44. chomp $root;
  45. die "Failed to find tree root" unless $root;
  46. $root=realpath($root).'/';
  47. logv "Chdir $root";
  48. chdir $root or die "chdir($root): $!";
  49. my $res;
  50. my $cmd;
  51. if ($opt_purge)
  52. {
  53. $cmd= "find . -name '*.da' -o -name '*.gcda' -o -name '*.gcov' -o ".
  54. "-name '*.dgcov' | grep -v 'README\.gcov' | xargs rm -f ''";
  55. logv "Running: $cmd";
  56. system($cmd)==0 or die "system($cmd): $? $!";
  57. exit 0;
  58. }
  59. find(\&gcov_one_file, $root);
  60. find(\&write_coverage, $root) if $opt_generate;
  61. exit 0 if $opt_only_gcov;
  62. if (@ARGV) {
  63. print_gcov_for_diff(@ARGV);
  64. } else {
  65. print_gcov_for_diff('HEAD') or print_gcov_for_diff('HEAD^');
  66. }
  67. exit 0;
  68. sub print_gcov_for_diff {
  69. $cmd="git diff --no-prefix --ignore-space-change @_";
  70. logv "Running: $cmd";
  71. open PIPE, '-|', $cmd or die "Failed to popen '$cmd': $!: $?";
  72. my ($lnum, $cnt, $fcov, $acc, $printme, $fname);
  73. while (<PIPE>) {
  74. if (/^diff --git (.*) \1\n/) {
  75. print $acc if $printme;
  76. $fname=$1;
  77. $acc="dgcov $fname";
  78. $acc=('*' x length($acc)) . "\n$acc\n" . ('*' x length($acc));
  79. $lnum=undef;
  80. $fcov=$cov{realpath($fname)};
  81. $printme=0;
  82. logv "File: $fname";
  83. next;
  84. }
  85. if (/^@@ -\d+,\d+ \+(\d+),(\d+) @@/ and $fcov) {
  86. $lnum=$1;
  87. $cnt=$2;
  88. $acc.="\n@@ +$lnum,$cnt @\@$'";
  89. logv " lines: $lnum,",$lnum+$cnt;
  90. next;
  91. }
  92. next unless $lnum and $cnt;
  93. $acc.=sprintf '%9s:%5s:%s', '', $lnum, $' if /^ /;
  94. ++$printme, $acc.=sprintf '%9s:%5s:%s', gcov_prefix($fcov->{$lnum}), $lnum, $' if /^\+/;
  95. die "$_^^^ dying", unless /^[- +]/;
  96. ++$lnum;
  97. --$cnt;
  98. }
  99. print $acc if $printme;
  100. close PIPE or die "command '$cmd' failed: $!: $?";
  101. return defined($fname);
  102. }
  103. sub usage {
  104. print <<END;
  105. Usage: $0 --help
  106. $0 [options] [git diff arguments]
  107. The dgcov program runs gcov for code coverage analysis, and reports missing
  108. coverage only for those lines that are changed by the specified commit(s).
  109. Commits are specified in the format of git diff arguments. For example:
  110. * All unpushed commits: $0 \@{u} HEAD
  111. * All uncommitted changes: $0 HEAD
  112. * Specific commit: $0 <commit>^ <commit>
  113. If no arguments are specified, it prints the coverage for all uncommitted
  114. changes, if any, otherwise for the last commit.
  115. Options:
  116. -h --help This help.
  117. -v --verbose Show commands run.
  118. -p --purge Delete all test coverage information, to prepare for a
  119. new coverage test.
  120. -o --only-gcov Stop after running gcov, don't run git
  121. -s --skip-gcov Do not run gcov, assume .gcov files are already in place
  122. -g --generate Create .dgcov files for all source files
  123. Prior to running this tool, MariaDB should be built with
  124. cmake -DENABLE_GCOV=ON
  125. and the testsuite should be run. dgcov will report the coverage
  126. for all lines modified in the specified commits.
  127. END
  128. exit 1;
  129. }
  130. sub gcov_one_file {
  131. return unless /\.gcda$/;
  132. unless ($opt_skip_gcov) {
  133. $cmd= "gcov -i '$_' 2>/dev/null >/dev/null";
  134. print STDERR ++$file_no,"\r" if not $opt_verbose and -t STDERR;
  135. logv "Running: $cmd";
  136. system($cmd)==0 or die "system($cmd): $? $!";
  137. }
  138. # now, read the generated file
  139. open FH, '<', "$_.gcov" or die "open(<$_.gcov): $!";
  140. my $fname;
  141. while (<FH>) {
  142. chomp;
  143. if (/^function:/) {
  144. next;
  145. }
  146. if (/^file:/) {
  147. $fname=realpath($');
  148. next;
  149. }
  150. next if /^lcount:\d+,-\d+/; # whatever that means
  151. unless (/^lcount:(\d+),(\d+)/ and $fname) {
  152. warn "unknown line '$_' after running '$cmd'";
  153. next;
  154. }
  155. $cov{$fname}->{$1}+=$2;
  156. }
  157. close(FH);
  158. }
  159. sub write_coverage {
  160. my $fn=$File::Find::name;
  161. my $h=$cov{$fn};
  162. return unless $h and $root eq substr $fn, 0, length($root);
  163. open I, '<', $fn or die "open(<$fn): $!";
  164. open O, '>', "$fn.dgcov" or die "open(>$fn.dgcov): $!";
  165. logv "Annotating: ", substr $fn, length($root);
  166. while (<I>) {
  167. printf O '%9s:%5s:%s', gcov_prefix($h->{$.}), $., $_;
  168. }
  169. close I;
  170. close O;
  171. }