| #!/usr/bin/env perl |
| |
| # 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 warnings; |
| 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); |
| } |