| #!/usr/bin/perl -w | |
| # Copyright (C) 2005, 2006, 2013 Apple Inc. All rights reserved. | |
| # Copyright (C) 2007 Holger Hans Peter Freyther. All rights reserved. | |
| # | |
| # Redistribution and use in source and binary forms, with or without | |
| # modification, are permitted provided that the following conditions | |
| # are met: | |
| # | |
| # 1. Redistributions of source code must retain the above copyright | |
| # notice, this list of conditions and the following disclaimer. | |
| # 2. Redistributions in binary form must reproduce the above copyright | |
| # notice, this list of conditions and the following disclaimer in the | |
| # documentation and/or other materials provided with the distribution. | |
| # 3. Neither the name of Apple Inc. ("Apple") nor the names of | |
| # its contributors may be used to endorse or promote products derived | |
| # from this software without specific prior written permission. | |
| # | |
| # THIS SOFTWARE IS PROVIDED BY APPLE AND ITS CONTRIBUTORS "AS IS" AND ANY | |
| # EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED | |
| # WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE | |
| # DISCLAIMED. IN NO EVENT SHALL APPLE OR ITS CONTRIBUTORS BE LIABLE FOR ANY | |
| # DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES | |
| # (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; | |
| # LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND | |
| # ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT | |
| # (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF | |
| # THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. | |
| # Script to build, run and visualize coverage information | |
| use strict; | |
| use File::Basename; | |
| use File::Spec; | |
| use FindBin; | |
| use Getopt::Long qw(:config pass_through); | |
| use JSON::PP; | |
| use lib $FindBin::Bin; | |
| use List::Util qw(sum); | |
| use List::Util qw(max); | |
| use POSIX; | |
| use webkitdirs; | |
| use XML::Simple; | |
| sub parseGcovrOutput($); | |
| sub getFileHitsAndBranches($); | |
| sub addLineCounts($$$$$$); | |
| sub createResultName(); | |
| sub generateReport($); | |
| chdirWebKit(); | |
| system("mkdir WebKitBuild/Coverage") if ! -d "WebKitBuild/Coverage"; | |
| # Delete old gcov files | |
| print "Cleaning up\n"; | |
| system("if [ -d WebKitBuild ]; then find WebKitBuild -name '*.gcda' -delete; fi;") == 0 or die "Cannot delete old gcda files (code coverage"; | |
| print "Building and testing\n"; | |
| system("Tools/Scripts/build-webkit", "--clean", @ARGV) == 0 or die "Cannot clean WebKit build"; | |
| system("Tools/Scripts/build-webkit", "--coverage", "--release", @ARGV) == 0 or die "Cannot compile WebKit with code coverage"; | |
| system("Tools/Scripts/run-javascriptcore-tests --no-build"); | |
| system("Tools/Scripts/run-api-tests"); | |
| system("Tools/Scripts/run-webkit-tests"); | |
| system("Tools/Scripts/run-webkit-tests -2"); | |
| generateReport(createResultName()); | |
| print "Done\n"; | |
| sub generateReport() | |
| { | |
| my ($reportName) = @_; | |
| # Generate the coverage data and report | |
| print "Collecting coverage data\n"; | |
| system("python Tools/Scripts/webkitpy/tool/gcovr --xml --output=WebKitBuild/Coverage/" . $reportName . ".xml") == 0 or die "Cannot run gcovr"; | |
| # Collect useful data from xml to json format | |
| my $jsonData = encode_json(parseGcovrOutput("WebKitBuild/Coverage/$reportName.xml")); | |
| open my $templateFile, "<", "Tools/CodeCoverage/results-template.html" or die "Cannot open Tools/CodeCoverage/results-template.html"; | |
| my $templateHtml = join("", <$templateFile>); | |
| close $templateFile; | |
| $templateHtml =~ s/%CoverageDataJSON%/$jsonData/; | |
| my $reportFilename = "WebKitBuild/Coverage/$reportName.html"; | |
| open my $reportFile, ">", $reportFilename or die "Cannot open $reportFilename"; | |
| print $reportFile $templateHtml; | |
| close $reportFile; | |
| # Open the report | |
| my $url = "file://" . sourceDir() . "/WebKitBuild/Coverage/$reportName.html"; | |
| system "open \"$url\""; | |
| } | |
| sub parseGcovrOutput($) | |
| { | |
| my ($xmlData) = @_; | |
| my $sourceDir = sourceDir(); | |
| my @files; | |
| # The xml output of gcovr uses a Java-like package/class names for directories and files | |
| my $packages = new XML::Simple->XMLin($xmlData)->{"packages"}->{"package"}; | |
| foreach my $packageName (keys %{$packages}) { | |
| my $classes = $packages->{$packageName}->{"classes"}->{"class"}; | |
| # Perl's XML::Simple causes files to be here in the parsed xml data structure | |
| # if there's only one child, even though they're a layer deeper in the xml tree | |
| if ($classes->{"filename"} && $classes->{"lines"}) { | |
| if ($classes->{"filename"} =~ /$sourceDir/) { | |
| push(@files, getFileHitsAndBranches($classes)); | |
| } | |
| } | |
| else { | |
| foreach my $key (keys %{$classes}) { | |
| my $class = $classes->{$key}; | |
| if ($class->{"filename"} =~ /$sourceDir/) { | |
| push(@files,getFileHitsAndBranches($class)); | |
| } | |
| } | |
| } | |
| } | |
| return \@files; | |
| } | |
| sub getFileHitsAndBranches($) | |
| { | |
| my ($class) = @_; | |
| my @hits; | |
| my @hitLines; | |
| my @branchesPossible; | |
| my @branchesTaken; | |
| my @branchLines; | |
| my $lines = $class->{"lines"}->{"line"}; | |
| if (ref($lines) eq "ARRAY") { | |
| foreach my $line (@$lines) { | |
| addLineCounts($line, \@hits, \@hitLines, \@branchesPossible, \@branchesTaken, \@branchLines); | |
| } | |
| } else { | |
| addLineCounts($lines, \@hits, \@hitLines, \@branchesPossible, \@branchesTaken, \@branchLines); | |
| } | |
| my $file = {}; | |
| $file->{"hits"} = \@hits; | |
| $file->{"hitLines"} = \@hitLines; | |
| $file->{"branchesPossible"} = \@branchesPossible; | |
| $file->{"branchesTaken"} = \@branchesTaken; | |
| $file->{"branchLines"} = \@branchLines; | |
| $file->{"filename"} = substr($class->{"filename"}, length(sourceDir())); | |
| $file->{"coverage"} = abs($class->{"line-rate"}); | |
| if (@branchLines) { | |
| $file->{"branchCoverage"} = abs($class->{"branch-rate"}); | |
| } else { | |
| $file->{"branchCoverage"} = 1; | |
| } | |
| $file->{"totalHeat"} = sum(@hits); | |
| $file->{"maxHeat"} = max(@hits); | |
| return $file; | |
| } | |
| sub addLineCounts($$$$$$) | |
| { | |
| my ($line, $hits, $hitLines, $branchesPossible, $branchesTaken, $branchLines) = @_; | |
| push(@$hits, int($line->{"hits"})); | |
| push(@$hitLines, int($line->{"number"})); | |
| if($line->{"branch"} eq "true") { | |
| # Extract the numerator and denominator of the condition-coverage attribute, which looks like "75% (3/4)" | |
| $line->{"condition-coverage"} =~ /\((.*)\/(.*)\)/; | |
| push(@$branchesTaken, int($1)); | |
| push(@$branchesPossible, int($2)); | |
| push(@$branchLines, int($line->{"number"})); | |
| } | |
| } | |
| sub createResultName() | |
| { | |
| my $svnVersion = determineCurrentSVNRevision(); | |
| my @timeData = localtime(time); | |
| return $svnVersion . "-" . join('_', @timeData); | |
| } |