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