source: branches/ali/UNIT_TESTER/gcov2msg.pl

Last change on this file was 18914, checked in by westram, 3 years ago
  • fix error handling for piped commands in perl
    • when forking piped commands
      • use error message ($!) instead of exitcode ($?).
      • use message 'failed to fork'.
    • when closing piped commands
      • show IPC errors and exitcode of command.
  • Property svn:executable set to *
File size: 10.3 KB
Line 
1#!/usr/bin/perl
2
3use strict;
4use warnings;
5
6use Cwd;
7
8my $verbose = 0;
9
10my $showCoverageForAll; # 1=all; 0=files containing tests only
11my $showCoverageForFilesMatching; # regexpr filter for covered files
12my $sortBySectionSize; # 0=by location; 1=by section size
13
14sub setup() {
15  sub env_defined_non_empty($) {
16    my ($varname) = @_;
17    my $value = $ENV{$varname};
18    if (not defined $value) { die "environment variable $varname is undefined"; }
19    if ($value eq '') { die "environment variable $varname is empty"; }
20    chomp($value);
21    $value =~ s/^\s+//go;
22    $value =~ s/\s+$//go;
23
24    if ($value =~ /^'(.*)'$/o) { $value = $1; }
25    return $value;
26  }
27
28  my $SORT_COVERAGE     = env_defined_non_empty('SORT_COVERAGE');
29  my $RESTRICT_COVERAGE = env_defined_non_empty('RESTRICT_COVERAGE');
30
31  print "SORT_COVERAGE='$SORT_COVERAGE'\n";
32  print "RESTRICT_COVERAGE='$RESTRICT_COVERAGE'\n";
33
34  $showCoverageForAll = 1;
35  $showCoverageForFilesMatching = qr/.*/;
36
37  if ($RESTRICT_COVERAGE eq 'NO') {
38    ;
39  }
40  elsif ($RESTRICT_COVERAGE eq 'MODULE') {
41    my $RESTRICT_MODULE   = env_defined_non_empty('RESTRICT_MODULE');
42    print "RESTRICT_MODULE='$RESTRICT_MODULE'";
43    if ($RESTRICT_MODULE eq '.') {
44      print " (restricting to tested modules)\n";
45      $showCoverageForAll = 0;
46    }
47    else {
48      print "\n";
49      $showCoverageForFilesMatching = qr/$RESTRICT_MODULE/;
50    }
51  }
52  else {
53    $showCoverageForFilesMatching = qr/$RESTRICT_COVERAGE/;
54  }
55
56  if ($SORT_COVERAGE eq 'LOCATION') { $sortBySectionSize = 0; }
57  elsif ($SORT_COVERAGE eq 'SIZE') { $sortBySectionSize = 1; }
58  else { die "SORT_COVERAGE '$SORT_COVERAGE' is unknown"; }
59}
60
61# --------------------------------------------------------------------------------
62
63my %code = (); # key=lineno, value=code (valid for recently parsed lines with type '#')
64
65# --------------------------------------------------------------------------------
66
67my ($loclen,$msglen) = (0,0);
68
69sub reset_trim() {
70  ($loclen,$msglen) = (0,0);
71}
72
73sub trim($\$) {
74  my ($str, $len_r) = @_;
75  my $len = length($str);
76  if ($len > $$len_r) { $$len_r = $len; }
77  else {
78    $str = sprintf("%-*s", $$len_r, $str);
79  }
80  return $str;
81}
82
83sub print_trimmed($$$$) {
84  my ($source,$lineno,$msg,$code) = @_;
85
86  my $loc = $source.':'.$lineno.':';
87  $loc .= ' ' if ($lineno<1000);
88  $loc .= ' ' if ($lineno<100);
89
90  $loc = trim($loc, $loclen);
91  $msg = trim($msg, $msglen);
92
93  $code =~ s/^\s*//go;
94
95  print $loc.' '.$msg.' | '.$code."\n";
96}
97
98sub print_annotated_message($$$) {
99  my ($source,$lineno,$msg) = @_;
100  print_trimmed($source, $lineno, $msg, $code{$lineno});
101}
102
103# --------------------------------------------------------------------------------
104
105sub parseCoveredLines($\@) {
106  my ($gcov, $covered_lines_r) = @_;
107
108  my ($lines,$covered,$tests_seen) = (0,0,0);
109  open(GCOV,'<'.$gcov) || die "gcov2msg.pl: can't read '$gcov' (Reason: $!)";
110  my $line;
111  while (defined ($line = <GCOV>)) {
112    if (not $line =~ /^\s*([^\s:][^:]*):\s*([^\s:][^:]*):(.*)$/o) { die "can't parse '$line'"; }
113    my ($counter,$lineno,$code) = ($1,$2,$3);
114    if ($lineno>0) {
115      if ($counter eq '-') {
116        $$covered_lines_r[$lineno] = '-';
117      }
118      elsif ($counter eq '#####') {
119        if ($code =~ /NEED_NO_COV/) { # handle like there was no code here
120          $$covered_lines_r[$lineno] = '-';
121        }
122        else {
123          $lines++;
124          $$covered_lines_r[$lineno] = '#';
125          $code{$lineno} = $code;
126        }
127      }
128      else {
129        if ($counter =~ /^[0-9]+$/) {
130          $lines++;
131          $covered++;
132          $$covered_lines_r[$lineno] = '+';
133        }
134        else {
135          die "Invalid counter '$counter' (expected number)";
136        }
137      }
138
139      if ($code =~ /^void\s+TEST_.*()/g) {
140        $tests_seen++;
141      }
142    }
143  }
144  close(GCOV);
145
146  return ($lines,$covered,$tests_seen);
147}
148
149sub next_uncovered_section_after(\@$$) {
150  my ($covered_lines_r,$lines,$after_line) = @_;
151
152  my $line = $after_line+1;
153  while ($line<$lines) {
154    my $type = $$covered_lines_r[$line];
155    if ($type eq '#') {
156      my ($first,$last,$loc) = ($line, $line, 0);
157
158    LINE: while (1) {
159        if ($type eq '+') { last LINE; } # covered -> stop
160        if ($type eq '#') {
161          $loc++;
162          $last = $line;
163        }
164        ++$line;
165        if ($line>=$lines) { last LINE; }
166        $type = $$covered_lines_r[$line];
167      }
168
169      return ($first,$last,$loc);
170    }
171    ++$line;
172  }
173  return (undef,undef,undef);
174
175}
176
177sub collect_gcov_data($$) {
178  my ($source,$gcov) = @_;
179
180  reset_trim();
181
182  my $cov = $gcov;
183  $cov =~ s/\.gcov$/\.cov/g;
184  if ($cov eq $gcov) { die "Invalid gcov name '$gcov'"; }
185
186  if (not -f $gcov) {
187    print "No such file '$gcov' (assuming it belongs to a standard header)\n";
188    return;
189  }
190
191  my @covered_lines = ();
192  my ($lines,$covered,$tests_seen) = parseCoveredLines($gcov,@covered_lines);
193  my $size = scalar(@covered_lines);
194
195  my $percent = 100*$covered/$lines;
196  $percent = int($percent*10)/10;
197
198  my $source_name = $source;
199  if ($source =~ /\/([^\/]+)$/) { $source_name = $1; }
200
201  if ($covered==$lines) {
202    print "Full test-coverage for $source_name\n";
203    unlink($gcov);
204  }
205  else {
206    my $summary = "lines=$lines covered=$covered (coverage=$percent%)";
207
208    $verbose==0 || print "collect_gcov_data($gcov): $summary\n";
209    $covered>0 || die "Argh.. collected data for completely uncovered file '$source'";
210
211    if    ($tests_seen==0 and $showCoverageForAll==0)         { print "$source_name defines no tests. $summary\n"; }
212    elsif (not $source_name =~ $showCoverageForFilesMatching) { print "Skipping $source_name by mask. $summary\n"; }
213    else {
214      my $line = 0;
215      my @sections = ();
216
217    SECTION: while (1) {
218        my ($first,$last,$loc) = next_uncovered_section_after(@covered_lines, $size, $line);
219        if (not defined $first) { last SECTION; }
220        push @sections, [$first,$last,$loc];
221        $line = $last;
222      }
223
224      if ($sortBySectionSize==1) { @sections = sort { $$a[2] <=> $$b[2]; } @sections; }
225
226      foreach my $sec_r (@sections) {
227        my ($first,$last,$loc) = ($$sec_r[0],$$sec_r[1], $$sec_r[2]);
228        if ($first==$last) {
229          print_annotated_message($source, $first, 'Uncovered line');
230        }
231        else {
232          print_annotated_message($source, $first, "[start] $loc uncovered lines");
233          print_annotated_message($source, $last, '[end]');
234        }
235      }
236
237      if ($percent<90) { print "$source_name:0: Warning: Summary $summary\n"; }
238      else { print "Summary $source_name: $summary\n"; }
239     
240      rename($gcov,$cov) || die "Failed to rename '$gcov' -> '$cov' (Reason: $!)";
241    }
242  }
243}
244
245# --------------------------------------------------------------------------------
246
247my @known_source_ext = qw/cxx cpp c/;
248
249sub find_gcda_files($) {
250  my ($dir) = @_;
251  my @gcda = (); 
252  opendir(DIR,$dir) || die "can't read directory '$dir' (Reason: $!)";
253  foreach (readdir(DIR)) {
254    if ($_ =~ /\.gcda$/o) { push @gcda, $_; }
255  }
256  closedir(DIR);
257  return @gcda;
258}
259sub gcda2code($\@) {
260  my ($gcda, $srcdirs_r) = @_;
261
262  if (not $gcda =~ /\.gcda$/o) {
263    die "wrong file in gcda2code: '$gcda'";
264  }
265  my $base = $`;
266  foreach my $dir (@$srcdirs_r) {
267    foreach (@known_source_ext) {
268      my $name = $base.'.'.$_;
269      my $full = $dir.'/'.$name;
270      if (-f $full) {
271        return [$name,$dir];
272      }
273    }
274  }
275  die "Failed to find code file for '$gcda'";
276}
277
278sub die_usage($) {
279  my ($err) = @_;
280  print("Usage: gcov2msg.pl [options] directory\n".
281        "Options: --srcdirs=dir,dir,dir  set sourcedirectories (default is 'directory')\n".
282        "         --builddir=dir         set dir from which build was done (default is 'directory')\n");
283  die "Error: $err\n";
284}
285
286sub main() {
287  my $args = scalar(@ARGV);
288  if ($args<1) { die_usage("Missing argument\n"); }
289
290  setup();
291
292  my $dir;
293  my @srcdirs;
294  my $builddir = undef;
295  {
296    my $srcdirs  = undef;
297
298    while ($ARGV[0] =~ /^--/) {
299      if ($ARGV[0] =~ /^--srcdirs=/) {
300        $srcdirs = $';
301        shift @ARGV;
302      }
303      elsif ($ARGV[0] =~ /^--builddir=/) {
304        $builddir = $';
305        shift @ARGV;
306      }
307    }
308    $dir = $ARGV[0];
309    if (not -d $dir) { die "No such directory '$dir'\n"; }
310
311    if (not defined $builddir) { $builddir = $dir; }
312    if (not defined $srcdirs) { $srcdirs = $dir; }
313    @srcdirs = split(',', $srcdirs);
314  }
315
316  my @gcda = find_gcda_files($dir);
317  my %gcda2code = map { $_ => gcda2code($_,@srcdirs); } @gcda; # value=[name,srcdir]
318
319  my $olddir = cwd();
320  chdir($dir) || die "can't cd to '$dir' (Reason: $!)\n";;
321
322  eval {
323    foreach (sort @gcda) {
324      my $cs_ref = $gcda2code{$_};
325      my ($code,$srcdir) = @$cs_ref;
326
327      my $fullcode  = $srcdir.'/'.$code;
328      my $objSwitch = '';
329
330      if ($srcdir ne $dir) {
331        $objSwitch = " -o '$dir'";
332      }
333
334      if ($builddir ne $dir) {
335        chdir($builddir) || die "can't cd to '$builddir' (Reason: $!)\n";;
336      }
337      my $cmd = "gcov '$fullcode' $objSwitch";
338
339      $verbose==0 || print "[Action: $cmd]\n";
340
341      open(CMD,$cmd.'|') || die "failed to fork '$cmd' (Reason: $!)";
342
343      if ($builddir ne $dir) {
344        chdir($dir) || die "can't cd to '$dir' (Reason: $!)\n";;
345      }
346
347      my ($file,$percent,$lines,$source,$gcov) = (undef,undef,undef,undef,undef);
348
349      foreach (<CMD>) {
350        chomp;
351        if ($_ eq '') { ; } # ignore empty lines
352        elsif (/^File '(.*)'$/o) { $file = $1; }
353        elsif (/^Lines executed:([0-9.]+)% of ([0-9]+)$/o) {
354          ($percent,$lines) = ($1,$2);
355        }
356        elsif (/^([^:]+):creating '(.*)'$/o) {
357          ($source,$gcov) = ($1,$2);
358
359          if ($percent>0 and $lines>0) {
360            if ($source =~ /^\/usr\/include/o) {
361              # print "Skipping '$gcov'\n";
362            }
363            else {
364              my $fullgcov = $gcov;
365              if ($dir ne $builddir) {
366                $fullgcov = $builddir.'/'.$gcov;
367              }
368              collect_gcov_data($source,$fullgcov);
369            }
370          }
371          if (-f $gcov) { unlink($gcov); }
372
373          ($file,$percent,$lines,$source,$gcov) = (undef,undef,undef,undef,undef);
374        }
375        else {
376          die "can't parse line '$_'";
377        }
378      }
379      close(CMD) || die "failed to execute '$cmd' (Reason: $! exitcode=$?)";
380
381      -f $_ || die "No such file '$_'";
382      unlink($_);
383    }
384  };
385  if ($@) {
386    my $err = $@;
387    chdir($olddir) || print "Failed to resume old working dir '$olddir' (Reason: $!)\n";
388    die "Error: $err\n";
389  }
390}
391
392main();
Note: See TracBrowser for help on using the repository browser.