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;
70 use webkitperl::httpd;
75 sub buildPlatformResultHierarchy();
76 sub buildPlatformTestHierarchy(@);
79 sub closeWebSocketServer();
80 sub configureAndOpenHTTPDIfNeeded();
81 sub countAndPrintLeaks($$$);
82 sub countFinishedTest($$$$);
83 sub deleteExpectedAndActualResults($);
84 sub dumpToolDidCrash();
85 sub epiloguesAndPrologues($$);
86 sub expectedDirectoryForTest($;$;$);
87 sub fileNameWithNumber($$);
88 sub htmlForResultsSection(\@$&);
89 sub isTextOnlyTest($);
90 sub launchWithEnv(\@\%);
91 sub resolveAndMakeTestResultsDirectory();
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();
147 my $testResultsDirectory = File::Spec->catfile($tmpDir, "layout-test-results");
148 my $testsPerDumpTool = 1000;
150 # DumpRenderTree has an internal timeout of 15 seconds, so this must be > 15.
151 my $timeoutSeconds = 20;
153 my $treatSkipped = "default";
154 my $useRemoteLinksToTests = 0;
157 my $shouldWaitForHTTPD = 0;
161 # Default to --no-http for wx for now.
162 $testHTTP = 0 if (isWx());
164 my $expectedTag = "expected";
165 my $actualTag = "actual";
166 my $prettyDiffTag = "pretty-diff";
167 my $diffsTag = "diffs";
168 my $errorTag = "stderr";
170 my @macPlatforms = ("mac-tiger", "mac-leopard", "mac-snowleopard", "mac");
172 if (isAppleMacWebKit()) {
174 $platform = "mac-tiger";
176 } elsif (isLeopard()) {
177 $platform = "mac-leopard";
179 } elsif (isSnowLeopard()) {
180 $platform = "mac-snowleopard";
187 $platform = "qt-mac";
188 } elsif (isLinux()) {
189 $platform = "qt-linux";
190 } elsif (isWindows() || isCygwin()) {
191 $platform = "qt-win";
197 if (!$ENV{"WEBKIT_TESTFONTS"}) {
198 print "The WEBKIT_TESTFONTS environment variable is not defined.\n";
199 print "You must set it before running the tests.\n";
200 print "Use git to grab the actual fonts from http://gitorious.org/qtwebkit/testfonts\n";
205 } elsif (isCygwin()) {
209 if (!defined($platform)) {
210 print "WARNING: Your platform is not recognized. Any platform-specific results will be generated in platform/undefined.\n";
211 $platform = "undefined";
214 my $programName = basename($0);
215 my $launchSafariDefault = $launchSafari ? "launch" : "do not launch";
216 my $httpDefault = $testHTTP ? "run" : "do not run";
217 my $sampleDefault = $runSample ? "run" : "do not run";
220 Usage: $programName [options] [testdir|testpath ...]
221 --add-platform-exceptions Put new results for non-platform-specific failing tests into the platform-specific results directory
222 --complex-text Use the complex text code path for all text (Mac OS X and Windows only)
223 -c|--configuration config Set DumpRenderTree build configuration
224 -g|--guard-malloc Enable malloc guard
225 --exit-after-n-failures N Exit after the first N failures instead of running all tests
226 -h|--help Show this help message
227 --[no-]http Run (or do not run) http tests (default: $httpDefault)
228 --[no-]wait-for-httpd Wait for httpd if some other test session is using it already (same as WEBKIT_WAIT_FOR_HTTPD=1). (default: $shouldWaitForHTTPD)
229 -i|--ignore-tests Comma-separated list of directories or tests to ignore
230 --iterations n Number of times to run the set of tests (e.g. ABCABCABC)
231 --[no-]launch-safari Launch (or do not launch) Safari to display test results (default: $launchSafariDefault)
232 -l|--leaks Enable leaks checking
233 --[no-]new-test-results Generate results for new tests
234 --nthly n Restart DumpRenderTree every n tests (default: $testsPerDumpTool)
235 -p|--pixel-tests Enable pixel tests
236 --tolerance t Ignore image differences less than this percentage (default: $tolerance)
237 --platform Override the detected platform to use for tests and results (default: $platform)
238 --port Web server port to use with http tests
239 -q|--quiet Less verbose output
240 --reset-results Reset ALL results (including pixel tests if --pixel-tests is set)
241 -o|--results-directory Output results directory (default: $testResultsDirectory)
242 --random Run the tests in a random order
243 --repeat-each n Number of times to run each test (e.g. AAABBBCCC)
244 --reverse Run the tests in reverse alphabetical order
245 --root Path to root tools build
246 --[no-]sample-on-timeout Run sample on timeout (default: $sampleDefault) (Mac OS X only)
247 -1|--singly Isolate each test case run (implies --nthly 1 --verbose)
248 --skipped=[default|ignore|only] Specifies how to treat the Skipped file
249 default: Tests/directories listed in the Skipped file are not tested
250 ignore: The Skipped file is ignored
251 only: Only those tests/directories listed in the Skipped file will be run
252 --slowest Report the 10 slowest tests
253 --ignore-metrics Ignore metrics in tests
254 --[no-]strip-editing-callbacks Remove editing callbacks from expected results
255 -t|--threaded Run a concurrent JavaScript thead with each test
256 --timeout t Sets the number of seconds before a test times out (default: $timeoutSeconds)
257 --valgrind Run DumpRenderTree inside valgrind (Qt/Linux only)
258 -v|--verbose More verbose output (overrides --quiet)
259 -m|--merge-leak-depth arg Merges leak callStacks and prints the number of unique leaks beneath a callstack depth of arg. Defaults to 5.
260 --use-remote-links-to-tests Link to test files within the SVN repository in the results.
265 my $getOptionsResult = GetOptions(
266 'add-platform-exceptions' => \$addPlatformExceptions,
267 'complex-text' => \$complexText,
268 'exit-after-n-failures=i' => \$exitAfterNFailures,
269 'guard-malloc|g' => \$guardMalloc,
270 'help|h' => \$showHelp,
271 'http!' => \$testHTTP,
272 'wait-for-httpd!' => \$shouldWaitForHTTPD,
273 'ignore-metrics!' => \$ignoreMetrics,
274 'ignore-tests|i=s' => \$ignoreTests,
275 'iterations=i' => \$iterations,
276 'launch-safari!' => \$launchSafari,
277 'leaks|l' => \$shouldCheckLeaks,
278 'merge-leak-depth|m:5' => \$mergeDepth,
279 'new-test-results!' => \$generateNewResults,
280 'nthly=i' => \$testsPerDumpTool,
281 'pixel-tests|p' => \$pixelTests,
282 'platform=s' => \$platform,
283 'port=i' => \$httpdPort,
284 'quiet|q' => \$quiet,
285 'random' => \$randomizeTests,
286 'repeat-each=i' => \$repeatEach,
287 'reset-results' => \$resetResults,
288 'results-directory|o=s' => \$testResultsDirectory,
289 'reverse' => \$reverseTests,
291 'sample-on-timeout!' => \$runSample,
292 'singly|1' => sub { $testsPerDumpTool = 1; },
293 'skipped=s' => \&validateSkippedArg,
294 'slowest' => \$report10Slowest,
295 'strip-editing-callbacks!' => \$stripEditingCallbacks,
296 'threaded|t' => \$threaded,
297 'timeout=i' => \$timeoutSeconds,
298 'tolerance=f' => \$tolerance,
299 'use-remote-links-to-tests' => \$useRemoteLinksToTests,
300 'valgrind' => \$useValgrind,
301 'verbose|v' => \$verbose,
304 if (!$getOptionsResult || $showHelp) {
309 my $ignoreSkipped = $treatSkipped eq "ignore";
310 my $skippedOnly = $treatSkipped eq "only";
312 my $configuration = configuration();
314 # We need an environment variable to be able to enable the feature per-slave
315 $shouldWaitForHTTPD = $ENV{"WEBKIT_WAIT_FOR_HTTPD"} unless ($shouldWaitForHTTPD);
316 $verbose = 1 if $testsPerDumpTool == 1;
318 if ($shouldCheckLeaks && $testsPerDumpTool > 1000) {
319 print STDERR "\nWARNING: Running more than 1000 tests at a time with MallocStackLogging enabled may cause a crash.\n\n";
322 # Stack logging does not play well with QuickTime on Tiger (rdar://problem/5537157)
323 $testMedia = 0 if $shouldCheckLeaks && isTiger();
325 # Generating remote links causes a lot of unnecessary spew on GTK build bot
326 $useRemoteLinksToTests = 0 if isGtk();
328 setConfigurationProductDir(Cwd::abs_path($root)) if (defined($root));
329 my $productDir = productDir();
330 $productDir .= "/bin" if isQt();
331 $productDir .= "/Programs" if isGtk();
335 if (!defined($root)) {
336 print STDERR "Running build-dumprendertree\n";
339 my ($childIn, $childOut, $childErr);
341 open(DEVNULL, ">", File::Spec->devnull()) or die "Failed to open /dev/null";
342 $childOut = ">&DEVNULL";
343 $childErr = ">&DEVNULL";
345 # When not quiet, let the child use our stdout/stderr.
346 $childOut = ">&STDOUT";
347 $childErr = ">&STDERR";
350 my @args = argumentsForConfiguration();
351 my $buildProcess = open3($childIn, $childOut, $childErr, "WebKitTools/Scripts/build-dumprendertree", @args) or die "Failed to run build-dumprendertree";
353 waitpid $buildProcess, 0;
354 my $buildResult = $?;
358 close DEVNULL if ($quiet);
361 print STDERR "Compiling DumpRenderTree failed!\n";
362 exit exitStatus($buildResult);
366 my $dumpToolName = "DumpRenderTree";
367 $dumpToolName .= "_debug" if isCygwin() && configurationForVisualStudio() !~ /^Release|Debug_Internal$/;
368 my $dumpTool = "$productDir/$dumpToolName";
369 die "can't find executable $dumpToolName (looked in $productDir)\n" unless -x $dumpTool;
371 my $imageDiffTool = "$productDir/ImageDiff";
372 $imageDiffTool .= "_debug" if isCygwin() && configurationForVisualStudio() !~ /^Release|Debug_Internal$/;
373 die "can't find executable $imageDiffTool (looked in $productDir)\n" if $pixelTests && !-x $imageDiffTool;
375 checkFrameworks() unless isCygwin();
377 if (isAppleMacWebKit()) {
378 push @INC, $productDir;
379 require DumpRenderTreeSupport;
382 my $layoutTestsName = "LayoutTests";
383 my $testDirectory = File::Spec->rel2abs($layoutTestsName);
384 my $expectedDirectory = $testDirectory;
385 my $platformBaseDirectory = catdir($testDirectory, "platform");
386 my $platformTestDirectory = catdir($platformBaseDirectory, $platform);
387 my @platformResultHierarchy = buildPlatformResultHierarchy();
388 my @platformTestHierarchy = buildPlatformTestHierarchy(@platformResultHierarchy);
390 $expectedDirectory = $ENV{"WebKitExpectedTestResultsDirectory"} if $ENV{"WebKitExpectedTestResultsDirectory"};
392 $testResultsDirectory = File::Spec->rel2abs($testResultsDirectory);
393 my $testResults = File::Spec->catfile($testResultsDirectory, "results.html");
395 print "Running tests from $testDirectory\n";
397 print "Enabling pixel tests with a tolerance of $tolerance%\n";
399 print "WARNING: Temporarily changing the main display color profile:\n";
400 print "\tThe colors on your screen will change for the duration of the testing.\n";
401 print "\tThis allows the pixel tests to have consistent color values across all machines.\n";
403 if (isPerianInstalled()) {
404 print "WARNING: Perian's QuickTime component is installed and this may affect pixel test results!\n";
405 print "\tYou should avoid generating new pixel results in this environment.\n";
406 print "\tSee https://bugs.webkit.org/show_bug.cgi?id=22615 for details.\n";
411 system "ln", "-s", $testDirectory, "/tmp/LayoutTests" unless -x "/tmp/LayoutTests";
413 my %ignoredFiles = ( "results.html" => 1 );
414 my %ignoredDirectories = map { $_ => 1 } qw(platform);
415 my %ignoredLocalDirectories = map { $_ => 1 } qw(.svn _svn resources script-tests);
416 my %supportedFileExtensions = map { $_ => 1 } qw(html shtml xml xhtml pl php);
418 if (!checkWebCoreMathMLSupport(0)) {
419 $ignoredDirectories{'mathml'} = 1;
422 # FIXME: We should fix webkitdirs.pm:hasSVG/WMLSupport() to do the correct feature detection for Cygwin.
423 if (checkWebCoreSVGSupport(0)) {
424 $supportedFileExtensions{'svg'} = 1;
425 } elsif (isCygwin()) {
426 $supportedFileExtensions{'svg'} = 1;
428 $ignoredLocalDirectories{'svg'} = 1;
432 $ignoredDirectories{'http'} = 1;
433 $ignoredDirectories{'websocket'} = 1;
437 $ignoredDirectories{'media'} = 1;
438 $ignoredDirectories{'http/tests/media'} = 1;
441 if (!checkWebCoreAcceleratedCompositingSupport(0)) {
442 $ignoredDirectories{'compositing'} = 1;
445 if (!checkWebCore3DRenderingSupport(0)) {
446 $ignoredDirectories{'animations/3d'} = 1;
447 $ignoredDirectories{'transforms/3d'} = 1;
450 if (!checkWebCore3DCanvasSupport(0)) {
451 $ignoredDirectories{'fast/canvas/webgl'} = 1;
454 if (checkWebCoreWMLSupport(0)) {
455 $supportedFileExtensions{'wml'} = 1;
457 $ignoredDirectories{'http/tests/wml'} = 1;
458 $ignoredDirectories{'fast/wml'} = 1;
459 $ignoredDirectories{'wml'} = 1;
462 if (!checkWebCoreXHTMLMPSupport(0)) {
463 $ignoredDirectories{'fast/xhtmlmp'} = 1;
466 if (!checkWebCoreWCSSSupport(0)) {
467 $ignoredDirectories{'fast/wcss'} = 1;
470 processIgnoreTests($ignoreTests, "ignore-tests") if $ignoreTests;
471 if (!$ignoreSkipped) {
472 if (!$skippedOnly || @ARGV == 0) {
473 readSkippedFiles("");
475 # Since readSkippedFiles() appends to @ARGV, we must use a foreach
476 # loop so that we only iterate over the original argument list.
477 foreach my $argnum (0 .. $#ARGV) {
478 readSkippedFiles(shift @ARGV);
483 my @tests = findTestsToRun();
485 die "no tests to run\n" if !@tests;
490 my %imageDifferences;
493 my $leaksOutputFileNumber = 1;
497 push @toolArgs, "--pixel-tests" if $pixelTests;
498 push @toolArgs, "--threaded" if $threaded;
499 push @toolArgs, "--complex-text" if $complexText;
502 my @diffToolArgs = ();
503 push @diffToolArgs, "--tolerance", $tolerance;
508 my $isDumpToolOpen = 0;
509 my $dumpToolCrashed = 0;
510 my $imageDiffToolPID;
511 my $isDiffToolOpen = 0;
514 my $lastDirectory = "";
517 my $isWebSocketServerOpen = 0;
518 my $webSocketServerPID = 0;
519 my $failedToStartWebSocketServer = 0;
520 # wss is disabled until all platforms support pyOpenSSL.
521 # my $webSocketSecureServerPID = 0;
523 sub catch_pipe { $dumpToolCrashed = 1; }
524 $SIG{"PIPE"} = "catch_pipe";
526 print "Testing ", scalar @tests, " test cases";
527 print " $iterations times" if ($iterations > 1);
528 print ", repeating each test $repeatEach times" if ($repeatEach > 1);
531 my $overallStartTime = time;
533 my %expectedResultPaths;
535 my @originalTests = @tests;
536 # Add individual test repetitions
537 if ($repeatEach > 1) {
539 foreach my $test (@originalTests) {
540 for (my $i = 0; $i < $repeatEach; $i++) {
545 # Add test set repetitions
546 for (my $i = 1; $i < $iterations; $i++) {
547 push(@tests, @originalTests);
550 for my $test (@tests) {
551 my $newDumpTool = not $isDumpToolOpen;
554 my $base = stripExtension($test);
555 my $expectedExtension = ".txt";
560 if ($newDumpTool || $dir ne $lastDirectory) {
561 foreach my $logue (epiloguesAndPrologues($newDumpTool ? "" : $lastDirectory, $dir)) {
563 $logue = toWindowsPath($logue);
565 $logue = canonpath($logue);
568 print "running epilogue or prologue $logue\n";
570 print OUT "$logue\n";
571 # Throw away output from DumpRenderTree.
572 # Once for the test output and once for pixel results (empty)
583 print "running $test -> ";
586 if ($dir ne $lastDirectory) {
587 print "\n" unless $atLineStart;
594 $lastDirectory = $dir;
598 my $startTime = time if $report10Slowest;
600 # Try to read expected hash file for pixel tests
601 my $suffixExpectedHash = "";
602 if ($pixelTests && !$resetResults) {
603 my $expectedPixelDir = expectedDirectoryForTest($base, 0, "png");
604 if (open EXPECTEDHASH, "$expectedPixelDir/$base-$expectedTag.checksum") {
605 my $expectedHash = <EXPECTEDHASH>;
606 chomp($expectedHash);
609 # Format expected hash into a suffix string that is appended to the path / URL passed to DRT
610 $suffixExpectedHash = "'$expectedHash";
614 if ($test =~ /^http\//) {
615 configureAndOpenHTTPDIfNeeded();
616 if ($test !~ /^http\/tests\/local\// && $test !~ /^http\/tests\/ssl\// && $test !~ /^http\/tests\/wml\// && $test !~ /^http\/tests\/media\//) {
617 my $path = canonpath($test);
618 $path =~ s/^http\/tests\///;
619 print OUT "http://127.0.0.1:$httpdPort/$path$suffixExpectedHash\n";
620 } elsif ($test =~ /^http\/tests\/ssl\//) {
621 my $path = canonpath($test);
622 $path =~ s/^http\/tests\///;
623 print OUT "https://127.0.0.1:$httpdSSLPort/$path$suffixExpectedHash\n";
625 my $testPath = "$testDirectory/$test";
627 $testPath = toWindowsPath($testPath);
629 $testPath = canonpath($testPath);
631 print OUT "$testPath$suffixExpectedHash\n";
633 } elsif ($test =~ /^websocket\//) {
634 if ($test =~ /^websocket\/tests\/local\//) {
635 my $testPath = "$testDirectory/$test";
637 $testPath = toWindowsPath($testPath);
639 $testPath = canonpath($testPath);
641 print OUT "$testPath\n";
643 if (openWebSocketServerIfNeeded()) {
644 my $path = canonpath($test);
645 if ($test =~ /^websocket\/tests\/ssl\//) {
646 # wss is disabled until all platforms support pyOpenSSL.
647 print STDERR "Error: wss is disabled until all platforms support pyOpenSSL.";
648 # print OUT "https://127.0.0.1:$webSocketSecurePort/$path\n";
650 print OUT "http://127.0.0.1:$webSocketPort/$path\n";
653 # We failed to launch the WebSocket server. Display a useful error message rather than attempting
654 # to run tests that expect the server to be available.
655 my $errorMessagePath = "$testDirectory/websocket/resources/server-failed-to-start.html";
656 $errorMessagePath = isCygwin() ? toWindowsPath($errorMessagePath) : canonpath($errorMessagePath);
657 print OUT "$errorMessagePath\n";
661 my $testPath = "$testDirectory/$test";
663 $testPath = toWindowsPath($testPath);
665 $testPath = canonpath($testPath);
667 print OUT "$testPath$suffixExpectedHash\n" if defined $testPath;
670 # DumpRenderTree is expected to dump two "blocks" to stdout for each test.
671 # Each block is terminated by a #EOF on a line by itself.
672 # The first block is the output of the test (in text, RenderTree or other formats).
673 # The second block is for optional pixel data in PNG format, and may be empty if
674 # pixel tests are not being run, or the test does not dump pixels (e.g. text tests).
675 my $readResults = readFromDumpToolWithTimer(IN, ERROR);
677 my $actual = $readResults->{output};
678 my $error = $readResults->{error};
680 $expectedExtension = $readResults->{extension};
681 my $expectedFileName = "$base-$expectedTag.$expectedExtension";
683 my $isText = isTextOnlyTest($actual);
685 my $expectedDir = expectedDirectoryForTest($base, $isText, $expectedExtension);
686 $expectedResultPaths{$base} = "$expectedDir/$expectedFileName";
688 unless ($readResults->{status} eq "success") {
689 my $crashed = $readResults->{status} eq "crashed";
690 testCrashedOrTimedOut($test, $base, $crashed, $actual, $error);
691 countFinishedTest($test, $base, $crashed ? "crash" : "timedout", 0);
695 $durations{$test} = time - $startTime if $report10Slowest;
699 if (!$resetResults && open EXPECTED, "<", "$expectedDir/$expectedFileName") {
702 next if $stripEditingCallbacks && $_ =~ /^EDITING DELEGATE:/;
708 if ($ignoreMetrics && !$isText && defined $expected) {
709 ($actual, $expected) = stripMetrics($actual, $expected);
712 if ($shouldCheckLeaks && $testsPerDumpTool == 1) {
718 my $diffPercentage = 0;
719 my $diffResult = "passed";
722 my $expectedHash = "";
723 my $actualPNGSize = 0;
727 if (/ActualHash: ([a-f0-9]{32})/) {
729 } elsif (/ExpectedHash: ([a-f0-9]{32})/) {
731 } elsif (/Content-Length: (\d+)\s*/) {
733 read(IN, $actualPNG, $actualPNGSize);
737 if ($verbose && $pixelTests && !$resetResults && $actualPNGSize) {
738 if ($actualHash eq "" && $expectedHash eq "") {
739 printFailureMessageForTest($test, "WARNING: actual & expected pixel hashes are missing!");
740 } elsif ($actualHash eq "") {
741 printFailureMessageForTest($test, "WARNING: actual pixel hash is missing!");
742 } elsif ($expectedHash eq "") {
743 printFailureMessageForTest($test, "WARNING: expected pixel hash is missing!");
747 if ($actualPNGSize > 0) {
748 my $expectedPixelDir = expectedDirectoryForTest($base, 0, "png");
750 if (!$resetResults && ($expectedHash ne $actualHash || ($actualHash eq "" && $expectedHash eq ""))) {
751 if (-f "$expectedPixelDir/$base-$expectedTag.png") {
752 my $expectedPNGSize = -s "$expectedPixelDir/$base-$expectedTag.png";
753 my $expectedPNG = "";
754 open EXPECTEDPNG, "$expectedPixelDir/$base-$expectedTag.png";
755 read(EXPECTEDPNG, $expectedPNG, $expectedPNGSize);
758 print DIFFOUT "Content-Length: $actualPNGSize\n";
759 print DIFFOUT $actualPNG;
761 print DIFFOUT "Content-Length: $expectedPNGSize\n";
762 print DIFFOUT $expectedPNG;
765 last if /^error/ || /^diff:/;
766 if (/Content-Length: (\d+)\s*/) {
767 read(DIFFIN, $diffPNG, $1);
771 if (/^diff: (.+)% (passed|failed)/) {
772 $diffPercentage = $1 + 0;
773 $imageDifferences{$base} = $diffPercentage;
777 if (!$diffPercentage) {
778 printFailureMessageForTest($test, "pixel hash failed (but pixel test still passes)");
781 printFailureMessageForTest($test, "WARNING: expected image is missing!");
785 if ($resetResults || !-f "$expectedPixelDir/$base-$expectedTag.png") {
786 mkpath catfile($expectedPixelDir, dirname($base)) if $testDirectory ne $expectedPixelDir;
787 writeToFile("$expectedPixelDir/$base-$expectedTag.png", $actualPNG);
790 if ($actualHash ne "" && ($resetResults || !-f "$expectedPixelDir/$base-$expectedTag.checksum")) {
791 writeToFile("$expectedPixelDir/$base-$expectedTag.checksum", $actualHash);
795 if (dumpToolDidCrash()) {
797 testCrashedOrTimedOut($test, $base, 1, $actual, $error);
798 } elsif (!defined $expected) {
800 print "new " . ($resetResults ? "result" : "test") ."\n";
805 if ($generateNewResults || $resetResults) {
806 mkpath catfile($expectedDir, dirname($base)) if $testDirectory ne $expectedDir;
807 writeToFile("$expectedDir/$expectedFileName", $actual);
809 deleteExpectedAndActualResults($base);
810 recordActualResultsAndDiff($base, $actual);
811 if (!$resetResults) {
812 # Always print the file name for new tests, as they will probably need some manual inspection.
813 # in verbose mode we already printed the test case, so no need to do it again.
815 print "\n" unless $atLineStart;
818 my $resultsDir = catdir($expectedDir, dirname($base));
819 if ($generateNewResults) {
820 print "new (results generated in $resultsDir)\n";
826 } elsif ($actual eq $expected && $diffResult eq "passed") {
832 deleteExpectedAndActualResults($base);
834 $result = "mismatch";
836 my $pixelTestFailed = $pixelTests && $diffPNG && $diffPNG ne "";
837 my $testFailed = $actual ne $expected;
839 my $message = !$testFailed ? "pixel test failed" : "failed";
841 if (($testFailed || $pixelTestFailed) && $addPlatformExceptions) {
842 my $testBase = catfile($testDirectory, $base);
843 my $expectedBase = catfile($expectedDir, $base);
844 my $testIsMaximallyPlatformSpecific = $testBase =~ m|^\Q$platformTestDirectory\E/|;
845 my $expectedResultIsMaximallyPlatformSpecific = $expectedBase =~ m|^\Q$platformTestDirectory\E/|;
846 if (!$testIsMaximallyPlatformSpecific && !$expectedResultIsMaximallyPlatformSpecific) {
847 mkpath catfile($platformTestDirectory, dirname($base));
849 my $expectedFile = catfile($platformTestDirectory, "$expectedFileName");
850 writeToFile("$expectedFile", $actual);
852 if ($pixelTestFailed) {
853 my $expectedFile = catfile($platformTestDirectory, "$base-$expectedTag.checksum");
854 writeToFile("$expectedFile", $actualHash);
856 $expectedFile = catfile($platformTestDirectory, "$base-$expectedTag.png");
857 writeToFile("$expectedFile", $actualPNG);
859 $message .= " (results generated in $platformTestDirectory)";
863 printFailureMessageForTest($test, $message);
865 my $dir = "$testResultsDirectory/$base";
866 $dir =~ s|/([^/]+)$|| or die "Failed to find test name from base\n";
870 deleteExpectedAndActualResults($base);
871 recordActualResultsAndDiff($base, $actual);
873 if ($pixelTestFailed) {
874 $imagesPresent{$base} = 1;
876 writeToFile("$testResultsDirectory/$base-$actualTag.png", $actualPNG);
877 writeToFile("$testResultsDirectory/$base-$diffsTag.png", $diffPNG);
879 my $expectedPixelDir = expectedDirectoryForTest($base, 0, "png");
880 copy("$expectedPixelDir/$base-$expectedTag.png", "$testResultsDirectory/$base-$expectedTag.png");
882 open DIFFHTML, ">$testResultsDirectory/$base-$diffsTag.html" or die;
883 print DIFFHTML "<html>\n";
884 print DIFFHTML "<head>\n";
885 print DIFFHTML "<title>$base Image Compare</title>\n";
886 print DIFFHTML "<script language=\"Javascript\" type=\"text/javascript\">\n";
887 print DIFFHTML "var currentImage = 0;\n";
888 print DIFFHTML "var imageNames = new Array(\"Actual\", \"Expected\");\n";
889 print DIFFHTML "var imagePaths = new Array(\"$testName-$actualTag.png\", \"$testName-$expectedTag.png\");\n";
890 if (-f "$testDirectory/$base-w3c.png") {
891 copy("$testDirectory/$base-w3c.png", "$testResultsDirectory/$base-w3c.png");
892 print DIFFHTML "imageNames.push(\"W3C\");\n";
893 print DIFFHTML "imagePaths.push(\"$testName-w3c.png\");\n";
895 print DIFFHTML "function animateImage() {\n";
896 print DIFFHTML " var image = document.getElementById(\"animatedImage\");\n";
897 print DIFFHTML " var imageText = document.getElementById(\"imageText\");\n";
898 print DIFFHTML " image.src = imagePaths[currentImage];\n";
899 print DIFFHTML " imageText.innerHTML = imageNames[currentImage] + \" Image\";\n";
900 print DIFFHTML " currentImage = (currentImage + 1) % imageNames.length;\n";
901 print DIFFHTML " setTimeout('animateImage()',2000);\n";
902 print DIFFHTML "}\n";
903 print DIFFHTML "</script>\n";
904 print DIFFHTML "</head>\n";
905 print DIFFHTML "<body onLoad=\"animateImage();\">\n";
906 print DIFFHTML "<table>\n";
907 if ($diffPercentage) {
908 print DIFFHTML "<tr>\n";
909 print DIFFHTML "<td>Difference between images: <a href=\"$testName-$diffsTag.png\">$diffPercentage%</a></td>\n";
910 print DIFFHTML "</tr>\n";
912 print DIFFHTML "<tr>\n";
913 print DIFFHTML "<td><a href=\"" . toURL("$testDirectory/$test") . "\">test file</a></td>\n";
914 print DIFFHTML "</tr>\n";
915 print DIFFHTML "<tr>\n";
916 print DIFFHTML "<td id=\"imageText\" style=\"text-weight: bold;\">Actual Image</td>\n";
917 print DIFFHTML "</tr>\n";
918 print DIFFHTML "<tr>\n";
919 print DIFFHTML "<td><img src=\"$testName-$actualTag.png\" id=\"animatedImage\"></td>\n";
920 print DIFFHTML "</tr>\n";
921 print DIFFHTML "</table>\n";
922 print DIFFHTML "</body>\n";
923 print DIFFHTML "</html>\n";
928 my $dir = "$testResultsDirectory/$base";
929 $dir =~ s|/([^/]+)$|| or die "Failed to find test name from base\n";
932 writeToFile("$testResultsDirectory/$base-$errorTag.txt", $error);
935 push @{$tests{error}}, $test;
938 countFinishedTest($test, $base, $result, $isText);
940 # --reset-results does not check pass vs. fail, so exitAfterNFailures makes no sense with --reset-results.
941 if ($exitAfterNFailures && !$resetResults) {
942 my $passCount = $counts{match} || 0; # $counts{match} will be undefined if we've not yet passed a test (e.g. the first test fails).
943 my $failureCount = $count - $passCount; # "Failure" here includes new tests, timeouts, crashes, etc.
944 if ($failureCount >= $exitAfterNFailures) {
945 print "\nExiting early after $failureCount failures. $count tests run.";
951 my $totalTestingTime = time - $overallStartTime;
952 my $waitTime = getWaitTime();
953 if ($waitTime > 0.1) {
954 my $normalizedTestingTime = $totalTestingTime - $waitTime;
955 printf "\n%0.2fs HTTPD waiting time\n", $waitTime . "";
956 printf "%0.2fs normalized testing time", $normalizedTestingTime . "";
958 printf "\n%0.2fs total testing time\n", $totalTestingTime . "";
960 !$isDumpToolOpen || die "Failed to close $dumpToolName.\n";
962 $isHttpdOpen = !closeHTTPD();
963 closeWebSocketServer();
965 # Because multiple instances of this script are running concurrently we cannot
966 # safely delete this symlink.
967 # system "rm /tmp/LayoutTests";
969 # FIXME: Do we really want to check the image-comparison tool for leaks every time?
970 if ($isDiffToolOpen && $shouldCheckLeaks) {
971 $totalLeaks += countAndPrintLeaks("ImageDiff", $imageDiffToolPID, "$testResultsDirectory/ImageDiff-leaks.txt");
976 parseLeaksandPrintUniqueLeaks();
978 print "\nWARNING: $totalLeaks total leaks found!\n";
979 print "See above for individual leaks results.\n" if ($leaksOutputFileNumber > 2);
987 if ($report10Slowest) {
988 print "\n\nThe 10 slowest tests:\n\n";
990 for my $test (sort slowestcmp keys %durations) {
991 printf "%0.2f secs: %s\n", $durations{$test}, $test;
992 last if ++$count == 10;
998 if ($skippedOnly && $counts{"match"}) {
999 print "The following tests are in the Skipped file (" . File::Spec->abs2rel("$platformTestDirectory/Skipped", $testDirectory) . "), but succeeded:\n";
1000 foreach my $test (@{$tests{"match"}}) {
1005 if ($resetResults || ($counts{match} && $counts{match} == $count)) {
1006 print "all $count test cases succeeded\n";
1007 unlink $testResults;
1013 mkpath $testResultsDirectory;
1015 open HTML, ">", $testResults or die "Failed to open $testResults. $!";
1016 print HTML "<html>\n";
1017 print HTML "<head>\n";
1018 print HTML "<title>Layout Test Results</title>\n";
1019 print HTML "</head>\n";
1020 print HTML "<body>\n";
1022 if ($ignoreMetrics) {
1023 print HTML "<h4>Tested with metrics ignored.</h4>";
1026 print HTML htmlForResultsSection(@{$tests{mismatch}}, "Tests where results did not match expected results", \&linksForMismatchTest);
1027 print HTML htmlForResultsSection(@{$tests{timedout}}, "Tests that timed out", \&linksForErrorTest);
1028 print HTML htmlForResultsSection(@{$tests{crash}}, "Tests that caused the DumpRenderTree tool to crash", \&linksForErrorTest);
1029 print HTML htmlForResultsSection(@{$tests{error}}, "Tests that had stderr output", \&linksForErrorTest);
1030 print HTML htmlForResultsSection(@{$tests{new}}, "Tests that had no expected results (probably new)", \&linksForNewTest);
1032 print HTML "</body>\n";
1033 print HTML "</html>\n";
1036 my @configurationArgs = argumentsForConfiguration();
1039 system "WebKitTools/Scripts/run-launcher", @configurationArgs, "file://".$testResults if $launchSafari;
1041 unshift @configurationArgs, qw(-graphicssystem raster -style windows);
1043 $testResults = "/" . toWindowsPath($testResults);
1044 $testResults =~ s/\\/\//g;
1046 system "WebKitTools/Scripts/run-launcher", @configurationArgs, "file://".$testResults if $launchSafari;
1047 } elsif (isCygwin()) {
1048 system "cygstart", $testResults if $launchSafari;
1050 system "WebKitTools/Scripts/run-safari", @configurationArgs, "-NSOpen", $testResults if $launchSafari;
1053 closeCygpaths() if isCygwin();
1057 sub countAndPrintLeaks($$$)
1059 my ($dumpToolName, $dumpToolPID, $leaksFilePath) = @_;
1061 print "\n" unless $atLineStart;
1064 # We are excluding the following reported leaks so they don't get in our way when looking for WebKit leaks:
1065 # This allows us ignore known leaks and only be alerted when new leaks occur. Some leaks are in the old
1066 # versions of the system frameworks that are being used by the leaks bots. Even though a leak has been
1067 # fixed, it will be listed here until the bot has been updated with the newer frameworks.
1069 my @typesToExclude = (
1072 my @callStacksToExclude = (
1073 "Flash_EnforceLocalSecurity" # leaks in Flash plug-in code, rdar://problem/4449747
1077 # Leak list for the version of Tiger used on the build bot.
1078 push @callStacksToExclude, (
1079 "CFRunLoopRunSpecific \\| malloc_zone_malloc", "CFRunLoopRunSpecific \\| CFAllocatorAllocate ", # leak in CFRunLoopRunSpecific, rdar://problem/4670839
1080 "CGImageSourceGetPropertiesAtIndex", # leak in ImageIO, rdar://problem/4628809
1081 "FOGetCoveredUnicodeChars", # leak in ATS, rdar://problem/3943604
1082 "GetLineDirectionPreference", "InitUnicodeUtilities", # leaks tool falsely reporting leak in CFNotificationCenterAddObserver, rdar://problem/4964790
1083 "ICCFPrefWrapper::GetPrefDictionary", # leaks in Internet Config. code, rdar://problem/4449794
1084 "NSHTTPURLProtocol setResponseHeader:", # leak in multipart/mixed-replace handling in Foundation, no Radar, but fixed in Leopard
1085 "NSURLCache cachedResponseForRequest", # leak in CFURL cache, rdar://problem/4768430
1086 "PCFragPrepareClosureFromFile", # leak in Code Fragment Manager, rdar://problem/3426998
1087 "WebCore::Selection::toRange", # bug in 'leaks', rdar://problem/4967949
1088 "WebCore::SubresourceLoader::create", # bug in 'leaks', rdar://problem/4985806
1089 "_CFPreferencesDomainDeepCopyDictionary", # leak in CFPreferences, rdar://problem/4220786
1090 "_objc_msgForward", # leak in NSSpellChecker, rdar://problem/4965278
1091 "gldGetString", # leak in OpenGL, rdar://problem/5013699
1092 "_setDefaultUserInfoFromURL", # leak in NSHTTPAuthenticator, rdar://problem/5546453
1093 "SSLHandshake", # leak in SSL, rdar://problem/5546440
1094 "SecCertificateCreateFromData", # leak in SSL code, rdar://problem/4464397
1096 push @typesToExclude, (
1097 "THRD", # bug in 'leaks', rdar://problem/3387783
1098 "DRHT", # ditto (endian little hate i)
1103 # Leak list for the version of Leopard used on the build bot.
1104 push @callStacksToExclude, (
1105 "CFHTTPMessageAppendBytes", # leak in CFNetwork, rdar://problem/5435912
1106 "sendDidReceiveDataCallback", # leak in CFNetwork, rdar://problem/5441619
1107 "_CFHTTPReadStreamReadMark", # leak in CFNetwork, rdar://problem/5441468
1108 "httpProtocolStart", # leak in CFNetwork, rdar://problem/5468837
1109 "_CFURLConnectionSendCallbacks", # leak in CFNetwork, rdar://problem/5441600
1110 "DispatchQTMsg", # leak in QuickTime, PPC only, rdar://problem/5667132
1111 "QTMovieContentView createVisualContext", # leak in QuickTime, PPC only, rdar://problem/5667132
1112 "_CopyArchitecturesForJVMVersion", # leak in Java, rdar://problem/5910823
1116 if (isSnowLeopard()) {
1117 push @callStacksToExclude, (
1118 "readMakerNoteProps", # <rdar://problem/7156432> leak in ImageIO
1119 "QTKitMovieControllerView completeUISetup", # <rdar://problem/7155156> leak in QTKit
1123 my $leaksTool = sourceDir() . "/WebKitTools/Scripts/run-leaks";
1124 my $excludeString = "--exclude-callstack '" . (join "' --exclude-callstack '", @callStacksToExclude) . "'";
1125 $excludeString .= " --exclude-type '" . (join "' --exclude-type '", @typesToExclude) . "'" if @typesToExclude;
1127 print " ? checking for leaks in $dumpToolName\n";
1128 my $leaksOutput = `$leaksTool $excludeString $dumpToolPID`;
1129 my ($count, $bytes) = $leaksOutput =~ /Process $dumpToolPID: (\d+) leaks? for (\d+) total/;
1130 my ($excluded) = $leaksOutput =~ /(\d+) leaks? excluded/;
1132 my $adjustedCount = $count;
1133 $adjustedCount -= $excluded if $excluded;
1135 if (!$adjustedCount) {
1136 print " - no leaks found\n";
1137 unlink $leaksFilePath;
1140 my $dir = $leaksFilePath;
1141 $dir =~ s|/[^/]+$|| or die;
1145 print " + $adjustedCount leaks ($bytes bytes including $excluded excluded leaks) were found, details in $leaksFilePath\n";
1147 print " + $count leaks ($bytes bytes) were found, details in $leaksFilePath\n";
1150 writeToFile($leaksFilePath, $leaksOutput);
1152 push @leaksFilenames, $leaksFilePath;
1155 return $adjustedCount;
1160 my ($filePath, $contents) = @_;
1161 open NEWFILE, ">", "$filePath" or die "Could not create $filePath. $!\n";
1162 print NEWFILE $contents;
1166 # Break up a path into the directory (with slash) and base name.
1171 my $pathSeparator = "/";
1172 my $dirname = dirname($path) . $pathSeparator;
1173 $dirname = "" if $dirname eq "." . $pathSeparator;
1175 return ($dirname, basename($path));
1178 # Sort first by directory, then by file, so all paths in one directory are grouped
1179 # rather than being interspersed with items from subdirectories.
1180 # Use numericcmp to sort directory and filenames to make order logical.
1183 my ($patha, $pathb) = @_;
1185 my ($dira, $namea) = splitpath($patha);
1186 my ($dirb, $nameb) = splitpath($pathb);
1188 return numericcmp($dira, $dirb) if $dira ne $dirb;
1189 return numericcmp($namea, $nameb);
1192 # Sort numeric parts of strings as numbers, other parts as strings.
1193 # Makes 1.33 come after 1.3, which is cool.
1198 my @a = split /(\d+)/, $aa;
1199 my @b = split /(\d+)/, $bb;
1201 # Compare one chunk at a time.
1202 # Each chunk is either all numeric digits, or all not numeric digits.
1207 # Use numeric comparison if chunks are non-equal numbers.
1208 return $a <=> $b if $a =~ /^\d/ && $b =~ /^\d/ && $a != $b;
1210 # Use string comparison if chunks are any other kind of non-equal string.
1211 return $a cmp $b if $a ne $b;
1214 # One of the two is now empty; compare lengths for result in this case.
1218 # Sort slowest tests first.
1221 my ($testa, $testb) = @_;
1223 my $dura = $durations{$testa};
1224 my $durb = $durations{$testb};
1225 return $durb <=> $dura if $dura != $durb;
1226 return pathcmp($testa, $testb);
1229 sub launchWithEnv(\@\%)
1231 my ($args, $env) = @_;
1233 # Dump the current environment as perl code and then put it in quotes so it is one parameter.
1234 my $environmentDumper = Data::Dumper->new([\%{$env}], [qw(*ENV)]);
1235 $environmentDumper->Indent(0);
1236 $environmentDumper->Purity(1);
1237 my $allEnvVars = $environmentDumper->Dump();
1238 unshift @{$args}, "\"$allEnvVars\"";
1240 my $execScript = File::Spec->catfile(sourceDir(), qw(WebKitTools Scripts execAppWithEnv));
1241 unshift @{$args}, $execScript;
1245 sub resolveAndMakeTestResultsDirectory()
1247 my $absTestResultsDirectory = File::Spec->rel2abs(glob $testResultsDirectory);
1248 mkpath $absTestResultsDirectory;
1249 return $absTestResultsDirectory;
1254 return if $isDiffToolOpen;
1255 return if !$pixelTests;
1258 $CLEAN_ENV{MallocStackLogging} = 1 if $shouldCheckLeaks;
1259 $imageDiffToolPID = open2(\*DIFFIN, \*DIFFOUT, $imageDiffTool, launchWithEnv(@diffToolArgs, %CLEAN_ENV)) or die "unable to open $imageDiffTool\n";
1260 $isDiffToolOpen = 1;
1265 return if $isDumpToolOpen;
1269 # Generic environment variables
1270 if (defined $ENV{'WEBKIT_TESTFONTS'}) {
1271 $CLEAN_ENV{WEBKIT_TESTFONTS} = $ENV{'WEBKIT_TESTFONTS'};
1274 $CLEAN_ENV{XML_CATALOG_FILES} = ""; # work around missing /etc/catalog <rdar://problem/4292995>
1276 # Platform spesifics
1278 if (defined $ENV{'DISPLAY'}) {
1279 $CLEAN_ENV{DISPLAY} = $ENV{'DISPLAY'};
1281 $CLEAN_ENV{DISPLAY} = ":1";
1283 if (defined $ENV{'XAUTHORITY'}) {
1284 $CLEAN_ENV{XAUTHORITY} = $ENV{'XAUTHORITY'};
1287 $CLEAN_ENV{HOME} = $ENV{'HOME'};
1289 if (defined $ENV{'LD_LIBRARY_PATH'}) {
1290 $CLEAN_ENV{LD_LIBRARY_PATH} = $ENV{'LD_LIBRARY_PATH'};
1292 if (defined $ENV{'DBUS_SESSION_BUS_ADDRESS'}) {
1293 $CLEAN_ENV{DBUS_SESSION_BUS_ADDRESS} = $ENV{'DBUS_SESSION_BUS_ADDRESS'};
1295 } elsif (isDarwin()) {
1296 if (defined $ENV{'DYLD_LIBRARY_PATH'}) {
1297 $CLEAN_ENV{DYLD_LIBRARY_PATH} = $ENV{'DYLD_LIBRARY_PATH'};
1300 $CLEAN_ENV{DYLD_FRAMEWORK_PATH} = $productDir;
1301 $CLEAN_ENV{DYLD_INSERT_LIBRARIES} = "/usr/lib/libgmalloc.dylib" if $guardMalloc;
1302 } elsif (isCygwin()) {
1303 $CLEAN_ENV{HOMEDRIVE} = $ENV{'HOMEDRIVE'};
1304 $CLEAN_ENV{HOMEPATH} = $ENV{'HOMEPATH'};
1306 setPathForRunningWebKitApp(\%CLEAN_ENV);
1311 $CLEAN_ENV{QTWEBKIT_PLUGIN_PATH} = productDir() . "/lib/plugins";
1314 my @args = ($dumpTool, @toolArgs);
1315 if (isAppleMacWebKit() and !isTiger()) {
1316 unshift @args, "arch", "-" . architecture();
1320 unshift @args, "valgrind", "--suppressions=$platformBaseDirectory/qt/SuppressedValgrindErrors";
1323 $CLEAN_ENV{MallocStackLogging} = 1 if $shouldCheckLeaks;
1325 $dumpToolPID = open3(\*OUT, \*IN, \*ERROR, launchWithEnv(@args, %CLEAN_ENV)) or die "Failed to start tool: $dumpTool\n";
1326 $isDumpToolOpen = 1;
1327 $dumpToolCrashed = 0;
1332 return if !$isDumpToolOpen;
1336 waitpid $dumpToolPID, 0;
1338 # check for WebCore counter leaks.
1339 if ($shouldCheckLeaks) {
1345 $isDumpToolOpen = 0;
1348 sub dumpToolDidCrash()
1350 return 1 if $dumpToolCrashed;
1351 return 0 unless $isDumpToolOpen;
1352 my $pid = waitpid(-1, WNOHANG);
1353 return 1 if ($pid == $dumpToolPID);
1355 # On Mac OS X, crashing may be significantly delayed by crash reporter.
1356 return 0 unless isAppleMacWebKit();
1358 return DumpRenderTreeSupport::processIsCrashing($dumpToolPID);
1361 sub configureAndOpenHTTPDIfNeeded()
1363 return if $isHttpdOpen;
1364 my $absTestResultsDirectory = resolveAndMakeTestResultsDirectory();
1365 my $listen = "127.0.0.1:$httpdPort";
1367 "-c", "CustomLog \"$absTestResultsDirectory/access_log.txt\" common",
1368 "-c", "ErrorLog \"$absTestResultsDirectory/error_log.txt\"",
1369 "-C", "Listen $listen"
1372 my @defaultArgs = getDefaultConfigForTestDirectory($testDirectory);
1373 @args = (@defaultArgs, @args);
1375 waitForHTTPDLock() if $shouldWaitForHTTPD;
1376 $isHttpdOpen = openHTTPD(@args);
1379 sub openWebSocketServerIfNeeded()
1381 return 1 if $isWebSocketServerOpen;
1382 return 0 if $failedToStartWebSocketServer;
1384 my $webSocketServerPath = "/usr/bin/python";
1385 my $webSocketPythonPath = "WebKitTools/pywebsocket";
1386 my $webSocketHandlerDir = "$testDirectory";
1387 my $webSocketHandlerScanDir = "$testDirectory/websocket/tests";
1388 my $webSocketHandlerMapFile = "$webSocketHandlerScanDir/handler_map.txt";
1389 my $sslCertificate = "$testDirectory/http/conf/webkit-httpd.pem";
1390 my $absTestResultsDirectory = resolveAndMakeTestResultsDirectory();
1391 my $logFile = "$absTestResultsDirectory/pywebsocket_log.txt";
1394 "WebKitTools/pywebsocket/mod_pywebsocket/standalone.py",
1395 "-p", "$webSocketPort",
1396 "-d", "$webSocketHandlerDir",
1397 "-s", "$webSocketHandlerScanDir",
1398 "-m", "$webSocketHandlerMapFile",
1402 # wss is disabled until all platforms support pyOpenSSL.
1403 # my @argsSecure = (
1404 # "WebKitTools/pywebsocket/mod_pywebsocket/standalone.py",
1405 # "-p", "$webSocketSecurePort",
1406 # "-d", "$webSocketHandlerDir",
1408 # "-k", "$sslCertificate",
1409 # "-c", "$sslCertificate",
1412 $ENV{"PYTHONPATH"} = $webSocketPythonPath;
1413 $webSocketServerPID = open3(\*WEBSOCKETSERVER_IN, \*WEBSOCKETSERVER_OUT, \*WEBSOCKETSERVER_ERR, $webSocketServerPath, @args);
1414 # wss is disabled until all platforms support pyOpenSSL.
1415 # $webSocketSecureServerPID = open3(\*WEBSOCKETSECURESERVER_IN, \*WEBSOCKETSECURESERVER_OUT, \*WEBSOCKETSECURESERVER_ERR, $webSocketServerPath, @argsSecure);
1416 # my @listen = ("http://127.0.0.1:$webSocketPort", "https://127.0.0.1:$webSocketSecurePort");
1417 my @listen = ("http://127.0.0.1:$webSocketPort");
1418 for (my $i = 0; $i < @listen; $i++) {
1419 my $retryCount = 10;
1420 while (system("/usr/bin/curl -k -q --silent --stderr - --output /dev/null $listen[$i]") && $retryCount) {
1424 unless ($retryCount) {
1425 print STDERR "Timed out waiting for WebSocketServer to start.\n";
1426 $failedToStartWebSocketServer = 1;
1431 $isWebSocketServerOpen = 1;
1435 sub closeWebSocketServer()
1437 return if !$isWebSocketServerOpen;
1439 close WEBSOCKETSERVER_IN;
1440 close WEBSOCKETSERVER_OUT;
1441 close WEBSOCKETSERVER_ERR;
1442 kill 15, $webSocketServerPID;
1444 # wss is disabled until all platforms support pyOpenSSL.
1445 # close WEBSOCKETSECURESERVER_IN;
1446 # close WEBSOCKETSECURESERVER_OUT;
1447 # close WEBSOCKETSECURESERVER_ERR;
1448 # kill 15, $webSocketSecureServerPID;
1450 $isWebSocketServerOpen = 0;
1453 sub fileNameWithNumber($$)
1455 my ($base, $number) = @_;
1456 return "$base$number" if ($number > 1);
1460 sub processIgnoreTests($$)
1462 my @ignoreList = split(/\s*,\s*/, shift);
1463 my $listName = shift;
1465 my $disabledSuffix = "-disabled";
1467 my $addIgnoredDirectories = sub {
1468 return () if exists $ignoredLocalDirectories{basename($File::Find::dir)};
1469 $ignoredDirectories{File::Spec->abs2rel($File::Find::dir, $testDirectory)} = 1;
1472 foreach my $item (@ignoreList) {
1473 my $path = catfile($testDirectory, $item);
1475 $ignoredDirectories{$item} = 1;
1476 find({ preprocess => $addIgnoredDirectories, wanted => sub {} }, $path);
1479 $ignoredFiles{$item} = 1;
1480 } elsif (-f $path . $disabledSuffix) {
1481 # The test is disabled, so do nothing.
1483 print "$listName list contained '$item', but no file of that name could be found\n";
1488 sub stripExtension($)
1492 $test =~ s/\.[a-zA-Z]+$//;
1496 sub isTextOnlyTest($)
1500 if ($actual =~ /^layer at/ms) {
1508 sub expectedDirectoryForTest($;$;$)
1510 my ($base, $isText, $expectedExtension) = @_;
1512 my @directories = @platformResultHierarchy;
1513 push @directories, map { catdir($platformBaseDirectory, $_) } qw(mac-snowleopard mac) if isCygwin();
1514 push @directories, $expectedDirectory;
1516 # If we already have expected results, just return their location.
1517 foreach my $directory (@directories) {
1518 return $directory if (-f "$directory/$base-$expectedTag.$expectedExtension");
1521 # For cross-platform tests, text-only results should go in the cross-platform directory,
1522 # while render tree dumps should go in the least-specific platform directory.
1523 return $isText ? $expectedDirectory : $platformResultHierarchy[$#platformResultHierarchy];
1526 sub countFinishedTest($$$$)
1528 my ($test, $base, $result, $isText) = @_;
1530 if (($count + 1) % $testsPerDumpTool == 0 || $count == $#tests) {
1531 if ($shouldCheckLeaks) {
1533 if ($testsPerDumpTool == 1) {
1534 $fileName = "$testResultsDirectory/$base-leaks.txt";
1536 $fileName = "$testResultsDirectory/" . fileNameWithNumber($dumpToolName, $leaksOutputFileNumber) . "-leaks.txt";
1538 my $leakCount = countAndPrintLeaks($dumpToolName, $dumpToolPID, $fileName);
1539 $totalLeaks += $leakCount;
1540 $leaksOutputFileNumber++ if ($leakCount);
1548 push @{$tests{$result}}, $test;
1551 sub testCrashedOrTimedOut($$$$$)
1553 my ($test, $base, $didCrash, $actual, $error) = @_;
1555 printFailureMessageForTest($test, $didCrash ? "crashed" : "timed out");
1557 sampleDumpTool() unless $didCrash;
1559 my $dir = "$testResultsDirectory/$base";
1560 $dir =~ s|/([^/]+)$|| or die "Failed to find test name from base\n";
1563 deleteExpectedAndActualResults($base);
1565 if (defined($error) && length($error)) {
1566 writeToFile("$testResultsDirectory/$base-$errorTag.txt", $error);
1569 recordActualResultsAndDiff($base, $actual);
1571 kill 9, $dumpToolPID unless $didCrash;
1576 sub printFailureMessageForTest($$)
1578 my ($test, $description) = @_;
1581 print "\n" unless $atLineStart;
1584 print "$description\n";
1590 sub openCygpathIfNeeded($)
1594 return unless isCygwin();
1595 return $cygpaths{$options} if $cygpaths{$options} && $cygpaths{$options}->{"open"};
1597 local (*CYGPATHIN, *CYGPATHOUT);
1598 my $pid = open2(\*CYGPATHIN, \*CYGPATHOUT, "cygpath -f - $options");
1602 "out" => *CYGPATHOUT,
1606 $cygpaths{$options} = $cygpath;
1613 return unless isCygwin();
1615 foreach my $cygpath (values(%cygpaths)) {
1616 close $cygpath->{"in"};
1617 close $cygpath->{"out"};
1618 waitpid($cygpath->{"pid"}, 0);
1619 $cygpath->{"open"} = 0;
1624 sub convertPathUsingCygpath($$)
1626 my ($path, $options) = @_;
1628 my $cygpath = openCygpathIfNeeded($options);
1629 local *inFH = $cygpath->{"in"};
1630 local *outFH = $cygpath->{"out"};
1631 print outFH $path . "\n";
1632 my $convertedPath = <inFH>;
1633 chomp($convertedPath) if defined $convertedPath;
1634 return $convertedPath;
1637 sub toWindowsPath($)
1640 return unless isCygwin();
1642 return convertPathUsingCygpath($path, "-w");
1649 if ($useRemoteLinksToTests) {
1650 my $relativePath = File::Spec->abs2rel($path, $testDirectory);
1652 # If the file is below the test directory then convert it into a link to the file in SVN
1653 if ($relativePath !~ /^\.\.\//) {
1654 my $revision = svnRevisionForDirectory($testDirectory);
1655 my $svnPath = pathRelativeToSVNRepositoryRootForPath($path);
1656 return "http://trac.webkit.org/export/$revision/$svnPath";
1660 return $path unless isCygwin();
1662 return "file:///" . convertPathUsingCygpath($path, "-m");
1665 sub validateSkippedArg($$;$)
1667 my ($option, $value, $value2) = @_;
1668 my %validSkippedValues = map { $_ => 1 } qw(default ignore only);
1669 $value = lc($value);
1670 die "Invalid argument '" . $value . "' for option $option" unless $validSkippedValues{$value};
1671 $treatSkipped = $value;
1674 sub htmlForResultsSection(\@$&)
1676 my ($tests, $description, $linkGetter) = @_;
1679 return join("\n", @html) unless @{$tests};
1681 push @html, "<p>$description:</p>";
1682 push @html, "<table>";
1683 foreach my $test (@{$tests}) {
1685 push @html, "<td><a href=\"" . toURL("$testDirectory/$test") . "\">$test</a></td>";
1686 foreach my $link (@{&{$linkGetter}($test)}) {
1687 push @html, "<td><a href=\"$link->{href}\">$link->{text}</a></td>";
1689 push @html, "</tr>";
1691 push @html, "</table>";
1693 return join("\n", @html);
1696 sub linksForExpectedAndActualResults($)
1702 return \@links unless -s "$testResultsDirectory/$base-$diffsTag.txt";
1704 my $expectedResultPath = $expectedResultPaths{$base};
1705 my ($expectedResultFileName, $expectedResultsDirectory, $expectedResultExtension) = fileparse($expectedResultPath, qr{\.[^.]+$});
1707 push @links, { href => "$base-$expectedTag$expectedResultExtension", text => "expected" };
1708 push @links, { href => "$base-$actualTag$expectedResultExtension", text => "actual" };
1709 push @links, { href => "$base-$diffsTag.txt", text => "diff" };
1710 push @links, { href => "$base-$prettyDiffTag.html", text => "pretty diff" };
1715 sub linksForMismatchTest
1721 my $base = stripExtension($test);
1723 push @links, @{linksForExpectedAndActualResults($base)};
1724 return \@links unless $pixelTests && $imagesPresent{$base};
1726 push @links, { href => "$base-$expectedTag.png", text => "expected image" };
1727 push @links, { href => "$base-$diffsTag.html", text => "image diffs" };
1728 push @links, { href => "$base-$diffsTag.png", text => "$imageDifferences{$base}%" };
1733 sub linksForErrorTest
1739 my $base = stripExtension($test);
1741 push @links, @{linksForExpectedAndActualResults($base)};
1742 push @links, { href => "$base-$errorTag.txt", text => "stderr" };
1753 my $base = stripExtension($test);
1755 my $expectedResultPath = $expectedResultPaths{$base};
1756 my ($expectedResultFileName, $expectedResultsDirectory, $expectedResultExtension) = fileparse($expectedResultPath, qr{\.[^.]+$});
1758 push @links, { href => "$base-$actualTag$expectedResultExtension", text => "result" };
1759 if ($pixelTests && $imagesPresent{$base}) {
1760 push @links, { href => "$base-$expectedTag.png", text => "image" };
1766 sub deleteExpectedAndActualResults($)
1770 unlink "$testResultsDirectory/$base-$actualTag.txt";
1771 unlink "$testResultsDirectory/$base-$diffsTag.txt";
1772 unlink "$testResultsDirectory/$base-$errorTag.txt";
1775 sub recordActualResultsAndDiff($$)
1777 my ($base, $actualResults) = @_;
1779 return unless defined($actualResults) && length($actualResults);
1781 my $expectedResultPath = $expectedResultPaths{$base};
1782 my ($expectedResultFileNameMinusExtension, $expectedResultDirectoryPath, $expectedResultExtension) = fileparse($expectedResultPath, qr{\.[^.]+$});
1783 my $actualResultsPath = "$testResultsDirectory/$base-$actualTag$expectedResultExtension";
1784 my $copiedExpectedResultsPath = "$testResultsDirectory/$base-$expectedTag$expectedResultExtension";
1786 mkpath(dirname($actualResultsPath));
1787 writeToFile("$actualResultsPath", $actualResults);
1789 if (-f $expectedResultPath) {
1790 copy("$expectedResultPath", "$copiedExpectedResultsPath");
1792 open EMPTY, ">$copiedExpectedResultsPath";
1796 my $diffOuputBasePath = "$testResultsDirectory/$base";
1797 my $diffOutputPath = "$diffOuputBasePath-$diffsTag.txt";
1798 system "diff -u \"$copiedExpectedResultsPath\" \"$actualResultsPath\" > \"$diffOutputPath\"";
1800 my $prettyDiffOutputPath = "$diffOuputBasePath-$prettyDiffTag.html";
1801 my $prettyPatchPath = "BugsSite/PrettyPatch/";
1802 my $prettifyPath = "$prettyPatchPath/prettify.rb";
1803 system "ruby -I \"$prettyPatchPath\" \"$prettifyPath\" \"$diffOutputPath\" > \"$prettyDiffOutputPath\"";
1806 sub buildPlatformResultHierarchy()
1808 mkpath($platformTestDirectory) if ($platform eq "undefined" && !-d "$platformTestDirectory");
1811 if ($platform =~ /^mac-/) {
1813 for ($i = 0; $i < @macPlatforms; $i++) {
1814 last if $macPlatforms[$i] eq $platform;
1816 for (; $i < @macPlatforms; $i++) {
1817 push @platforms, $macPlatforms[$i];
1819 } elsif ($platform =~ /^qt-/) {
1820 push @platforms, $platform;
1821 push @platforms, "qt";
1823 @platforms = $platform;
1827 for (my $i = 0; $i < @platforms; $i++) {
1828 my $scoped = catdir($platformBaseDirectory, $platforms[$i]);
1829 push(@hierarchy, $scoped) if (-d $scoped);
1835 sub buildPlatformTestHierarchy(@)
1837 my (@platformHierarchy) = @_;
1838 return @platformHierarchy if (@platformHierarchy < 2);
1840 return ($platformHierarchy[0], $platformHierarchy[$#platformHierarchy]);
1843 sub epiloguesAndPrologues($$)
1845 my ($lastDirectory, $directory) = @_;
1846 my @lastComponents = split('/', $lastDirectory);
1847 my @components = split('/', $directory);
1849 while (@lastComponents) {
1850 if (!defined($components[0]) || $lastComponents[0] ne $components[0]) {
1854 shift @lastComponents;
1858 my $leaving = $lastDirectory;
1859 foreach (@lastComponents) {
1860 my $epilogue = $leaving . "/resources/run-webkit-tests-epilogue.html";
1861 foreach (@platformResultHierarchy) {
1862 push @result, catdir($_, $epilogue) if (stat(catdir($_, $epilogue)));
1864 push @result, catdir($testDirectory, $epilogue) if (stat(catdir($testDirectory, $epilogue)));
1865 $leaving =~ s|(^\|/)[^/]+$||;
1868 my $entering = $leaving;
1869 foreach (@components) {
1870 $entering .= '/' . $_;
1871 my $prologue = $entering . "/resources/run-webkit-tests-prologue.html";
1872 push @result, catdir($testDirectory, $prologue) if (stat(catdir($testDirectory, $prologue)));
1873 foreach (reverse @platformResultHierarchy) {
1874 push @result, catdir($_, $prologue) if (stat(catdir($_, $prologue)));
1880 sub parseLeaksandPrintUniqueLeaks()
1882 return unless @leaksFilenames;
1884 my $mergedFilenames = join " ", @leaksFilenames;
1885 my $parseMallocHistoryTool = sourceDir() . "/WebKitTools/Scripts/parse-malloc-history";
1887 open MERGED_LEAKS, "cat $mergedFilenames | $parseMallocHistoryTool --merge-depth $mergeDepth - |" ;
1888 my @leakLines = <MERGED_LEAKS>;
1891 my $uniqueLeakCount = 0;
1893 foreach my $line (@leakLines) {
1894 ++$uniqueLeakCount if ($line =~ /^(\d*)\scalls/);
1895 $totalBytes = $1 if $line =~ /^total\:\s(.*)\s\(/;
1898 print "\nWARNING: $totalLeaks total leaks found for a total of $totalBytes!\n";
1899 print "WARNING: $uniqueLeakCount unique leaks found!\n";
1900 print "See above for individual leaks results.\n" if ($leaksOutputFileNumber > 2);
1904 sub extensionForMimeType($)
1906 my ($mimeType) = @_;
1908 if ($mimeType eq "application/x-webarchive") {
1909 return "webarchive";
1910 } elsif ($mimeType eq "application/pdf") {
1916 # Read up to the first #EOF (the content block of the test), or until detecting crashes or timeouts.
1917 sub readFromDumpToolWithTimer(**)
1919 my ($fhIn, $fhError) = @_;
1921 setFileHandleNonBlocking($fhIn, 1);
1922 setFileHandleNonBlocking($fhError, 1);
1924 my $maximumSecondsWithoutOutput = $timeoutSeconds;
1925 $maximumSecondsWithoutOutput *= 10 if $guardMalloc;
1926 my $microsecondsToWaitBeforeReadingAgain = 1000;
1928 my $timeOfLastSuccessfulRead = time;
1932 my $status = "success";
1933 my $mimeType = "text/plain";
1934 # We don't have a very good way to know when the "headers" stop
1935 # and the content starts, so we use this as a hack:
1936 my $haveSeenContentType = 0;
1937 my $haveSeenEofIn = 0;
1938 my $haveSeenEofError = 0;
1941 if (time - $timeOfLastSuccessfulRead > $maximumSecondsWithoutOutput) {
1942 $status = dumpToolDidCrash() ? "crashed" : "timedOut";
1946 # Once we've seen the EOF, we must not read anymore.
1947 my $lineIn = readline($fhIn) unless $haveSeenEofIn;
1948 my $lineError = readline($fhError) unless $haveSeenEofError;
1949 if (!defined($lineIn) && !defined($lineError)) {
1950 last if ($haveSeenEofIn && $haveSeenEofError);
1953 $status = "crashed";
1958 usleep($microsecondsToWaitBeforeReadingAgain);
1962 $timeOfLastSuccessfulRead = time;
1964 if (defined($lineIn)) {
1965 if (!$haveSeenContentType && $lineIn =~ /^Content-Type: (\S+)$/) {
1967 $haveSeenContentType = 1;
1968 } elsif ($lineIn =~ /#EOF/) {
1971 push @output, $lineIn;
1974 if (defined($lineError)) {
1975 if ($lineError =~ /#EOF/) {
1976 $haveSeenEofError = 1;
1978 push @error, $lineError;
1983 setFileHandleNonBlocking($fhIn, 0);
1984 setFileHandleNonBlocking($fhError, 0);
1986 output => join("", @output),
1987 error => join("", @error),
1989 mimeType => $mimeType,
1990 extension => extensionForMimeType($mimeType)
1994 sub setFileHandleNonBlocking(*$)
1996 my ($fh, $nonBlocking) = @_;
1998 my $flags = fcntl($fh, F_GETFL, 0) or die "Couldn't get filehandle flags";
2001 $flags |= O_NONBLOCK;
2003 $flags &= ~O_NONBLOCK;
2006 fcntl($fh, F_SETFL, $flags) or die "Couldn't set filehandle flags";
2011 sub sampleDumpTool()
2013 return unless isAppleMacWebKit();
2014 return unless $runSample;
2016 my $outputDirectory = "$ENV{HOME}/Library/Logs/DumpRenderTree";
2017 -d $outputDirectory or mkdir $outputDirectory;
2019 my $outputFile = "$outputDirectory/HangReport.txt";
2020 system "/usr/bin/sample", $dumpToolPID, qw(10 10 -file), $outputFile;
2023 sub stripMetrics($$)
2025 my ($actual, $expected) = @_;
2027 foreach my $result ($actual, $expected) {
2028 $result =~ s/at \(-?[0-9]+,-?[0-9]+\) *//g;
2029 $result =~ s/size -?[0-9]+x-?[0-9]+ *//g;
2030 $result =~ s/text run width -?[0-9]+: //g;
2031 $result =~ s/text run width -?[0-9]+ [a-zA-Z ]+: //g;
2032 $result =~ s/RenderButton {BUTTON} .*/RenderButton {BUTTON}/g;
2033 $result =~ s/RenderImage {INPUT} .*/RenderImage {INPUT}/g;
2034 $result =~ s/RenderBlock {INPUT} .*/RenderBlock {INPUT}/g;
2035 $result =~ s/RenderTextControl {INPUT} .*/RenderTextControl {INPUT}/g;
2036 $result =~ s/\([0-9]+px/px/g;
2037 $result =~ s/ *" *\n +" */ /g;
2038 $result =~ s/" +$/"/g;
2040 $result =~ s/- /-/g;
2041 $result =~ s/\n( *)"\s+/\n$1"/g;
2042 $result =~ s/\s+"\n/"\n/g;
2043 $result =~ s/scrollWidth [0-9]+/scrollWidth/g;
2044 $result =~ s/scrollHeight [0-9]+/scrollHeight/g;
2047 return ($actual, $expected);
2050 sub fileShouldBeIgnored
2052 my ($filePath) = @_;
2053 foreach my $ignoredDir (keys %ignoredDirectories) {
2054 if ($filePath =~ m/^$ignoredDir/) {
2061 sub readSkippedFiles($)
2063 my ($constraintPath) = @_;
2065 foreach my $level (@platformTestHierarchy) {
2066 if (open SKIPPED, "<", "$level/Skipped") {
2068 my ($dir, $name) = splitpath($level);
2069 print "Skipped tests in $name:\n";
2075 $skipped =~ s/^[ \n\r]+//;
2076 $skipped =~ s/[ \n\r]+$//;
2077 if ($skipped && $skipped !~ /^#/) {
2079 if (!fileShouldBeIgnored($skipped)) {
2080 if (!$constraintPath) {
2081 # Always add $skipped since no constraint path was specified on the command line.
2082 push(@ARGV, $skipped);
2083 } elsif ($skipped =~ /^($constraintPath)/) {
2084 # Add $skipped only if it matches the current path constraint, e.g.,
2085 # "--skipped=only dir1" with "dir1/file1.html" on the skipped list.
2086 push(@ARGV, $skipped);
2087 } elsif ($constraintPath =~ /^($skipped)/) {
2088 # Add current path constraint if it is more specific than the skip list entry,
2089 # e.g., "--skipped=only dir1/dir2/dir3" with "dir1" on the skipped list.
2090 push(@ARGV, $constraintPath);
2092 } elsif ($verbose) {
2093 print " $skipped\n";
2097 print " $skipped\n";
2099 processIgnoreTests($skipped, "Skipped");
2112 return () if exists $ignoredLocalDirectories{basename($File::Find::dir)};
2113 return () if exists $ignoredDirectories{File::Spec->abs2rel($File::Find::dir, $testDirectory)};
2120 if ($filename =~ /\.([^.]+)$/) {
2121 if (exists $supportedFileExtensions{$1}) {
2122 my $path = File::Spec->abs2rel(catfile($File::Find::dir, $filename), $testDirectory);
2123 push @testsToRun, $path if !exists $ignoredFiles{$path};
2132 for my $test (@ARGV) {
2133 $test =~ s/^($layoutTestsName|$testDirectory)\///;
2134 my $fullPath = catfile($testDirectory, $test);
2135 if (file_name_is_absolute($test)) {
2136 print "can't run test $test outside $testDirectory\n";
2137 } elsif (-f $fullPath) {
2138 my ($filename, $pathname, $fileExtension) = fileparse($test, qr{\.[^.]+$});
2139 if (!exists $supportedFileExtensions{substr($fileExtension, 1)}) {
2140 print "test $test does not have a supported extension\n";
2141 } elsif ($testHTTP || $pathname !~ /^http\//) {
2142 push @testsToRun, $test;
2144 } elsif (-d $fullPath) {
2145 find({ preprocess => \&directoryFilter, wanted => \&fileFilter }, $fullPath);
2146 for my $level (@platformTestHierarchy) {
2147 my $platformPath = catfile($level, $test);
2148 find({ preprocess => \&directoryFilter, wanted => \&fileFilter }, $platformPath) if (-d $platformPath);
2151 print "test $test not found\n";
2155 if (!scalar @ARGV) {
2156 find({ preprocess => \&directoryFilter, wanted => \&fileFilter }, $testDirectory);
2157 for my $level (@platformTestHierarchy) {
2158 find({ preprocess => \&directoryFilter, wanted => \&fileFilter }, $level);
2162 # Remove duplicate tests
2163 @testsToRun = keys %{{ map { $_ => 1 } @testsToRun }};
2165 @testsToRun = sort pathcmp @testsToRun;
2167 # We need to minimize the time when Apache and WebSocketServer is locked by tests
2168 # so run them last if no explicit order was specified in the argument list.
2169 if (!scalar @ARGV) {
2173 foreach my $test (@testsToRun) {
2174 if ($test =~ /^http\//) {
2175 push(@httpTests, $test);
2176 } elsif ($test =~ /^websocket\//) {
2177 push(@websocketTests, $test);
2179 push(@otherTests, $test);
2182 @testsToRun = (@otherTests, @httpTests, @websocketTests);
2186 @testsToRun = reverse @testsToRun if $reverseTests;
2189 @testsToRun = shuffle(@testsToRun) if $randomizeTests;
2197 match => "succeeded",
2198 mismatch => "had incorrect layout",
2200 timedout => "timed out",
2202 error => "had stderr output"
2205 for my $type ("match", "mismatch", "new", "timedout", "crash", "error") {
2206 my $typeCount = $counts{$type};
2207 next unless $typeCount;
2208 my $typeText = $text{$type};
2210 if ($typeCount == 1) {
2211 $typeText =~ s/were/was/;
2212 $message = sprintf "1 test case (%d%%) %s\n", 1 * 100 / $count, $typeText;
2214 $message = sprintf "%d test cases (%d%%) %s\n", $typeCount, $typeCount * 100 / $count, $typeText;
2216 $message =~ s-\(0%\)-(<1%)-;