1#!/usr/bin/perl 2 3# Copyright (C) 2005, 2006, 2007, 2008, 2009 Apple Inc. All rights reserved. 4# Copyright (C) 2006 Alexey Proskuryakov (ap@nypop.com) 5# Copyright (C) 2007 Matt Lilek (pewtermoose@gmail.com) 6# Copyright (C) 2007 Eric Seidel <eric@webkit.org> 7# Copyright (C) 2009 Google Inc. All rights reserved. 8# 9# Redistribution and use in source and binary forms, with or without 10# modification, are permitted provided that the following conditions 11# are met: 12# 13# 1. Redistributions of source code must retain the above copyright 14# notice, this list of conditions and the following disclaimer. 15# 2. Redistributions in binary form must reproduce the above copyright 16# notice, this list of conditions and the following disclaimer in the 17# documentation and/or other materials provided with the distribution. 18# 3. Neither the name of Apple Computer, Inc. ("Apple") nor the names of 19# its contributors may be used to endorse or promote products derived 20# from this software without specific prior written permission. 21# 22# THIS SOFTWARE IS PROVIDED BY APPLE AND ITS CONTRIBUTORS "AS IS" AND ANY 23# EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 24# WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 25# DISCLAIMED. IN NO EVENT SHALL APPLE OR ITS CONTRIBUTORS BE LIABLE FOR ANY 26# DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES 27# (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 28# LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND 29# ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 30# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF 31# THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 32 33# Script to run the WebKit Open Source Project layout tests. 34 35# Run all the tests passed in on the command line. 36# If no tests are passed, find all the .html, .shtml, .xml, .xhtml, .pl, .php (and svg) files in the test directory. 37 38# Run each text. 39# Compare against the existing file xxx-expected.txt. 40# If there is a mismatch, generate xxx-actual.txt and xxx-diffs.txt. 41 42# At the end, report: 43# the number of tests that got the expected results 44# the number of tests that ran, but did not get the expected results 45# the number of tests that failed to run 46# the number of tests that were run but had no expected results to compare against 47 48use strict; 49use warnings; 50 51use Cwd; 52use Data::Dumper; 53use Fcntl qw(F_GETFL F_SETFL O_NONBLOCK); 54use File::Basename; 55use File::Copy; 56use File::Find; 57use File::Path; 58use File::Spec; 59use File::Spec::Functions; 60use FindBin; 61use Getopt::Long; 62use IPC::Open2; 63use IPC::Open3; 64use Time::HiRes qw(time usleep); 65 66use List::Util 'shuffle'; 67 68use lib $FindBin::Bin; 69use webkitdirs; 70use VCSUtils; 71use POSIX; 72 73sub launchWithCurrentEnv(@); 74sub openDiffTool(); 75sub openDumpTool(); 76sub closeDumpTool(); 77sub dumpToolDidCrash(); 78sub closeHTTPD(); 79sub countAndPrintLeaks($$$); 80sub fileNameWithNumber($$); 81sub numericcmp($$); 82sub openHTTPDIfNeeded(); 83sub pathcmp($$); 84sub processIgnoreTests($$); 85sub slowestcmp($$); 86sub splitpath($); 87sub stripExtension($); 88sub isTextOnlyTest($); 89sub expectedDirectoryForTest($;$;$); 90sub countFinishedTest($$$$); 91sub testCrashedOrTimedOut($$$$$); 92sub sampleDumpTool(); 93sub printFailureMessageForTest($$); 94sub toURL($); 95sub toWindowsPath($); 96sub closeCygpaths(); 97sub validateSkippedArg($$;$); 98sub htmlForResultsSection(\@$&); 99sub deleteExpectedAndActualResults($); 100sub recordActualResultsAndDiff($$); 101sub buildPlatformResultHierarchy(); 102sub buildPlatformTestHierarchy(@); 103sub epiloguesAndPrologues($$); 104sub parseLeaksandPrintUniqueLeaks(); 105sub readFromDumpToolWithTimer(*;$); 106sub setFileHandleNonBlocking(*$); 107sub writeToFile($$); 108 109# Argument handling 110my $addPlatformExceptions = 0; 111my $complexText = 0; 112my $guardMalloc = ''; 113my $httpdPort = 8000; 114my $httpdSSLPort = 8443; 115my $ignoreTests = ''; 116my $launchSafari = 1; 117my $platform; 118my $pixelTests = ''; 119my $quiet = ''; 120my $report10Slowest = 0; 121my $resetResults = 0; 122my $shouldCheckLeaks = 0; 123my $showHelp = 0; 124my $testsPerDumpTool; 125my $testHTTP = 1; 126my $testMedia = 1; 127my $testResultsDirectory = "/tmp/layout-test-results"; 128my $threaded = 0; 129my $tolerance = 0; 130my $treatSkipped = "default"; 131my $verbose = 0; 132my $useValgrind = 0; 133my $strictTesting = 0; 134my $generateNewResults = isAppleMacWebKit() ? 1 : 0; 135my $stripEditingCallbacks = isCygwin(); 136my $runSample = 1; 137my $root; 138my $reverseTests = 0; 139my $randomizeTests = 0; 140my $mergeDepth; 141my $timeoutSeconds = 15; 142my $useRemoteLinksToTests = 0; 143my @leaksFilenames; 144 145# Default to --no-http for Qt, and wx for now. 146$testHTTP = 0 if (isQt() || isWx()); 147 148my $expectedTag = "expected"; 149my $actualTag = "actual"; 150my $prettyDiffTag = "pretty-diff"; 151my $diffsTag = "diffs"; 152my $errorTag = "stderr"; 153 154my @macPlatforms = ("mac-tiger", "mac-leopard", "mac-snowleopard", "mac"); 155 156if (isAppleMacWebKit()) { 157 if (isTiger()) { 158 $platform = "mac-tiger"; 159 $tolerance = 1.0; 160 } elsif (isLeopard()) { 161 $platform = "mac-leopard"; 162 $tolerance = 0.1; 163 } elsif (isSnowLeopard()) { 164 $platform = "mac-snowleopard"; 165 $tolerance = 0.1; 166 } else { 167 $platform = "mac"; 168 } 169} elsif (isQt()) { 170 $platform = "qt"; 171} elsif (isGtk()) { 172 $platform = "gtk"; 173} elsif (isWx()) { 174 $platform = "wx"; 175} elsif (isCygwin()) { 176 $platform = "win"; 177} 178 179if (!defined($platform)) { 180 print "WARNING: Your platform is not recognized. Any platform-specific results will be generated in platform/undefined.\n"; 181 $platform = "undefined"; 182} 183 184my $programName = basename($0); 185my $launchSafariDefault = $launchSafari ? "launch" : "do not launch"; 186my $httpDefault = $testHTTP ? "run" : "do not run"; 187my $sampleDefault = $runSample ? "run" : "do not run"; 188 189# FIXME: "--strict" should be renamed to qt-mac-comparison, or something along those lines. 190my $usage = <<EOF; 191Usage: $programName [options] [testdir|testpath ...] 192 --add-platform-exceptions Put new results for non-platform-specific failing tests into the platform-specific results directory 193 --complex-text Use the complex text code path for all text (Mac OS X and Windows only) 194 -c|--configuration config Set DumpRenderTree build configuration 195 -g|--guard-malloc Enable malloc guard 196 --help Show this help message 197 --[no-]http Run (or do not run) http tests (default: $httpDefault) 198 -i|--ignore-tests Comma-separated list of directories or tests to ignore 199 --[no-]launch-safari Launch (or do not launch) Safari to display test results (default: $launchSafariDefault) 200 -l|--leaks Enable leaks checking 201 --[no-]new-test-results Generate results for new tests 202 -p|--pixel-tests Enable pixel tests 203 --tolerance t Ignore image differences less than this percentage (default: $tolerance) 204 --platform Override the detected platform to use for tests and results (default: $platform) 205 --port Web server port to use with http tests 206 -q|--quiet Less verbose output 207 --reset-results Reset ALL results (including pixel tests if --pixel-tests is set) 208 -o|--results-directory Output results directory (default: $testResultsDirectory) 209 --random Run the tests in a random order 210 --reverse Run the tests in reverse alphabetical order 211 --root Path to root tools build 212 --[no-]sample-on-timeout Run sample on timeout (default: $sampleDefault) (Mac OS X only) 213 -1|--singly Isolate each test case run (implies --verbose) 214 --skipped=[default|ignore|only] Specifies how to treat the Skipped file 215 default: Tests/directories listed in the Skipped file are not tested 216 ignore: The Skipped file is ignored 217 only: Only those tests/directories listed in the Skipped file will be run 218 --slowest Report the 10 slowest tests 219 --strict Do a comparison with the output on Mac (Qt only) 220 --[no-]strip-editing-callbacks Remove editing callbacks from expected results 221 -t|--threaded Run a concurrent JavaScript thead with each test 222 --timeout t Sets the number of seconds before a test times out (default: $timeoutSeconds) 223 --valgrind Run DumpRenderTree inside valgrind (Qt/Linux only) 224 -v|--verbose More verbose output (overrides --quiet) 225 -m|--merge-leak-depth arg Merges leak callStacks and prints the number of unique leaks beneath a callstack depth of arg. Defaults to 5. 226 --use-remote-links-to-tests Link to test files within the SVN repository in the results. 227EOF 228 229setConfiguration(); 230 231my $getOptionsResult = GetOptions( 232 'complex-text' => \$complexText, 233 'guard-malloc|g' => \$guardMalloc, 234 'help' => \$showHelp, 235 'http!' => \$testHTTP, 236 'ignore-tests|i=s' => \$ignoreTests, 237 'launch-safari!' => \$launchSafari, 238 'leaks|l' => \$shouldCheckLeaks, 239 'pixel-tests|p' => \$pixelTests, 240 'platform=s' => \$platform, 241 'port=i' => \$httpdPort, 242 'quiet|q' => \$quiet, 243 'reset-results' => \$resetResults, 244 'new-test-results!' => \$generateNewResults, 245 'results-directory|o=s' => \$testResultsDirectory, 246 'singly|1' => sub { $testsPerDumpTool = 1; }, 247 'nthly=i' => \$testsPerDumpTool, 248 'skipped=s' => \&validateSkippedArg, 249 'slowest' => \$report10Slowest, 250 'threaded|t' => \$threaded, 251 'tolerance=f' => \$tolerance, 252 'verbose|v' => \$verbose, 253 'valgrind' => \$useValgrind, 254 'sample-on-timeout!' => \$runSample, 255 'strict' => \$strictTesting, 256 'strip-editing-callbacks!' => \$stripEditingCallbacks, 257 'random' => \$randomizeTests, 258 'reverse' => \$reverseTests, 259 'root=s' => \$root, 260 'add-platform-exceptions' => \$addPlatformExceptions, 261 'merge-leak-depth|m:5' => \$mergeDepth, 262 'timeout=i' => \$timeoutSeconds, 263 'use-remote-links-to-tests' => \$useRemoteLinksToTests, 264); 265 266if (!$getOptionsResult || $showHelp) { 267 print STDERR $usage; 268 exit 1; 269} 270 271my $ignoreSkipped = $treatSkipped eq "ignore"; 272my $skippedOnly = $treatSkipped eq "only"; 273 274!$skippedOnly || @ARGV == 0 or die "--skipped=only cannot be used when tests are specified on the command line."; 275 276my $configuration = configuration(); 277 278$testsPerDumpTool = 1000 if !$testsPerDumpTool; 279 280$verbose = 1 if $testsPerDumpTool == 1; 281 282if ($shouldCheckLeaks && $testsPerDumpTool > 1000) { 283 print STDERR "\nWARNING: Running more than 1000 tests at a time with MallocStackLogging enabled may cause a crash.\n\n"; 284} 285 286# Stack logging does not play well with QuickTime on Tiger (rdar://problem/5537157) 287$testMedia = 0 if $shouldCheckLeaks && isTiger(); 288 289# Generating remote links causes a lot of unnecessary spew on GTK and Qt build bot 290$useRemoteLinksToTests = 0 if (isGtk() || isQt()); 291 292setConfigurationProductDir(Cwd::abs_path($root)) if (defined($root)); 293my $productDir = productDir(); 294$productDir .= "/bin" if isQt(); 295$productDir .= "/Programs" if isGtk(); 296 297chdirWebKit(); 298 299if (!defined($root)) { 300 # Push the parameters to build-dumprendertree as an array 301 my @args = argumentsForConfiguration(); 302 303 my $buildResult = system "WebKitTools/Scripts/build-dumprendertree", @args; 304 if ($buildResult) { 305 print STDERR "Compiling DumpRenderTree failed!\n"; 306 exit exitStatus($buildResult); 307 } 308} 309 310my $dumpToolName = "DumpRenderTree"; 311$dumpToolName .= "_debug" if isCygwin() && $configuration ne "Release"; 312my $dumpTool = "$productDir/$dumpToolName"; 313die "can't find executable $dumpToolName (looked in $productDir)\n" unless -x $dumpTool; 314 315my $imageDiffTool = "$productDir/ImageDiff"; 316$imageDiffTool .= "_debug" if isCygwin() && $configuration ne "Release"; 317die "can't find executable $imageDiffTool (looked in $productDir)\n" if $pixelTests && !-x $imageDiffTool; 318 319checkFrameworks() unless isCygwin(); 320 321if (isAppleMacWebKit()) { 322 push @INC, $productDir; 323 eval 'use DumpRenderTreeSupport;'; 324} 325 326my $layoutTestsName = "LayoutTests"; 327my $testDirectory = File::Spec->rel2abs($layoutTestsName); 328my $expectedDirectory = $testDirectory; 329my $platformBaseDirectory = catdir($testDirectory, "platform"); 330my $platformTestDirectory = catdir($platformBaseDirectory, $platform); 331my @platformResultHierarchy = buildPlatformResultHierarchy(); 332my @platformTestHierarchy = buildPlatformTestHierarchy(@platformResultHierarchy); 333 334$expectedDirectory = $ENV{"WebKitExpectedTestResultsDirectory"} if $ENV{"WebKitExpectedTestResultsDirectory"}; 335 336my $testResults = catfile($testResultsDirectory, "results.html"); 337 338print "Running tests from $testDirectory\n"; 339if ($pixelTests) { 340 print "Enabling pixel tests with a tolerance of $tolerance%\n"; 341 if (isDarwin()) { 342 print "WARNING: Temporarily changing the main display color profile:\n"; 343 print "\tThe colors on your screen will change for the duration of the testing.\n"; 344 print "\tThis allows the pixel tests to have consistent color values across all machines.\n"; 345 346 if (isPerianInstalled()) { 347 print "WARNING: Perian's QuickTime component is installed and this may affect pixel test results!\n"; 348 print "\tYou should avoid generating new pixel results in this environment.\n"; 349 print "\tSee https://bugs.webkit.org/show_bug.cgi?id=22615 for details.\n"; 350 } 351 } 352} 353 354my @tests = (); 355my %testType = (); 356 357system "ln", "-s", $testDirectory, "/tmp/LayoutTests" unless -x "/tmp/LayoutTests"; 358 359my %ignoredFiles = (); 360my %ignoredDirectories = map { $_ => 1 } qw(platform); 361my %ignoredLocalDirectories = map { $_ => 1 } qw(.svn _svn resources); 362my %supportedFileExtensions = map { $_ => 1 } qw(html shtml xml xhtml pl php); 363 364# FIXME: We should fix webkitdirs.pm:hasSVG/WMLSupport() to do the correct feature detection for Cygwin. 365if (checkWebCoreSVGSupport(0)) { 366 $supportedFileExtensions{'svg'} = 1; 367} elsif (isCygwin()) { 368 $supportedFileExtensions{'svg'} = 1; 369} else { 370 $ignoredLocalDirectories{'svg'} = 1; 371} 372 373if (!$testHTTP) { 374 $ignoredDirectories{'http'} = 1; 375} 376 377if (!$testMedia) { 378 $ignoredDirectories{'media'} = 1; 379 $ignoredDirectories{'http/tests/media'} = 1; 380} 381 382if (!checkWebCoreAcceleratedCompositingSupport(0)) { 383 $ignoredDirectories{'compositing'} = 1; 384} 385 386if (!checkWebCore3DRenderingSupport(0)) { 387 $ignoredDirectories{'animations/3d'} = 1; 388 $ignoredDirectories{'transforms/3d'} = 1; 389} 390 391if (checkWebCoreWMLSupport(0)) { 392 $supportedFileExtensions{'wml'} = 1; 393} else { 394 $ignoredDirectories{'http/tests/wml'} = 1; 395 $ignoredDirectories{'fast/wml'} = 1; 396 $ignoredDirectories{'wml'} = 1; 397} 398 399if (!checkWebCoreXHTMLMPSupport(0)) { 400 $ignoredDirectories{'fast/xhtmlmp'} = 1; 401} 402 403if (!checkWebCoreWCSSSupport(0)) { 404 $ignoredDirectories{'fast/wcss'} = 1; 405} 406 407if ($ignoreTests) { 408 processIgnoreTests($ignoreTests, "ignore-tests"); 409} 410 411sub fileShouldBeIgnored { 412 my($filePath) = @_; 413 foreach my $ignoredDir (keys %ignoredDirectories) { 414 if ($filePath =~ m/^$ignoredDir/) { 415 return 1; 416 } 417 } 418 return 0; 419} 420 421if (!$ignoreSkipped) { 422 foreach my $level (@platformTestHierarchy) { 423 if (open SKIPPED, "<", "$level/Skipped") { 424 if ($verbose) { 425 my ($dir, $name) = splitpath($level); 426 print "Skipped tests in $name:\n"; 427 } 428 429 while (<SKIPPED>) { 430 my $skipped = $_; 431 chomp $skipped; 432 $skipped =~ s/^[ \n\r]+//; 433 $skipped =~ s/[ \n\r]+$//; 434 if ($skipped && $skipped !~ /^#/) { 435 if ($skippedOnly) { 436 if (!&fileShouldBeIgnored($skipped)) { 437 push(@ARGV, $skipped); 438 } elsif ($verbose) { 439 print " $skipped\n"; 440 } 441 } else { 442 if ($verbose) { 443 print " $skipped\n"; 444 } 445 processIgnoreTests($skipped, "Skipped"); 446 } 447 } 448 } 449 close SKIPPED; 450 } 451 } 452} 453 454 455my $directoryFilter = sub { 456 return () if exists $ignoredLocalDirectories{basename($File::Find::dir)}; 457 return () if exists $ignoredDirectories{File::Spec->abs2rel($File::Find::dir, $testDirectory)}; 458 return @_; 459}; 460 461my $fileFilter = sub { 462 my $filename = $_; 463 if ($filename =~ /\.([^.]+)$/) { 464 if (exists $supportedFileExtensions{$1}) { 465 my $path = File::Spec->abs2rel(catfile($File::Find::dir, $filename), $testDirectory); 466 push @tests, $path if !exists $ignoredFiles{$path}; 467 } 468 } 469}; 470 471for my $test (@ARGV) { 472 $test =~ s/^($layoutTestsName|$testDirectory)\///; 473 my $fullPath = catfile($testDirectory, $test); 474 if (file_name_is_absolute($test)) { 475 print "can't run test $test outside $testDirectory\n"; 476 } elsif (-f $fullPath) { 477 my ($filename, $pathname, $fileExtension) = fileparse($test, qr{\.[^.]+$}); 478 if (!exists $supportedFileExtensions{substr($fileExtension, 1)}) { 479 print "test $test does not have a supported extension\n"; 480 } elsif ($testHTTP || $pathname !~ /^http\//) { 481 push @tests, $test; 482 } 483 } elsif (-d $fullPath) { 484 find({ preprocess => $directoryFilter, wanted => $fileFilter }, $fullPath); 485 486 for my $level (@platformTestHierarchy) { 487 my $platformPath = catfile($level, $test); 488 find({ preprocess => $directoryFilter, wanted => $fileFilter }, $platformPath) if (-d $platformPath); 489 } 490 } else { 491 print "test $test not found\n"; 492 } 493} 494if (!scalar @ARGV) { 495 find({ preprocess => $directoryFilter, wanted => $fileFilter }, $testDirectory); 496 497 for my $level (@platformTestHierarchy) { 498 find({ preprocess => $directoryFilter, wanted => $fileFilter }, $level); 499 } 500} 501 502die "no tests to run\n" if !@tests; 503 504@tests = sort pathcmp @tests; 505 506my %counts; 507my %tests; 508my %imagesPresent; 509my %imageDifferences; 510my %durations; 511my $count = 0; 512my $leaksOutputFileNumber = 1; 513my $totalLeaks = 0; 514 515my @toolArgs = (); 516push @toolArgs, "--pixel-tests" if $pixelTests; 517push @toolArgs, "--threaded" if $threaded; 518push @toolArgs, "--complex-text" if $complexText; 519push @toolArgs, "-"; 520 521my @diffToolArgs = (); 522push @diffToolArgs, "--tolerance", $tolerance; 523 524$| = 1; 525 526my $dumpToolPID; 527my $isDumpToolOpen = 0; 528my $dumpToolCrashed = 0; 529my $imageDiffToolPID; 530my $isDiffToolOpen = 0; 531 532my $atLineStart = 1; 533my $lastDirectory = ""; 534 535my $isHttpdOpen = 0; 536 537sub catch_pipe { $dumpToolCrashed = 1; } 538$SIG{"PIPE"} = "catch_pipe"; 539 540print "Testing ", scalar @tests, " test cases.\n"; 541my $overallStartTime = time; 542 543my %expectedResultPaths; 544 545# Reverse the tests 546@tests = reverse @tests if $reverseTests; 547 548# Shuffle the array 549@tests = shuffle(@tests) if $randomizeTests; 550 551for my $test (@tests) { 552 next if $test eq 'results.html'; 553 554 my $newDumpTool = not $isDumpToolOpen; 555 openDumpTool(); 556 557 my $base = stripExtension($test); 558 my $expectedExtension = ".txt"; 559 560 my $dir = $base; 561 $dir =~ s|/[^/]+$||; 562 563 if ($newDumpTool || $dir ne $lastDirectory) { 564 foreach my $logue (epiloguesAndPrologues($newDumpTool ? "" : $lastDirectory, $dir)) { 565 if (isCygwin()) { 566 $logue = toWindowsPath($logue); 567 } else { 568 $logue = canonpath($logue); 569 } 570 if ($verbose) { 571 print "running epilogue or prologue $logue\n"; 572 } 573 print OUT "$logue\n"; 574 # Throw away output from DumpRenderTree. 575 # Once for the test output and once for pixel results (empty) 576 while (<IN>) { 577 last if /#EOF/; 578 } 579 while (<IN>) { 580 last if /#EOF/; 581 } 582 } 583 } 584 585 if ($verbose) { 586 print "running $test -> "; 587 $atLineStart = 0; 588 } elsif (!$quiet) { 589 if ($dir ne $lastDirectory) { 590 print "\n" unless $atLineStart; 591 print "$dir "; 592 } 593 print "."; 594 $atLineStart = 0; 595 } 596 597 $lastDirectory = $dir; 598 599 my $result; 600 601 my $startTime = time if $report10Slowest; 602 603 # Try to read expected hash file for pixel tests 604 my $suffixExpectedHash = ""; 605 if ($pixelTests && !$resetResults) { 606 my $expectedPixelDir = expectedDirectoryForTest($base, 0, "png"); 607 if (open EXPECTEDHASH, "$expectedPixelDir/$base-$expectedTag.checksum") { 608 my $expectedHash = <EXPECTEDHASH>; 609 chomp($expectedHash); 610 close EXPECTEDHASH; 611 612 # Format expected hash into a suffix string that is appended to the path / URL passed to DRT 613 $suffixExpectedHash = "'$expectedHash"; 614 } 615 } 616 617 if ($test !~ /^http\//) { 618 my $testPath = "$testDirectory/$test"; 619 if (isCygwin()) { 620 $testPath = toWindowsPath($testPath); 621 } else { 622 $testPath = canonpath($testPath); 623 } 624 print OUT "$testPath$suffixExpectedHash\n"; 625 } else { 626 openHTTPDIfNeeded(); 627 if ($test !~ /^http\/tests\/local\// && $test !~ /^http\/tests\/ssl\// && $test !~ /^http\/tests\/wml\// && $test !~ /^http\/tests\/media\//) { 628 my $path = canonpath($test); 629 $path =~ s/^http\/tests\///; 630 print OUT "http://127.0.0.1:$httpdPort/$path$suffixExpectedHash\n"; 631 } elsif ($test =~ /^http\/tests\/ssl\//) { 632 my $path = canonpath($test); 633 $path =~ s/^http\/tests\///; 634 print OUT "https://127.0.0.1:$httpdSSLPort/$path$suffixExpectedHash\n"; 635 } else { 636 my $testPath = "$testDirectory/$test"; 637 if (isCygwin()) { 638 $testPath = toWindowsPath($testPath); 639 } else { 640 $testPath = canonpath($testPath); 641 } 642 print OUT "$testPath$suffixExpectedHash\n"; 643 } 644 } 645 646 # DumpRenderTree is expected to dump two "blocks" to stdout for each test. 647 # Each block is terminated by a #EOF on a line by itself. 648 # The first block is the output of the test (in text, RenderTree or other formats). 649 # The second block is for optional pixel data in PNG format, and may be empty if 650 # pixel tests are not being run, or the test does not dump pixels (e.g. text tests). 651 652 my $actualRead = readFromDumpToolWithTimer(IN); 653 my $errorRead = readFromDumpToolWithTimer(ERROR, $actualRead->{status} eq "timedOut"); 654 655 my $actual = $actualRead->{output}; 656 my $error = $errorRead->{output}; 657 658 $expectedExtension = $actualRead->{extension}; 659 my $expectedFileName = "$base-$expectedTag.$expectedExtension"; 660 661 my $isText = isTextOnlyTest($actual); 662 663 my $expectedDir = expectedDirectoryForTest($base, $isText, $expectedExtension); 664 $expectedResultPaths{$base} = "$expectedDir/$expectedFileName"; 665 666 unless ($actualRead->{status} eq "success" && $errorRead->{status} eq "success") { 667 my $crashed = $actualRead->{status} eq "crashed" || $errorRead->{status} eq "crashed"; 668 testCrashedOrTimedOut($test, $base, $crashed, $actual, $error); 669 countFinishedTest($test, $base, $crashed ? "crash" : "timedout", 0); 670 next; 671 } 672 673 $durations{$test} = time - $startTime if $report10Slowest; 674 675 my $expected; 676 677 if (!$resetResults && open EXPECTED, "<", "$expectedDir/$expectedFileName") { 678 $expected = ""; 679 while (<EXPECTED>) { 680 next if $stripEditingCallbacks && $_ =~ /^EDITING DELEGATE:/; 681 $expected .= $_; 682 } 683 close EXPECTED; 684 } 685 my $expectedMac; 686 if (!isAppleMacWebKit() && $strictTesting && !$isText) { 687 if (!$resetResults && open EXPECTED, "<", "$testDirectory/platform/mac/$expectedFileName") { 688 $expectedMac = ""; 689 while (<EXPECTED>) { 690 $expectedMac .= $_; 691 } 692 close EXPECTED; 693 } 694 } 695 696 if ($shouldCheckLeaks && $testsPerDumpTool == 1) { 697 print " $test -> "; 698 } 699 700 my $actualPNG = ""; 701 my $diffPNG = ""; 702 my $diffPercentage = ""; 703 my $diffResult = "passed"; 704 705 my $actualHash = ""; 706 my $expectedHash = ""; 707 my $actualPNGSize = 0; 708 709 while (<IN>) { 710 last if /#EOF/; 711 if (/ActualHash: ([a-f0-9]{32})/) { 712 $actualHash = $1; 713 } elsif (/ExpectedHash: ([a-f0-9]{32})/) { 714 $expectedHash = $1; 715 } elsif (/Content-Length: (\d+)\s*/) { 716 $actualPNGSize = $1; 717 read(IN, $actualPNG, $actualPNGSize); 718 } 719 } 720 721 if ($verbose && $pixelTests && !$resetResults && $actualPNGSize) { 722 if ($actualHash eq "" && $expectedHash eq "") { 723 printFailureMessageForTest($test, "WARNING: actual & expected pixel hashes are missing!"); 724 } elsif ($actualHash eq "") { 725 printFailureMessageForTest($test, "WARNING: actual pixel hash is missing!"); 726 } elsif ($expectedHash eq "") { 727 printFailureMessageForTest($test, "WARNING: expected pixel hash is missing!"); 728 } 729 } 730 731 if ($actualPNGSize > 0) { 732 my $expectedPixelDir = expectedDirectoryForTest($base, 0, "png"); 733 734 if (!$resetResults && ($expectedHash ne $actualHash || ($actualHash eq "" && $expectedHash eq ""))) { 735 if (-f "$expectedPixelDir/$base-$expectedTag.png") { 736 my $expectedPNGSize = -s "$expectedPixelDir/$base-$expectedTag.png"; 737 my $expectedPNG = ""; 738 open EXPECTEDPNG, "$expectedPixelDir/$base-$expectedTag.png"; 739 read(EXPECTEDPNG, $expectedPNG, $expectedPNGSize); 740 741 openDiffTool(); 742 print DIFFOUT "Content-Length: $actualPNGSize\n"; 743 print DIFFOUT $actualPNG; 744 745 print DIFFOUT "Content-Length: $expectedPNGSize\n"; 746 print DIFFOUT $expectedPNG; 747 748 while (<DIFFIN>) { 749 last if /^error/ || /^diff:/; 750 if (/Content-Length: (\d+)\s*/) { 751 read(DIFFIN, $diffPNG, $1); 752 } 753 } 754 755 if (/^diff: (.+)% (passed|failed)/) { 756 $diffPercentage = $1; 757 $imageDifferences{$base} = $diffPercentage; 758 $diffResult = $2; 759 } 760 761 if ($diffPercentage == 0) { 762 printFailureMessageForTest($test, "pixel hash failed (but pixel test still passes)"); 763 } 764 } elsif ($verbose) { 765 printFailureMessageForTest($test, "WARNING: expected image is missing!"); 766 } 767 } 768 769 if ($resetResults || !-f "$expectedPixelDir/$base-$expectedTag.png") { 770 mkpath catfile($expectedPixelDir, dirname($base)) if $testDirectory ne $expectedPixelDir; 771 writeToFile("$expectedPixelDir/$base-$expectedTag.png", $actualPNG); 772 } 773 774 if ($actualHash ne "" && ($resetResults || !-f "$expectedPixelDir/$base-$expectedTag.checksum")) { 775 writeToFile("$expectedPixelDir/$base-$expectedTag.checksum", $actualHash); 776 } 777 } 778 779 if (!isAppleMacWebKit() && $strictTesting && !$isText) { 780 if (defined $expectedMac) { 781 my $simplified_actual; 782 $simplified_actual = $actual; 783 $simplified_actual =~ s/at \(-?[0-9]+,-?[0-9]+\) *//g; 784 $simplified_actual =~ s/size -?[0-9]+x-?[0-9]+ *//g; 785 $simplified_actual =~ s/text run width -?[0-9]+: //g; 786 $simplified_actual =~ s/text run width -?[0-9]+ [a-zA-Z ]+: //g; 787 $simplified_actual =~ s/RenderButton {BUTTON} .*/RenderButton {BUTTON}/g; 788 $simplified_actual =~ s/RenderImage {INPUT} .*/RenderImage {INPUT}/g; 789 $simplified_actual =~ s/RenderBlock {INPUT} .*/RenderBlock {INPUT}/g; 790 $simplified_actual =~ s/RenderTextControl {INPUT} .*/RenderTextControl {INPUT}/g; 791 $simplified_actual =~ s/\([0-9]+px/px/g; 792 $simplified_actual =~ s/ *" *\n +" */ /g; 793 $simplified_actual =~ s/" +$/"/g; 794 795 $simplified_actual =~ s/- /-/g; 796 $simplified_actual =~ s/\n( *)"\s+/\n$1"/g; 797 $simplified_actual =~ s/\s+"\n/"\n/g; 798 799 $expectedMac =~ s/at \(-?[0-9]+,-?[0-9]+\) *//g; 800 $expectedMac =~ s/size -?[0-9]+x-?[0-9]+ *//g; 801 $expectedMac =~ s/text run width -?[0-9]+: //g; 802 $expectedMac =~ s/text run width -?[0-9]+ [a-zA-Z ]+: //g; 803 $expectedMac =~ s/RenderButton {BUTTON} .*/RenderButton {BUTTON}/g; 804 $expectedMac =~ s/RenderImage {INPUT} .*/RenderImage {INPUT}/g; 805 $expectedMac =~ s/RenderBlock {INPUT} .*/RenderBlock {INPUT}/g; 806 $expectedMac =~ s/RenderTextControl {INPUT} .*/RenderTextControl {INPUT}/g; 807 $expectedMac =~ s/\([0-9]+px/px/g; 808 $expectedMac =~ s/ *" *\n +" */ /g; 809 $expectedMac =~ s/" +$/"/g; 810 811 $expectedMac =~ s/- /-/g; 812 $expectedMac =~ s/\n( *)"\s+/\n$1"/g; 813 $expectedMac =~ s/\s+"\n/"\n/g; 814 815 if ($simplified_actual ne $expectedMac) { 816 writeToFile("/tmp/actual.txt", $simplified_actual); 817 writeToFile("/tmp/expected.txt", $expectedMac); 818 system "diff -u \"/tmp/expected.txt\" \"/tmp/actual.txt\" > \"/tmp/simplified.diff\""; 819 820 $diffResult = "failed"; 821 if ($verbose) { 822 print "\n"; 823 system "cat /tmp/simplified.diff"; 824 print "failed!!!!!"; 825 } 826 } 827 } 828 } 829 830 if (dumpToolDidCrash()) { 831 $result = "crash"; 832 testCrashedOrTimedOut($test, $base, 1, $actual, $error); 833 } elsif (!defined $expected) { 834 if ($verbose) { 835 print "new " . ($resetResults ? "result" : "test") ."\n"; 836 $atLineStart = 1; 837 } 838 $result = "new"; 839 840 if ($generateNewResults || $resetResults) { 841 mkpath catfile($expectedDir, dirname($base)) if $testDirectory ne $expectedDir; 842 writeToFile("$expectedDir/$expectedFileName", $actual); 843 } 844 deleteExpectedAndActualResults($base); 845 recordActualResultsAndDiff($base, $actual); 846 if (!$resetResults) { 847 # Always print the file name for new tests, as they will probably need some manual inspection. 848 # in verbose mode we already printed the test case, so no need to do it again. 849 unless ($verbose) { 850 print "\n" unless $atLineStart; 851 print "$test -> "; 852 } 853 my $resultsDir = catdir($expectedDir, dirname($base)); 854 if ($generateNewResults) { 855 print "new (results generated in $resultsDir)\n"; 856 } else { 857 print "new\n"; 858 } 859 $atLineStart = 1; 860 } 861 } elsif ($actual eq $expected && $diffResult eq "passed") { 862 if ($verbose) { 863 print "succeeded\n"; 864 $atLineStart = 1; 865 } 866 $result = "match"; 867 deleteExpectedAndActualResults($base); 868 } else { 869 $result = "mismatch"; 870 871 my $pixelTestFailed = $pixelTests && $diffPNG && $diffPNG ne ""; 872 my $testFailed = $actual ne $expected; 873 874 my $message = !$testFailed ? "pixel test failed" : "failed"; 875 876 if (($testFailed || $pixelTestFailed) && $addPlatformExceptions) { 877 my $testBase = catfile($testDirectory, $base); 878 my $expectedBase = catfile($expectedDir, $base); 879 my $testIsMaximallyPlatformSpecific = $testBase =~ m|^\Q$platformTestDirectory\E/|; 880 my $expectedResultIsMaximallyPlatformSpecific = $expectedBase =~ m|^\Q$platformTestDirectory\E/|; 881 if (!$testIsMaximallyPlatformSpecific && !$expectedResultIsMaximallyPlatformSpecific) { 882 mkpath catfile($platformTestDirectory, dirname($base)); 883 if ($testFailed) { 884 my $expectedFile = catfile($platformTestDirectory, "$expectedFileName"); 885 writeToFile("$expectedFile", $actual); 886 } 887 if ($pixelTestFailed) { 888 my $expectedFile = catfile($platformTestDirectory, "$base-$expectedTag.checksum"); 889 writeToFile("$expectedFile", $actualHash); 890 891 $expectedFile = catfile($platformTestDirectory, "$base-$expectedTag.png"); 892 writeToFile("$expectedFile", $actualPNG); 893 } 894 $message .= " (results generated in $platformTestDirectory)"; 895 } 896 } 897 898 printFailureMessageForTest($test, $message); 899 900 my $dir = "$testResultsDirectory/$base"; 901 $dir =~ s|/([^/]+)$|| or die "Failed to find test name from base\n"; 902 my $testName = $1; 903 mkpath $dir; 904 905 deleteExpectedAndActualResults($base); 906 recordActualResultsAndDiff($base, $actual); 907 908 if ($pixelTestFailed) { 909 $imagesPresent{$base} = 1; 910 911 writeToFile("$testResultsDirectory/$base-$actualTag.png", $actualPNG); 912 writeToFile("$testResultsDirectory/$base-$diffsTag.png", $diffPNG); 913 914 my $expectedPixelDir = expectedDirectoryForTest($base, 0, "png"); 915 copy("$expectedPixelDir/$base-$expectedTag.png", "$testResultsDirectory/$base-$expectedTag.png"); 916 917 open DIFFHTML, ">$testResultsDirectory/$base-$diffsTag.html" or die; 918 print DIFFHTML "<html>\n"; 919 print DIFFHTML "<head>\n"; 920 print DIFFHTML "<title>$base Image Compare</title>\n"; 921 print DIFFHTML "<script language=\"Javascript\" type=\"text/javascript\">\n"; 922 print DIFFHTML "var currentImage = 0;\n"; 923 print DIFFHTML "var imageNames = new Array(\"Actual\", \"Expected\");\n"; 924 print DIFFHTML "var imagePaths = new Array(\"$testName-$actualTag.png\", \"$testName-$expectedTag.png\");\n"; 925 if (-f "$testDirectory/$base-w3c.png") { 926 copy("$testDirectory/$base-w3c.png", "$testResultsDirectory/$base-w3c.png"); 927 print DIFFHTML "imageNames.push(\"W3C\");\n"; 928 print DIFFHTML "imagePaths.push(\"$testName-w3c.png\");\n"; 929 } 930 print DIFFHTML "function animateImage() {\n"; 931 print DIFFHTML " var image = document.getElementById(\"animatedImage\");\n"; 932 print DIFFHTML " var imageText = document.getElementById(\"imageText\");\n"; 933 print DIFFHTML " image.src = imagePaths[currentImage];\n"; 934 print DIFFHTML " imageText.innerHTML = imageNames[currentImage] + \" Image\";\n"; 935 print DIFFHTML " currentImage = (currentImage + 1) % imageNames.length;\n"; 936 print DIFFHTML " setTimeout('animateImage()',2000);\n"; 937 print DIFFHTML "}\n"; 938 print DIFFHTML "</script>\n"; 939 print DIFFHTML "</head>\n"; 940 print DIFFHTML "<body onLoad=\"animateImage();\">\n"; 941 print DIFFHTML "<table>\n"; 942 if ($diffPercentage) { 943 print DIFFHTML "<tr>\n"; 944 print DIFFHTML "<td>Difference between images: <a href=\"$testName-$diffsTag.png\">$diffPercentage%</a></td>\n"; 945 print DIFFHTML "</tr>\n"; 946 } 947 print DIFFHTML "<tr>\n"; 948 print DIFFHTML "<td><a href=\"" . toURL("$testDirectory/$test") . "\">test file</a></td>\n"; 949 print DIFFHTML "</tr>\n"; 950 print DIFFHTML "<tr>\n"; 951 print DIFFHTML "<td id=\"imageText\" style=\"text-weight: bold;\">Actual Image</td>\n"; 952 print DIFFHTML "</tr>\n"; 953 print DIFFHTML "<tr>\n"; 954 print DIFFHTML "<td><img src=\"$testName-$actualTag.png\" id=\"animatedImage\"></td>\n"; 955 print DIFFHTML "</tr>\n"; 956 print DIFFHTML "</table>\n"; 957 print DIFFHTML "</body>\n"; 958 print DIFFHTML "</html>\n"; 959 } 960 } 961 962 if ($error) { 963 my $dir = "$testResultsDirectory/$base"; 964 $dir =~ s|/([^/]+)$|| or die "Failed to find test name from base\n"; 965 mkpath $dir; 966 967 writeToFile("$testResultsDirectory/$base-$errorTag.txt", $error); 968 969 $counts{error}++; 970 push @{$tests{error}}, $test; 971 } 972 973 countFinishedTest($test, $base, $result, $isText); 974} 975printf "\n%0.2fs total testing time\n", (time - $overallStartTime) . ""; 976 977!$isDumpToolOpen || die "Failed to close $dumpToolName.\n"; 978 979closeHTTPD(); 980 981# Because multiple instances of this script are running concurrently we cannot 982# safely delete this symlink. 983# system "rm /tmp/LayoutTests"; 984 985# FIXME: Do we really want to check the image-comparison tool for leaks every time? 986if ($isDiffToolOpen && $shouldCheckLeaks) { 987 $totalLeaks += countAndPrintLeaks("ImageDiff", $imageDiffToolPID, "$testResultsDirectory/ImageDiff-leaks.txt"); 988} 989 990if ($totalLeaks) { 991 if ($mergeDepth) { 992 parseLeaksandPrintUniqueLeaks(); 993 } 994 else { 995 print "\nWARNING: $totalLeaks total leaks found!\n"; 996 print "See above for individual leaks results.\n" if ($leaksOutputFileNumber > 2); 997 } 998} 999 1000close IN; 1001close OUT; 1002close ERROR; 1003 1004if ($report10Slowest) { 1005 print "\n\nThe 10 slowest tests:\n\n"; 1006 my $count = 0; 1007 for my $test (sort slowestcmp keys %durations) { 1008 printf "%0.2f secs: %s\n", $durations{$test}, $test; 1009 last if ++$count == 10; 1010 } 1011} 1012 1013print "\n"; 1014 1015if ($skippedOnly && $counts{"match"}) { 1016 print "The following tests are in the Skipped file (" . File::Spec->abs2rel("$platformTestDirectory/Skipped", $testDirectory) . "), but succeeded:\n"; 1017 foreach my $test (@{$tests{"match"}}) { 1018 print " $test\n"; 1019 } 1020} 1021 1022if ($resetResults || ($counts{match} && $counts{match} == $count)) { 1023 print "all $count test cases succeeded\n"; 1024 unlink $testResults; 1025 exit; 1026} 1027 1028 1029my %text = ( 1030 match => "succeeded", 1031 mismatch => "had incorrect layout", 1032 new => "were new", 1033 timedout => "timed out", 1034 crash => "crashed", 1035 error => "had stderr output" 1036); 1037 1038for my $type ("match", "mismatch", "new", "timedout", "crash", "error") { 1039 my $c = $counts{$type}; 1040 if ($c) { 1041 my $t = $text{$type}; 1042 my $message; 1043 if ($c == 1) { 1044 $t =~ s/were/was/; 1045 $message = sprintf "1 test case (%d%%) %s\n", 1 * 100 / $count, $t; 1046 } else { 1047 $message = sprintf "%d test cases (%d%%) %s\n", $c, $c * 100 / $count, $t; 1048 } 1049 $message =~ s-\(0%\)-(<1%)-; 1050 print $message; 1051 } 1052} 1053 1054mkpath $testResultsDirectory; 1055 1056open HTML, ">", $testResults or die "Failed to open $testResults. $!"; 1057print HTML "<html>\n"; 1058print HTML "<head>\n"; 1059print HTML "<title>Layout Test Results</title>\n"; 1060print HTML "</head>\n"; 1061print HTML "<body>\n"; 1062 1063print HTML htmlForResultsSection(@{$tests{mismatch}}, "Tests where results did not match expected results", \&linksForMismatchTest); 1064print HTML htmlForResultsSection(@{$tests{timedout}}, "Tests that timed out", \&linksForErrorTest); 1065print HTML htmlForResultsSection(@{$tests{crash}}, "Tests that caused the DumpRenderTree tool to crash", \&linksForErrorTest); 1066print HTML htmlForResultsSection(@{$tests{error}}, "Tests that had stderr output", \&linksForErrorTest); 1067print HTML htmlForResultsSection(@{$tests{new}}, "Tests that had no expected results (probably new)", \&linksForNewTest); 1068 1069print HTML "</body>\n"; 1070print HTML "</html>\n"; 1071close HTML; 1072 1073my @configurationArgs = argumentsForConfiguration(); 1074 1075if (isQt() || isGtk()) { 1076 system "WebKitTools/Scripts/run-launcher", @configurationArgs, "file://".$testResults if $launchSafari; 1077} elsif (isCygwin()) { 1078 system "cygstart", $testResults if $launchSafari; 1079} else { 1080 system "WebKitTools/Scripts/run-safari", @configurationArgs, "-NSOpen", $testResults if $launchSafari; 1081} 1082 1083closeCygpaths() if isCygwin(); 1084 1085exit 1; 1086 1087sub countAndPrintLeaks($$$) 1088{ 1089 my ($dumpToolName, $dumpToolPID, $leaksFilePath) = @_; 1090 1091 print "\n" unless $atLineStart; 1092 $atLineStart = 1; 1093 1094 # We are excluding the following reported leaks so they don't get in our way when looking for WebKit leaks: 1095 # This allows us ignore known leaks and only be alerted when new leaks occur. Some leaks are in the old 1096 # versions of the system frameworks that are being used by the leaks bots. Even though a leak has been 1097 # fixed, it will be listed here until the bot has been updated with the newer frameworks. 1098 1099 my @typesToExclude = ( 1100 ); 1101 1102 my @callStacksToExclude = ( 1103 "Flash_EnforceLocalSecurity" # leaks in Flash plug-in code, rdar://problem/4449747 1104 ); 1105 1106 if (isTiger()) { 1107 # Leak list for the version of Tiger used on the build bot. 1108 push @callStacksToExclude, ( 1109 "CFRunLoopRunSpecific \\| malloc_zone_malloc", "CFRunLoopRunSpecific \\| CFAllocatorAllocate ", # leak in CFRunLoopRunSpecific, rdar://problem/4670839 1110 "CGImageSourceGetPropertiesAtIndex", # leak in ImageIO, rdar://problem/4628809 1111 "FOGetCoveredUnicodeChars", # leak in ATS, rdar://problem/3943604 1112 "GetLineDirectionPreference", "InitUnicodeUtilities", # leaks tool falsely reporting leak in CFNotificationCenterAddObserver, rdar://problem/4964790 1113 "ICCFPrefWrapper::GetPrefDictionary", # leaks in Internet Config. code, rdar://problem/4449794 1114 "NSHTTPURLProtocol setResponseHeader:", # leak in multipart/mixed-replace handling in Foundation, no Radar, but fixed in Leopard 1115 "NSURLCache cachedResponseForRequest", # leak in CFURL cache, rdar://problem/4768430 1116 "PCFragPrepareClosureFromFile", # leak in Code Fragment Manager, rdar://problem/3426998 1117 "WebCore::Selection::toRange", # bug in 'leaks', rdar://problem/4967949 1118 "WebCore::SubresourceLoader::create", # bug in 'leaks', rdar://problem/4985806 1119 "_CFPreferencesDomainDeepCopyDictionary", # leak in CFPreferences, rdar://problem/4220786 1120 "_objc_msgForward", # leak in NSSpellChecker, rdar://problem/4965278 1121 "gldGetString", # leak in OpenGL, rdar://problem/5013699 1122 "_setDefaultUserInfoFromURL", # leak in NSHTTPAuthenticator, rdar://problem/5546453 1123 "SSLHandshake", # leak in SSL, rdar://problem/5546440 1124 "SecCertificateCreateFromData", # leak in SSL code, rdar://problem/4464397 1125 ); 1126 push @typesToExclude, ( 1127 "THRD", # bug in 'leaks', rdar://problem/3387783 1128 "DRHT", # ditto (endian little hate i) 1129 ); 1130 } 1131 1132 if (isLeopard()) { 1133 # Leak list for the version of Leopard used on the build bot. 1134 push @callStacksToExclude, ( 1135 "CFHTTPMessageAppendBytes", # leak in CFNetwork, rdar://problem/5435912 1136 "sendDidReceiveDataCallback", # leak in CFNetwork, rdar://problem/5441619 1137 "_CFHTTPReadStreamReadMark", # leak in CFNetwork, rdar://problem/5441468 1138 "httpProtocolStart", # leak in CFNetwork, rdar://problem/5468837 1139 "_CFURLConnectionSendCallbacks", # leak in CFNetwork, rdar://problem/5441600 1140 "DispatchQTMsg", # leak in QuickTime, PPC only, rdar://problem/5667132 1141 "QTMovieContentView createVisualContext", # leak in QuickTime, PPC only, rdar://problem/5667132 1142 "_CopyArchitecturesForJVMVersion", # leak in Java, rdar://problem/5910823 1143 ); 1144 } 1145 1146 my $leaksTool = sourceDir() . "/WebKitTools/Scripts/run-leaks"; 1147 my $excludeString = "--exclude-callstack '" . (join "' --exclude-callstack '", @callStacksToExclude) . "'"; 1148 $excludeString .= " --exclude-type '" . (join "' --exclude-type '", @typesToExclude) . "'" if @typesToExclude; 1149 1150 print " ? checking for leaks in $dumpToolName\n"; 1151 my $leaksOutput = `$leaksTool $excludeString $dumpToolPID`; 1152 my ($count, $bytes) = $leaksOutput =~ /Process $dumpToolPID: (\d+) leaks? for (\d+) total/; 1153 my ($excluded) = $leaksOutput =~ /(\d+) leaks? excluded/; 1154 1155 my $adjustedCount = $count; 1156 $adjustedCount -= $excluded if $excluded; 1157 1158 if (!$adjustedCount) { 1159 print " - no leaks found\n"; 1160 unlink $leaksFilePath; 1161 return 0; 1162 } else { 1163 my $dir = $leaksFilePath; 1164 $dir =~ s|/[^/]+$|| or die; 1165 mkpath $dir; 1166 1167 if ($excluded) { 1168 print " + $adjustedCount leaks ($bytes bytes including $excluded excluded leaks) were found, details in $leaksFilePath\n"; 1169 } else { 1170 print " + $count leaks ($bytes bytes) were found, details in $leaksFilePath\n"; 1171 } 1172 1173 writeToFile($leaksFilePath, $leaksOutput); 1174 1175 push( @leaksFilenames, $leaksFilePath ); 1176 } 1177 1178 return $adjustedCount; 1179} 1180 1181sub writeToFile($$) 1182{ 1183 my ($filePath, $contents) = @_; 1184 open NEWFILE, ">", "$filePath" or die "Could not create $filePath. $!\n"; 1185 print NEWFILE $contents; 1186 close NEWFILE; 1187} 1188 1189# Break up a path into the directory (with slash) and base name. 1190sub splitpath($) 1191{ 1192 my ($path) = @_; 1193 1194 my $pathSeparator = "/"; 1195 my $dirname = dirname($path) . $pathSeparator; 1196 $dirname = "" if $dirname eq "." . $pathSeparator; 1197 1198 return ($dirname, basename($path)); 1199} 1200 1201# Sort first by directory, then by file, so all paths in one directory are grouped 1202# rather than being interspersed with items from subdirectories. 1203# Use numericcmp to sort directory and filenames to make order logical. 1204sub pathcmp($$) 1205{ 1206 my ($patha, $pathb) = @_; 1207 1208 my ($dira, $namea) = splitpath($patha); 1209 my ($dirb, $nameb) = splitpath($pathb); 1210 1211 return numericcmp($dira, $dirb) if $dira ne $dirb; 1212 return numericcmp($namea, $nameb); 1213} 1214 1215# Sort numeric parts of strings as numbers, other parts as strings. 1216# Makes 1.33 come after 1.3, which is cool. 1217sub numericcmp($$) 1218{ 1219 my ($aa, $bb) = @_; 1220 1221 my @a = split /(\d+)/, $aa; 1222 my @b = split /(\d+)/, $bb; 1223 1224 # Compare one chunk at a time. 1225 # Each chunk is either all numeric digits, or all not numeric digits. 1226 while (@a && @b) { 1227 my $a = shift @a; 1228 my $b = shift @b; 1229 1230 # Use numeric comparison if chunks are non-equal numbers. 1231 return $a <=> $b if $a =~ /^\d/ && $b =~ /^\d/ && $a != $b; 1232 1233 # Use string comparison if chunks are any other kind of non-equal string. 1234 return $a cmp $b if $a ne $b; 1235 } 1236 1237 # One of the two is now empty; compare lengths for result in this case. 1238 return @a <=> @b; 1239} 1240 1241# Sort slowest tests first. 1242sub slowestcmp($$) 1243{ 1244 my ($testa, $testb) = @_; 1245 1246 my $dura = $durations{$testa}; 1247 my $durb = $durations{$testb}; 1248 return $durb <=> $dura if $dura != $durb; 1249 return pathcmp($testa, $testb); 1250} 1251 1252sub launchWithCurrentEnv(@) 1253{ 1254 my (@args) = @_; 1255 1256 # Dump the current environment as perl code and then put it in quotes so it is one parameter. 1257 my $environmentDumper = Data::Dumper->new([\%ENV], [qw(*ENV)]); 1258 $environmentDumper->Indent(0); 1259 $environmentDumper->Purity(1); 1260 my $allEnvVars = $environmentDumper->Dump(); 1261 unshift @args, "\"$allEnvVars\""; 1262 1263 my $execScript = File::Spec->catfile(sourceDir(), qw(WebKitTools Scripts execAppWithEnv)); 1264 unshift @args, $execScript; 1265 return @args; 1266} 1267 1268sub openDiffTool() 1269{ 1270 return if $isDiffToolOpen; 1271 return if !$pixelTests; 1272 1273 local %ENV; 1274 $ENV{MallocStackLogging} = 1 if $shouldCheckLeaks; 1275 $imageDiffToolPID = open2(\*DIFFIN, \*DIFFOUT, $imageDiffTool, launchWithCurrentEnv(@diffToolArgs)) or die "unable to open $imageDiffTool\n"; 1276 $ENV{MallocStackLogging} = 0 if $shouldCheckLeaks; 1277 $isDiffToolOpen = 1; 1278} 1279 1280sub openDumpTool() 1281{ 1282 return if $isDumpToolOpen; 1283 1284 # Save environment variables required for the linux environment. 1285 my $homeDir = $ENV{'HOME'}; 1286 my $libraryPath = $ENV{'LD_LIBRARY_PATH'}; 1287 my $dyldLibraryPath = $ENV{'DYLD_LIBRARY_PATH'}; 1288 my $dbusAddress = $ENV{'DBUS_SESSION_BUS_ADDRESS'}; 1289 my $display = $ENV{'DISPLAY'}; 1290 my $xauthority = $ENV{'XAUTHORITY'}; 1291 my $testfonts = $ENV{'WEBKIT_TESTFONTS'}; 1292 1293 my $homeDrive = $ENV{'HOMEDRIVE'}; 1294 my $homePath = $ENV{'HOMEPATH'}; 1295 1296 local %ENV; 1297 if (isQt() || isGtk()) { 1298 if (defined $display) { 1299 $ENV{DISPLAY} = $display; 1300 } else { 1301 $ENV{DISPLAY} = ":1"; 1302 } 1303 if (defined $xauthority) { 1304 $ENV{XAUTHORITY} = $xauthority; 1305 } 1306 $ENV{'WEBKIT_TESTFONTS'} = $testfonts if defined($testfonts); 1307 $ENV{HOME} = $homeDir; 1308 if (defined $libraryPath) { 1309 $ENV{LD_LIBRARY_PATH} = $libraryPath; 1310 } 1311 if (defined $dyldLibraryPath) { 1312 $ENV{DYLD_LIBRARY_PATH} = $dyldLibraryPath; 1313 } 1314 if (defined $dbusAddress) { 1315 $ENV{DBUS_SESSION_BUS_ADDRESS} = $dbusAddress; 1316 } 1317 } 1318 if (isQt()) { 1319 $ENV{QTWEBKIT_PLUGIN_PATH} = productDir() . "/lib/plugins"; 1320 } 1321 $ENV{DYLD_FRAMEWORK_PATH} = $productDir; 1322 $ENV{XML_CATALOG_FILES} = ""; # work around missing /etc/catalog <rdar://problem/4292995> 1323 $ENV{DYLD_INSERT_LIBRARIES} = "/usr/lib/libgmalloc.dylib" if $guardMalloc; 1324 1325 if (isCygwin()) { 1326 $ENV{HOMEDRIVE} = $homeDrive; 1327 $ENV{HOMEPATH} = $homePath; 1328 if ($testfonts) { 1329 $ENV{WEBKIT_TESTFONTS} = $testfonts; 1330 } 1331 setPathForRunningWebKitApp(\%ENV) if isCygwin(); 1332 } 1333 1334 my @args = ($dumpTool, @toolArgs); 1335 if (isAppleMacWebKit() and !isTiger()) { 1336 unshift @args, "arch", "-" . architecture(); 1337 } 1338 1339 if ($useValgrind) { 1340 unshift @args, "valgrind"; 1341 } 1342 1343 $ENV{MallocStackLogging} = 1 if $shouldCheckLeaks; 1344 $dumpToolPID = open3(\*OUT, \*IN, \*ERROR, launchWithCurrentEnv(@args)) or die "Failed to start tool: $dumpTool\n"; 1345 $ENV{MallocStackLogging} = 0 if $shouldCheckLeaks; 1346 $isDumpToolOpen = 1; 1347 $dumpToolCrashed = 0; 1348} 1349 1350sub closeDumpTool() 1351{ 1352 return if !$isDumpToolOpen; 1353 1354 close IN; 1355 close OUT; 1356 waitpid $dumpToolPID, 0; 1357 1358 # check for WebCore counter leaks. 1359 if ($shouldCheckLeaks) { 1360 while (<ERROR>) { 1361 print; 1362 } 1363 } 1364 close ERROR; 1365 $isDumpToolOpen = 0; 1366} 1367 1368sub dumpToolDidCrash() 1369{ 1370 return 1 if $dumpToolCrashed; 1371 return 0 unless $isDumpToolOpen; 1372 1373 my $pid = waitpid(-1, WNOHANG); 1374 return 1 if ($pid == $dumpToolPID); 1375 1376 # On Mac OS X, crashing may be significantly delayed by crash reporter. 1377 return 0 unless isAppleMacWebKit(); 1378 1379 return DumpRenderTreeSupport::processIsCrashing($dumpToolPID); 1380} 1381 1382sub openHTTPDIfNeeded() 1383{ 1384 return if $isHttpdOpen; 1385 1386 mkdir "/tmp/WebKit"; 1387 1388 if (-f "/tmp/WebKit/httpd.pid") { 1389 my $oldPid = `cat /tmp/WebKit/httpd.pid`; 1390 chomp $oldPid; 1391 if (0 != kill 0, $oldPid) { 1392 print "\nhttpd is already running: pid $oldPid, killing...\n"; 1393 kill 15, $oldPid; 1394 1395 my $retryCount = 20; 1396 while ((0 != kill 0, $oldPid) && $retryCount) { 1397 sleep 1; 1398 --$retryCount; 1399 } 1400 1401 die "Timed out waiting for httpd to quit" unless $retryCount; 1402 } 1403 } 1404 1405 my $httpdPath = "/usr/sbin/httpd"; 1406 my $httpdConfig; 1407 if (isCygwin()) { 1408 my $windowsConfDirectory = "$testDirectory/http/conf/"; 1409 unless (-x "/usr/lib/apache/libphp4.dll") { 1410 copy("$windowsConfDirectory/libphp4.dll", "/usr/lib/apache/libphp4.dll"); 1411 chmod(0755, "/usr/lib/apache/libphp4.dll"); 1412 } 1413 $httpdConfig = "$windowsConfDirectory/cygwin-httpd.conf"; 1414 } elsif (isDebianBased()) { 1415 $httpdPath = "/usr/sbin/apache2"; 1416 $httpdConfig = "$testDirectory/http/conf/apache2-debian-httpd.conf"; 1417 } else { 1418 $httpdConfig = "$testDirectory/http/conf/httpd.conf"; 1419 $httpdConfig = "$testDirectory/http/conf/apache2-httpd.conf" if `$httpdPath -v` =~ m|Apache/2|; 1420 } 1421 my $documentRoot = "$testDirectory/http/tests"; 1422 my $jsTestResourcesDirectory = $testDirectory . "/fast/js/resources"; 1423 my $typesConfig = "$testDirectory/http/conf/mime.types"; 1424 my $listen = "127.0.0.1:$httpdPort"; 1425 my $absTestResultsDirectory = File::Spec->rel2abs(glob $testResultsDirectory); 1426 my $sslCertificate = "$testDirectory/http/conf/webkit-httpd.pem"; 1427 1428 mkpath $absTestResultsDirectory; 1429 1430 my @args = ( 1431 "-f", "$httpdConfig", 1432 "-C", "DocumentRoot \"$documentRoot\"", 1433 # Setup a link to where the js test templates are stored, use -c so that mod_alias will already be laoded. 1434 "-c", "Alias /js-test-resources \"$jsTestResourcesDirectory\"", 1435 "-C", "Listen $listen", 1436 "-c", "TypesConfig \"$typesConfig\"", 1437 "-c", "CustomLog \"$absTestResultsDirectory/access_log.txt\" common", 1438 "-c", "ErrorLog \"$absTestResultsDirectory/error_log.txt\"", 1439 # Apache wouldn't run CGIs with permissions==700 otherwise 1440 "-c", "User \"#$<\"" 1441 ); 1442 1443 # FIXME: Enable this on Windows once <rdar://problem/5345985> is fixed 1444 # The version of Apache we use with Cygwin does not support SSL 1445 push(@args, "-c", "SSLCertificateFile \"$sslCertificate\"") unless isCygwin(); 1446 1447 open2(\*HTTPDIN, \*HTTPDOUT, $httpdPath, @args); 1448 1449 my $retryCount = 20; 1450 while (system("/usr/bin/curl -q --silent --stderr - --output /dev/null $listen") && $retryCount) { 1451 sleep 1; 1452 --$retryCount; 1453 } 1454 1455 die "Timed out waiting for httpd to start" unless $retryCount; 1456 1457 $isHttpdOpen = 1; 1458} 1459 1460sub closeHTTPD() 1461{ 1462 return if !$isHttpdOpen; 1463 1464 close HTTPDIN; 1465 close HTTPDOUT; 1466 1467 kill 15, `cat /tmp/WebKit/httpd.pid` if -f "/tmp/WebKit/httpd.pid"; 1468 1469 $isHttpdOpen = 0; 1470} 1471 1472sub fileNameWithNumber($$) 1473{ 1474 my ($base, $number) = @_; 1475 return "$base$number" if ($number > 1); 1476 return $base; 1477} 1478 1479sub processIgnoreTests($$) { 1480 my @ignoreList = split(/\s*,\s*/, shift); 1481 my $listName = shift; 1482 1483 my $disabledSuffix = "-disabled"; 1484 1485 my $addIgnoredDirectories = sub { 1486 return () if exists $ignoredLocalDirectories{basename($File::Find::dir)}; 1487 $ignoredDirectories{File::Spec->abs2rel($File::Find::dir, $testDirectory)} = 1; 1488 return @_; 1489 }; 1490 foreach my $item (@ignoreList) { 1491 my $path = catfile($testDirectory, $item); 1492 if (-d $path) { 1493 $ignoredDirectories{$item} = 1; 1494 find({ preprocess => $addIgnoredDirectories, wanted => sub {} }, $path); 1495 } 1496 elsif (-f $path) { 1497 $ignoredFiles{$item} = 1; 1498 } elsif (-f $path . $disabledSuffix) { 1499 # The test is disabled, so do nothing. 1500 } else { 1501 print "$listName list contained '$item', but no file of that name could be found\n"; 1502 } 1503 } 1504} 1505 1506sub stripExtension($) 1507{ 1508 my ($test) = @_; 1509 1510 $test =~ s/\.[a-zA-Z]+$//; 1511 return $test; 1512} 1513 1514sub isTextOnlyTest($) 1515{ 1516 my ($actual) = @_; 1517 my $isText; 1518 if ($actual =~ /^layer at/ms) { 1519 $isText = 0; 1520 } else { 1521 $isText = 1; 1522 } 1523 return $isText; 1524} 1525 1526sub expectedDirectoryForTest($;$;$) 1527{ 1528 my ($base, $isText, $expectedExtension) = @_; 1529 1530 my @directories = @platformResultHierarchy; 1531 push @directories, map { catdir($platformBaseDirectory, $_) } qw(mac-snowleopard mac) if isCygwin(); 1532 push @directories, $expectedDirectory; 1533 1534 # If we already have expected results, just return their location. 1535 foreach my $directory (@directories) { 1536 return $directory if (-f "$directory/$base-$expectedTag.$expectedExtension"); 1537 } 1538 1539 # For cross-platform tests, text-only results should go in the cross-platform directory, 1540 # while render tree dumps should go in the least-specific platform directory. 1541 return $isText ? $expectedDirectory : $platformResultHierarchy[$#platformResultHierarchy]; 1542} 1543 1544sub countFinishedTest($$$$) { 1545 my ($test, $base, $result, $isText) = @_; 1546 1547 if (($count + 1) % $testsPerDumpTool == 0 || $count == $#tests) { 1548 if ($shouldCheckLeaks) { 1549 my $fileName; 1550 if ($testsPerDumpTool == 1) { 1551 $fileName = "$testResultsDirectory/$base-leaks.txt"; 1552 } else { 1553 $fileName = "$testResultsDirectory/" . fileNameWithNumber($dumpToolName, $leaksOutputFileNumber) . "-leaks.txt"; 1554 } 1555 my $leakCount = countAndPrintLeaks($dumpToolName, $dumpToolPID, $fileName); 1556 $totalLeaks += $leakCount; 1557 $leaksOutputFileNumber++ if ($leakCount); 1558 } 1559 1560 closeDumpTool(); 1561 } 1562 1563 $count++; 1564 $counts{$result}++; 1565 push @{$tests{$result}}, $test; 1566 $testType{$test} = $isText; 1567} 1568 1569sub testCrashedOrTimedOut($$$$$) 1570{ 1571 my ($test, $base, $didCrash, $actual, $error) = @_; 1572 1573 printFailureMessageForTest($test, $didCrash ? "crashed" : "timed out"); 1574 1575 sampleDumpTool() unless $didCrash; 1576 1577 my $dir = "$testResultsDirectory/$base"; 1578 $dir =~ s|/([^/]+)$|| or die "Failed to find test name from base\n"; 1579 mkpath $dir; 1580 1581 deleteExpectedAndActualResults($base); 1582 1583 if (defined($error) && length($error)) { 1584 writeToFile("$testResultsDirectory/$base-$errorTag.txt", $error); 1585 } 1586 1587 recordActualResultsAndDiff($base, $actual); 1588 1589 kill 9, $dumpToolPID unless $didCrash; 1590 1591 closeDumpTool(); 1592} 1593 1594sub printFailureMessageForTest($$) 1595{ 1596 my ($test, $description) = @_; 1597 1598 unless ($verbose) { 1599 print "\n" unless $atLineStart; 1600 print "$test -> "; 1601 } 1602 print "$description\n"; 1603 $atLineStart = 1; 1604} 1605 1606my %cygpaths = (); 1607 1608sub openCygpathIfNeeded($) 1609{ 1610 my ($options) = @_; 1611 1612 return unless isCygwin(); 1613 return $cygpaths{$options} if $cygpaths{$options} && $cygpaths{$options}->{"open"}; 1614 1615 local (*CYGPATHIN, *CYGPATHOUT); 1616 my $pid = open2(\*CYGPATHIN, \*CYGPATHOUT, "cygpath -f - $options"); 1617 my $cygpath = { 1618 "pid" => $pid, 1619 "in" => *CYGPATHIN, 1620 "out" => *CYGPATHOUT, 1621 "open" => 1 1622 }; 1623 1624 $cygpaths{$options} = $cygpath; 1625 1626 return $cygpath; 1627} 1628 1629sub closeCygpaths() 1630{ 1631 return unless isCygwin(); 1632 1633 foreach my $cygpath (values(%cygpaths)) { 1634 close $cygpath->{"in"}; 1635 close $cygpath->{"out"}; 1636 waitpid($cygpath->{"pid"}, 0); 1637 $cygpath->{"open"} = 0; 1638 1639 } 1640} 1641 1642sub convertPathUsingCygpath($$) 1643{ 1644 my ($path, $options) = @_; 1645 1646 my $cygpath = openCygpathIfNeeded($options); 1647 local *inFH = $cygpath->{"in"}; 1648 local *outFH = $cygpath->{"out"}; 1649 print outFH $path . "\n"; 1650 chomp(my $convertedPath = <inFH>); 1651 return $convertedPath; 1652} 1653 1654sub toWindowsPath($) 1655{ 1656 my ($path) = @_; 1657 return unless isCygwin(); 1658 1659 return convertPathUsingCygpath($path, "-w"); 1660} 1661 1662sub toURL($) 1663{ 1664 my ($path) = @_; 1665 1666 if ($useRemoteLinksToTests) { 1667 my $relativePath = File::Spec->abs2rel($path, $testDirectory); 1668 1669 # If the file is below the test directory then convert it into a link to the file in SVN 1670 if ($relativePath !~ /^\.\.\//) { 1671 my $revision = svnRevisionForDirectory($testDirectory); 1672 my $svnPath = pathRelativeToSVNRepositoryRootForPath($path); 1673 return "http://trac.webkit.org/export/$revision/$svnPath"; 1674 } 1675 } 1676 1677 return $path unless isCygwin(); 1678 1679 return "file:///" . convertPathUsingCygpath($path, "-m"); 1680} 1681 1682sub validateSkippedArg($$;$) 1683{ 1684 my ($option, $value, $value2) = @_; 1685 my %validSkippedValues = map { $_ => 1 } qw(default ignore only); 1686 $value = lc($value); 1687 die "Invalid argument '" . $value . "' for option $option" unless $validSkippedValues{$value}; 1688 $treatSkipped = $value; 1689} 1690 1691sub htmlForResultsSection(\@$&) 1692{ 1693 my ($tests, $description, $linkGetter) = @_; 1694 1695 my @html = (); 1696 return join("\n", @html) unless @{$tests}; 1697 1698 push @html, "<p>$description:</p>"; 1699 push @html, "<table>"; 1700 foreach my $test (@{$tests}) { 1701 push @html, "<tr>"; 1702 push @html, "<td><a href=\"" . toURL("$testDirectory/$test") . "\">$test</a></td>"; 1703 foreach my $link (@{&{$linkGetter}($test)}) { 1704 push @html, "<td><a href=\"$link->{href}\">$link->{text}</a></td>"; 1705 } 1706 push @html, "</tr>"; 1707 } 1708 push @html, "</table>"; 1709 1710 return join("\n", @html); 1711} 1712 1713sub linksForExpectedAndActualResults($) 1714{ 1715 my ($base) = @_; 1716 1717 my @links = (); 1718 1719 return \@links unless -s "$testResultsDirectory/$base-$diffsTag.txt"; 1720 1721 my $expectedResultPath = $expectedResultPaths{$base}; 1722 my ($expectedResultFileName, $expectedResultsDirectory, $expectedResultExtension) = fileparse($expectedResultPath, qr{\.[^.]+$}); 1723 1724 push @links, { href => "$base-$expectedTag$expectedResultExtension", text => "expected" }; 1725 push @links, { href => "$base-$actualTag$expectedResultExtension", text => "actual" }; 1726 push @links, { href => "$base-$diffsTag.txt", text => "diff" }; 1727 push @links, { href => "$base-$prettyDiffTag.html", text => "pretty diff" }; 1728 1729 return \@links; 1730} 1731 1732sub linksForMismatchTest 1733{ 1734 my ($test) = @_; 1735 1736 my @links = (); 1737 1738 my $base = stripExtension($test); 1739 1740 push @links, @{linksForExpectedAndActualResults($base)}; 1741 return \@links unless $pixelTests && $imagesPresent{$base}; 1742 1743 push @links, { href => "$base-$expectedTag.png", text => "expected image" }; 1744 push @links, { href => "$base-$diffsTag.html", text => "image diffs" }; 1745 push @links, { href => "$base-$diffsTag.png", text => "$imageDifferences{$base}%" }; 1746 1747 return \@links; 1748} 1749 1750sub linksForErrorTest 1751{ 1752 my ($test) = @_; 1753 1754 my @links = (); 1755 1756 my $base = stripExtension($test); 1757 1758 push @links, @{linksForExpectedAndActualResults($base)}; 1759 push @links, { href => "$base-$errorTag.txt", text => "stderr" }; 1760 1761 return \@links; 1762} 1763 1764sub linksForNewTest 1765{ 1766 my ($test) = @_; 1767 1768 my @links = (); 1769 1770 my $base = stripExtension($test); 1771 1772 my $expectedResultPath = $expectedResultPaths{$base}; 1773 my ($expectedResultFileName, $expectedResultsDirectory, $expectedResultExtension) = fileparse($expectedResultPath, qr{\.[^.]+$}); 1774 1775 push @links, { href => "$base-$actualTag$expectedResultExtension", text => "result" }; 1776 if ($pixelTests && $imagesPresent{$base}) { 1777 push @links, { href => "$base-$expectedTag.png", text => "image" }; 1778 } 1779 1780 return \@links; 1781} 1782 1783sub deleteExpectedAndActualResults($) 1784{ 1785 my ($base) = @_; 1786 1787 unlink "$testResultsDirectory/$base-$actualTag.txt"; 1788 unlink "$testResultsDirectory/$base-$diffsTag.txt"; 1789 unlink "$testResultsDirectory/$base-$errorTag.txt"; 1790} 1791 1792sub recordActualResultsAndDiff($$) 1793{ 1794 my ($base, $actualResults) = @_; 1795 1796 return unless defined($actualResults) && length($actualResults); 1797 1798 my $expectedResultPath = $expectedResultPaths{$base}; 1799 my ($expectedResultFileNameMinusExtension, $expectedResultDirectoryPath, $expectedResultExtension) = fileparse($expectedResultPath, qr{\.[^.]+$}); 1800 my $actualResultsPath = "$testResultsDirectory/$base-$actualTag$expectedResultExtension"; 1801 my $copiedExpectedResultsPath = "$testResultsDirectory/$base-$expectedTag$expectedResultExtension"; 1802 1803 mkpath(dirname($actualResultsPath)); 1804 writeToFile("$actualResultsPath", $actualResults); 1805 1806 if (-f $expectedResultPath) { 1807 copy("$expectedResultPath", "$copiedExpectedResultsPath"); 1808 } else { 1809 open EMPTY, ">$copiedExpectedResultsPath"; 1810 close EMPTY; 1811 } 1812 1813 my $diffOuputBasePath = "$testResultsDirectory/$base"; 1814 my $diffOutputPath = "$diffOuputBasePath-$diffsTag.txt"; 1815 system "diff -u \"$copiedExpectedResultsPath\" \"$actualResultsPath\" > \"$diffOutputPath\""; 1816 1817 my $prettyDiffOutputPath = "$diffOuputBasePath-$prettyDiffTag.html"; 1818 my $prettyPatchPath = "BugsSite/PrettyPatch/"; 1819 my $prettifyPath = "$prettyPatchPath/prettify.rb"; 1820 system "ruby -I \"$prettyPatchPath\" \"$prettifyPath\" \"$diffOutputPath\" > \"$prettyDiffOutputPath\""; 1821} 1822 1823sub buildPlatformResultHierarchy() 1824{ 1825 mkpath($platformTestDirectory) if ($platform eq "undefined" && !-d "$platformTestDirectory"); 1826 1827 my @platforms; 1828 if ($platform =~ /^mac-/) { 1829 my $i; 1830 for ($i = 0; $i < @macPlatforms; $i++) { 1831 last if $macPlatforms[$i] eq $platform; 1832 } 1833 for (; $i < @macPlatforms; $i++) { 1834 push @platforms, $macPlatforms[$i]; 1835 } 1836 } else { 1837 @platforms = $platform; 1838 } 1839 1840 my @hierarchy; 1841 for (my $i = 0; $i < @platforms; $i++) { 1842 my $scoped = catdir($platformBaseDirectory, $platforms[$i]); 1843 push(@hierarchy, $scoped) if (-d $scoped); 1844 } 1845 1846 return @hierarchy; 1847} 1848 1849sub buildPlatformTestHierarchy(@) 1850{ 1851 my (@platformHierarchy) = @_; 1852 return @platformHierarchy if (@platformHierarchy < 2); 1853 1854 return ($platformHierarchy[0], $platformHierarchy[$#platformHierarchy]); 1855} 1856 1857sub epiloguesAndPrologues($$) { 1858 my ($lastDirectory, $directory) = @_; 1859 my @lastComponents = split('/', $lastDirectory); 1860 my @components = split('/', $directory); 1861 1862 while (@lastComponents) { 1863 if (!defined($components[0]) || $lastComponents[0] ne $components[0]) { 1864 last; 1865 } 1866 shift @components; 1867 shift @lastComponents; 1868 } 1869 1870 my @result; 1871 my $leaving = $lastDirectory; 1872 foreach (@lastComponents) { 1873 my $epilogue = $leaving . "/resources/run-webkit-tests-epilogue.html"; 1874 foreach (@platformResultHierarchy) { 1875 push @result, catdir($_, $epilogue) if (stat(catdir($_, $epilogue))); 1876 } 1877 push @result, catdir($testDirectory, $epilogue) if (stat(catdir($testDirectory, $epilogue))); 1878 $leaving =~ s|(^\|/)[^/]+$||; 1879 } 1880 1881 my $entering = $leaving; 1882 foreach (@components) { 1883 $entering .= '/' . $_; 1884 my $prologue = $entering . "/resources/run-webkit-tests-prologue.html"; 1885 push @result, catdir($testDirectory, $prologue) if (stat(catdir($testDirectory, $prologue))); 1886 foreach (reverse @platformResultHierarchy) { 1887 push @result, catdir($_, $prologue) if (stat(catdir($_, $prologue))); 1888 } 1889 } 1890 return @result; 1891} 1892 1893sub parseLeaksandPrintUniqueLeaks() { 1894 return unless @leaksFilenames; 1895 1896 my $mergedFilenames = join " ", @leaksFilenames; 1897 my $parseMallocHistoryTool = sourceDir() . "/WebKitTools/Scripts/parse-malloc-history"; 1898 1899 open MERGED_LEAKS, "cat $mergedFilenames | $parseMallocHistoryTool --merge-depth $mergeDepth - |" ; 1900 my @leakLines = <MERGED_LEAKS>; 1901 close MERGED_LEAKS; 1902 1903 my $uniqueLeakCount = 0; 1904 my $totalBytes; 1905 foreach my $line (@leakLines) { 1906 ++$uniqueLeakCount if ($line =~ /^(\d*)\scalls/); 1907 $totalBytes = $1 if $line =~ /^total\:\s(.*)\s\(/; 1908 } 1909 1910 print "\nWARNING: $totalLeaks total leaks found for a total of $totalBytes!\n"; 1911 print "WARNING: $uniqueLeakCount unique leaks found!\n"; 1912 print "See above for individual leaks results.\n" if ($leaksOutputFileNumber > 2); 1913 1914} 1915 1916sub extensionForMimeType($) 1917{ 1918 my ($mimeType) = @_; 1919 1920 if ($mimeType eq "application/x-webarchive") { 1921 return "webarchive"; 1922 } elsif ($mimeType eq "application/pdf") { 1923 return "pdf"; 1924 } 1925 return "txt"; 1926} 1927 1928# Read up to the first #EOF (the content block of the test), or until detecting crashes or timeouts. 1929sub readFromDumpToolWithTimer(*;$) 1930{ 1931 my ($fh, $dontWaitForTimeOut) = @_; 1932 1933 setFileHandleNonBlocking($fh, 1); 1934 1935 my $maximumSecondsWithoutOutput = $timeoutSeconds; 1936 $maximumSecondsWithoutOutput *= 10 if $guardMalloc; 1937 my $microsecondsToWaitBeforeReadingAgain = 1000; 1938 1939 my $timeOfLastSuccessfulRead = time; 1940 1941 my @output = (); 1942 my $status = "success"; 1943 my $mimeType = "text/plain"; 1944 # We don't have a very good way to know when the "headers" stop 1945 # and the content starts, so we use this as a hack: 1946 my $haveSeenContentType = 0; 1947 1948 while (1) { 1949 if (time - $timeOfLastSuccessfulRead > $maximumSecondsWithoutOutput) { 1950 $status = dumpToolDidCrash() ? "crashed" : "timedOut"; 1951 last; 1952 } 1953 1954 my $line = readline($fh); 1955 if (!defined($line)) { 1956 if ($! != EAGAIN) { 1957 $status = "crashed"; 1958 last; 1959 } 1960 1961 if ($dontWaitForTimeOut) { 1962 last; 1963 } 1964 1965 # No data ready 1966 usleep($microsecondsToWaitBeforeReadingAgain); 1967 next; 1968 } 1969 1970 $timeOfLastSuccessfulRead = time; 1971 1972 if (!$haveSeenContentType && $line =~ /^Content-Type: (\S+)$/) { 1973 $mimeType = $1; 1974 $haveSeenContentType = 1; 1975 next; 1976 } 1977 last if ($line =~ /#EOF/); 1978 1979 push @output, $line; 1980 } 1981 1982 setFileHandleNonBlocking($fh, 0); 1983 return { 1984 output => join("", @output), 1985 status => $status, 1986 mimeType => $mimeType, 1987 extension => extensionForMimeType($mimeType) 1988 }; 1989} 1990 1991sub setFileHandleNonBlocking(*$) 1992{ 1993 my ($fh, $nonBlocking) = @_; 1994 1995 my $flags = fcntl($fh, F_GETFL, 0) or die "Couldn't get filehandle flags"; 1996 1997 if ($nonBlocking) { 1998 $flags |= O_NONBLOCK; 1999 } else { 2000 $flags &= ~O_NONBLOCK; 2001 } 2002 2003 fcntl($fh, F_SETFL, $flags) or die "Couldn't set filehandle flags"; 2004 2005 return 1; 2006} 2007 2008sub sampleDumpTool() 2009{ 2010 return unless isAppleMacWebKit(); 2011 return unless $runSample; 2012 2013 my $outputDirectory = "$ENV{HOME}/Library/Logs/DumpRenderTree"; 2014 -d $outputDirectory or mkdir $outputDirectory; 2015 2016 my $outputFile = "$outputDirectory/HangReport.txt"; 2017 system "/usr/bin/sample", $dumpToolPID, qw(10 10 -file), $outputFile; 2018} 2019