Home | History | Annotate | Download | only in Scripts
      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