2007-10-28 Eric Seidel <eric@webkit.org>
[WebKit-https.git] / WebKitTools / Scripts / run-webkit-tests
1 #!/usr/bin/perl
2
3 # Copyright (C) 2005, 2006, 2007 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 #
8 # Redistribution and use in source and binary forms, with or without
9 # modification, are permitted provided that the following conditions
10 # are met:
11 #
12 # 1.  Redistributions of source code must retain the above copyright
13 #     notice, this list of conditions and the following disclaimer. 
14 # 2.  Redistributions in binary form must reproduce the above copyright
15 #     notice, this list of conditions and the following disclaimer in the
16 #     documentation and/or other materials provided with the distribution. 
17 # 3.  Neither the name of Apple Computer, Inc. ("Apple") nor the names of
18 #     its contributors may be used to endorse or promote products derived
19 #     from this software without specific prior written permission. 
20 #
21 # THIS SOFTWARE IS PROVIDED BY APPLE AND ITS CONTRIBUTORS "AS IS" AND ANY
22 # EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
23 # WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
24 # DISCLAIMED. IN NO EVENT SHALL APPLE OR ITS CONTRIBUTORS BE LIABLE FOR ANY
25 # DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
26 # (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
27 # LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
28 # ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
29 # (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
30 # THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
31
32 # Script to run the Web Kit Open Source Project layout tests.
33
34 # Run all the tests passed in on the command line.
35 # If no tests are passed, find all the .html, .shtml, .xml, .xhtml, .pl, .php (and svg) files in the test directory.
36
37 # Run each text.
38 # Compare against the existing file xxx-expected.txt.
39 # If there is a mismatch, generate xxx-actual.txt and xxx-diffs.txt.
40
41 # At the end, report:
42 #   the number of tests that got the expected results
43 #   the number of tests that ran, but did not get the expected results
44 #   the number of tests that failed to run
45 #   the number of tests that were run but had no expected results to compare against
46
47 use strict;
48 use warnings;
49
50 use Cwd;
51 use File::Basename;
52 use File::Copy;
53 use File::Find;
54 use File::Path;
55 use File::Spec;
56 use File::Spec::Functions;
57 use FindBin;
58 use Getopt::Long;
59 use IPC::Open2;
60 use IPC::Open3;
61 use Time::HiRes qw(time);
62
63 use lib $FindBin::Bin;
64 use webkitdirs;
65 use POSIX;
66
67 sub openDumpTool();
68 sub closeDumpTool();
69 sub dumpToolDidCrash();
70 sub closeHTTPD();
71 sub countAndPrintLeaks($$$);
72 sub fileNameWithNumber($$);
73 sub numericcmp($$);
74 sub openHTTPDIfNeeded();
75 sub pathcmp($$);
76 sub processIgnoreTests($);
77 sub slowestcmp($$);
78 sub splitpath($);
79 sub stripExtension($);
80 sub isTextOnlyTest($);
81 sub expectedDirectoryForTest($;$);
82 sub printFailureMessageForTest($$);
83 sub toURL($);
84 sub toWindowsPath($);
85 sub closeCygpaths();
86 sub validateSkippedArg($$;$);
87 sub htmlForExpectedAndActualResults($);
88 sub deleteExpectedAndActualResults($);
89 sub recordActualResultsAndDiff($$);
90 sub buildPlatformHierarchy();
91
92 # Argument handling
93 my $addPlatformExceptions = 0;
94 my $configuration = configuration();
95 my $guardMalloc = '';
96 my $httpdPort = 8000;
97 my $httpdSSLPort = 8443;
98 my $ignoreTests = '';
99 my $launchSafari = 1;
100 my $platform;
101 my $pixelTests = '';
102 my $quiet = '';
103 my $repaintSweepHorizontally = '';
104 my $repaintTests = '';
105 my $report10Slowest = 0;
106 my $resetResults = 0;
107 my $shouldCheckLeaks = 0;
108 my $showHelp = 0;
109 my $testsPerDumpTool = 1000;
110 my $testHTTP = 1;
111 my $testResultsDirectory = "/tmp/layout-test-results";
112 my $threaded = 0;
113 my $treatSkipped = "default";
114 my $verbose = 0;
115 my $useValgrind = 0;
116 my $strictTesting = 0;
117 my $generateNewResults = 1;
118 my $stripEditingCallbacks = isCygwin();
119 my $root;
120
121 my $expectedTag = "expected";
122 my $actualTag = "actual";
123 my $diffsTag = "diffs";
124 my $errorTag = "stderr";
125
126 if (isTiger()) {
127     $platform = "mac-tiger";
128 } elsif (isLeopard()) {
129     $platform = "mac-leopard";
130 } elsif (isOSX()) {
131     $platform = "mac";
132 } elsif (isQt()) {
133     $platform = "qt";
134 } elsif (isGtk()) {
135     $platform = "gtk";
136 } elsif (isCygwin()) {
137     $platform = "win";
138 }
139
140 if (!defined($platform)) {
141     print "WARNING: Your platform is not recognized. Any platform-specific results will be generated in platform/undefined.\n";
142     $platform = "undefined";
143 }
144
145 my $programName = basename($0);
146 my $launchSafariDefault = $launchSafari ? "launch" : "do not launch";
147 my $httpDefault = $testHTTP ? "run" : "do not run";
148
149 # FIXME: "--strict" should be renamed to qt-mac-comparison, or something along those lines.
150 my $usage = <<EOF;
151 Usage: $programName [options] [testdir|testpath ...]
152   --add-platform-exceptions       Put new results for non-platform-specific failing tests into the platform-specific results directory
153   -c|--configuration config       Set DumpRenderTree build configuration
154   -g|--guard-malloc               Enable malloc guard
155   --help                          Show this help message
156   -h|--horizontal-sweep           Change repaint to sweep horizontally instead of vertically (implies --repaint-tests)
157   --[no-]http                     Run (or do not run) http tests (default: $httpDefault)
158   -i|--ignore-tests               Comma-separated list of directories or tests to ignore
159   --[no-]launch-safari            Launch (or do not launch) Safari to display test results (default: $launchSafariDefault)
160   -l|--leaks                      Enable leaks checking
161   --[no-]new-test-results         Generate results for new tests
162   -p|--pixel-tests                Enable pixel tests
163   --platform                      Override the detected platform to use for tests and results (default: $platform)
164   --port                          Web server port to use with http tests
165   -q|--quiet                      Less verbose output
166   -r|--repaint-tests              Run repaint tests (implies --pixel-tests)
167   --reset-results                 Reset ALL results (including pixel tests if --pixel-tests is set)
168   -o|--results-directory          Output results directory (default: $testResultsDirectory)
169   --root                          Path to root tools build
170   -1|--singly                     Isolate each test case run (implies --verbose)
171   --skipped=[default|ignore|only] Specifies how to treat the Skipped file
172                                      default: Tests/directories listed in the Skipped file are not tested
173                                      ignore:  The Skipped file is ignored
174                                      only:    Only those tests/directories listed in the Skipped file will be run
175   --slowest                       Report the 10 slowest tests
176   --strict                        Do a comparison with the output on Mac (Qt only)
177   --[no-]strip-editing-callbacks  Remove editing callbacks from expected results
178   -t|--threaded                   Run a concurrent JavaScript thead with each test
179   --valgrind                      Run DumpRenderTree inside valgrind (Qt/Linux only)
180   -v|--verbose                    More verbose output (overrides --quiet)
181 EOF
182
183 my $getOptionsResult = GetOptions(
184     'c|configuration=s' => \$configuration,
185     'debug|devel' => sub { $configuration = "Debug" },
186     'guard-malloc|g' => \$guardMalloc,
187     'help' => \$showHelp,
188     'horizontal-sweep|h' => \$repaintSweepHorizontally,
189     'http!' => \$testHTTP,
190     'ignore-tests|i=s' => \$ignoreTests,
191     'launch-safari!' => \$launchSafari,
192     'leaks|l' => \$shouldCheckLeaks,
193     'pixel-tests|p' => \$pixelTests,
194     'platform=s' => \$platform,
195     'port=i' => \$httpdPort,
196     'quiet|q' => \$quiet,
197     'release|deploy' => sub { $configuration = "Release" },
198     'repaint-tests|r' => \$repaintTests,
199     'reset-results' => \$resetResults,
200     'new-test-results!' => \$generateNewResults,
201     'results-directory|o=s' => \$testResultsDirectory,
202     'singly|1' => sub { $testsPerDumpTool = 1; },
203     'nthly=i' => \$testsPerDumpTool,
204     'skipped=s' => \&validateSkippedArg,
205     'slowest' => \$report10Slowest,
206     'threaded|t' => \$threaded,
207     'verbose|v' => \$verbose,
208     'valgrind' => \$useValgrind,
209     'strict' => \$strictTesting,
210     'strip-editing-callbacks!' => \$stripEditingCallbacks,
211     'root=s' => \$root,
212     'add-platform-exceptions' => \$addPlatformExceptions,
213 );
214
215 if (!$getOptionsResult || $showHelp) {
216     print STDERR $usage;
217     exit 1;
218 }
219
220 my $ignoreSkipped = $treatSkipped eq "ignore";
221 my $skippedOnly = $treatSkipped eq "only";
222
223 !$skippedOnly || @ARGV == 0 or die "--skipped=only cannot be used when tests are specified on the command line.";
224
225 setConfiguration($configuration);
226
227 my $configurationOption = "--" . lc($configuration);
228
229 $repaintTests = 1 if $repaintSweepHorizontally;
230
231 $pixelTests = 1 if $repaintTests;
232
233 $verbose = 1 if $testsPerDumpTool == 1;
234
235 if ($shouldCheckLeaks && $testsPerDumpTool > 1000) {
236     print STDERR "\nWARNING: Running more than 1000 tests at a time with MallocStackLogging enabled may cause a crash.\n\n";
237 }
238
239 # Force --no-http for Qt/Linux, for now.
240 $testHTTP = 0 if isQt();
241
242 setConfigurationProductDir(Cwd::abs_path($root)) if (defined($root));
243 my $productDir = productDir();
244 $productDir .= "/bin" if (isQt());
245
246 chdirWebKit();
247
248 if(!defined($root)){
249     # Push the parameters to build-dumprendertree as an array
250     my @args;
251     push(@args, "--" . $configuration);
252     push(@args, "--qt")  if isQt();
253     push(@args, "--gtk")  if isGtk();
254
255     my $buildResult = system "WebKitTools/Scripts/build-dumprendertree", @args;
256     if ($buildResult) {
257         print STDERR "Compiling DumpRenderTree failed!\n";
258         exit exitStatus($buildResult);
259     }
260 }
261
262 my $dumpToolName = "DumpRenderTree";
263 $dumpToolName .= "_debug" if isCygwin() && $configuration ne "Release";
264 my $dumpTool = "$productDir/$dumpToolName";
265 die "can't find executable $dumpToolName (looked in $productDir)\n" unless -x $dumpTool;
266
267 my $imageDiffTool = "$productDir/ImageDiff";
268 die "can't find executable $imageDiffTool (looked in $productDir)\n" if $pixelTests && !-x $imageDiffTool;
269
270 checkFrameworks() unless isCygwin();
271
272 my $layoutTestsName = "LayoutTests";
273 my $testDirectory = File::Spec->rel2abs($layoutTestsName);
274 my $expectedDirectory = $testDirectory;
275 my $platformBaseDirectory = catdir($testDirectory, "platform");
276 my $platformTestDirectory = catdir($platformBaseDirectory, $platform);
277 my @platformHierarchy = buildPlatformHierarchy();
278
279 $expectedDirectory = $ENV{"WebKitExpectedTestResultsDirectory"} if $ENV{"WebKitExpectedTestResultsDirectory"};
280
281 my $testResults = catfile($testResultsDirectory, "results.html");
282
283 print "Running tests from $testDirectory\n";
284
285 my @tests = ();
286 my %testType = ();
287
288 system "ln", "-s", $testDirectory, "/tmp/LayoutTests" unless -x "/tmp/LayoutTests";
289
290 my %ignoredFiles = ();
291 my %ignoredDirectories = map { $_ => 1 } qw(platform);
292 my %ignoredLocalDirectories = map { $_ => 1 } qw(.svn _svn resources);
293 my %supportedFileExtensions = map { $_ => 1 } qw(html shtml xml xhtml pl php);
294 if (checkWebCoreSVGSupport(0)) { 
295     $supportedFileExtensions{'svg'} = 1;
296 } elsif (isCygwin()) {
297     # FIXME: We should fix webkitdirs.pm:hasSVGSupport() to do the correct
298     # check for Windows instead of forcing this here.
299     $supportedFileExtensions{'svg'} = 1;
300 } else {
301     $ignoredLocalDirectories{'svg'} = 1;
302 }
303 if (!$testHTTP) {
304     $ignoredDirectories{'http'} = 1;
305 }
306
307 if ($ignoreTests) {
308     processIgnoreTests($ignoreTests);
309 }
310
311 if (!$ignoreSkipped) {
312     foreach my $level (@platformHierarchy) {
313         if (open SKIPPED, "<", "$level/Skipped") {
314             if ($verbose && !$skippedOnly) {
315                 my ($dir, $name) = splitpath($level);
316                 print "Skipped tests in $name:\n";
317             }
318
319             while (<SKIPPED>) {
320                 my $skipped = $_;
321                 chomp $skipped;
322                 $skipped =~ s/^[ \n\r]+//;
323                 $skipped =~ s/[ \n\r]+$//;
324                 if ($skipped && $skipped !~ /^#/) {
325                     if ($skippedOnly) {
326                         push(@ARGV, $skipped);
327                     } else {
328                         if ($verbose) {
329                             print "    $skipped\n";
330                         }
331                         processIgnoreTests($skipped);
332                     }
333                 }
334             }
335             close SKIPPED;
336         }
337     }
338 }
339
340
341 my $directoryFilter = sub {
342     return () if exists $ignoredLocalDirectories{basename($File::Find::dir)};
343     return () if exists $ignoredDirectories{File::Spec->abs2rel($File::Find::dir, $testDirectory)};
344     return @_;
345 };
346
347 my $fileFilter = sub {
348     my $filename = $_;
349     if ($filename =~ /\.([^.]+)$/) {
350         if (exists $supportedFileExtensions{$1}) {
351             my $path = File::Spec->abs2rel(catfile($File::Find::dir, $filename), $testDirectory);
352             push @tests, $path if !exists $ignoredFiles{$path};
353         }
354     }
355 };
356
357 for my $test (@ARGV) {
358     $test =~ s/^($layoutTestsName|$testDirectory)\///;
359     my $fullPath = catfile($testDirectory, $test);
360     if (file_name_is_absolute($test)) {
361         print "can't run test $test outside $testDirectory\n";
362     } elsif (-f $fullPath) {
363         my ($filename, $pathname, $fileExtension) = fileparse($test, qr{\.[^.]+$});
364         if (!exists $supportedFileExtensions{substr($fileExtension, 1)}) {
365             print "test $test does not have a supported extension\n";
366         } elsif ($testHTTP || $pathname !~ /^http\//) {
367             push @tests, $test;
368         }
369     } elsif (-d $fullPath) {
370         find({ preprocess => $directoryFilter, wanted => $fileFilter }, $fullPath);
371
372         for my $level (@platformHierarchy) {
373             my $platformPath = catfile($level, $test);
374             find({ preprocess => $directoryFilter, wanted => $fileFilter }, $platformPath) if (-d $platformPath);
375         }
376     } else {
377         print "test $test not found\n";
378     }
379 }
380 if (!scalar @ARGV) {
381     find({ preprocess => $directoryFilter, wanted => $fileFilter }, $testDirectory);
382
383     for my $level (@platformHierarchy) {
384         find({ preprocess => $directoryFilter, wanted => $fileFilter }, $level);
385     }
386 }
387
388 die "no tests to run\n" if !@tests;
389
390 @tests = sort pathcmp @tests;
391
392 my %counts;
393 my %tests;
394 my %imagesPresent;
395 my %imageDifferences;
396 my %durations;
397 my $count = 0;
398 my $leaksOutputFileNumber = 1;
399 my $totalLeaks = 0;
400
401 my @toolArgs = ();
402 push @toolArgs, "--dump-all-pixels" if $pixelTests && $resetResults;
403 push @toolArgs, "--pixel-tests" if $pixelTests;
404 push @toolArgs, "--repaint" if $repaintTests;
405 push @toolArgs, "--horizontal-sweep" if $repaintSweepHorizontally;
406 push @toolArgs, "--threaded" if $threaded;
407 push @toolArgs, "--paint" if $shouldCheckLeaks; # Otherwise, DRT won't exercise painting leaks.
408 push @toolArgs, "-";
409
410 $| = 1;
411
412 my $imageDiffToolPID;
413 if ($pixelTests) {
414     local %ENV;
415     $ENV{MallocStackLogging} = 1 if $shouldCheckLeaks;
416     $imageDiffToolPID = open2(\*DIFFIN, \*DIFFOUT, $imageDiffTool, "") or die "unable to open $imageDiffTool\n";
417 }
418
419 my $dumpToolPID;
420 my $isDumpToolOpen = 0;
421 my $dumpToolCrashed = 0;
422
423 my $atLineStart = 1;
424 my $lastDirectory = "";
425
426 my $isHttpdOpen = 0;
427
428 sub catch_pipe { $dumpToolCrashed = 1; }
429 $SIG{"PIPE"} = "catch_pipe";
430
431 print "Testing ", scalar @tests, " test cases.\n";
432 my $overallStartTime = time;
433
434 my %expectedResultDirectory;
435
436 for my $test (@tests) {
437     next if $test eq 'results.html';
438
439     openDumpTool();
440
441     my $base = stripExtension($test);
442     
443     if ($verbose) {
444         print "running $test -> ";
445         $atLineStart = 0;
446     } elsif (!$quiet) {
447         my $dir = $base;
448         $dir =~ s|/[^/]+$||;
449         if ($dir ne $lastDirectory) {
450             print "\n" unless $atLineStart;
451             print "$dir ";
452             $lastDirectory = $dir;
453         }
454         print ".";
455         $atLineStart = 0;
456     }
457
458     my $result;
459
460     my $startTime = time if $report10Slowest;
461
462     if ($test !~ /^http\//) {
463         my $testPath = "$testDirectory/$test";
464         if (isCygwin()) {
465             $testPath = toWindowsPath($testPath);
466         } else {
467             $testPath = canonpath($testPath);
468         }
469         print OUT "$testPath\n";
470     } else {
471         openHTTPDIfNeeded();
472         if ($test !~ /^http\/tests\/local\// && $test !~ /^http\/tests\/ssl\//) {
473             my $path = canonpath($test);
474             $path =~ s/^http\/tests\///;
475             print OUT "http://127.0.0.1:$httpdPort/$path\n";
476         } elsif ($test =~ /^http\/tests\/ssl\//) {
477             my $path = canonpath($test);
478             $path =~ s/^http\/tests\///;
479             print OUT "https://127.0.0.1:$httpdSSLPort/$path\n";
480         } else {
481             my $testPath = "$testDirectory/$test";
482             if (isCygwin()) {
483                 $testPath = toWindowsPath($testPath);
484             } else {
485                 $testPath = canonpath($testPath);
486             }
487             print OUT "$testPath\n";
488         }
489     }
490
491     my $actual = "";
492     while (<IN>) {
493         last if /#EOF/;
494         $actual .= $_;
495     }
496     $actual =~ s/\r//g if isCygwin();
497
498     my $isText = isTextOnlyTest($actual);
499
500     $durations{$test} = time - $startTime if $report10Slowest;
501
502     my $expected;
503     my $expectedDir = expectedDirectoryForTest($base, $isText);
504     $expectedResultDirectory{$base} = $expectedDir;
505
506     if (!$resetResults && open EXPECTED, "<", "$expectedDir/$base-$expectedTag.txt") {
507         $expected = "";
508         while (<EXPECTED>) {
509             next if $stripEditingCallbacks && $_ =~ /^EDITING DELEGATE:/;
510             $expected .= $_;
511         }
512         close EXPECTED;
513     }
514     my $expectedMac;
515     if (!isOSX() && $strictTesting && !$isText) {
516       if (!$resetResults && open EXPECTED, "<", "$testDirectory/platform/mac/$base-$expectedTag.txt") {
517         $expectedMac = "";
518         while (<EXPECTED>) {
519           $expectedMac .= $_;
520         }
521         close EXPECTED;
522       }
523     }
524
525     if ($shouldCheckLeaks && $testsPerDumpTool == 1) {
526         print "        $test -> ";
527     }
528
529     my $actualPNG = "";
530     my $diffPNG = "";
531     my $diffPercentage = "";
532     my $diffResult = "passed";
533     
534     if ($pixelTests) {
535         my $actualHash = "";
536         my $expectedHash = "";
537         my $actualPNGSize = 0;
538
539         while (<IN>) {
540             last if /#EOF/;
541             if (/ActualHash: ([a-f0-9]{32})/) {
542                 $actualHash = $1;
543             } elsif (/BaselineHash: ([a-f0-9]{32})/) {
544                 $expectedHash = $1;
545             } elsif (/Content-length: (\d+)\s*/) {
546                 $actualPNGSize = $1;
547                 read(IN, $actualPNG, $actualPNGSize);
548             }
549         }
550
551         if ($expectedHash ne $actualHash && -f "$expectedDir/$base-$expectedTag.png") {
552             my $expectedPNGSize = -s "$expectedDir/$base-$expectedTag.png";
553             my $expectedPNG = "";
554             open EXPECTEDPNG, "$expectedDir/$base-$expectedTag.png";
555             read(EXPECTEDPNG, $expectedPNG, $expectedPNGSize);
556
557             print DIFFOUT "Content-length: $actualPNGSize\n";
558             print DIFFOUT $actualPNG;
559
560             print DIFFOUT "Content-length: $expectedPNGSize\n";
561             print DIFFOUT $expectedPNG;
562
563             while (<DIFFIN>) {
564                 last if /^error/ || /^diff:/;
565                 if (/Content-length: (\d+)\s*/) {
566                     read(DIFFIN, $diffPNG, $1);
567                 }
568             }
569
570             if (/^diff: (.+)% (passed|failed)/) {
571                 $diffPercentage = $1;
572                 $imageDifferences{$base} = $diffPercentage;
573                 $diffResult = $2;
574             }
575         }
576
577         if ($actualPNGSize && ($resetResults || !-f "$expectedDir/$base-$expectedTag.png")) {
578             mkpath catfile($expectedDir, dirname($base)) if $testDirectory ne $expectedDir;
579             open EXPECTED, ">", "$expectedDir/$base-expected.png" or die "could not create $expectedDir/$base-expected.png\n";
580             print EXPECTED $actualPNG;
581             close EXPECTED;
582         }
583
584         # update the expected hash if the image diff said that there was no difference
585         if ($actualHash ne "" && ($resetResults || !-f "$expectedDir/$base-$expectedTag.checksum")) {
586             open EXPECTED, ">", "$expectedDir/$base-$expectedTag.checksum" or die "could not create $expectedDir/$base-$expectedTag.checksum\n";
587             print EXPECTED $actualHash;
588             close EXPECTED;
589         }
590     }
591
592     if (!isOSX() && $strictTesting && !$isText) {
593       if (defined $expectedMac) {
594         my $simplified_actual;
595         $simplified_actual = $actual;
596         $simplified_actual =~ s/at \(-?[0-9]+,-?[0-9]+\) *//g;
597         $simplified_actual =~ s/size -?[0-9]+x-?[0-9]+ *//g;
598         $simplified_actual =~ s/text run width -?[0-9]+: //g;
599         $simplified_actual =~ s/text run width -?[0-9]+ [a-zA-Z ]+: //g;
600         $simplified_actual =~ s/RenderButton {BUTTON} .*/RenderButton {BUTTON}/g;
601         $simplified_actual =~ s/RenderImage {INPUT} .*/RenderImage {INPUT}/g;
602         $simplified_actual =~ s/RenderBlock {INPUT} .*/RenderBlock {INPUT}/g;
603         $simplified_actual =~ s/RenderTextControl {INPUT} .*/RenderTextControl {INPUT}/g;
604         $simplified_actual =~ s/\([0-9]+px/px/g;
605         $simplified_actual =~ s/ *" *\n +" */ /g;
606         $simplified_actual =~ s/" +$/"/g;
607
608         $simplified_actual =~ s/- /-/g;
609         $simplified_actual =~ s/\n( *)"\s+/\n$1"/g;
610         $simplified_actual =~ s/\s+"\n/"\n/g;
611         
612         $expectedMac =~ s/at \(-?[0-9]+,-?[0-9]+\) *//g;
613         $expectedMac =~ s/size -?[0-9]+x-?[0-9]+ *//g;
614         $expectedMac =~ s/text run width -?[0-9]+: //g;
615         $expectedMac =~ s/text run width -?[0-9]+ [a-zA-Z ]+: //g;
616         $expectedMac =~ s/RenderButton {BUTTON} .*/RenderButton {BUTTON}/g;
617         $expectedMac =~ s/RenderImage {INPUT} .*/RenderImage {INPUT}/g;
618         $expectedMac =~ s/RenderBlock {INPUT} .*/RenderBlock {INPUT}/g;
619         $expectedMac =~ s/RenderTextControl {INPUT} .*/RenderTextControl {INPUT}/g;
620         $expectedMac =~ s/\([0-9]+px/px/g;
621         $expectedMac =~ s/ *" *\n +" */ /g;
622         $expectedMac =~ s/" +$/"/g;
623
624         $expectedMac =~ s/- /-/g;
625         $expectedMac =~ s/\n( *)"\s+/\n$1"/g;
626         $expectedMac =~ s/\s+"\n/"\n/g;
627         
628         if ($simplified_actual ne $expectedMac) {
629           open ACTUAL, ">", "/tmp/actual.txt" or die;
630           print ACTUAL $simplified_actual;
631           close ACTUAL;
632           open ACTUAL, ">", "/tmp/expected.txt" or die;
633           print ACTUAL $expectedMac;
634           close ACTUAL;
635           system "diff -u \"/tmp/expected.txt\" \"/tmp/actual.txt\" > \"/tmp/simplified.diff\"";
636
637           $diffResult = "failed";
638           if($verbose) {
639             print "\n";
640             system "cat /tmp/simplified.diff";
641             print "failed!!!!!";
642           }
643         }
644       }
645     }
646
647     if (dumpToolDidCrash()) {
648         $result = "crash";
649
650         printFailureMessageForTest($test, "crashed");
651
652         my $dir = "$testResultsDirectory/$base";
653         $dir =~ s|/([^/]+)$|| or die "Failed to find test name from base\n";
654         mkpath $dir;
655
656         deleteExpectedAndActualResults($base);
657
658         open CRASH, ">", "$testResultsDirectory/$base-$errorTag.txt" or die;
659         print CRASH <ERROR>;
660         close CRASH;
661
662         recordActualResultsAndDiff($base, $actual);
663
664         closeDumpTool();
665     } elsif (!defined $expected) {
666         if ($verbose) {
667             print "new " . ($resetResults ? "result" : "test") ."\n";
668             $atLineStart = 1;
669         }
670         $result = "new";
671
672         if ($generateNewResults || $resetResults) {
673             mkpath catfile($expectedDir, dirname($base)) if $testDirectory ne $expectedDir;
674             open EXPECTED, ">", "$expectedDir/$base-$expectedTag.txt" or die "could not create $expectedDir/$base-$expectedTag.txt\n";
675             print EXPECTED $actual;
676             close EXPECTED;
677         }
678         deleteExpectedAndActualResults($base);
679         unless ($resetResults) {
680             # Always print the file name for new tests, as they will probably need some manual inspection.
681             # in verbose mode we already printed the test case, so no need to do it again.
682             unless ($verbose) {
683                 print "\n" unless $atLineStart;
684                 print "$test -> ";
685             }
686             my $resultsDir = catdir($expectedDir, dirname($base));
687             print "new (results generated in $resultsDir)\n";
688             $atLineStart = 1;
689         }
690     } elsif ($actual eq $expected && $diffResult eq "passed") {
691         if ($verbose) {
692             print "succeeded\n";
693             $atLineStart = 1;
694         }
695         $result = "match";
696         deleteExpectedAndActualResults($base);
697     } else {
698         $result = "mismatch";
699
700         my $message = $actual eq $expected ? "pixel test failed" : "failed";
701
702         if ($actual ne $expected && $addPlatformExceptions) {
703             my $testBase = catfile($testDirectory, $base);
704             my $expectedBase = catfile($expectedDir, $base);
705             my $testIsMaximallyPlatformSpecific = $testBase =~ m|^\Q$platformTestDirectory\E/|;
706             my $expectedResultIsMaximallyPlatformSpecific = $expectedBase =~ m|^\Q$platformTestDirectory\E/|;
707             if (!$testIsMaximallyPlatformSpecific && !$expectedResultIsMaximallyPlatformSpecific) {
708                 mkpath catfile($platformTestDirectory, dirname($base));
709                 my $expectedFile = catfile($platformTestDirectory, "$base-$expectedTag.txt");
710                 open EXPECTED, ">", $expectedFile or die "could not create $expectedFile\n";
711                 print EXPECTED $actual;
712                 close EXPECTED;
713                 $message .= " (results generated in $platformTestDirectory)";
714             }
715         }
716
717         printFailureMessageForTest($test, $message);
718
719         my $dir = "$testResultsDirectory/$base";
720         $dir =~ s|/([^/]+)$|| or die "Failed to find test name from base\n";
721         my $testName = $1;
722         mkpath $dir;
723
724         deleteExpectedAndActualResults($base);
725         recordActualResultsAndDiff($base, $actual);
726
727         if ($pixelTests && $diffPNG && $diffPNG ne "") {
728             $imagesPresent{$base} = 1;
729
730             open ACTUAL, ">", "$testResultsDirectory/$base-$actualTag.png" or die;
731             print ACTUAL $actualPNG;
732             close ACTUAL;
733
734             open DIFF, ">", "$testResultsDirectory/$base-$diffsTag.png" or die;
735             print DIFF $diffPNG;
736             close DIFF;
737             
738             copy("$expectedDir/$base-$expectedTag.png", "$testResultsDirectory/$base-$expectedTag.png");
739
740             open DIFFHTML, ">$testResultsDirectory/$base-$diffsTag.html" or die;
741             print DIFFHTML "<html>\n";
742             print DIFFHTML "<head>\n";
743             print DIFFHTML "<title>$base Image Compare</title>\n";
744             print DIFFHTML "<script language=\"Javascript\" type=\"text/javascript\">\n";
745             print DIFFHTML "var currentImage = 0;\n";
746             print DIFFHTML "var imageNames = new Array(\"Actual\", \"Expected\");\n";
747             print DIFFHTML "var imagePaths = new Array(\"$testName-$actualTag.png\", \"$testName-$expectedTag.png\");\n";
748             if (-f "$testDirectory/$base-w3c.png") {
749                 copy("$testDirectory/$base-w3c.png", "$testResultsDirectory/$base-w3c.png");
750                 print DIFFHTML "imageNames.push(\"W3C\");\n";
751                 print DIFFHTML "imagePaths.push(\"$testName-w3c.png\");\n";
752             }
753             print DIFFHTML "function animateImage() {\n";
754             print DIFFHTML "    var image = document.getElementById(\"animatedImage\");\n";
755             print DIFFHTML "    var imageText = document.getElementById(\"imageText\");\n";
756             print DIFFHTML "    image.src = imagePaths[currentImage];\n";
757             print DIFFHTML "    imageText.innerHTML = imageNames[currentImage] + \" Image\";\n";
758             print DIFFHTML "    currentImage = (currentImage + 1) % imageNames.length;\n";
759             print DIFFHTML "    setTimeout('animateImage()',2000);\n";
760             print DIFFHTML "}\n";
761             print DIFFHTML "</script>\n";
762             print DIFFHTML "</head>\n";
763             print DIFFHTML "<body onLoad=\"animateImage();\">\n";
764             print DIFFHTML "<table>\n";
765             if ($diffPercentage) {
766                 print DIFFHTML "<tr>\n";
767                 print DIFFHTML "<td>Difference between images: <a href=\"$testName-$diffsTag.png\">$diffPercentage%</a></td>\n";
768                 print DIFFHTML "</tr>\n";
769             }
770             print DIFFHTML "<tr>\n";
771             print DIFFHTML "<td><a href=\"" . toURL("$testDirectory/$test") . "\">test file</a></td>\n";
772             print DIFFHTML "</tr>\n";
773             print DIFFHTML "<tr>\n";
774             print DIFFHTML "<td id=\"imageText\" style=\"text-weight: bold;\">Actual Image</td>\n";
775             print DIFFHTML "</tr>\n";
776             print DIFFHTML "<tr>\n";
777             print DIFFHTML "<td><img src=\"$testName-$actualTag.png\" id=\"animatedImage\"></td>\n";
778             print DIFFHTML "</tr>\n";
779             print DIFFHTML "</table>\n";
780             print DIFFHTML "</body>\n";
781             print DIFFHTML "</html>\n";
782         }
783     }
784
785     if (($count + 1) % $testsPerDumpTool == 0 || $count == $#tests) {
786         if ($shouldCheckLeaks) {
787             my $fileName;
788             if ($testsPerDumpTool == 1) {
789                 $fileName = "$testResultsDirectory/$base-leaks.txt";
790             } else {
791                 $fileName = "$testResultsDirectory/" . fileNameWithNumber($dumpToolName, $leaksOutputFileNumber) . "-leaks.txt";
792             }
793             my $leakCount = countAndPrintLeaks($dumpToolName, $dumpToolPID, $fileName);
794             $totalLeaks += $leakCount;
795             $leaksOutputFileNumber++ if ($leakCount);
796         }
797
798         closeDumpTool();
799     }
800
801     $count++;
802     $counts{$result}++;
803     push @{$tests{$result}}, $test;
804     $testType{$test} = $isText;
805 }
806 printf "\n%0.2fs total testing time\n", (time - $overallStartTime) . "";
807
808 !$isDumpToolOpen || die "Failed to close $dumpToolName.\n";
809
810 closeHTTPD();
811
812 # Because multiple instances of this script are running concurrently we cannot 
813 # safely delete this symlink.
814 # system "rm /tmp/LayoutTests";
815
816 # FIXME: Do we really want to check the image-comparison tool for leaks every time?
817 if ($shouldCheckLeaks && $pixelTests) {
818     $totalLeaks += countAndPrintLeaks("ImageDiff", $imageDiffToolPID, "$testResultsDirectory/ImageDiff-leaks.txt");
819 }
820
821 if ($totalLeaks) {
822     print "\nWARNING: $totalLeaks total leaks found!\n";
823     print "See above for individual leaks results.\n" if ($leaksOutputFileNumber > 2);
824 }
825
826 close IN;
827 close OUT;
828 close ERROR;
829
830 if ($report10Slowest) {
831     print "\n\nThe 10 slowest tests:\n\n";
832     my $count = 0;
833     for my $test (sort slowestcmp keys %durations) {
834         printf "%0.2f secs: %s\n", $durations{$test}, $test;
835         last if ++$count == 10;
836     }
837 }
838
839 print "\n";
840
841 if ($skippedOnly && $counts{"match"}) {
842     print "The following tests are in the Skipped file (" . File::Spec->abs2rel("$platformTestDirectory/Skipped", $testDirectory) . "), but succeeded:\n";
843     foreach my $test (@{$tests{"match"}}) {
844         print "  $test\n";
845     }
846 }
847
848 if ($resetResults || ($counts{match} && $counts{match} == $count)) {
849     print "all $count test cases succeeded\n";
850     unlink $testResults;
851     exit;
852 }
853
854
855 my %text = (
856     match => "succeeded",
857     mismatch => "had incorrect layout",
858     new => "were new",
859     crash => "crashed",
860 );
861
862 for my $type ("match", "mismatch", "new", "crash") {
863     my $c = $counts{$type};
864     if ($c) {
865         my $t = $text{$type};
866         my $message;
867         if ($c == 1) {
868             $t =~ s/were/was/;
869             $message = sprintf "1 test case (%d%%) %s\n", 1 * 100 / $count, $t;
870         } else {
871             $message = sprintf "%d test cases (%d%%) %s\n", $c, $c * 100 / $count, $t;
872         }
873         $message =~ s-\(0%\)-(<1%)-;
874         print $message;
875     }
876 }
877
878 mkpath $testResultsDirectory;
879
880 open HTML, ">", $testResults or die;
881 print HTML "<html>\n";
882 print HTML "<head>\n";
883 print HTML "<title>Layout Test Results</title>\n";
884 print HTML "</head>\n";
885 print HTML "<body>\n";
886
887 if ($counts{mismatch}) {
888     print HTML "<p>Tests where results did not match expected results:</p>\n";
889     print HTML "<table>\n";
890     for my $test (@{$tests{mismatch}}) {
891         my $base = stripExtension($test);
892         print HTML "<tr>\n";            
893         print HTML "<td><a href=\"" . toURL("$testDirectory/$test") . "\">$test</a></td>\n";
894         print HTML htmlForExpectedAndActualResults($base);
895         if ($pixelTests) {
896             if ($imagesPresent{$base}) {
897                 print HTML "<td><a href=\"" . toURL("$base-$expectedTag.png") . "\">expected image</a></td>\n";
898                 print HTML "<td><a href=\"" . toURL("$base-$diffsTag.html") . "\">image diffs</a>\n";
899                 print HTML "<a href=\"" . toURL("$base-$diffsTag.png") . "\">$imageDifferences{$base}%</a></td>\n";
900             } else {
901                 print HTML "<td></td><td></td>\n";
902             }            
903         }
904         print HTML "</tr>\n";
905     }
906     print HTML "</table>\n";
907 }
908
909 if ($counts{crash}) {
910     print HTML "<p>Tests that caused the DumpRenderTree tool to crash:</p>\n";
911     print HTML "<table>\n";
912     for my $test (@{$tests{crash}}) {
913         my $base = stripExtension($test);
914         my $expectedDir = $expectedResultDirectory{$base};
915         print HTML "<tr>\n";
916         print HTML "<td><a href=\"" . toURL("$testDirectory/$test") . "\">$base</a></td>\n";
917         print HTML htmlForExpectedAndActualResults($base);
918         print HTML "<td><a href=\"$base-$errorTag.txt\">stderr</a></td>\n";
919         print HTML "</tr>\n";
920     }
921     print HTML "</table>\n";
922 }
923
924 if ($counts{new}) {
925     print HTML "<p>Tests that had no expected results (probably new):</p>\n";
926     print HTML "<table>\n";
927     for my $test (@{$tests{new}}) {
928         my $base = stripExtension($test);
929         my $expectedDir = $expectedResultDirectory{$base};
930         print HTML "<tr>\n";
931         print HTML "<td><a href=\"" . toURL("$testDirectory/$test") . "\">$base</a></td>\n";
932         print HTML "<td><a href=\"" . toURL("$expectedDir/$base-$expectedTag.txt") . "\">results</a></td>\n";
933         if ($pixelTests && -f "$expectedDir/$base-$expectedTag.png") {
934             print HTML "<td><a href=\"" . toURL("$expectedDir/$base-$expectedTag.png") . "\">image</a></td>\n";
935         }
936         print HTML "</tr>\n";
937     }
938     print HTML "</table>\n";
939 }
940
941 print HTML "</body>\n";
942 print HTML "</html>\n";
943 close HTML;
944
945 if (isQt()) {
946   system "konqueror", $testResults if $launchSafari;
947 } elsif (isCygwin()) {
948   system "cygstart", $testResults if $launchSafari;
949 } else {
950   system "WebKitTools/Scripts/run-safari", $configurationOption, "-NSOpen", $testResults if $launchSafari;
951 }
952
953 closeCygpaths() if isCygwin();
954
955 exit 1;
956
957 sub countAndPrintLeaks($$$)
958 {
959     my ($dumpToolName, $dumpToolPID, $leaksFilePath) = @_;
960
961     print "\n" unless $atLineStart;
962     $atLineStart = 1;
963
964     # We are excluding the following reported leaks so they don't get in our way when looking for WebKit leaks:
965     # This allows us ignore known leaks and only be alerted when new leaks occur. Some leaks are in the old
966     # versions of the system frameworks that are being used by the leaks bots. Even though a leak has been
967     # fixed, it will be listed here until the bot has been updated with the newer frameworks.
968
969     my @typesToExclude = (
970     );
971
972     my @callStacksToExclude = (
973         "Flash_EnforceLocalSecurity" # leaks in Flash plug-in code, rdar://problem/4449747
974     );
975
976     if (isTiger()) {
977         # Leak list for the version of Tiger used on the build bot.
978         push @callStacksToExclude, (
979             "CFRunLoopRunSpecific \\| malloc_zone_malloc", "CFRunLoopRunSpecific \\| CFAllocatorAllocate ", # leak in CFRunLoopRunSpecific, rdar://problem/4670839
980             "CGImageSourceGetPropertiesAtIndex", # leak in ImageIO, rdar://problem/4628809
981             "FOGetCoveredUnicodeChars", # leak in ATS, rdar://problem/3943604
982             "GetLineDirectionPreference", "InitUnicodeUtilities", # leaks tool falsely reporting leak in CFNotificationCenterAddObserver, rdar://problem/4964790
983             "ICCFPrefWrapper::GetPrefDictionary", # leaks in Internet Config. code, rdar://problem/4449794
984             "NSHTTPURLProtocol setResponseHeader:", # leak in multipart/mixed-replace handling in Foundation, no Radar, but fixed in Leopard
985             "NSURLCache cachedResponseForRequest", # leak in CFURL cache, rdar://problem/4768430
986             "PCFragPrepareClosureFromFile", # leak in Code Fragment Manager, rdar://problem/3426998
987             "WebCore::Selection::toRange", # bug in 'leaks', rdar://problem/4967949
988             "WebCore::SubresourceLoader::create", # bug in 'leaks', rdar://problem/4985806
989             "_CFPreferencesDomainDeepCopyDictionary", # leak in CFPreferences, rdar://problem/4220786
990             "_objc_msgForward", # leak in NSSpellChecker, rdar://problem/4965278
991             "gldGetString", # leak in OpenGL, rdar://problem/5013699
992             "_setDefaultUserInfoFromURL", #rdar://problem/5546453 REGRESSION: 3 leaks on Tiger under _setDefaultUserInfoFromURL
993             "SSLHandshake" #rdar://problem/5546440 REGRESSION: 1 leak on Tiger under SSL
994         );
995         push @typesToExclude, (
996             "THRD", # bug in 'leaks', Radar 3387783
997             "DRHT" # ditto (endian little hate i)
998         );
999     }
1000
1001      if (isLeopard()) {
1002         # Leak list for the version of Leopard used on the build bot.
1003         push @callStacksToExclude, (
1004             "CFHTTPMessageAppendBytes", # rdar://problem/5435912
1005             "sendDidReceiveDataCallback", # rdar://problem/5441619
1006             "_CFHTTPReadStreamReadMark", # rdar://problem/5441468
1007             "httpProtocolStart", # rdar://problem/5468837
1008             "_CFURLConnectionSendCallbacks", # rdar://problem/5441600
1009         );
1010     }
1011
1012     my $leaksTool = sourceDir() . "/WebKitTools/Scripts/run-leaks";
1013     my $excludeString = "--exclude-callstack '" . (join "' --exclude-callstack '", @callStacksToExclude) . "'";
1014     $excludeString .= " --exclude-type '" . (join "' --exclude-type '", @typesToExclude) . "'" if @typesToExclude;
1015
1016     print " ? checking for leaks in $dumpToolName\n";
1017     my $leaksOutput = `$leaksTool $excludeString $dumpToolPID`;
1018     my ($count, $bytes) = $leaksOutput =~ /Process $dumpToolPID: (\d+) leaks? for (\d+) total/;
1019     my ($excluded) = $leaksOutput =~ /(\d+) leaks? excluded/;
1020
1021     my $adjustedCount = $count;
1022     $adjustedCount -= $excluded if $excluded;
1023
1024     if (!$adjustedCount) {
1025         print " - no leaks found\n";
1026         unlink $leaksFilePath;
1027         return 0;
1028     } else {
1029         my $dir = $leaksFilePath;
1030         $dir =~ s|/[^/]+$|| or die;
1031         mkpath $dir;
1032
1033         if ($excluded) {
1034             print " + $adjustedCount leaks ($bytes bytes including $excluded excluded leaks) were found, details in $leaksFilePath\n";
1035         } else {
1036             print " + $count leaks ($bytes bytes) were found, details in $leaksFilePath\n";
1037         }
1038
1039         open LEAKS, ">", $leaksFilePath or die;
1040         print LEAKS $leaksOutput;
1041         close LEAKS;
1042     }
1043
1044     return $adjustedCount;
1045 }
1046
1047 # Break up a path into the directory (with slash) and base name.
1048 sub splitpath($)
1049 {
1050     my ($path) = @_;
1051
1052     my $pathSeparator = "/";
1053     my $dirname = dirname($path) . $pathSeparator;
1054     $dirname = "" if $dirname eq "." . $pathSeparator;
1055
1056     return ($dirname, basename($path));
1057 }
1058
1059 # Sort first by directory, then by file, so all paths in one directory are grouped
1060 # rather than being interspersed with items from subdirectories.
1061 # Use numericcmp to sort directory and filenames to make order logical.
1062 sub pathcmp($$)
1063 {
1064     my ($patha, $pathb) = @_;
1065
1066     my ($dira, $namea) = splitpath($patha);
1067     my ($dirb, $nameb) = splitpath($pathb);
1068
1069     return numericcmp($dira, $dirb) if $dira ne $dirb;
1070     return numericcmp($namea, $nameb);
1071 }
1072
1073 # Sort numeric parts of strings as numbers, other parts as strings.
1074 # Makes 1.33 come after 1.3, which is cool.
1075 sub numericcmp($$)
1076 {
1077     my ($aa, $bb) = @_;
1078
1079     my @a = split /(\d+)/, $aa;
1080     my @b = split /(\d+)/, $bb;
1081
1082     # Compare one chunk at a time.
1083     # Each chunk is either all numeric digits, or all not numeric digits.
1084     while (@a && @b) {
1085         my $a = shift @a;
1086         my $b = shift @b;
1087         
1088         # Use numeric comparison if chunks are non-equal numbers.
1089         return $a <=> $b if $a =~ /^\d/ && $b =~ /^\d/ && $a != $b;
1090
1091         # Use string comparison if chunks are any other kind of non-equal string.
1092         return $a cmp $b if $a ne $b;
1093     }
1094     
1095     # One of the two is now empty; compare lengths for result in this case.
1096     return @a <=> @b;
1097 }
1098
1099 # Sort slowest tests first.
1100 sub slowestcmp($$)
1101 {
1102     my ($testa, $testb) = @_;
1103
1104     my $dura = $durations{$testa};
1105     my $durb = $durations{$testb};
1106     return $durb <=> $dura if $dura != $durb;
1107     return pathcmp($testa, $testb);
1108 }
1109
1110 sub openDumpTool()
1111 {
1112     return if $isDumpToolOpen;
1113
1114     # Save some requires variables for the linux environment...
1115     my $homeDir = $ENV{'HOME'};
1116     my $libraryPath = $ENV{'LD_LIBRARY_PATH'};
1117     my $dyldLibraryPath = $ENV{'DYLD_LIBRARY_PATH'};
1118     my $dbusAddress = $ENV{'DBUS_SESSION_BUS_ADDRESS'};
1119     my $display = $ENV{'DISPLAY'};
1120     my $testfonts = $ENV{'WEBKIT_TESTFONTS'};
1121
1122     my $homeDrive = $ENV{'HOMEDRIVE'};
1123     my $homePath = $ENV{'HOMEPATH'};
1124         
1125     local %ENV;
1126     if (isQt()) {
1127         if (defined $display) {
1128             $ENV{DISPLAY} = $display;
1129         } else {
1130             $ENV{DISPLAY} = ":1";
1131         }
1132         $ENV{'WEBKIT_TESTFONTS'} = $testfonts;
1133         $ENV{HOME} = $homeDir;
1134         if (defined $libraryPath) {
1135             $ENV{LD_LIBRARY_PATH} = $libraryPath;
1136         }
1137         if (defined $dyldLibraryPath) {
1138             $ENV{DYLD_LIBRARY_PATH} = $dyldLibraryPath;
1139         }
1140         if (defined $dbusAddress) {
1141             $ENV{DBUS_SESSION_BUS_ADDRESS} = $dbusAddress;
1142         }
1143     }
1144     $ENV{DYLD_FRAMEWORK_PATH} = $productDir;
1145     $ENV{XML_CATALOG_FILES} = ""; # work around missing /etc/catalog <rdar://problem/4292995>
1146     $ENV{MallocStackLogging} = 1 if $shouldCheckLeaks;
1147     $ENV{DYLD_INSERT_LIBRARIES} = "/usr/lib/libgmalloc.dylib" if $guardMalloc;
1148     
1149     if (isCygwin()) {
1150         $ENV{HOMEDRIVE} = $homeDrive;
1151         $ENV{HOMEPATH} = $homePath;
1152         setPathForRunningWebKitApp(\%ENV) if isCygwin();
1153     }
1154         
1155     my @args = ();
1156     if ($useValgrind) {
1157       push @args, $dumpTool;
1158     }
1159     push @args, @toolArgs;
1160     if ($useValgrind) {
1161       $dumpTool = "valgrind";
1162     }
1163     $dumpToolPID = open3(\*OUT, \*IN, \*ERROR, $dumpTool, @args) or die "Failed to start tool: $dumpTool\n";
1164     $isDumpToolOpen = 1;
1165     $dumpToolCrashed = 0;
1166 }
1167
1168 sub closeDumpTool()
1169 {
1170     return if !$isDumpToolOpen;
1171
1172     close IN;
1173     close OUT;
1174     close ERROR;
1175     waitpid $dumpToolPID, 0;
1176     $isDumpToolOpen = 0;
1177 }
1178
1179 sub dumpToolDidCrash()
1180 {
1181     return 1 if $dumpToolCrashed;
1182     return 0 unless $isDumpToolOpen;
1183
1184     my $pid = waitpid(-1, WNOHANG);
1185     return $pid == $dumpToolPID;
1186 }
1187
1188 sub openHTTPDIfNeeded()
1189 {
1190     return if $isHttpdOpen;
1191
1192     mkdir "/tmp/WebKit";
1193     
1194     if (-f "/tmp/WebKit/httpd.pid") {
1195         my $oldPid = `cat /tmp/WebKit/httpd.pid`;
1196         chomp $oldPid;
1197         if (0 != kill 0, $oldPid) {
1198             print "\nhttpd is already running: pid $oldPid, killing...\n";
1199             kill 15, $oldPid;
1200             
1201             my $retryCount = 20;
1202             while ((0 != kill 0, $oldPid) && $retryCount) {
1203                 sleep 1;
1204                 --$retryCount;
1205             }
1206             
1207             die "Timed out waiting for httpd to quit" unless $retryCount;
1208         }
1209     }
1210     
1211     my $httpdPath = "/usr/sbin/httpd";
1212     my $httpdConfig;
1213     if (isCygwin()) {
1214         my $windowsConfDirectory = "$testDirectory/http/conf/";
1215         unless (-x "/usr/lib/apache/libphp4.dll") {
1216             copy("$windowsConfDirectory/libphp4.dll", "/usr/lib/apache/libphp4.dll");
1217             chmod(0755, "/usr/lib/apache/libphp4.dll");
1218         }
1219         $httpdConfig = "$windowsConfDirectory/cygwin-httpd.conf";
1220     } else {
1221         $httpdConfig = "$testDirectory/http/conf/httpd.conf";
1222         $httpdConfig = "$testDirectory/http/conf/apache2-httpd.conf" if `$httpdPath -v` =~ m|Apache/2|;
1223     }
1224     my $documentRoot = "$testDirectory/http/tests";
1225     my $typesConfig = "$testDirectory/http/conf/mime.types";
1226     my $listen = "127.0.0.1:$httpdPort";
1227     my $absTestResultsDirectory = File::Spec->rel2abs(glob $testResultsDirectory);
1228     my $sslCertificate = "$testDirectory/http/conf/webkit-httpd.pem";
1229
1230     mkpath $absTestResultsDirectory;
1231
1232     my @args = (
1233         "-f", "$httpdConfig",
1234         "-C", "DocumentRoot \"$documentRoot\"",
1235         "-C", "Listen $listen",
1236         "-c", "TypesConfig \"$typesConfig\"",
1237         "-c", "CustomLog \"$absTestResultsDirectory/access_log.txt\" common",
1238         "-c", "ErrorLog \"$absTestResultsDirectory/error_log.txt\"",
1239         # Apache wouldn't run CGIs with permissions==700 otherwise
1240         "-c", "User \"#$<\""
1241     );
1242
1243     # FIXME: Enable this on Windows once <rdar://problem/5345985> is fixed
1244     push(@args, "-c", "SSLCertificateFile \"$sslCertificate\"") unless isCygwin();
1245
1246     open2(\*HTTPDIN, \*HTTPDOUT, $httpdPath, @args);
1247
1248     my $retryCount = 20;
1249     while (system("/usr/bin/curl -q --silent --stderr - --output /dev/null $listen") && $retryCount) {
1250         sleep 1;
1251         --$retryCount;
1252     }
1253     
1254     die "Timed out waiting for httpd to start" unless $retryCount;
1255     
1256     $isHttpdOpen = 1;
1257 }
1258
1259 sub closeHTTPD()
1260 {
1261     return if !$isHttpdOpen;
1262
1263     close HTTPDIN;
1264     close HTTPDOUT;
1265
1266     kill 15, `cat /tmp/WebKit/httpd.pid` if -f "/tmp/WebKit/httpd.pid";
1267
1268     $isHttpdOpen = 0;
1269 }
1270
1271 sub fileNameWithNumber($$)
1272 {
1273     my ($base, $number) = @_;
1274     return "$base$number" if ($number > 1);
1275     return $base;
1276 }
1277
1278 sub processIgnoreTests($) {
1279     my @ignoreList = split(/\s*,\s*/, shift);
1280     my $addIgnoredDirectories = sub {
1281         return () if exists $ignoredLocalDirectories{basename($File::Find::dir)};
1282         $ignoredDirectories{File::Spec->abs2rel($File::Find::dir, $testDirectory)} = 1;
1283         return @_;
1284     };
1285     foreach my $item (@ignoreList) {
1286         my $path = catfile($testDirectory, $item); 
1287         if (-d $path) {
1288             $ignoredDirectories{$item} = 1;
1289             find({ preprocess => $addIgnoredDirectories, wanted => sub {} }, $path);
1290         }
1291         elsif (-f $path) {
1292             $ignoredFiles{$item} = 1;
1293         }
1294         else {
1295             print "ignoring '$item' on ignore-tests list\n";
1296         }
1297     }
1298 }
1299
1300 sub stripExtension($)
1301 {
1302     my ($test) = @_;
1303
1304     $test =~ s/\.[a-zA-Z]+$//;
1305     return $test;
1306 }
1307
1308 sub isTextOnlyTest($)
1309 {
1310     my ($actual) = @_;
1311     my $isText;
1312     if ($actual =~ /^layer at/ms) {
1313         $isText = 0;
1314     } else {
1315         $isText = 1;
1316     }
1317     return $isText;
1318 }
1319
1320 sub expectedDirectoryForTest($;$)
1321 {
1322     my ($base, $isText) = @_;
1323
1324     my @directories = @platformHierarchy;
1325     push @directories, map { catdir($platformBaseDirectory, $_) } qw(mac-leopard mac) if isCygwin();
1326     push @directories, $expectedDirectory;
1327
1328     # If we already have expected results, just return their location.
1329     foreach my $directory (@directories) {
1330         return $directory if (-f "$directory/$base-$expectedTag.txt");
1331     }
1332
1333     # For platform-specific tests, the results should go right next to the test itself.
1334     # Note: The return value of this subroutine will be concatenated with $base
1335     # to determine the location of the new results, so returning $expectedDirectory
1336     # will put the results right next to the test.
1337     # FIXME: We want to allow platform/mac tests with platform/mac-leopard results,
1338     # so this needs to be enhanced.
1339     return $expectedDirectory if $base =~ /^platform/;
1340
1341     # For cross-platform tests, text-only results should go in the cross-platform directory,
1342     # while render tree dumps should go in the least-specific platform directory.
1343     return $isText ? $expectedDirectory : $platformHierarchy[$#platformHierarchy];
1344 }
1345
1346 sub printFailureMessageForTest($$)
1347 {
1348     my ($test, $description) = @_;
1349
1350     unless ($verbose) {
1351         print "\n" unless $atLineStart;
1352         print "$test -> ";
1353     }
1354     print "$description\n";
1355     $atLineStart = 1;
1356 }
1357
1358 my %cygpaths = ();
1359
1360 sub openCygpathIfNeeded($)
1361 {
1362     my ($options) = @_;
1363
1364     return unless isCygwin();
1365     return $cygpaths{$options} if $cygpaths{$options} && $cygpaths{$options}->{"open"};
1366
1367     local (*CYGPATHIN, *CYGPATHOUT);
1368     my $pid = open2(\*CYGPATHIN, \*CYGPATHOUT, "cygpath -f - $options");
1369     my $cygpath =  {
1370         "pid" => $pid,
1371         "in" => *CYGPATHIN,
1372         "out" => *CYGPATHOUT,
1373         "open" => 1
1374     };
1375
1376     $cygpaths{$options} = $cygpath;
1377
1378     return $cygpath;
1379 }
1380
1381 sub closeCygpaths()
1382 {
1383     return unless isCygwin();
1384
1385     foreach my $cygpath (values(%cygpaths)) {
1386         close $cygpath->{"in"};
1387         close $cygpath->{"out"};
1388         waitpid($cygpath->{"pid"}, 0);
1389         $cygpath->{"open"} = 0;
1390
1391     }
1392 }
1393
1394 sub convertPathUsingCygpath($$)
1395 {
1396     my ($path, $options) = @_;
1397
1398     my $cygpath = openCygpathIfNeeded($options);
1399     local *inFH = $cygpath->{"in"};
1400     local *outFH = $cygpath->{"out"};
1401     print outFH $path . "\n";
1402     chomp(my $convertedPath = <inFH>);
1403     return $convertedPath;
1404 }
1405
1406 sub toWindowsPath($)
1407 {
1408     my ($path) = @_;
1409     return unless isCygwin();
1410
1411     return convertPathUsingCygpath($path, "-w");
1412 }
1413
1414 sub toURL($)
1415 {
1416     my ($path) = @_;
1417     return $path unless isCygwin();
1418     
1419     return "file:///" . convertPathUsingCygpath($path, "-m");
1420 }
1421
1422 sub validateSkippedArg($$;$)
1423 {
1424     my ($option, $value, $value2) = @_;
1425     my %validSkippedValues = map { $_ => 1 } qw(default ignore only);
1426     $value = lc($value);
1427     die "Invalid argument '" . $value . "' for option $option" unless $validSkippedValues{$value};
1428     $treatSkipped = $value;
1429 }
1430
1431 sub htmlForExpectedAndActualResults($)
1432 {
1433     my ($base) = @_;
1434
1435     return "<td></td><td></td><td></td>\n" unless -s "$testResultsDirectory/$base-$diffsTag.txt";
1436
1437     return "<td><a href=\"$base-$expectedTag.txt\">expected</a></td>\n"
1438          . "<td><a href=\"$base-$actualTag.txt\">actual</a></td>\n"
1439          . "<td><a href=\"$base-$diffsTag.txt\">diffs</a></td>\n";
1440 }
1441
1442 sub deleteExpectedAndActualResults($)
1443 {
1444     my ($base) = @_;
1445
1446     unlink "$testResultsDirectory/$base-$actualTag.txt";
1447     unlink "$testResultsDirectory/$base-$diffsTag.txt";
1448     unlink "$testResultsDirectory/$base-$errorTag.txt";
1449 }
1450
1451 sub recordActualResultsAndDiff($$)
1452 {
1453     my ($base, $actual) = @_;
1454
1455     return unless length($actual);
1456
1457     open ACTUAL, ">", "$testResultsDirectory/$base-$actualTag.txt" or die "Couldn't open actual results file for $base";
1458     print ACTUAL $actual;
1459     close ACTUAL;
1460
1461     my $expectedDir = $expectedResultDirectory{$base};
1462     copy("$expectedDir/$base-$expectedTag.txt", "$testResultsDirectory/$base-$expectedTag.txt");
1463
1464     system "diff -u \"$testResultsDirectory/$base-$expectedTag.txt\" \"$testResultsDirectory/$base-$actualTag.txt\" > \"$testResultsDirectory/$base-$diffsTag.txt\"";
1465 }
1466
1467 sub buildPlatformHierarchy()
1468 {
1469     mkpath($platformTestDirectory) if ($platform eq "undefined" && !-d "$platformTestDirectory");
1470
1471     my @platforms = split('-', $platform);
1472     my @hierarchy;
1473     for (my $i=0; $i < @platforms; $i++) {
1474         my $scoped = catdir($platformBaseDirectory, join('-', @platforms[0..($#platforms - $i)]));
1475         push(@hierarchy, $scoped) if (-d $scoped);
1476     }
1477
1478     return @hierarchy;
1479 }