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