2 # Copyright (C) 2001-2005, Parrot Foundation.
7 tools/dev/parrot_coverage.pl - Run coverage tests and report
11 % mkdir parrot_coverage
12 % perl tools/dev/parrot_coverage.pl recompile
13 % perl tools/dev/parrot_coverage.pl
17 This script runs a coverage test and then generates HTML reports. It requires
18 C<gcc> and C<gcov> to be installed.
20 The reports start at F<parrot_coverage/index.html>.
30 use POSIX
qw(strftime);
32 my $SRCDIR = "./"; # with trailing /
33 my $HTMLDIR = "parrot_coverage";
36 if ( $ARGV[0] && $ARGV[0] =~ /recompile/ ) {
38 # clean up remnants of prior builds
42 /\.(bb|bba|bbf|da|gcov)$/
43 && unlink($File::Find
::name
);
49 # build parrot with coverage support
50 system("perl Configure.pl --ccflags=\"-fprofile-arcs -ftest-coverage\"");
54 system("make fulltest");
57 # And generate the reports.
63 /\.da$/ && push @dafiles, $File::Find
::name
;
69 my ( %file_line_coverage, %file_branch_coverage, %file_call_coverage );
70 my ( %function_line_coverage, %function_branch_coverage, %function_call_coverage );
76 covered_branches
=> 0,
81 # We parse the output of the 'gcov' command, so we do not want german output
84 foreach my $da_file (@dafiles) {
85 my $dirname = dirname
($da_file) || '.';
86 my $filename = basename
($da_file);
87 my $src_filename = $da_file;
88 $src_filename =~ s/\.da$/.c/;
90 # gcov must be run from the directory that the compiler was
91 # invoked from. Currently, this is the parrot root directory.
92 # However, it also leaves it output file in this directory, which
93 # we need to move to the appropriate place, alongside the
94 # sourcefile that produced it. Hence, as soon as we know the true
95 # name of the object file being profiled, we rename the gcov log
96 # file. The -o flag is necessary to help gcov locate it's basic
98 my $cmd = "gcov -f -b -o $dirname $src_filename";
99 print "Running $cmd\n" if $DEBUG;
100 open( my $GCOVSUMMARY, '<', "$cmd |" ) or die "Error invoking '$cmd': $!";
103 while (<$GCOVSUMMARY>) {
104 if (/^Creating (.*)\./) {
105 my $path = "$dirname/$1";
106 rename( $1, "$dirname/$1" )
107 or die("Couldn't rename $1 to $dirname/$1.");
108 $path =~ s/\Q$SRCDIR\E//g;
109 $generated_files{$path} = $tmp;
118 foreach my $gcov_file ( keys %generated_files ) {
119 my $source_file = $gcov_file;
120 $source_file =~ s/\.gcov$//g;
122 # avoid collisions where multiple files are generated from the
123 # same back-end file (core.ops, for example)
124 if ( exists( $file_line_coverage{$source_file} ) ) {
125 $source_file = "$source_file (from $da_file)";
128 print "Processing $gcov_file ($source_file)\n";
130 foreach ( split m/\n/, $generated_files{$gcov_file} ) {
131 my ( $percent, $total_lines, $real_filename ) =
132 /\s*([^%]+)% of (\d+)(?: source)? lines executed in file (.*)/;
134 my $covered_lines = int( ( $percent / 100 ) * $total_lines );
135 $totals{lines
} += $total_lines;
136 $totals{covered_lines
} += $covered_lines;
137 $file_line_coverage{$source_file} = $percent;
138 $real_filename{$source_file} = $real_filename;
142 ( $percent, $total_lines, my $function ) =
143 /\s*([^%]+)% of (\d+)(?: source)? lines executed in function (.*)/;
145 $function_line_coverage{$source_file}{$function} = $percent;
149 ( $percent, my $total_branches ) =
150 /\s*([^%]+)% of (\d+) branches taken at least once in file/;
151 if ($total_branches) {
152 my $covered_branches = int( ( $percent / 100 ) * $total_branches );
153 $totals{branches
} += $total_branches;
154 $totals{covered_branches
} += $covered_branches;
155 $file_branch_coverage{$source_file} = $percent;
159 ( $percent, $total_branches, $function ) =
160 /\s*([^%]+)% of (\d+) branches taken at least once in function (.*)/;
161 if ($total_branches) {
162 $function_branch_coverage{$source_file}{$function} = $percent;
166 ( $percent, my $total_calls, $function ) =
167 /\s*([^%]+)% of (\d+) calls executed in function (.*)/;
169 $function_call_coverage{$source_file}{$function} = $percent;
173 ( $percent, $total_calls ) = /\s*([^%]+)% of (\d+) calls executed in file/;
175 my $covered_calls = int( ( $percent / 100 ) * $total_calls );
176 $totals{calls
} += $total_calls;
177 $totals{covered_calls
} += $covered_calls;
178 $file_call_coverage{$source_file} = $percent;
183 filter_gcov
($gcov_file);
187 write_file_coverage_summary
();
188 write_function_coverage_summary
();
194 print "Writing $HTMLDIR/index.html..\n" if $DEBUG;
195 open( my $OUT, ">", "$HTMLDIR/index.html" )
196 or die "Can't open $HTMLDIR/index.html for writing: $!\n";
198 $totals{line_coverage
} = sprintf( "%.2f",
199 ( $totals{lines
} ?
( $totals{covered_lines
} / $totals{lines
} * 100 ) : 0 ) );
200 $totals{branch_coverage
} = sprintf( "%.2f",
201 ( $totals{branches
} ?
( $totals{covered_branches
} / $totals{branches
} * 100 ) : 0 ) );
202 $totals{call_coverage
} = sprintf( "%.2f",
203 ( $totals{calls
} ?
( $totals{covered_calls
} / $totals{calls
} * 100 ) : 0 ) );
205 print $OUT page_header
("Parrot Test Coverage");
208 <li
><a href
="file_summary.html">File Summary
</a
>
209 <li
><a href
="function_summary.html">Function Summary
</a
>
210 <li
>Overall Summary
:<br
>
215 <th
></th><th>Lines</th
><th
>Branches
</th><th>Calls</th
>
219 <td
>$totals{covered_lines
} of
$totals{lines
} ($totals{line_coverage
} %)</td
>
220 <td
>$totals{covered_branches
} of
$totals{branches
} ($totals{branch_coverage
} %)</td
>
221 <td
>$totals{covered_calls
} of
$totals{calls
} ($totals{call_coverage
} %)</td
>
227 print $OUT page_footer
();
230 sub write_file_coverage_summary
{
232 print "Writing $HTMLDIR/file_summary.html..\n" if $DEBUG;
233 open( my $OUT, ">", "$HTMLDIR/file_summary.html" )
234 or die "Can't open $HTMLDIR/file_summary.html for writing: $!\n";
236 print $OUT page_header
("File Coverage Summary");
238 <i
>You may click on a percentage to see line
-by
-line detail
</i
>
243 <th
>Line Coverage
</th
>
244 <th
>Branch Coverage
</th
>
245 <th
>Call Coverage
</th
>
249 foreach my $source_file ( sort keys %file_line_coverage ) {
250 my $outfile_base = $source_file;
251 $outfile_base =~ s/\//_
/g
;
255 <td
>$source_file</td
>
256 <td
><a href
="$outfile_base.lines.html">@
{[$file_line_coverage{$source_file} ?
"$file_line_coverage{$source_file} %" : "n/a" ]}</a
></td
>
257 <td
><a href
="$outfile_base.branches.html">@
{[$file_branch_coverage{$source_file} ?
"$file_branch_coverage{$source_file} %" : "n/a" ]}</a
></td
>
258 <td
><a href
="$outfile_base.calls.html">@
{[$file_call_coverage{$source_file} ?
"$file_call_coverage{$source_file} %" : "n/a" ]}</a
></td
>
259 <td
>[<a href
="function_summary.html#$source_file">function detail
</a>]</td
>
268 print $OUT page_footer
();
273 sub write_function_coverage_summary
{
275 print "Writing $HTMLDIR/function_summary.html..\n" if $DEBUG;
276 open( my $OUT, ">", "$HTMLDIR/function_summary.html" )
277 or die "Can't open $HTMLDIR/function_summary.html for writing: $!\n";
279 print $OUT page_header
("Function Coverage Summary");
281 <i
>You may click on a percentage to see line
-by
-line detail
</i
>
284 foreach my $source_file ( sort keys %file_line_coverage ) {
288 <a name
="$source_file"></a
>
289 <b
>File
: $source_file</b
><br
>
294 <th
>Line Coverage
</th
>
295 <th
>Branch Coverage
</th
>
296 <th
>Call Coverage
</th
>
299 my $outfile_base = $source_file;
300 $outfile_base =~ s/\//_
/g
;
302 foreach my $function ( sort keys %{ $function_line_coverage{$source_file} } ) {
307 <td
><a href
="$outfile_base.lines.html#$function">@
{[$function_line_coverage{$source_file}{$function} ?
"$function_line_coverage{$source_file}{$function} %" : "n/a" ]}</a
></td
>
308 <td
><a href
="$outfile_base.branches.html#$function">@
{[$function_branch_coverage{$source_file}{$function} ?
"$function_branch_coverage{$source_file}{$function} %" : "n/a" ]}</a
></td
>
309 <td
><a href
="$outfile_base.calls.html#$function">@
{[$function_call_coverage{$source_file}{$function} ?
"$function_call_coverage{$source_file}{$function} %" : "n/a" ]}</a
></td
>
319 print $OUT page_footer
();
327 my $source_file = $infile;
328 $source_file =~ s/\.gcov$//g;
330 my $outfile_base = $source_file;
331 $outfile_base =~ s/\//_
/g
;
332 $outfile_base = "$HTMLDIR/$outfile_base";
334 my $outfile = "$outfile_base.lines.html";
335 print "Writing $outfile..\n" if $DEBUG;
337 open( $IN, "<", "$infile" ) or die "Can't read $infile: $!\n";
338 open( $OUT, ">", "$outfile" ) or die "Can't write $outfile: $!\n";
340 print $OUT page_header
("Line Coverage for $source_file");
343 # filter out any branch or call coverage lines.
344 do_filter
( sub { /^(call|branch)/ } );
347 print $OUT page_footer
();
352 $outfile = "$outfile_base.branches.html";
353 print "Writing $outfile..\n" if $DEBUG;
354 open( $IN, "<", "$infile" ) or die "Can't read $infile: $!\n";
355 open( $OUT, ">", "$outfile" ) or die "Can't write $outfile: $!\n";
357 print $OUT page_header
("Branch Coverage for $source_file");
360 # filter out any call coverage lines.
361 do_filter
( sub { /^call/ } );
364 print $OUT page_footer
();
369 $outfile = "$outfile_base.calls.html";
370 print "Writing $outfile..\n" if $DEBUG;
371 open( $IN, "<", "$infile" ) or die "Can't read $infile: $!\n";
372 open( $OUT, ">", "$outfile" ) or die "Can't write $outfile: $!\n";
374 print $OUT page_header
("Call Coverage for $source_file");
377 # filter out any branch coverage lines.
378 do_filter
( sub { /^branch/ } );
381 print $OUT page_footer
();
389 my ($skip_func) = @_;
396 next if ( &{$skip_func}($_) );
399 if (/^\s*([^\(\s]+)\(/) {
400 $atag = "<a name=\"$1\"></a>";
403 my ($initial) = substr( $_, 0, 16 );
404 if ( $initial =~ /^\s*\d+\s*$/ ) {
405 print $OUT qq($atag<font color
="green">$_</font
>);
407 elsif ( $_ =~ /branch \d+ taken = 0%/ ) {
408 print $OUT qq($atag<font color
="red">$_</font
>);
410 elsif ( $_ =~ /call \d+ returns = 0%/ ) {
411 print $OUT qq($atag<font color
="red">$_</font
>);
413 elsif ( $_ =~ /^call \d+ never executed/ ) {
414 print $OUT qq($atag<font color
="red">$_</font
>);
416 elsif ( $_ =~ /^branch \d+ never executed/ ) {
417 print $OUT qq($atag<font color
="red">$_</font
>);
419 elsif ( $initial =~ /\#\#\#/ ) {
420 print $OUT qq($atag<font color
="red">$_</font
>);
435 <title
>$title</title
>
437 <body bgcolor
="white">
444 "<hr noshade><i>Last Updated: @{[ scalar(localtime) . strftime(' (%Z)', localtime(time)) ]} </i>
450 # cperl-indent-level: 4
453 # vim: expandtab shiftwidth=4: