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
10 # Redistribution and use in source and binary forms, with or without
11 # modification, are permitted provided that the following conditions
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.
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.
34 # Script to run the WebKit Open Source Project layout tests.
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.
40 # Compare against the existing file xxx-expected.txt.
41 # If there is a mismatch, generate xxx-actual.txt and xxx-diffs.txt.
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
54 use Fcntl qw(F_GETFL F_SETFL O_NONBLOCK);
60 use File::Spec::Functions;
65 use Time::HiRes qw(time usleep);
67 use List::Util 'shuffle';
69 use lib $FindBin::Bin;
74 sub buildPlatformResultHierarchy();
75 sub buildPlatformTestHierarchy(@);
79 sub closeWebSocketServer();
80 sub countAndPrintLeaks($$$);
81 sub countFinishedTest($$$$);
82 sub deleteExpectedAndActualResults($);
83 sub dumpToolDidCrash();
84 sub epiloguesAndPrologues($$);
85 sub expectedDirectoryForTest($;$;$);
86 sub fileNameWithNumber($$);
87 sub htmlForResultsSection(\@$&);
88 sub isTextOnlyTest($);
89 sub launchWithCurrentEnv(@);
90 sub resolveAndMakeTestResultsDirectory();
94 sub openHTTPDIfNeeded();
95 sub parseLeaksandPrintUniqueLeaks();
96 sub openWebSocketServerIfNeeded();
98 sub printFailureMessageForTest($$);
99 sub processIgnoreTests($$);
100 sub readFromDumpToolWithTimer(**);
101 sub readSkippedFiles($);
102 sub recordActualResultsAndDiff($$);
103 sub sampleDumpTool();
104 sub setFileHandleNonBlocking(*$);
107 sub stripExtension($);
108 sub stripMetrics($$);
109 sub testCrashedOrTimedOut($$$$$);
111 sub toWindowsPath($);
112 sub validateSkippedArg($$;$);
116 my $addPlatformExceptions = 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 = '';
129 my $launchSafari = 1;
134 my $randomizeTests = 0;
136 my $report10Slowest = 0;
137 my $resetResults = 0;
138 my $reverseTests = 0;
141 my $shouldCheckLeaks = 0;
143 my $stripEditingCallbacks = isCygwin();
146 my $testResultsDirectory = "/tmp/layout-test-results";
147 my $testsPerDumpTool = 1000;
149 # DumpRenderTree has an internal timeout of 15 seconds, so this must be > 15.
150 my $timeoutSeconds = 20;
152 my $treatSkipped = "default";
153 my $useRemoteLinksToTests = 0;
159 # Default to --no-http for wx for now.
160 $testHTTP = 0 if (isWx());
162 my $expectedTag = "expected";
163 my $actualTag = "actual";
164 my $prettyDiffTag = "pretty-diff";
165 my $diffsTag = "diffs";
166 my $errorTag = "stderr";
168 my @macPlatforms = ("mac-tiger", "mac-leopard", "mac-snowleopard", "mac");
170 if (isAppleMacWebKit()) {
172 $platform = "mac-tiger";
174 } elsif (isLeopard()) {
175 $platform = "mac-leopard";
177 } elsif (isSnowLeopard()) {
178 $platform = "mac-snowleopard";
185 $platform = "qt-mac";
186 } elsif (isLinux()) {
187 $platform = "qt-linux";
188 } elsif (isWindows() || isCygwin()) {
189 $platform = "qt-win";
195 if (!$ENV{"WEBKIT_TESTFONTS"}) {
196 print "The WEBKIT_TESTFONTS environment variable is not defined.\n";
197 print "You must set it before running the tests.\n";
198 print "Use git to grab the actual fonts from http://gitorious.org/qtwebkit/testfonts\n";
203 } elsif (isCygwin()) {
207 if (!defined($platform)) {
208 print "WARNING: Your platform is not recognized. Any platform-specific results will be generated in platform/undefined.\n";
209 $platform = "undefined";
212 my $programName = basename($0);
213 my $launchSafariDefault = $launchSafari ? "launch" : "do not launch";
214 my $httpDefault = $testHTTP ? "run" : "do not run";
215 my $sampleDefault = $runSample ? "run" : "do not run";
218 Usage: $programName [options] [testdir|testpath ...]
219 --add-platform-exceptions Put new results for non-platform-specific failing tests into the platform-specific results directory
220 --complex-text Use the complex text code path for all text (Mac OS X and Windows only)
221 -c|--configuration config Set DumpRenderTree build configuration
222 -g|--guard-malloc Enable malloc guard
223 --exit-after-n-failures N Exit after the first N failures instead of running all tests
224 -h|--help Show this help message
225 --[no-]http Run (or do not run) http tests (default: $httpDefault)
226 -i|--ignore-tests Comma-separated list of directories or tests to ignore
227 --iterations n Number of times to run the set of tests (e.g. ABCABCABC)
228 --[no-]launch-safari Launch (or do not launch) Safari to display test results (default: $launchSafariDefault)
229 -l|--leaks Enable leaks checking
230 --[no-]new-test-results Generate results for new tests
231 --nthly n Restart DumpRenderTree every n tests (default: $testsPerDumpTool)
232 -p|--pixel-tests Enable pixel tests
233 --tolerance t Ignore image differences less than this percentage (default: $tolerance)
234 --platform Override the detected platform to use for tests and results (default: $platform)
235 --port Web server port to use with http tests
236 -q|--quiet Less verbose output
237 --reset-results Reset ALL results (including pixel tests if --pixel-tests is set)
238 -o|--results-directory Output results directory (default: $testResultsDirectory)
239 --random Run the tests in a random order
240 --repeat-each n Number of times to run each test (e.g. AAABBBCCC)
241 --reverse Run the tests in reverse alphabetical order
242 --root Path to root tools build
243 --[no-]sample-on-timeout Run sample on timeout (default: $sampleDefault) (Mac OS X only)
244 -1|--singly Isolate each test case run (implies --nthly 1 --verbose)
245 --skipped=[default|ignore|only] Specifies how to treat the Skipped file
246 default: Tests/directories listed in the Skipped file are not tested
247 ignore: The Skipped file is ignored
248 only: Only those tests/directories listed in the Skipped file will be run
249 --slowest Report the 10 slowest tests
250 --ignore-metrics Ignore metrics in tests
251 --[no-]strip-editing-callbacks Remove editing callbacks from expected results
252 -t|--threaded Run a concurrent JavaScript thead with each test
253 --timeout t Sets the number of seconds before a test times out (default: $timeoutSeconds)
254 --valgrind Run DumpRenderTree inside valgrind (Qt/Linux only)
255 -v|--verbose More verbose output (overrides --quiet)
256 -m|--merge-leak-depth arg Merges leak callStacks and prints the number of unique leaks beneath a callstack depth of arg. Defaults to 5.
257 --use-remote-links-to-tests Link to test files within the SVN repository in the results.
262 my $getOptionsResult = GetOptions(
263 'add-platform-exceptions' => \$addPlatformExceptions,
264 'complex-text' => \$complexText,
265 'exit-after-n-failures=i' => \$exitAfterNFailures,
266 'guard-malloc|g' => \$guardMalloc,
267 'help|h' => \$showHelp,
268 'http!' => \$testHTTP,
269 'ignore-metrics!' => \$ignoreMetrics,
270 'ignore-tests|i=s' => \$ignoreTests,
271 'iterations=i' => \$iterations,
272 'launch-safari!' => \$launchSafari,
273 'leaks|l' => \$shouldCheckLeaks,
274 'merge-leak-depth|m:5' => \$mergeDepth,
275 'new-test-results!' => \$generateNewResults,
276 'nthly=i' => \$testsPerDumpTool,
277 'pixel-tests|p' => \$pixelTests,
278 'platform=s' => \$platform,
279 'port=i' => \$httpdPort,
280 'quiet|q' => \$quiet,
281 'random' => \$randomizeTests,
282 'repeat-each=i' => \$repeatEach,
283 'reset-results' => \$resetResults,
284 'results-directory|o=s' => \$testResultsDirectory,
285 'reverse' => \$reverseTests,
287 'sample-on-timeout!' => \$runSample,
288 'singly|1' => sub { $testsPerDumpTool = 1; },
289 'skipped=s' => \&validateSkippedArg,
290 'slowest' => \$report10Slowest,
291 'strip-editing-callbacks!' => \$stripEditingCallbacks,
292 'threaded|t' => \$threaded,
293 'timeout=i' => \$timeoutSeconds,
294 'tolerance=f' => \$tolerance,
295 'use-remote-links-to-tests' => \$useRemoteLinksToTests,
296 'valgrind' => \$useValgrind,
297 'verbose|v' => \$verbose,
300 if (!$getOptionsResult || $showHelp) {
305 my $ignoreSkipped = $treatSkipped eq "ignore";
306 my $skippedOnly = $treatSkipped eq "only";
308 my $configuration = configuration();
310 $verbose = 1 if $testsPerDumpTool == 1;
312 if ($shouldCheckLeaks && $testsPerDumpTool > 1000) {
313 print STDERR "\nWARNING: Running more than 1000 tests at a time with MallocStackLogging enabled may cause a crash.\n\n";
316 # Stack logging does not play well with QuickTime on Tiger (rdar://problem/5537157)
317 $testMedia = 0 if $shouldCheckLeaks && isTiger();
319 # Generating remote links causes a lot of unnecessary spew on GTK build bot
320 $useRemoteLinksToTests = 0 if isGtk();
322 setConfigurationProductDir(Cwd::abs_path($root)) if (defined($root));
323 my $productDir = productDir();
324 $productDir .= "/bin" if isQt();
325 $productDir .= "/Programs" if isGtk();
329 if (!defined($root)) {
330 print STDERR "Running build-dumprendertree\n";
333 my ($childIn, $childOut, $childErr);
335 open(DEVNULL, ">", File::Spec->devnull()) or die "Failed to open /dev/null";
336 $childOut = ">&DEVNULL";
337 $childErr = ">&DEVNULL";
339 # When not quiet, let the child use our stdout/stderr.
340 $childOut = ">&STDOUT";
341 $childErr = ">&STDERR";
344 my @args = argumentsForConfiguration();
345 my $buildProcess = open3($childIn, $childOut, $childErr, "WebKitTools/Scripts/build-dumprendertree", @args) or die "Failed to run build-dumprendertree";
347 waitpid $buildProcess, 0;
348 my $buildResult = $?;
352 close DEVNULL if ($quiet);
355 print STDERR "Compiling DumpRenderTree failed!\n";
356 exit exitStatus($buildResult);
360 my $dumpToolName = "DumpRenderTree";
361 $dumpToolName .= "_debug" if isCygwin() && configurationForVisualStudio() !~ /^Release|Debug_Internal$/;
362 my $dumpTool = "$productDir/$dumpToolName";
363 die "can't find executable $dumpToolName (looked in $productDir)\n" unless -x $dumpTool;
365 my $imageDiffTool = "$productDir/ImageDiff";
366 $imageDiffTool .= "_debug" if isCygwin() && configurationForVisualStudio() !~ /^Release|Debug_Internal$/;
367 die "can't find executable $imageDiffTool (looked in $productDir)\n" if $pixelTests && !-x $imageDiffTool;
369 checkFrameworks() unless isCygwin();
371 if (isAppleMacWebKit()) {
372 push @INC, $productDir;
373 require DumpRenderTreeSupport;
376 my $layoutTestsName = "LayoutTests";
377 my $testDirectory = File::Spec->rel2abs($layoutTestsName);
378 my $expectedDirectory = $testDirectory;
379 my $platformBaseDirectory = catdir($testDirectory, "platform");
380 my $platformTestDirectory = catdir($platformBaseDirectory, $platform);
381 my @platformResultHierarchy = buildPlatformResultHierarchy();
382 my @platformTestHierarchy = buildPlatformTestHierarchy(@platformResultHierarchy);
384 $expectedDirectory = $ENV{"WebKitExpectedTestResultsDirectory"} if $ENV{"WebKitExpectedTestResultsDirectory"};
386 my $testResults = catfile($testResultsDirectory, "results.html");
388 print "Running tests from $testDirectory\n";
390 print "Enabling pixel tests with a tolerance of $tolerance%\n";
392 print "WARNING: Temporarily changing the main display color profile:\n";
393 print "\tThe colors on your screen will change for the duration of the testing.\n";
394 print "\tThis allows the pixel tests to have consistent color values across all machines.\n";
396 if (isPerianInstalled()) {
397 print "WARNING: Perian's QuickTime component is installed and this may affect pixel test results!\n";
398 print "\tYou should avoid generating new pixel results in this environment.\n";
399 print "\tSee https://bugs.webkit.org/show_bug.cgi?id=22615 for details.\n";
404 system "ln", "-s", $testDirectory, "/tmp/LayoutTests" unless -x "/tmp/LayoutTests";
406 my %ignoredFiles = ( "results.html" => 1 );
407 my %ignoredDirectories = map { $_ => 1 } qw(platform);
408 my %ignoredLocalDirectories = map { $_ => 1 } qw(.svn _svn resources script-tests);
409 my %supportedFileExtensions = map { $_ => 1 } qw(html shtml xml xhtml pl php);
411 if (!checkWebCoreMathMLSupport(0)) {
412 $ignoredDirectories{'mathml'} = 1;
415 # FIXME: We should fix webkitdirs.pm:hasSVG/WMLSupport() to do the correct feature detection for Cygwin.
416 if (checkWebCoreSVGSupport(0)) {
417 $supportedFileExtensions{'svg'} = 1;
418 } elsif (isCygwin()) {
419 $supportedFileExtensions{'svg'} = 1;
421 $ignoredLocalDirectories{'svg'} = 1;
425 $ignoredDirectories{'http'} = 1;
426 $ignoredDirectories{'websocket'} = 1;
430 $ignoredDirectories{'media'} = 1;
431 $ignoredDirectories{'http/tests/media'} = 1;
434 if (!checkWebCoreAcceleratedCompositingSupport(0)) {
435 $ignoredDirectories{'compositing'} = 1;
438 if (!checkWebCore3DRenderingSupport(0)) {
439 $ignoredDirectories{'animations/3d'} = 1;
440 $ignoredDirectories{'transforms/3d'} = 1;
443 if (!checkWebCore3DCanvasSupport(0)) {
444 $ignoredDirectories{'fast/canvas/webgl'} = 1;
447 if (checkWebCoreWMLSupport(0)) {
448 $supportedFileExtensions{'wml'} = 1;
450 $ignoredDirectories{'http/tests/wml'} = 1;
451 $ignoredDirectories{'fast/wml'} = 1;
452 $ignoredDirectories{'wml'} = 1;
455 if (!checkWebCoreXHTMLMPSupport(0)) {
456 $ignoredDirectories{'fast/xhtmlmp'} = 1;
459 if (!checkWebCoreWCSSSupport(0)) {
460 $ignoredDirectories{'fast/wcss'} = 1;
463 processIgnoreTests($ignoreTests, "ignore-tests") if $ignoreTests;
464 if (!$ignoreSkipped) {
465 if (!$skippedOnly || @ARGV == 0) {
466 readSkippedFiles("");
468 # Since readSkippedFiles() appends to @ARGV, we must use a foreach
469 # loop so that we only iterate over the original argument list.
470 foreach my $argnum (0 .. $#ARGV) {
471 readSkippedFiles(shift @ARGV);
476 my @tests = findTestsToRun();
478 die "no tests to run\n" if !@tests;
483 my %imageDifferences;
486 my $leaksOutputFileNumber = 1;
490 push @toolArgs, "--pixel-tests" if $pixelTests;
491 push @toolArgs, "--threaded" if $threaded;
492 push @toolArgs, "--complex-text" if $complexText;
495 my @diffToolArgs = ();
496 push @diffToolArgs, "--tolerance", $tolerance;
501 my $isDumpToolOpen = 0;
502 my $dumpToolCrashed = 0;
503 my $imageDiffToolPID;
504 my $isDiffToolOpen = 0;
507 my $lastDirectory = "";
510 my $isWebSocketServerOpen = 0;
511 my $webSocketServerPID = 0;
512 my $failedToStartWebSocketServer = 0;
513 # wss is disabled until all platforms support pyOpenSSL.
514 # my $webSocketSecureServerPID = 0;
516 sub catch_pipe { $dumpToolCrashed = 1; }
517 $SIG{"PIPE"} = "catch_pipe";
519 print "Testing ", scalar @tests, " test cases";
520 print " $iterations times" if ($iterations > 1);
521 print ", repeating each test $repeatEach times" if ($repeatEach > 1);
524 my $overallStartTime = time;
526 my %expectedResultPaths;
528 my @originalTests = @tests;
529 # Add individual test repetitions
530 if ($repeatEach > 1) {
532 foreach my $test (@originalTests) {
533 for (my $i = 0; $i < $repeatEach; $i++) {
538 # Add test set repetitions
539 for (my $i = 1; $i < $iterations; $i++) {
540 push(@tests, @originalTests);
543 for my $test (@tests) {
544 my $newDumpTool = not $isDumpToolOpen;
547 my $base = stripExtension($test);
548 my $expectedExtension = ".txt";
553 if ($newDumpTool || $dir ne $lastDirectory) {
554 foreach my $logue (epiloguesAndPrologues($newDumpTool ? "" : $lastDirectory, $dir)) {
556 $logue = toWindowsPath($logue);
558 $logue = canonpath($logue);
561 print "running epilogue or prologue $logue\n";
563 print OUT "$logue\n";
564 # Throw away output from DumpRenderTree.
565 # Once for the test output and once for pixel results (empty)
576 print "running $test -> ";
579 if ($dir ne $lastDirectory) {
580 print "\n" unless $atLineStart;
587 $lastDirectory = $dir;
591 my $startTime = time if $report10Slowest;
593 # Try to read expected hash file for pixel tests
594 my $suffixExpectedHash = "";
595 if ($pixelTests && !$resetResults) {
596 my $expectedPixelDir = expectedDirectoryForTest($base, 0, "png");
597 if (open EXPECTEDHASH, "$expectedPixelDir/$base-$expectedTag.checksum") {
598 my $expectedHash = <EXPECTEDHASH>;
599 chomp($expectedHash);
602 # Format expected hash into a suffix string that is appended to the path / URL passed to DRT
603 $suffixExpectedHash = "'$expectedHash";
607 if ($test =~ /^http\//) {
609 if ($test !~ /^http\/tests\/local\// && $test !~ /^http\/tests\/ssl\// && $test !~ /^http\/tests\/wml\// && $test !~ /^http\/tests\/media\//) {
610 my $path = canonpath($test);
611 $path =~ s/^http\/tests\///;
612 print OUT "http://127.0.0.1:$httpdPort/$path$suffixExpectedHash\n";
613 } elsif ($test =~ /^http\/tests\/ssl\//) {
614 my $path = canonpath($test);
615 $path =~ s/^http\/tests\///;
616 print OUT "https://127.0.0.1:$httpdSSLPort/$path$suffixExpectedHash\n";
618 my $testPath = "$testDirectory/$test";
620 $testPath = toWindowsPath($testPath);
622 $testPath = canonpath($testPath);
624 print OUT "$testPath$suffixExpectedHash\n";
626 } elsif ($test =~ /^websocket\//) {
627 if ($test =~ /^websocket\/tests\/local\//) {
628 my $testPath = "$testDirectory/$test";
630 $testPath = toWindowsPath($testPath);
632 $testPath = canonpath($testPath);
634 print OUT "$testPath\n";
636 if (openWebSocketServerIfNeeded()) {
637 my $path = canonpath($test);
638 if ($test =~ /^websocket\/tests\/ssl\//) {
639 # wss is disabled until all platforms support pyOpenSSL.
640 print STDERR "Error: wss is disabled until all platforms support pyOpenSSL.";
641 # print OUT "https://127.0.0.1:$webSocketSecurePort/$path\n";
643 print OUT "http://127.0.0.1:$webSocketPort/$path\n";
646 # We failed to launch the WebSocket server. Display a useful error message rather than attempting
647 # to run tests that expect the server to be available.
648 my $errorMessagePath = "$testDirectory/websocket/resources/server-failed-to-start.html";
649 $errorMessagePath = isCygwin() ? toWindowsPath($errorMessagePath) : canonpath($errorMessagePath);
650 print OUT "$errorMessagePath\n";
654 my $testPath = "$testDirectory/$test";
656 $testPath = toWindowsPath($testPath);
658 $testPath = canonpath($testPath);
660 print OUT "$testPath$suffixExpectedHash\n";
663 # DumpRenderTree is expected to dump two "blocks" to stdout for each test.
664 # Each block is terminated by a #EOF on a line by itself.
665 # The first block is the output of the test (in text, RenderTree or other formats).
666 # The second block is for optional pixel data in PNG format, and may be empty if
667 # pixel tests are not being run, or the test does not dump pixels (e.g. text tests).
668 my $readResults = readFromDumpToolWithTimer(IN, ERROR);
670 my $actual = $readResults->{output};
671 my $error = $readResults->{error};
673 $expectedExtension = $readResults->{extension};
674 my $expectedFileName = "$base-$expectedTag.$expectedExtension";
676 my $isText = isTextOnlyTest($actual);
678 my $expectedDir = expectedDirectoryForTest($base, $isText, $expectedExtension);
679 $expectedResultPaths{$base} = "$expectedDir/$expectedFileName";
681 unless ($readResults->{status} eq "success") {
682 my $crashed = $readResults->{status} eq "crashed";
683 testCrashedOrTimedOut($test, $base, $crashed, $actual, $error);
684 countFinishedTest($test, $base, $crashed ? "crash" : "timedout", 0);
688 $durations{$test} = time - $startTime if $report10Slowest;
692 if (!$resetResults && open EXPECTED, "<", "$expectedDir/$expectedFileName") {
695 next if $stripEditingCallbacks && $_ =~ /^EDITING DELEGATE:/;
701 if ($ignoreMetrics && !$isText && defined $expected) {
702 ($actual, $expected) = stripMetrics($actual, $expected);
705 if ($shouldCheckLeaks && $testsPerDumpTool == 1) {
711 my $diffPercentage = "";
712 my $diffResult = "passed";
715 my $expectedHash = "";
716 my $actualPNGSize = 0;
720 if (/ActualHash: ([a-f0-9]{32})/) {
722 } elsif (/ExpectedHash: ([a-f0-9]{32})/) {
724 } elsif (/Content-Length: (\d+)\s*/) {
726 read(IN, $actualPNG, $actualPNGSize);
730 if ($verbose && $pixelTests && !$resetResults && $actualPNGSize) {
731 if ($actualHash eq "" && $expectedHash eq "") {
732 printFailureMessageForTest($test, "WARNING: actual & expected pixel hashes are missing!");
733 } elsif ($actualHash eq "") {
734 printFailureMessageForTest($test, "WARNING: actual pixel hash is missing!");
735 } elsif ($expectedHash eq "") {
736 printFailureMessageForTest($test, "WARNING: expected pixel hash is missing!");
740 if ($actualPNGSize > 0) {
741 my $expectedPixelDir = expectedDirectoryForTest($base, 0, "png");
743 if (!$resetResults && ($expectedHash ne $actualHash || ($actualHash eq "" && $expectedHash eq ""))) {
744 if (-f "$expectedPixelDir/$base-$expectedTag.png") {
745 my $expectedPNGSize = -s "$expectedPixelDir/$base-$expectedTag.png";
746 my $expectedPNG = "";
747 open EXPECTEDPNG, "$expectedPixelDir/$base-$expectedTag.png";
748 read(EXPECTEDPNG, $expectedPNG, $expectedPNGSize);
751 print DIFFOUT "Content-Length: $actualPNGSize\n";
752 print DIFFOUT $actualPNG;
754 print DIFFOUT "Content-Length: $expectedPNGSize\n";
755 print DIFFOUT $expectedPNG;
758 last if /^error/ || /^diff:/;
759 if (/Content-Length: (\d+)\s*/) {
760 read(DIFFIN, $diffPNG, $1);
764 if (/^diff: (.+)% (passed|failed)/) {
765 $diffPercentage = $1;
766 $imageDifferences{$base} = $diffPercentage;
770 if ($diffPercentage == 0) {
771 printFailureMessageForTest($test, "pixel hash failed (but pixel test still passes)");
774 printFailureMessageForTest($test, "WARNING: expected image is missing!");
778 if ($resetResults || !-f "$expectedPixelDir/$base-$expectedTag.png") {
779 mkpath catfile($expectedPixelDir, dirname($base)) if $testDirectory ne $expectedPixelDir;
780 writeToFile("$expectedPixelDir/$base-$expectedTag.png", $actualPNG);
783 if ($actualHash ne "" && ($resetResults || !-f "$expectedPixelDir/$base-$expectedTag.checksum")) {
784 writeToFile("$expectedPixelDir/$base-$expectedTag.checksum", $actualHash);
788 if (dumpToolDidCrash()) {
790 testCrashedOrTimedOut($test, $base, 1, $actual, $error);
791 } elsif (!defined $expected) {
793 print "new " . ($resetResults ? "result" : "test") ."\n";
798 if ($generateNewResults || $resetResults) {
799 mkpath catfile($expectedDir, dirname($base)) if $testDirectory ne $expectedDir;
800 writeToFile("$expectedDir/$expectedFileName", $actual);
802 deleteExpectedAndActualResults($base);
803 recordActualResultsAndDiff($base, $actual);
804 if (!$resetResults) {
805 # Always print the file name for new tests, as they will probably need some manual inspection.
806 # in verbose mode we already printed the test case, so no need to do it again.
808 print "\n" unless $atLineStart;
811 my $resultsDir = catdir($expectedDir, dirname($base));
812 if ($generateNewResults) {
813 print "new (results generated in $resultsDir)\n";
819 } elsif ($actual eq $expected && $diffResult eq "passed") {
825 deleteExpectedAndActualResults($base);
827 $result = "mismatch";
829 my $pixelTestFailed = $pixelTests && $diffPNG && $diffPNG ne "";
830 my $testFailed = $actual ne $expected;
832 my $message = !$testFailed ? "pixel test failed" : "failed";
834 if (($testFailed || $pixelTestFailed) && $addPlatformExceptions) {
835 my $testBase = catfile($testDirectory, $base);
836 my $expectedBase = catfile($expectedDir, $base);
837 my $testIsMaximallyPlatformSpecific = $testBase =~ m|^\Q$platformTestDirectory\E/|;
838 my $expectedResultIsMaximallyPlatformSpecific = $expectedBase =~ m|^\Q$platformTestDirectory\E/|;
839 if (!$testIsMaximallyPlatformSpecific && !$expectedResultIsMaximallyPlatformSpecific) {
840 mkpath catfile($platformTestDirectory, dirname($base));
842 my $expectedFile = catfile($platformTestDirectory, "$expectedFileName");
843 writeToFile("$expectedFile", $actual);
845 if ($pixelTestFailed) {
846 my $expectedFile = catfile($platformTestDirectory, "$base-$expectedTag.checksum");
847 writeToFile("$expectedFile", $actualHash);
849 $expectedFile = catfile($platformTestDirectory, "$base-$expectedTag.png");
850 writeToFile("$expectedFile", $actualPNG);
852 $message .= " (results generated in $platformTestDirectory)";
856 printFailureMessageForTest($test, $message);
858 my $dir = "$testResultsDirectory/$base";
859 $dir =~ s|/([^/]+)$|| or die "Failed to find test name from base\n";
863 deleteExpectedAndActualResults($base);
864 recordActualResultsAndDiff($base, $actual);
866 if ($pixelTestFailed) {
867 $imagesPresent{$base} = 1;
869 writeToFile("$testResultsDirectory/$base-$actualTag.png", $actualPNG);
870 writeToFile("$testResultsDirectory/$base-$diffsTag.png", $diffPNG);
872 my $expectedPixelDir = expectedDirectoryForTest($base, 0, "png");
873 copy("$expectedPixelDir/$base-$expectedTag.png", "$testResultsDirectory/$base-$expectedTag.png");
875 open DIFFHTML, ">$testResultsDirectory/$base-$diffsTag.html" or die;
876 print DIFFHTML "<html>\n";
877 print DIFFHTML "<head>\n";
878 print DIFFHTML "<title>$base Image Compare</title>\n";
879 print DIFFHTML "<script language=\"Javascript\" type=\"text/javascript\">\n";
880 print DIFFHTML "var currentImage = 0;\n";
881 print DIFFHTML "var imageNames = new Array(\"Actual\", \"Expected\");\n";
882 print DIFFHTML "var imagePaths = new Array(\"$testName-$actualTag.png\", \"$testName-$expectedTag.png\");\n";
883 if (-f "$testDirectory/$base-w3c.png") {
884 copy("$testDirectory/$base-w3c.png", "$testResultsDirectory/$base-w3c.png");
885 print DIFFHTML "imageNames.push(\"W3C\");\n";
886 print DIFFHTML "imagePaths.push(\"$testName-w3c.png\");\n";
888 print DIFFHTML "function animateImage() {\n";
889 print DIFFHTML " var image = document.getElementById(\"animatedImage\");\n";
890 print DIFFHTML " var imageText = document.getElementById(\"imageText\");\n";
891 print DIFFHTML " image.src = imagePaths[currentImage];\n";
892 print DIFFHTML " imageText.innerHTML = imageNames[currentImage] + \" Image\";\n";
893 print DIFFHTML " currentImage = (currentImage + 1) % imageNames.length;\n";
894 print DIFFHTML " setTimeout('animateImage()',2000);\n";
895 print DIFFHTML "}\n";
896 print DIFFHTML "</script>\n";
897 print DIFFHTML "</head>\n";
898 print DIFFHTML "<body onLoad=\"animateImage();\">\n";
899 print DIFFHTML "<table>\n";
900 if ($diffPercentage) {
901 print DIFFHTML "<tr>\n";
902 print DIFFHTML "<td>Difference between images: <a href=\"$testName-$diffsTag.png\">$diffPercentage%</a></td>\n";
903 print DIFFHTML "</tr>\n";
905 print DIFFHTML "<tr>\n";
906 print DIFFHTML "<td><a href=\"" . toURL("$testDirectory/$test") . "\">test file</a></td>\n";
907 print DIFFHTML "</tr>\n";
908 print DIFFHTML "<tr>\n";
909 print DIFFHTML "<td id=\"imageText\" style=\"text-weight: bold;\">Actual Image</td>\n";
910 print DIFFHTML "</tr>\n";
911 print DIFFHTML "<tr>\n";
912 print DIFFHTML "<td><img src=\"$testName-$actualTag.png\" id=\"animatedImage\"></td>\n";
913 print DIFFHTML "</tr>\n";
914 print DIFFHTML "</table>\n";
915 print DIFFHTML "</body>\n";
916 print DIFFHTML "</html>\n";
921 my $dir = "$testResultsDirectory/$base";
922 $dir =~ s|/([^/]+)$|| or die "Failed to find test name from base\n";
925 writeToFile("$testResultsDirectory/$base-$errorTag.txt", $error);
928 push @{$tests{error}}, $test;
931 countFinishedTest($test, $base, $result, $isText);
933 # --reset-results does not check pass vs. fail, so exitAfterNFailures makes no sense with --reset-results.
934 if ($exitAfterNFailures && !$resetResults) {
935 my $passCount = $counts{match} || 0; # $counts{match} will be undefined if we've not yet passed a test (e.g. the first test fails).
936 my $failureCount = $count - $passCount; # "Failure" here includes new tests, timeouts, crashes, etc.
937 if ($failureCount >= $exitAfterNFailures) {
938 print "\nExiting early after $failureCount failures. $count tests run.";
944 printf "\n%0.2fs total testing time\n", (time - $overallStartTime) . "";
946 !$isDumpToolOpen || die "Failed to close $dumpToolName.\n";
949 closeWebSocketServer();
951 # Because multiple instances of this script are running concurrently we cannot
952 # safely delete this symlink.
953 # system "rm /tmp/LayoutTests";
955 # FIXME: Do we really want to check the image-comparison tool for leaks every time?
956 if ($isDiffToolOpen && $shouldCheckLeaks) {
957 $totalLeaks += countAndPrintLeaks("ImageDiff", $imageDiffToolPID, "$testResultsDirectory/ImageDiff-leaks.txt");
962 parseLeaksandPrintUniqueLeaks();
964 print "\nWARNING: $totalLeaks total leaks found!\n";
965 print "See above for individual leaks results.\n" if ($leaksOutputFileNumber > 2);
973 if ($report10Slowest) {
974 print "\n\nThe 10 slowest tests:\n\n";
976 for my $test (sort slowestcmp keys %durations) {
977 printf "%0.2f secs: %s\n", $durations{$test}, $test;
978 last if ++$count == 10;
984 if ($skippedOnly && $counts{"match"}) {
985 print "The following tests are in the Skipped file (" . File::Spec->abs2rel("$platformTestDirectory/Skipped", $testDirectory) . "), but succeeded:\n";
986 foreach my $test (@{$tests{"match"}}) {
991 if ($resetResults || ($counts{match} && $counts{match} == $count)) {
992 print "all $count test cases succeeded\n";
999 mkpath $testResultsDirectory;
1001 open HTML, ">", $testResults or die "Failed to open $testResults. $!";
1002 print HTML "<html>\n";
1003 print HTML "<head>\n";
1004 print HTML "<title>Layout Test Results</title>\n";
1005 print HTML "</head>\n";
1006 print HTML "<body>\n";
1008 if ($ignoreMetrics) {
1009 print HTML "<h4>Tested with metrics ignored.</h4>";
1012 print HTML htmlForResultsSection(@{$tests{mismatch}}, "Tests where results did not match expected results", \&linksForMismatchTest);
1013 print HTML htmlForResultsSection(@{$tests{timedout}}, "Tests that timed out", \&linksForErrorTest);
1014 print HTML htmlForResultsSection(@{$tests{crash}}, "Tests that caused the DumpRenderTree tool to crash", \&linksForErrorTest);
1015 print HTML htmlForResultsSection(@{$tests{error}}, "Tests that had stderr output", \&linksForErrorTest);
1016 print HTML htmlForResultsSection(@{$tests{new}}, "Tests that had no expected results (probably new)", \&linksForNewTest);
1018 print HTML "</body>\n";
1019 print HTML "</html>\n";
1022 my @configurationArgs = argumentsForConfiguration();
1025 system "WebKitTools/Scripts/run-launcher", @configurationArgs, "file://".$testResults if $launchSafari;
1027 unshift @configurationArgs, qw(-graphicssystem raster -style windows);
1028 system "WebKitTools/Scripts/run-launcher", @configurationArgs, "file://".$testResults if $launchSafari;
1029 } elsif (isCygwin()) {
1030 system "cygstart", $testResults if $launchSafari;
1032 system "WebKitTools/Scripts/run-safari", @configurationArgs, "-NSOpen", $testResults if $launchSafari;
1035 closeCygpaths() if isCygwin();
1039 sub countAndPrintLeaks($$$)
1041 my ($dumpToolName, $dumpToolPID, $leaksFilePath) = @_;
1043 print "\n" unless $atLineStart;
1046 # We are excluding the following reported leaks so they don't get in our way when looking for WebKit leaks:
1047 # This allows us ignore known leaks and only be alerted when new leaks occur. Some leaks are in the old
1048 # versions of the system frameworks that are being used by the leaks bots. Even though a leak has been
1049 # fixed, it will be listed here until the bot has been updated with the newer frameworks.
1051 my @typesToExclude = (
1054 my @callStacksToExclude = (
1055 "Flash_EnforceLocalSecurity" # leaks in Flash plug-in code, rdar://problem/4449747
1059 # Leak list for the version of Tiger used on the build bot.
1060 push @callStacksToExclude, (
1061 "CFRunLoopRunSpecific \\| malloc_zone_malloc", "CFRunLoopRunSpecific \\| CFAllocatorAllocate ", # leak in CFRunLoopRunSpecific, rdar://problem/4670839
1062 "CGImageSourceGetPropertiesAtIndex", # leak in ImageIO, rdar://problem/4628809
1063 "FOGetCoveredUnicodeChars", # leak in ATS, rdar://problem/3943604
1064 "GetLineDirectionPreference", "InitUnicodeUtilities", # leaks tool falsely reporting leak in CFNotificationCenterAddObserver, rdar://problem/4964790
1065 "ICCFPrefWrapper::GetPrefDictionary", # leaks in Internet Config. code, rdar://problem/4449794
1066 "NSHTTPURLProtocol setResponseHeader:", # leak in multipart/mixed-replace handling in Foundation, no Radar, but fixed in Leopard
1067 "NSURLCache cachedResponseForRequest", # leak in CFURL cache, rdar://problem/4768430
1068 "PCFragPrepareClosureFromFile", # leak in Code Fragment Manager, rdar://problem/3426998
1069 "WebCore::Selection::toRange", # bug in 'leaks', rdar://problem/4967949
1070 "WebCore::SubresourceLoader::create", # bug in 'leaks', rdar://problem/4985806
1071 "_CFPreferencesDomainDeepCopyDictionary", # leak in CFPreferences, rdar://problem/4220786
1072 "_objc_msgForward", # leak in NSSpellChecker, rdar://problem/4965278
1073 "gldGetString", # leak in OpenGL, rdar://problem/5013699
1074 "_setDefaultUserInfoFromURL", # leak in NSHTTPAuthenticator, rdar://problem/5546453
1075 "SSLHandshake", # leak in SSL, rdar://problem/5546440
1076 "SecCertificateCreateFromData", # leak in SSL code, rdar://problem/4464397
1078 push @typesToExclude, (
1079 "THRD", # bug in 'leaks', rdar://problem/3387783
1080 "DRHT", # ditto (endian little hate i)
1085 # Leak list for the version of Leopard used on the build bot.
1086 push @callStacksToExclude, (
1087 "CFHTTPMessageAppendBytes", # leak in CFNetwork, rdar://problem/5435912
1088 "sendDidReceiveDataCallback", # leak in CFNetwork, rdar://problem/5441619
1089 "_CFHTTPReadStreamReadMark", # leak in CFNetwork, rdar://problem/5441468
1090 "httpProtocolStart", # leak in CFNetwork, rdar://problem/5468837
1091 "_CFURLConnectionSendCallbacks", # leak in CFNetwork, rdar://problem/5441600
1092 "DispatchQTMsg", # leak in QuickTime, PPC only, rdar://problem/5667132
1093 "QTMovieContentView createVisualContext", # leak in QuickTime, PPC only, rdar://problem/5667132
1094 "_CopyArchitecturesForJVMVersion", # leak in Java, rdar://problem/5910823
1098 if (isSnowLeopard()) {
1099 push @callStacksToExclude, (
1100 "readMakerNoteProps", # <rdar://problem/7156432> leak in ImageIO
1101 "QTKitMovieControllerView completeUISetup", # <rdar://problem/7155156> leak in QTKit
1105 my $leaksTool = sourceDir() . "/WebKitTools/Scripts/run-leaks";
1106 my $excludeString = "--exclude-callstack '" . (join "' --exclude-callstack '", @callStacksToExclude) . "'";
1107 $excludeString .= " --exclude-type '" . (join "' --exclude-type '", @typesToExclude) . "'" if @typesToExclude;
1109 print " ? checking for leaks in $dumpToolName\n";
1110 my $leaksOutput = `$leaksTool $excludeString $dumpToolPID`;
1111 my ($count, $bytes) = $leaksOutput =~ /Process $dumpToolPID: (\d+) leaks? for (\d+) total/;
1112 my ($excluded) = $leaksOutput =~ /(\d+) leaks? excluded/;
1114 my $adjustedCount = $count;
1115 $adjustedCount -= $excluded if $excluded;
1117 if (!$adjustedCount) {
1118 print " - no leaks found\n";
1119 unlink $leaksFilePath;
1122 my $dir = $leaksFilePath;
1123 $dir =~ s|/[^/]+$|| or die;
1127 print " + $adjustedCount leaks ($bytes bytes including $excluded excluded leaks) were found, details in $leaksFilePath\n";
1129 print " + $count leaks ($bytes bytes) were found, details in $leaksFilePath\n";
1132 writeToFile($leaksFilePath, $leaksOutput);
1134 push @leaksFilenames, $leaksFilePath;
1137 return $adjustedCount;
1142 my ($filePath, $contents) = @_;
1143 open NEWFILE, ">", "$filePath" or die "Could not create $filePath. $!\n";
1144 print NEWFILE $contents;
1148 # Break up a path into the directory (with slash) and base name.
1153 my $pathSeparator = "/";
1154 my $dirname = dirname($path) . $pathSeparator;
1155 $dirname = "" if $dirname eq "." . $pathSeparator;
1157 return ($dirname, basename($path));
1160 # Sort first by directory, then by file, so all paths in one directory are grouped
1161 # rather than being interspersed with items from subdirectories.
1162 # Use numericcmp to sort directory and filenames to make order logical.
1165 my ($patha, $pathb) = @_;
1167 my ($dira, $namea) = splitpath($patha);
1168 my ($dirb, $nameb) = splitpath($pathb);
1170 return numericcmp($dira, $dirb) if $dira ne $dirb;
1171 return numericcmp($namea, $nameb);
1174 # Sort numeric parts of strings as numbers, other parts as strings.
1175 # Makes 1.33 come after 1.3, which is cool.
1180 my @a = split /(\d+)/, $aa;
1181 my @b = split /(\d+)/, $bb;
1183 # Compare one chunk at a time.
1184 # Each chunk is either all numeric digits, or all not numeric digits.
1189 # Use numeric comparison if chunks are non-equal numbers.
1190 return $a <=> $b if $a =~ /^\d/ && $b =~ /^\d/ && $a != $b;
1192 # Use string comparison if chunks are any other kind of non-equal string.
1193 return $a cmp $b if $a ne $b;
1196 # One of the two is now empty; compare lengths for result in this case.
1200 # Sort slowest tests first.
1203 my ($testa, $testb) = @_;
1205 my $dura = $durations{$testa};
1206 my $durb = $durations{$testb};
1207 return $durb <=> $dura if $dura != $durb;
1208 return pathcmp($testa, $testb);
1211 sub launchWithCurrentEnv(@)
1215 # Dump the current environment as perl code and then put it in quotes so it is one parameter.
1216 my $environmentDumper = Data::Dumper->new([\%ENV], [qw(*ENV)]);
1217 $environmentDumper->Indent(0);
1218 $environmentDumper->Purity(1);
1219 my $allEnvVars = $environmentDumper->Dump();
1220 unshift @args, "\"$allEnvVars\"";
1222 my $execScript = File::Spec->catfile(sourceDir(), qw(WebKitTools Scripts execAppWithEnv));
1223 unshift @args, $execScript;
1227 sub resolveAndMakeTestResultsDirectory()
1229 my $absTestResultsDirectory = File::Spec->rel2abs(glob $testResultsDirectory);
1230 mkpath $absTestResultsDirectory;
1231 return $absTestResultsDirectory;
1236 return if $isDiffToolOpen;
1237 return if !$pixelTests;
1240 $ENV{MallocStackLogging} = 1 if $shouldCheckLeaks;
1241 $imageDiffToolPID = open2(\*DIFFIN, \*DIFFOUT, $imageDiffTool, launchWithCurrentEnv(@diffToolArgs)) or die "unable to open $imageDiffTool\n";
1242 $ENV{MallocStackLogging} = 0 if $shouldCheckLeaks;
1243 $isDiffToolOpen = 1;
1248 return if $isDumpToolOpen;
1250 # Save environment variables required for the linux environment.
1251 my $homeDir = $ENV{'HOME'};
1252 my $libraryPath = $ENV{'LD_LIBRARY_PATH'};
1253 my $dyldLibraryPath = $ENV{'DYLD_LIBRARY_PATH'};
1254 my $dbusAddress = $ENV{'DBUS_SESSION_BUS_ADDRESS'};
1255 my $display = $ENV{'DISPLAY'};
1256 my $xauthority = $ENV{'XAUTHORITY'};
1257 my $testfonts = $ENV{'WEBKIT_TESTFONTS'};
1259 my $homeDrive = $ENV{'HOMEDRIVE'};
1260 my $homePath = $ENV{'HOMEPATH'};
1263 if (isQt() || isGtk()) {
1264 if (defined $display) {
1265 $ENV{DISPLAY} = $display;
1267 $ENV{DISPLAY} = ":1";
1269 if (defined $xauthority) {
1270 $ENV{XAUTHORITY} = $xauthority;
1272 $ENV{'WEBKIT_TESTFONTS'} = $testfonts if defined($testfonts);
1273 $ENV{HOME} = $homeDir;
1274 if (defined $libraryPath) {
1275 $ENV{LD_LIBRARY_PATH} = $libraryPath;
1277 if (defined $dyldLibraryPath) {
1278 $ENV{DYLD_LIBRARY_PATH} = $dyldLibraryPath;
1280 if (defined $dbusAddress) {
1281 $ENV{DBUS_SESSION_BUS_ADDRESS} = $dbusAddress;
1285 $ENV{QTWEBKIT_PLUGIN_PATH} = productDir() . "/lib/plugins";
1287 $ENV{DYLD_FRAMEWORK_PATH} = $productDir;
1288 $ENV{XML_CATALOG_FILES} = ""; # work around missing /etc/catalog <rdar://problem/4292995>
1289 $ENV{DYLD_INSERT_LIBRARIES} = "/usr/lib/libgmalloc.dylib" if $guardMalloc;
1292 $ENV{HOMEDRIVE} = $homeDrive;
1293 $ENV{HOMEPATH} = $homePath;
1295 $ENV{WEBKIT_TESTFONTS} = $testfonts;
1297 setPathForRunningWebKitApp(\%ENV) if isCygwin();
1300 my @args = ($dumpTool, @toolArgs);
1301 if (isAppleMacWebKit() and !isTiger()) {
1302 unshift @args, "arch", "-" . architecture();
1306 unshift @args, "valgrind", "--suppressions=$platformBaseDirectory/qt/SuppressedValgrindErrors";
1309 $ENV{MallocStackLogging} = 1 if $shouldCheckLeaks;
1310 $dumpToolPID = open3(\*OUT, \*IN, \*ERROR, launchWithCurrentEnv(@args)) or die "Failed to start tool: $dumpTool\n";
1311 $ENV{MallocStackLogging} = 0 if $shouldCheckLeaks;
1312 $isDumpToolOpen = 1;
1313 $dumpToolCrashed = 0;
1318 return if !$isDumpToolOpen;
1322 waitpid $dumpToolPID, 0;
1324 # check for WebCore counter leaks.
1325 if ($shouldCheckLeaks) {
1331 $isDumpToolOpen = 0;
1334 sub dumpToolDidCrash()
1336 return 1 if $dumpToolCrashed;
1337 return 0 unless $isDumpToolOpen;
1339 my $pid = waitpid(-1, WNOHANG);
1340 return 1 if ($pid == $dumpToolPID);
1342 # On Mac OS X, crashing may be significantly delayed by crash reporter.
1343 return 0 unless isAppleMacWebKit();
1345 return DumpRenderTreeSupport::processIsCrashing($dumpToolPID);
1348 sub openHTTPDIfNeeded()
1350 return if $isHttpdOpen;
1352 mkdir "/tmp/WebKit";
1354 if (-f "/tmp/WebKit/httpd.pid") {
1355 my $oldPid = `cat /tmp/WebKit/httpd.pid`;
1357 if (0 != kill 0, $oldPid) {
1358 print "\nhttpd is already running: pid $oldPid, killing...\n";
1361 my $retryCount = 20;
1362 while ((0 != kill 0, $oldPid) && $retryCount) {
1367 die "Timed out waiting for httpd to quit" unless $retryCount;
1371 my $httpdPath = "/usr/sbin/httpd";
1374 my $windowsConfDirectory = "$testDirectory/http/conf/";
1375 unless (-x "/usr/lib/apache/libphp4.dll") {
1376 copy("$windowsConfDirectory/libphp4.dll", "/usr/lib/apache/libphp4.dll");
1377 chmod(0755, "/usr/lib/apache/libphp4.dll");
1379 $httpdConfig = "$windowsConfDirectory/cygwin-httpd.conf";
1380 } elsif (isDebianBased()) {
1381 $httpdPath = "/usr/sbin/apache2";
1382 $httpdConfig = "$testDirectory/http/conf/apache2-debian-httpd.conf";
1383 } elsif (isFedoraBased()) {
1384 $httpdPath = "/usr/sbin/httpd";
1385 $httpdConfig = "$testDirectory/http/conf/fedora-httpd.conf";
1387 $httpdConfig = "$testDirectory/http/conf/httpd.conf";
1388 $httpdConfig = "$testDirectory/http/conf/apache2-httpd.conf" if `$httpdPath -v` =~ m|Apache/2|;
1390 my $documentRoot = "$testDirectory/http/tests";
1391 my $jsTestResourcesDirectory = $testDirectory . "/fast/js/resources";
1392 my $typesConfig = "$testDirectory/http/conf/mime.types";
1393 my $listen = "127.0.0.1:$httpdPort";
1394 my $absTestResultsDirectory = resolveAndMakeTestResultsDirectory();
1395 my $sslCertificate = "$testDirectory/http/conf/webkit-httpd.pem";
1398 "-f", "$httpdConfig",
1399 "-C", "DocumentRoot \"$documentRoot\"",
1400 # Setup a link to where the js test templates are stored, use -c so that mod_alias will already be laoded.
1401 "-c", "Alias /js-test-resources \"$jsTestResourcesDirectory\"",
1402 "-C", "Listen $listen",
1403 "-c", "TypesConfig \"$typesConfig\"",
1404 "-c", "CustomLog \"$absTestResultsDirectory/access_log.txt\" common",
1405 "-c", "ErrorLog \"$absTestResultsDirectory/error_log.txt\"",
1406 # Apache wouldn't run CGIs with permissions==700 otherwise
1407 "-c", "User \"#$<\""
1410 # FIXME: Enable this on Windows once <rdar://problem/5345985> is fixed
1411 # The version of Apache we use with Cygwin does not support SSL
1412 push(@args, "-c", "SSLCertificateFile \"$sslCertificate\"") unless isCygwin();
1414 open2(\*HTTPDIN, \*HTTPDOUT, $httpdPath, @args);
1416 my $retryCount = 20;
1417 while (system("/usr/bin/curl -q --silent --stderr - --output " . File::Spec->devnull() . " $listen") && $retryCount) {
1422 die "Timed out waiting for httpd to start" unless $retryCount;
1429 return if !$isHttpdOpen;
1434 kill 15, `cat /tmp/WebKit/httpd.pid` if -f "/tmp/WebKit/httpd.pid";
1439 sub openWebSocketServerIfNeeded()
1441 return 1 if $isWebSocketServerOpen;
1442 return 0 if $failedToStartWebSocketServer;
1444 my $webSocketServerPath = "/usr/bin/python";
1445 my $webSocketPythonPath = "WebKitTools/pywebsocket";
1446 my $webSocketHandlerDir = "$testDirectory";
1447 my $webSocketHandlerScanDir = "$testDirectory/websocket/tests";
1448 my $sslCertificate = "$testDirectory/http/conf/webkit-httpd.pem";
1449 my $absTestResultsDirectory = resolveAndMakeTestResultsDirectory();
1450 my $logFile = "$absTestResultsDirectory/pywebsocket_log.txt";
1453 "WebKitTools/pywebsocket/mod_pywebsocket/standalone.py",
1454 "-p", "$webSocketPort",
1455 "-d", "$webSocketHandlerDir",
1456 "-s", "$webSocketHandlerScanDir",
1459 # wss is disabled until all platforms support pyOpenSSL.
1460 # my @argsSecure = (
1461 # "WebKitTools/pywebsocket/mod_pywebsocket/standalone.py",
1462 # "-p", "$webSocketSecurePort",
1463 # "-d", "$webSocketHandlerDir",
1465 # "-k", "$sslCertificate",
1466 # "-c", "$sslCertificate",
1469 $ENV{"PYTHONPATH"} = $webSocketPythonPath;
1470 $webSocketServerPID = open3(\*WEBSOCKETSERVER_IN, \*WEBSOCKETSERVER_OUT, \*WEBSOCKETSERVER_ERR, $webSocketServerPath, @args);
1471 # wss is disabled until all platforms support pyOpenSSL.
1472 # $webSocketSecureServerPID = open3(\*WEBSOCKETSECURESERVER_IN, \*WEBSOCKETSECURESERVER_OUT, \*WEBSOCKETSECURESERVER_ERR, $webSocketServerPath, @argsSecure);
1473 # my @listen = ("http://127.0.0.1:$webSocketPort", "https://127.0.0.1:$webSocketSecurePort");
1474 my @listen = ("http://127.0.0.1:$webSocketPort");
1475 for (my $i = 0; $i < @listen; $i++) {
1476 my $retryCount = 10;
1477 while (system("/usr/bin/curl -k -q --silent --stderr - --output /dev/null $listen[$i]") && $retryCount) {
1481 unless ($retryCount) {
1482 print STDERR "Timed out waiting for WebSocketServer to start.\n";
1483 $failedToStartWebSocketServer = 1;
1488 $isWebSocketServerOpen = 1;
1492 sub closeWebSocketServer()
1494 return if !$isWebSocketServerOpen;
1496 close WEBSOCKETSERVER_IN;
1497 close WEBSOCKETSERVER_OUT;
1498 close WEBSOCKETSERVER_ERR;
1499 kill 15, $webSocketServerPID;
1501 # wss is disabled until all platforms support pyOpenSSL.
1502 # close WEBSOCKETSECURESERVER_IN;
1503 # close WEBSOCKETSECURESERVER_OUT;
1504 # close WEBSOCKETSECURESERVER_ERR;
1505 # kill 15, $webSocketSecureServerPID;
1507 $isWebSocketServerOpen = 0;
1510 sub fileNameWithNumber($$)
1512 my ($base, $number) = @_;
1513 return "$base$number" if ($number > 1);
1517 sub processIgnoreTests($$)
1519 my @ignoreList = split(/\s*,\s*/, shift);
1520 my $listName = shift;
1522 my $disabledSuffix = "-disabled";
1524 my $addIgnoredDirectories = sub {
1525 return () if exists $ignoredLocalDirectories{basename($File::Find::dir)};
1526 $ignoredDirectories{File::Spec->abs2rel($File::Find::dir, $testDirectory)} = 1;
1529 foreach my $item (@ignoreList) {
1530 my $path = catfile($testDirectory, $item);
1532 $ignoredDirectories{$item} = 1;
1533 find({ preprocess => $addIgnoredDirectories, wanted => sub {} }, $path);
1536 $ignoredFiles{$item} = 1;
1537 } elsif (-f $path . $disabledSuffix) {
1538 # The test is disabled, so do nothing.
1540 print "$listName list contained '$item', but no file of that name could be found\n";
1545 sub stripExtension($)
1549 $test =~ s/\.[a-zA-Z]+$//;
1553 sub isTextOnlyTest($)
1557 if ($actual =~ /^layer at/ms) {
1565 sub expectedDirectoryForTest($;$;$)
1567 my ($base, $isText, $expectedExtension) = @_;
1569 my @directories = @platformResultHierarchy;
1570 push @directories, map { catdir($platformBaseDirectory, $_) } qw(mac-snowleopard mac) if isCygwin();
1571 push @directories, $expectedDirectory;
1573 # If we already have expected results, just return their location.
1574 foreach my $directory (@directories) {
1575 return $directory if (-f "$directory/$base-$expectedTag.$expectedExtension");
1578 # For cross-platform tests, text-only results should go in the cross-platform directory,
1579 # while render tree dumps should go in the least-specific platform directory.
1580 return $isText ? $expectedDirectory : $platformResultHierarchy[$#platformResultHierarchy];
1583 sub countFinishedTest($$$$)
1585 my ($test, $base, $result, $isText) = @_;
1587 if (($count + 1) % $testsPerDumpTool == 0 || $count == $#tests) {
1588 if ($shouldCheckLeaks) {
1590 if ($testsPerDumpTool == 1) {
1591 $fileName = "$testResultsDirectory/$base-leaks.txt";
1593 $fileName = "$testResultsDirectory/" . fileNameWithNumber($dumpToolName, $leaksOutputFileNumber) . "-leaks.txt";
1595 my $leakCount = countAndPrintLeaks($dumpToolName, $dumpToolPID, $fileName);
1596 $totalLeaks += $leakCount;
1597 $leaksOutputFileNumber++ if ($leakCount);
1605 push @{$tests{$result}}, $test;
1608 sub testCrashedOrTimedOut($$$$$)
1610 my ($test, $base, $didCrash, $actual, $error) = @_;
1612 printFailureMessageForTest($test, $didCrash ? "crashed" : "timed out");
1614 sampleDumpTool() unless $didCrash;
1616 my $dir = "$testResultsDirectory/$base";
1617 $dir =~ s|/([^/]+)$|| or die "Failed to find test name from base\n";
1620 deleteExpectedAndActualResults($base);
1622 if (defined($error) && length($error)) {
1623 writeToFile("$testResultsDirectory/$base-$errorTag.txt", $error);
1626 recordActualResultsAndDiff($base, $actual);
1628 kill 9, $dumpToolPID unless $didCrash;
1633 sub printFailureMessageForTest($$)
1635 my ($test, $description) = @_;
1638 print "\n" unless $atLineStart;
1641 print "$description\n";
1647 sub openCygpathIfNeeded($)
1651 return unless isCygwin();
1652 return $cygpaths{$options} if $cygpaths{$options} && $cygpaths{$options}->{"open"};
1654 local (*CYGPATHIN, *CYGPATHOUT);
1655 my $pid = open2(\*CYGPATHIN, \*CYGPATHOUT, "cygpath -f - $options");
1659 "out" => *CYGPATHOUT,
1663 $cygpaths{$options} = $cygpath;
1670 return unless isCygwin();
1672 foreach my $cygpath (values(%cygpaths)) {
1673 close $cygpath->{"in"};
1674 close $cygpath->{"out"};
1675 waitpid($cygpath->{"pid"}, 0);
1676 $cygpath->{"open"} = 0;
1681 sub convertPathUsingCygpath($$)
1683 my ($path, $options) = @_;
1685 my $cygpath = openCygpathIfNeeded($options);
1686 local *inFH = $cygpath->{"in"};
1687 local *outFH = $cygpath->{"out"};
1688 print outFH $path . "\n";
1689 chomp(my $convertedPath = <inFH>);
1690 return $convertedPath;
1693 sub toWindowsPath($)
1696 return unless isCygwin();
1698 return convertPathUsingCygpath($path, "-w");
1705 if ($useRemoteLinksToTests) {
1706 my $relativePath = File::Spec->abs2rel($path, $testDirectory);
1708 # If the file is below the test directory then convert it into a link to the file in SVN
1709 if ($relativePath !~ /^\.\.\//) {
1710 my $revision = svnRevisionForDirectory($testDirectory);
1711 my $svnPath = pathRelativeToSVNRepositoryRootForPath($path);
1712 return "http://trac.webkit.org/export/$revision/$svnPath";
1716 return $path unless isCygwin();
1718 return "file:///" . convertPathUsingCygpath($path, "-m");
1721 sub validateSkippedArg($$;$)
1723 my ($option, $value, $value2) = @_;
1724 my %validSkippedValues = map { $_ => 1 } qw(default ignore only);
1725 $value = lc($value);
1726 die "Invalid argument '" . $value . "' for option $option" unless $validSkippedValues{$value};
1727 $treatSkipped = $value;
1730 sub htmlForResultsSection(\@$&)
1732 my ($tests, $description, $linkGetter) = @_;
1735 return join("\n", @html) unless @{$tests};
1737 push @html, "<p>$description:</p>";
1738 push @html, "<table>";
1739 foreach my $test (@{$tests}) {
1741 push @html, "<td><a href=\"" . toURL("$testDirectory/$test") . "\">$test</a></td>";
1742 foreach my $link (@{&{$linkGetter}($test)}) {
1743 push @html, "<td><a href=\"$link->{href}\">$link->{text}</a></td>";
1745 push @html, "</tr>";
1747 push @html, "</table>";
1749 return join("\n", @html);
1752 sub linksForExpectedAndActualResults($)
1758 return \@links unless -s "$testResultsDirectory/$base-$diffsTag.txt";
1760 my $expectedResultPath = $expectedResultPaths{$base};
1761 my ($expectedResultFileName, $expectedResultsDirectory, $expectedResultExtension) = fileparse($expectedResultPath, qr{\.[^.]+$});
1763 push @links, { href => "$base-$expectedTag$expectedResultExtension", text => "expected" };
1764 push @links, { href => "$base-$actualTag$expectedResultExtension", text => "actual" };
1765 push @links, { href => "$base-$diffsTag.txt", text => "diff" };
1766 push @links, { href => "$base-$prettyDiffTag.html", text => "pretty diff" };
1771 sub linksForMismatchTest
1777 my $base = stripExtension($test);
1779 push @links, @{linksForExpectedAndActualResults($base)};
1780 return \@links unless $pixelTests && $imagesPresent{$base};
1782 push @links, { href => "$base-$expectedTag.png", text => "expected image" };
1783 push @links, { href => "$base-$diffsTag.html", text => "image diffs" };
1784 push @links, { href => "$base-$diffsTag.png", text => "$imageDifferences{$base}%" };
1789 sub linksForErrorTest
1795 my $base = stripExtension($test);
1797 push @links, @{linksForExpectedAndActualResults($base)};
1798 push @links, { href => "$base-$errorTag.txt", text => "stderr" };
1809 my $base = stripExtension($test);
1811 my $expectedResultPath = $expectedResultPaths{$base};
1812 my ($expectedResultFileName, $expectedResultsDirectory, $expectedResultExtension) = fileparse($expectedResultPath, qr{\.[^.]+$});
1814 push @links, { href => "$base-$actualTag$expectedResultExtension", text => "result" };
1815 if ($pixelTests && $imagesPresent{$base}) {
1816 push @links, { href => "$base-$expectedTag.png", text => "image" };
1822 sub deleteExpectedAndActualResults($)
1826 unlink "$testResultsDirectory/$base-$actualTag.txt";
1827 unlink "$testResultsDirectory/$base-$diffsTag.txt";
1828 unlink "$testResultsDirectory/$base-$errorTag.txt";
1831 sub recordActualResultsAndDiff($$)
1833 my ($base, $actualResults) = @_;
1835 return unless defined($actualResults) && length($actualResults);
1837 my $expectedResultPath = $expectedResultPaths{$base};
1838 my ($expectedResultFileNameMinusExtension, $expectedResultDirectoryPath, $expectedResultExtension) = fileparse($expectedResultPath, qr{\.[^.]+$});
1839 my $actualResultsPath = "$testResultsDirectory/$base-$actualTag$expectedResultExtension";
1840 my $copiedExpectedResultsPath = "$testResultsDirectory/$base-$expectedTag$expectedResultExtension";
1842 mkpath(dirname($actualResultsPath));
1843 writeToFile("$actualResultsPath", $actualResults);
1845 if (-f $expectedResultPath) {
1846 copy("$expectedResultPath", "$copiedExpectedResultsPath");
1848 open EMPTY, ">$copiedExpectedResultsPath";
1852 my $diffOuputBasePath = "$testResultsDirectory/$base";
1853 my $diffOutputPath = "$diffOuputBasePath-$diffsTag.txt";
1854 system "diff -u \"$copiedExpectedResultsPath\" \"$actualResultsPath\" > \"$diffOutputPath\"";
1856 my $prettyDiffOutputPath = "$diffOuputBasePath-$prettyDiffTag.html";
1857 my $prettyPatchPath = "BugsSite/PrettyPatch/";
1858 my $prettifyPath = "$prettyPatchPath/prettify.rb";
1859 system "ruby -I \"$prettyPatchPath\" \"$prettifyPath\" \"$diffOutputPath\" > \"$prettyDiffOutputPath\"";
1862 sub buildPlatformResultHierarchy()
1864 mkpath($platformTestDirectory) if ($platform eq "undefined" && !-d "$platformTestDirectory");
1867 if ($platform =~ /^mac-/) {
1869 for ($i = 0; $i < @macPlatforms; $i++) {
1870 last if $macPlatforms[$i] eq $platform;
1872 for (; $i < @macPlatforms; $i++) {
1873 push @platforms, $macPlatforms[$i];
1875 } elsif ($platform =~ /^qt-/) {
1876 push @platforms, $platform;
1877 push @platforms, "qt";
1879 @platforms = $platform;
1883 for (my $i = 0; $i < @platforms; $i++) {
1884 my $scoped = catdir($platformBaseDirectory, $platforms[$i]);
1885 push(@hierarchy, $scoped) if (-d $scoped);
1891 sub buildPlatformTestHierarchy(@)
1893 my (@platformHierarchy) = @_;
1894 return @platformHierarchy if (@platformHierarchy < 2);
1896 return ($platformHierarchy[0], $platformHierarchy[$#platformHierarchy]);
1899 sub epiloguesAndPrologues($$)
1901 my ($lastDirectory, $directory) = @_;
1902 my @lastComponents = split('/', $lastDirectory);
1903 my @components = split('/', $directory);
1905 while (@lastComponents) {
1906 if (!defined($components[0]) || $lastComponents[0] ne $components[0]) {
1910 shift @lastComponents;
1914 my $leaving = $lastDirectory;
1915 foreach (@lastComponents) {
1916 my $epilogue = $leaving . "/resources/run-webkit-tests-epilogue.html";
1917 foreach (@platformResultHierarchy) {
1918 push @result, catdir($_, $epilogue) if (stat(catdir($_, $epilogue)));
1920 push @result, catdir($testDirectory, $epilogue) if (stat(catdir($testDirectory, $epilogue)));
1921 $leaving =~ s|(^\|/)[^/]+$||;
1924 my $entering = $leaving;
1925 foreach (@components) {
1926 $entering .= '/' . $_;
1927 my $prologue = $entering . "/resources/run-webkit-tests-prologue.html";
1928 push @result, catdir($testDirectory, $prologue) if (stat(catdir($testDirectory, $prologue)));
1929 foreach (reverse @platformResultHierarchy) {
1930 push @result, catdir($_, $prologue) if (stat(catdir($_, $prologue)));
1936 sub parseLeaksandPrintUniqueLeaks()
1938 return unless @leaksFilenames;
1940 my $mergedFilenames = join " ", @leaksFilenames;
1941 my $parseMallocHistoryTool = sourceDir() . "/WebKitTools/Scripts/parse-malloc-history";
1943 open MERGED_LEAKS, "cat $mergedFilenames | $parseMallocHistoryTool --merge-depth $mergeDepth - |" ;
1944 my @leakLines = <MERGED_LEAKS>;
1947 my $uniqueLeakCount = 0;
1949 foreach my $line (@leakLines) {
1950 ++$uniqueLeakCount if ($line =~ /^(\d*)\scalls/);
1951 $totalBytes = $1 if $line =~ /^total\:\s(.*)\s\(/;
1954 print "\nWARNING: $totalLeaks total leaks found for a total of $totalBytes!\n";
1955 print "WARNING: $uniqueLeakCount unique leaks found!\n";
1956 print "See above for individual leaks results.\n" if ($leaksOutputFileNumber > 2);
1960 sub extensionForMimeType($)
1962 my ($mimeType) = @_;
1964 if ($mimeType eq "application/x-webarchive") {
1965 return "webarchive";
1966 } elsif ($mimeType eq "application/pdf") {
1972 # Read up to the first #EOF (the content block of the test), or until detecting crashes or timeouts.
1973 sub readFromDumpToolWithTimer(**)
1975 my ($fhIn, $fhError) = @_;
1977 setFileHandleNonBlocking($fhIn, 1);
1978 setFileHandleNonBlocking($fhError, 1);
1980 my $maximumSecondsWithoutOutput = $timeoutSeconds;
1981 $maximumSecondsWithoutOutput *= 10 if $guardMalloc;
1982 my $microsecondsToWaitBeforeReadingAgain = 1000;
1984 my $timeOfLastSuccessfulRead = time;
1988 my $status = "success";
1989 my $mimeType = "text/plain";
1990 # We don't have a very good way to know when the "headers" stop
1991 # and the content starts, so we use this as a hack:
1992 my $haveSeenContentType = 0;
1993 my $haveSeenEofIn = 0;
1994 my $haveSeenEofError = 0;
1997 if (time - $timeOfLastSuccessfulRead > $maximumSecondsWithoutOutput) {
1998 $status = dumpToolDidCrash() ? "crashed" : "timedOut";
2002 # Once we've seen the EOF, we must not read anymore.
2003 my $lineIn = readline($fhIn) unless $haveSeenEofIn;
2004 my $lineError = readline($fhError) unless $haveSeenEofError;
2005 if (!defined($lineIn) && !defined($lineError)) {
2006 last if ($haveSeenEofIn && $haveSeenEofError);
2009 $status = "crashed";
2014 usleep($microsecondsToWaitBeforeReadingAgain);
2018 $timeOfLastSuccessfulRead = time;
2020 if (defined($lineIn)) {
2021 if (!$haveSeenContentType && $lineIn =~ /^Content-Type: (\S+)$/) {
2023 $haveSeenContentType = 1;
2024 } elsif ($lineIn =~ /#EOF/) {
2027 push @output, $lineIn;
2030 if (defined($lineError)) {
2031 if ($lineError =~ /#EOF/) {
2032 $haveSeenEofError = 1;
2034 push @error, $lineError;
2039 setFileHandleNonBlocking($fhIn, 0);
2040 setFileHandleNonBlocking($fhError, 0);
2042 output => join("", @output),
2043 error => join("", @error),
2045 mimeType => $mimeType,
2046 extension => extensionForMimeType($mimeType)
2050 sub setFileHandleNonBlocking(*$)
2052 my ($fh, $nonBlocking) = @_;
2054 my $flags = fcntl($fh, F_GETFL, 0) or die "Couldn't get filehandle flags";
2057 $flags |= O_NONBLOCK;
2059 $flags &= ~O_NONBLOCK;
2062 fcntl($fh, F_SETFL, $flags) or die "Couldn't set filehandle flags";
2067 sub sampleDumpTool()
2069 return unless isAppleMacWebKit();
2070 return unless $runSample;
2072 my $outputDirectory = "$ENV{HOME}/Library/Logs/DumpRenderTree";
2073 -d $outputDirectory or mkdir $outputDirectory;
2075 my $outputFile = "$outputDirectory/HangReport.txt";
2076 system "/usr/bin/sample", $dumpToolPID, qw(10 10 -file), $outputFile;
2079 sub stripMetrics($$)
2081 my ($actual, $expected) = @_;
2083 foreach my $result ($actual, $expected) {
2084 $result =~ s/at \(-?[0-9]+,-?[0-9]+\) *//g;
2085 $result =~ s/size -?[0-9]+x-?[0-9]+ *//g;
2086 $result =~ s/text run width -?[0-9]+: //g;
2087 $result =~ s/text run width -?[0-9]+ [a-zA-Z ]+: //g;
2088 $result =~ s/RenderButton {BUTTON} .*/RenderButton {BUTTON}/g;
2089 $result =~ s/RenderImage {INPUT} .*/RenderImage {INPUT}/g;
2090 $result =~ s/RenderBlock {INPUT} .*/RenderBlock {INPUT}/g;
2091 $result =~ s/RenderTextControl {INPUT} .*/RenderTextControl {INPUT}/g;
2092 $result =~ s/\([0-9]+px/px/g;
2093 $result =~ s/ *" *\n +" */ /g;
2094 $result =~ s/" +$/"/g;
2096 $result =~ s/- /-/g;
2097 $result =~ s/\n( *)"\s+/\n$1"/g;
2098 $result =~ s/\s+"\n/"\n/g;
2099 $result =~ s/scrollWidth [0-9]+/scrollWidth/g;
2100 $result =~ s/scrollHeight [0-9]+/scrollHeight/g;
2103 return ($actual, $expected);
2106 sub fileShouldBeIgnored
2108 my ($filePath) = @_;
2109 foreach my $ignoredDir (keys %ignoredDirectories) {
2110 if ($filePath =~ m/^$ignoredDir/) {
2117 sub readSkippedFiles($)
2119 my ($constraintPath) = @_;
2121 foreach my $level (@platformTestHierarchy) {
2122 if (open SKIPPED, "<", "$level/Skipped") {
2124 my ($dir, $name) = splitpath($level);
2125 print "Skipped tests in $name:\n";
2131 $skipped =~ s/^[ \n\r]+//;
2132 $skipped =~ s/[ \n\r]+$//;
2133 if ($skipped && $skipped !~ /^#/) {
2135 if (!fileShouldBeIgnored($skipped)) {
2136 if (!$constraintPath) {
2137 # Always add $skipped since no constraint path was specified on the command line.
2138 push(@ARGV, $skipped);
2139 } elsif ($skipped =~ /^($constraintPath)/) {
2140 # Add $skipped only if it matches the current path constraint, e.g.,
2141 # "--skipped=only dir1" with "dir1/file1.html" on the skipped list.
2142 push(@ARGV, $skipped);
2143 } elsif ($constraintPath =~ /^($skipped)/) {
2144 # Add current path constraint if it is more specific than the skip list entry,
2145 # e.g., "--skipped=only dir1/dir2/dir3" with "dir1" on the skipped list.
2146 push(@ARGV, $constraintPath);
2148 } elsif ($verbose) {
2149 print " $skipped\n";
2153 print " $skipped\n";
2155 processIgnoreTests($skipped, "Skipped");
2168 return () if exists $ignoredLocalDirectories{basename($File::Find::dir)};
2169 return () if exists $ignoredDirectories{File::Spec->abs2rel($File::Find::dir, $testDirectory)};
2176 if ($filename =~ /\.([^.]+)$/) {
2177 if (exists $supportedFileExtensions{$1}) {
2178 my $path = File::Spec->abs2rel(catfile($File::Find::dir, $filename), $testDirectory);
2179 push @testsToRun, $path if !exists $ignoredFiles{$path};
2188 for my $test (@ARGV) {
2189 $test =~ s/^($layoutTestsName|$testDirectory)\///;
2190 my $fullPath = catfile($testDirectory, $test);
2191 if (file_name_is_absolute($test)) {
2192 print "can't run test $test outside $testDirectory\n";
2193 } elsif (-f $fullPath) {
2194 my ($filename, $pathname, $fileExtension) = fileparse($test, qr{\.[^.]+$});
2195 if (!exists $supportedFileExtensions{substr($fileExtension, 1)}) {
2196 print "test $test does not have a supported extension\n";
2197 } elsif ($testHTTP || $pathname !~ /^http\//) {
2198 push @testsToRun, $test;
2200 } elsif (-d $fullPath) {
2201 find({ preprocess => \&directoryFilter, wanted => \&fileFilter }, $fullPath);
2202 for my $level (@platformTestHierarchy) {
2203 my $platformPath = catfile($level, $test);
2204 find({ preprocess => \&directoryFilter, wanted => \&fileFilter }, $platformPath) if (-d $platformPath);
2207 print "test $test not found\n";
2211 if (!scalar @ARGV) {
2212 find({ preprocess => \&directoryFilter, wanted => \&fileFilter }, $testDirectory);
2213 for my $level (@platformTestHierarchy) {
2214 find({ preprocess => \&directoryFilter, wanted => \&fileFilter }, $level);
2218 # Remove duplicate tests
2219 @testsToRun = keys %{{ map { $_ => 1 } @testsToRun }};
2221 @testsToRun = sort pathcmp @testsToRun;
2224 @testsToRun = reverse @testsToRun if $reverseTests;
2227 @testsToRun = shuffle(@testsToRun) if $randomizeTests;
2235 match => "succeeded",
2236 mismatch => "had incorrect layout",
2238 timedout => "timed out",
2240 error => "had stderr output"
2243 for my $type ("match", "mismatch", "new", "timedout", "crash", "error") {
2244 my $typeCount = $counts{$type};
2245 next unless $typeCount;
2246 my $typeText = $text{$type};
2248 if ($typeCount == 1) {
2249 $typeText =~ s/were/was/;
2250 $message = sprintf "1 test case (%d%%) %s\n", 1 * 100 / $count, $typeText;
2252 $message = sprintf "%d test cases (%d%%) %s\n", $typeCount, $typeCount * 100 / $count, $typeText;
2254 $message =~ s-\(0%\)-(<1%)-;