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;
160 # Default to --no-http for wx for now.
161 $testHTTP = 0 if (isWx());
163 my $expectedTag = "expected";
164 my $actualTag = "actual";
165 my $prettyDiffTag = "pretty-diff";
166 my $diffsTag = "diffs";
167 my $errorTag = "stderr";
169 my @macPlatforms = ("mac-tiger", "mac-leopard", "mac-snowleopard", "mac");
171 if (isAppleMacWebKit()) {
173 $platform = "mac-tiger";
175 } elsif (isLeopard()) {
176 $platform = "mac-leopard";
178 } elsif (isSnowLeopard()) {
179 $platform = "mac-snowleopard";
186 $platform = "qt-mac";
187 } elsif (isLinux()) {
188 $platform = "qt-linux";
189 } elsif (isWindows() || isCygwin()) {
190 $platform = "qt-win";
196 if (!$ENV{"WEBKIT_TESTFONTS"}) {
197 print "The WEBKIT_TESTFONTS environment variable is not defined.\n";
198 print "You must set it before running the tests.\n";
199 print "Use git to grab the actual fonts from http://gitorious.org/qtwebkit/testfonts\n";
204 } elsif (isCygwin()) {
208 if (!defined($platform)) {
209 print "WARNING: Your platform is not recognized. Any platform-specific results will be generated in platform/undefined.\n";
210 $platform = "undefined";
213 my $programName = basename($0);
214 my $launchSafariDefault = $launchSafari ? "launch" : "do not launch";
215 my $httpDefault = $testHTTP ? "run" : "do not run";
216 my $sampleDefault = $runSample ? "run" : "do not run";
219 Usage: $programName [options] [testdir|testpath ...]
220 --add-platform-exceptions Put new results for non-platform-specific failing tests into the platform-specific results directory
221 --complex-text Use the complex text code path for all text (Mac OS X and Windows only)
222 -c|--configuration config Set DumpRenderTree build configuration
223 -g|--guard-malloc Enable malloc guard
224 --exit-after-n-failures N Exit after the first N failures instead of running all tests
225 -h|--help Show this help message
226 --[no-]http Run (or do not run) http tests (default: $httpDefault)
227 -i|--ignore-tests Comma-separated list of directories or tests to ignore
228 --iterations n Number of times to run the set of tests (e.g. ABCABCABC)
229 --[no-]launch-safari Launch (or do not launch) Safari to display test results (default: $launchSafariDefault)
230 -l|--leaks Enable leaks checking
231 --[no-]new-test-results Generate results for new tests
232 --nthly n Restart DumpRenderTree every n tests (default: $testsPerDumpTool)
233 -p|--pixel-tests Enable pixel tests
234 --tolerance t Ignore image differences less than this percentage (default: $tolerance)
235 --platform Override the detected platform to use for tests and results (default: $platform)
236 --port Web server port to use with http tests
237 -q|--quiet Less verbose output
238 --reset-results Reset ALL results (including pixel tests if --pixel-tests is set)
239 -o|--results-directory Output results directory (default: $testResultsDirectory)
240 --random Run the tests in a random order
241 --repeat-each n Number of times to run each test (e.g. AAABBBCCC)
242 --reverse Run the tests in reverse alphabetical order
243 --root Path to root tools build
244 --[no-]sample-on-timeout Run sample on timeout (default: $sampleDefault) (Mac OS X only)
245 -1|--singly Isolate each test case run (implies --nthly 1 --verbose)
246 --skipped=[default|ignore|only] Specifies how to treat the Skipped file
247 default: Tests/directories listed in the Skipped file are not tested
248 ignore: The Skipped file is ignored
249 only: Only those tests/directories listed in the Skipped file will be run
250 --slowest Report the 10 slowest tests
251 --ignore-metrics Ignore metrics in tests
252 --[no-]strip-editing-callbacks Remove editing callbacks from expected results
253 -t|--threaded Run a concurrent JavaScript thead with each test
254 --timeout t Sets the number of seconds before a test times out (default: $timeoutSeconds)
255 --valgrind Run DumpRenderTree inside valgrind (Qt/Linux only)
256 -v|--verbose More verbose output (overrides --quiet)
257 -m|--merge-leak-depth arg Merges leak callStacks and prints the number of unique leaks beneath a callstack depth of arg. Defaults to 5.
258 --use-remote-links-to-tests Link to test files within the SVN repository in the results.
263 my $getOptionsResult = GetOptions(
264 'add-platform-exceptions' => \$addPlatformExceptions,
265 'complex-text' => \$complexText,
266 'exit-after-n-failures=i' => \$exitAfterNFailures,
267 'guard-malloc|g' => \$guardMalloc,
268 'help|h' => \$showHelp,
269 'http!' => \$testHTTP,
270 'ignore-metrics!' => \$ignoreMetrics,
271 'ignore-tests|i=s' => \$ignoreTests,
272 'iterations=i' => \$iterations,
273 'launch-safari!' => \$launchSafari,
274 'leaks|l' => \$shouldCheckLeaks,
275 'merge-leak-depth|m:5' => \$mergeDepth,
276 'new-test-results!' => \$generateNewResults,
277 'nthly=i' => \$testsPerDumpTool,
278 'pixel-tests|p' => \$pixelTests,
279 'platform=s' => \$platform,
280 'port=i' => \$httpdPort,
281 'quiet|q' => \$quiet,
282 'random' => \$randomizeTests,
283 'repeat-each=i' => \$repeatEach,
284 'reset-results' => \$resetResults,
285 'results-directory|o=s' => \$testResultsDirectory,
286 'reverse' => \$reverseTests,
288 'sample-on-timeout!' => \$runSample,
289 'singly|1' => sub { $testsPerDumpTool = 1; },
290 'skipped=s' => \&validateSkippedArg,
291 'slowest' => \$report10Slowest,
292 'strip-editing-callbacks!' => \$stripEditingCallbacks,
293 'threaded|t' => \$threaded,
294 'timeout=i' => \$timeoutSeconds,
295 'tolerance=f' => \$tolerance,
296 'use-remote-links-to-tests' => \$useRemoteLinksToTests,
297 'valgrind' => \$useValgrind,
298 'verbose|v' => \$verbose,
301 if (!$getOptionsResult || $showHelp) {
306 my $ignoreSkipped = $treatSkipped eq "ignore";
307 my $skippedOnly = $treatSkipped eq "only";
309 my $configuration = configuration();
311 $verbose = 1 if $testsPerDumpTool == 1;
313 if ($shouldCheckLeaks && $testsPerDumpTool > 1000) {
314 print STDERR "\nWARNING: Running more than 1000 tests at a time with MallocStackLogging enabled may cause a crash.\n\n";
317 # Stack logging does not play well with QuickTime on Tiger (rdar://problem/5537157)
318 $testMedia = 0 if $shouldCheckLeaks && isTiger();
320 # Generating remote links causes a lot of unnecessary spew on GTK build bot
321 $useRemoteLinksToTests = 0 if isGtk();
323 setConfigurationProductDir(Cwd::abs_path($root)) if (defined($root));
324 my $productDir = productDir();
325 $productDir .= "/bin" if isQt();
326 $productDir .= "/Programs" if isGtk();
330 if (!defined($root)) {
331 print STDERR "Running build-dumprendertree\n";
334 my ($childIn, $childOut, $childErr);
336 open(DEVNULL, ">", File::Spec->devnull()) or die "Failed to open /dev/null";
337 $childOut = ">&DEVNULL";
338 $childErr = ">&DEVNULL";
340 # When not quiet, let the child use our stdout/stderr.
341 $childOut = ">&STDOUT";
342 $childErr = ">&STDERR";
345 my @args = argumentsForConfiguration();
346 my $buildProcess = open3($childIn, $childOut, $childErr, "WebKitTools/Scripts/build-dumprendertree", @args) or die "Failed to run build-dumprendertree";
348 waitpid $buildProcess, 0;
349 my $buildResult = $?;
353 close DEVNULL if ($quiet);
356 print STDERR "Compiling DumpRenderTree failed!\n";
357 exit exitStatus($buildResult);
361 my $dumpToolName = "DumpRenderTree";
362 $dumpToolName .= "_debug" if isCygwin() && configurationForVisualStudio() !~ /^Release|Debug_Internal$/;
363 my $dumpTool = "$productDir/$dumpToolName";
364 die "can't find executable $dumpToolName (looked in $productDir)\n" unless -x $dumpTool;
366 my $imageDiffTool = "$productDir/ImageDiff";
367 $imageDiffTool .= "_debug" if isCygwin() && configurationForVisualStudio() !~ /^Release|Debug_Internal$/;
368 die "can't find executable $imageDiffTool (looked in $productDir)\n" if $pixelTests && !-x $imageDiffTool;
370 checkFrameworks() unless isCygwin();
372 if (isAppleMacWebKit()) {
373 push @INC, $productDir;
374 require DumpRenderTreeSupport;
377 my $layoutTestsName = "LayoutTests";
378 my $testDirectory = File::Spec->rel2abs($layoutTestsName);
379 my $expectedDirectory = $testDirectory;
380 my $platformBaseDirectory = catdir($testDirectory, "platform");
381 my $platformTestDirectory = catdir($platformBaseDirectory, $platform);
382 my @platformResultHierarchy = buildPlatformResultHierarchy();
383 my @platformTestHierarchy = buildPlatformTestHierarchy(@platformResultHierarchy);
385 $expectedDirectory = $ENV{"WebKitExpectedTestResultsDirectory"} if $ENV{"WebKitExpectedTestResultsDirectory"};
387 $testResultsDirectory = File::Spec->rel2abs($testResultsDirectory);
388 my $testResults = File::Spec->catfile($testResultsDirectory, "results.html");
390 print "Running tests from $testDirectory\n";
392 print "Enabling pixel tests with a tolerance of $tolerance%\n";
394 print "WARNING: Temporarily changing the main display color profile:\n";
395 print "\tThe colors on your screen will change for the duration of the testing.\n";
396 print "\tThis allows the pixel tests to have consistent color values across all machines.\n";
398 if (isPerianInstalled()) {
399 print "WARNING: Perian's QuickTime component is installed and this may affect pixel test results!\n";
400 print "\tYou should avoid generating new pixel results in this environment.\n";
401 print "\tSee https://bugs.webkit.org/show_bug.cgi?id=22615 for details.\n";
406 system "ln", "-s", $testDirectory, "/tmp/LayoutTests" unless -x "/tmp/LayoutTests";
408 my %ignoredFiles = ( "results.html" => 1 );
409 my %ignoredDirectories = map { $_ => 1 } qw(platform);
410 my %ignoredLocalDirectories = map { $_ => 1 } qw(.svn _svn resources script-tests);
411 my %supportedFileExtensions = map { $_ => 1 } qw(html shtml xml xhtml pl php);
413 if (!checkWebCoreMathMLSupport(0)) {
414 $ignoredDirectories{'mathml'} = 1;
417 # FIXME: We should fix webkitdirs.pm:hasSVG/WMLSupport() to do the correct feature detection for Cygwin.
418 if (checkWebCoreSVGSupport(0)) {
419 $supportedFileExtensions{'svg'} = 1;
420 } elsif (isCygwin()) {
421 $supportedFileExtensions{'svg'} = 1;
423 $ignoredLocalDirectories{'svg'} = 1;
427 $ignoredDirectories{'http'} = 1;
428 $ignoredDirectories{'websocket'} = 1;
432 $ignoredDirectories{'media'} = 1;
433 $ignoredDirectories{'http/tests/media'} = 1;
436 if (!checkWebCoreAcceleratedCompositingSupport(0)) {
437 $ignoredDirectories{'compositing'} = 1;
440 if (!checkWebCore3DRenderingSupport(0)) {
441 $ignoredDirectories{'animations/3d'} = 1;
442 $ignoredDirectories{'transforms/3d'} = 1;
445 if (!checkWebCore3DCanvasSupport(0)) {
446 $ignoredDirectories{'fast/canvas/webgl'} = 1;
449 if (checkWebCoreWMLSupport(0)) {
450 $supportedFileExtensions{'wml'} = 1;
452 $ignoredDirectories{'http/tests/wml'} = 1;
453 $ignoredDirectories{'fast/wml'} = 1;
454 $ignoredDirectories{'wml'} = 1;
457 if (!checkWebCoreXHTMLMPSupport(0)) {
458 $ignoredDirectories{'fast/xhtmlmp'} = 1;
461 if (!checkWebCoreWCSSSupport(0)) {
462 $ignoredDirectories{'fast/wcss'} = 1;
465 processIgnoreTests($ignoreTests, "ignore-tests") if $ignoreTests;
466 if (!$ignoreSkipped) {
467 if (!$skippedOnly || @ARGV == 0) {
468 readSkippedFiles("");
470 # Since readSkippedFiles() appends to @ARGV, we must use a foreach
471 # loop so that we only iterate over the original argument list.
472 foreach my $argnum (0 .. $#ARGV) {
473 readSkippedFiles(shift @ARGV);
478 my @tests = findTestsToRun();
480 die "no tests to run\n" if !@tests;
485 my %imageDifferences;
488 my $leaksOutputFileNumber = 1;
492 push @toolArgs, "--pixel-tests" if $pixelTests;
493 push @toolArgs, "--threaded" if $threaded;
494 push @toolArgs, "--complex-text" if $complexText;
497 my @diffToolArgs = ();
498 push @diffToolArgs, "--tolerance", $tolerance;
503 my $isDumpToolOpen = 0;
504 my $dumpToolCrashed = 0;
505 my $imageDiffToolPID;
506 my $isDiffToolOpen = 0;
509 my $lastDirectory = "";
512 my $isWebSocketServerOpen = 0;
513 my $webSocketServerPID = 0;
514 my $failedToStartWebSocketServer = 0;
515 # wss is disabled until all platforms support pyOpenSSL.
516 # my $webSocketSecureServerPID = 0;
518 sub catch_pipe { $dumpToolCrashed = 1; }
519 $SIG{"PIPE"} = "catch_pipe";
521 print "Testing ", scalar @tests, " test cases";
522 print " $iterations times" if ($iterations > 1);
523 print ", repeating each test $repeatEach times" if ($repeatEach > 1);
526 my $overallStartTime = time;
528 my %expectedResultPaths;
530 my @originalTests = @tests;
531 # Add individual test repetitions
532 if ($repeatEach > 1) {
534 foreach my $test (@originalTests) {
535 for (my $i = 0; $i < $repeatEach; $i++) {
540 # Add test set repetitions
541 for (my $i = 1; $i < $iterations; $i++) {
542 push(@tests, @originalTests);
545 for my $test (@tests) {
546 my $newDumpTool = not $isDumpToolOpen;
549 my $base = stripExtension($test);
550 my $expectedExtension = ".txt";
555 if ($newDumpTool || $dir ne $lastDirectory) {
556 foreach my $logue (epiloguesAndPrologues($newDumpTool ? "" : $lastDirectory, $dir)) {
558 $logue = toWindowsPath($logue);
560 $logue = canonpath($logue);
563 print "running epilogue or prologue $logue\n";
565 print OUT "$logue\n";
566 # Throw away output from DumpRenderTree.
567 # Once for the test output and once for pixel results (empty)
578 print "running $test -> ";
581 if ($dir ne $lastDirectory) {
582 print "\n" unless $atLineStart;
589 $lastDirectory = $dir;
593 my $startTime = time if $report10Slowest;
595 # Try to read expected hash file for pixel tests
596 my $suffixExpectedHash = "";
597 if ($pixelTests && !$resetResults) {
598 my $expectedPixelDir = expectedDirectoryForTest($base, 0, "png");
599 if (open EXPECTEDHASH, "$expectedPixelDir/$base-$expectedTag.checksum") {
600 my $expectedHash = <EXPECTEDHASH>;
601 chomp($expectedHash);
604 # Format expected hash into a suffix string that is appended to the path / URL passed to DRT
605 $suffixExpectedHash = "'$expectedHash";
609 if ($test =~ /^http\//) {
610 configureAndOpenHTTPDIfNeeded();
611 if ($test !~ /^http\/tests\/local\// && $test !~ /^http\/tests\/ssl\// && $test !~ /^http\/tests\/wml\// && $test !~ /^http\/tests\/media\//) {
612 my $path = canonpath($test);
613 $path =~ s/^http\/tests\///;
614 print OUT "http://127.0.0.1:$httpdPort/$path$suffixExpectedHash\n";
615 } elsif ($test =~ /^http\/tests\/ssl\//) {
616 my $path = canonpath($test);
617 $path =~ s/^http\/tests\///;
618 print OUT "https://127.0.0.1:$httpdSSLPort/$path$suffixExpectedHash\n";
620 my $testPath = "$testDirectory/$test";
622 $testPath = toWindowsPath($testPath);
624 $testPath = canonpath($testPath);
626 print OUT "$testPath$suffixExpectedHash\n";
628 } elsif ($test =~ /^websocket\//) {
629 if ($test =~ /^websocket\/tests\/local\//) {
630 my $testPath = "$testDirectory/$test";
632 $testPath = toWindowsPath($testPath);
634 $testPath = canonpath($testPath);
636 print OUT "$testPath\n";
638 if (openWebSocketServerIfNeeded()) {
639 my $path = canonpath($test);
640 if ($test =~ /^websocket\/tests\/ssl\//) {
641 # wss is disabled until all platforms support pyOpenSSL.
642 print STDERR "Error: wss is disabled until all platforms support pyOpenSSL.";
643 # print OUT "https://127.0.0.1:$webSocketSecurePort/$path\n";
645 print OUT "http://127.0.0.1:$webSocketPort/$path\n";
648 # We failed to launch the WebSocket server. Display a useful error message rather than attempting
649 # to run tests that expect the server to be available.
650 my $errorMessagePath = "$testDirectory/websocket/resources/server-failed-to-start.html";
651 $errorMessagePath = isCygwin() ? toWindowsPath($errorMessagePath) : canonpath($errorMessagePath);
652 print OUT "$errorMessagePath\n";
656 my $testPath = "$testDirectory/$test";
658 $testPath = toWindowsPath($testPath);
660 $testPath = canonpath($testPath);
662 print OUT "$testPath$suffixExpectedHash\n" if defined $testPath;
665 # DumpRenderTree is expected to dump two "blocks" to stdout for each test.
666 # Each block is terminated by a #EOF on a line by itself.
667 # The first block is the output of the test (in text, RenderTree or other formats).
668 # The second block is for optional pixel data in PNG format, and may be empty if
669 # pixel tests are not being run, or the test does not dump pixels (e.g. text tests).
670 my $readResults = readFromDumpToolWithTimer(IN, ERROR);
672 my $actual = $readResults->{output};
673 my $error = $readResults->{error};
675 $expectedExtension = $readResults->{extension};
676 my $expectedFileName = "$base-$expectedTag.$expectedExtension";
678 my $isText = isTextOnlyTest($actual);
680 my $expectedDir = expectedDirectoryForTest($base, $isText, $expectedExtension);
681 $expectedResultPaths{$base} = "$expectedDir/$expectedFileName";
683 unless ($readResults->{status} eq "success") {
684 my $crashed = $readResults->{status} eq "crashed";
685 testCrashedOrTimedOut($test, $base, $crashed, $actual, $error);
686 countFinishedTest($test, $base, $crashed ? "crash" : "timedout", 0);
690 $durations{$test} = time - $startTime if $report10Slowest;
694 if (!$resetResults && open EXPECTED, "<", "$expectedDir/$expectedFileName") {
697 next if $stripEditingCallbacks && $_ =~ /^EDITING DELEGATE:/;
703 if ($ignoreMetrics && !$isText && defined $expected) {
704 ($actual, $expected) = stripMetrics($actual, $expected);
707 if ($shouldCheckLeaks && $testsPerDumpTool == 1) {
713 my $diffPercentage = 0;
714 my $diffResult = "passed";
717 my $expectedHash = "";
718 my $actualPNGSize = 0;
722 if (/ActualHash: ([a-f0-9]{32})/) {
724 } elsif (/ExpectedHash: ([a-f0-9]{32})/) {
726 } elsif (/Content-Length: (\d+)\s*/) {
728 read(IN, $actualPNG, $actualPNGSize);
732 if ($verbose && $pixelTests && !$resetResults && $actualPNGSize) {
733 if ($actualHash eq "" && $expectedHash eq "") {
734 printFailureMessageForTest($test, "WARNING: actual & expected pixel hashes are missing!");
735 } elsif ($actualHash eq "") {
736 printFailureMessageForTest($test, "WARNING: actual pixel hash is missing!");
737 } elsif ($expectedHash eq "") {
738 printFailureMessageForTest($test, "WARNING: expected pixel hash is missing!");
742 if ($actualPNGSize > 0) {
743 my $expectedPixelDir = expectedDirectoryForTest($base, 0, "png");
745 if (!$resetResults && ($expectedHash ne $actualHash || ($actualHash eq "" && $expectedHash eq ""))) {
746 if (-f "$expectedPixelDir/$base-$expectedTag.png") {
747 my $expectedPNGSize = -s "$expectedPixelDir/$base-$expectedTag.png";
748 my $expectedPNG = "";
749 open EXPECTEDPNG, "$expectedPixelDir/$base-$expectedTag.png";
750 read(EXPECTEDPNG, $expectedPNG, $expectedPNGSize);
753 print DIFFOUT "Content-Length: $actualPNGSize\n";
754 print DIFFOUT $actualPNG;
756 print DIFFOUT "Content-Length: $expectedPNGSize\n";
757 print DIFFOUT $expectedPNG;
760 last if /^error/ || /^diff:/;
761 if (/Content-Length: (\d+)\s*/) {
762 read(DIFFIN, $diffPNG, $1);
766 if (/^diff: (.+)% (passed|failed)/) {
767 $diffPercentage = $1 + 0;
768 $imageDifferences{$base} = $diffPercentage;
772 if (!$diffPercentage) {
773 printFailureMessageForTest($test, "pixel hash failed (but pixel test still passes)");
776 printFailureMessageForTest($test, "WARNING: expected image is missing!");
780 if ($resetResults || !-f "$expectedPixelDir/$base-$expectedTag.png") {
781 mkpath catfile($expectedPixelDir, dirname($base)) if $testDirectory ne $expectedPixelDir;
782 writeToFile("$expectedPixelDir/$base-$expectedTag.png", $actualPNG);
785 if ($actualHash ne "" && ($resetResults || !-f "$expectedPixelDir/$base-$expectedTag.checksum")) {
786 writeToFile("$expectedPixelDir/$base-$expectedTag.checksum", $actualHash);
790 if (dumpToolDidCrash()) {
792 testCrashedOrTimedOut($test, $base, 1, $actual, $error);
793 } elsif (!defined $expected) {
795 print "new " . ($resetResults ? "result" : "test") ."\n";
800 if ($generateNewResults || $resetResults) {
801 mkpath catfile($expectedDir, dirname($base)) if $testDirectory ne $expectedDir;
802 writeToFile("$expectedDir/$expectedFileName", $actual);
804 deleteExpectedAndActualResults($base);
805 recordActualResultsAndDiff($base, $actual);
806 if (!$resetResults) {
807 # Always print the file name for new tests, as they will probably need some manual inspection.
808 # in verbose mode we already printed the test case, so no need to do it again.
810 print "\n" unless $atLineStart;
813 my $resultsDir = catdir($expectedDir, dirname($base));
814 if ($generateNewResults) {
815 print "new (results generated in $resultsDir)\n";
821 } elsif ($actual eq $expected && $diffResult eq "passed") {
827 deleteExpectedAndActualResults($base);
829 $result = "mismatch";
831 my $pixelTestFailed = $pixelTests && $diffPNG && $diffPNG ne "";
832 my $testFailed = $actual ne $expected;
834 my $message = !$testFailed ? "pixel test failed" : "failed";
836 if (($testFailed || $pixelTestFailed) && $addPlatformExceptions) {
837 my $testBase = catfile($testDirectory, $base);
838 my $expectedBase = catfile($expectedDir, $base);
839 my $testIsMaximallyPlatformSpecific = $testBase =~ m|^\Q$platformTestDirectory\E/|;
840 my $expectedResultIsMaximallyPlatformSpecific = $expectedBase =~ m|^\Q$platformTestDirectory\E/|;
841 if (!$testIsMaximallyPlatformSpecific && !$expectedResultIsMaximallyPlatformSpecific) {
842 mkpath catfile($platformTestDirectory, dirname($base));
844 my $expectedFile = catfile($platformTestDirectory, "$expectedFileName");
845 writeToFile("$expectedFile", $actual);
847 if ($pixelTestFailed) {
848 my $expectedFile = catfile($platformTestDirectory, "$base-$expectedTag.checksum");
849 writeToFile("$expectedFile", $actualHash);
851 $expectedFile = catfile($platformTestDirectory, "$base-$expectedTag.png");
852 writeToFile("$expectedFile", $actualPNG);
854 $message .= " (results generated in $platformTestDirectory)";
858 printFailureMessageForTest($test, $message);
860 my $dir = "$testResultsDirectory/$base";
861 $dir =~ s|/([^/]+)$|| or die "Failed to find test name from base\n";
865 deleteExpectedAndActualResults($base);
866 recordActualResultsAndDiff($base, $actual);
868 if ($pixelTestFailed) {
869 $imagesPresent{$base} = 1;
871 writeToFile("$testResultsDirectory/$base-$actualTag.png", $actualPNG);
872 writeToFile("$testResultsDirectory/$base-$diffsTag.png", $diffPNG);
874 my $expectedPixelDir = expectedDirectoryForTest($base, 0, "png");
875 copy("$expectedPixelDir/$base-$expectedTag.png", "$testResultsDirectory/$base-$expectedTag.png");
877 open DIFFHTML, ">$testResultsDirectory/$base-$diffsTag.html" or die;
878 print DIFFHTML "<html>\n";
879 print DIFFHTML "<head>\n";
880 print DIFFHTML "<title>$base Image Compare</title>\n";
881 print DIFFHTML "<script language=\"Javascript\" type=\"text/javascript\">\n";
882 print DIFFHTML "var currentImage = 0;\n";
883 print DIFFHTML "var imageNames = new Array(\"Actual\", \"Expected\");\n";
884 print DIFFHTML "var imagePaths = new Array(\"$testName-$actualTag.png\", \"$testName-$expectedTag.png\");\n";
885 if (-f "$testDirectory/$base-w3c.png") {
886 copy("$testDirectory/$base-w3c.png", "$testResultsDirectory/$base-w3c.png");
887 print DIFFHTML "imageNames.push(\"W3C\");\n";
888 print DIFFHTML "imagePaths.push(\"$testName-w3c.png\");\n";
890 print DIFFHTML "function animateImage() {\n";
891 print DIFFHTML " var image = document.getElementById(\"animatedImage\");\n";
892 print DIFFHTML " var imageText = document.getElementById(\"imageText\");\n";
893 print DIFFHTML " image.src = imagePaths[currentImage];\n";
894 print DIFFHTML " imageText.innerHTML = imageNames[currentImage] + \" Image\";\n";
895 print DIFFHTML " currentImage = (currentImage + 1) % imageNames.length;\n";
896 print DIFFHTML " setTimeout('animateImage()',2000);\n";
897 print DIFFHTML "}\n";
898 print DIFFHTML "</script>\n";
899 print DIFFHTML "</head>\n";
900 print DIFFHTML "<body onLoad=\"animateImage();\">\n";
901 print DIFFHTML "<table>\n";
902 if ($diffPercentage) {
903 print DIFFHTML "<tr>\n";
904 print DIFFHTML "<td>Difference between images: <a href=\"$testName-$diffsTag.png\">$diffPercentage%</a></td>\n";
905 print DIFFHTML "</tr>\n";
907 print DIFFHTML "<tr>\n";
908 print DIFFHTML "<td><a href=\"" . toURL("$testDirectory/$test") . "\">test file</a></td>\n";
909 print DIFFHTML "</tr>\n";
910 print DIFFHTML "<tr>\n";
911 print DIFFHTML "<td id=\"imageText\" style=\"text-weight: bold;\">Actual Image</td>\n";
912 print DIFFHTML "</tr>\n";
913 print DIFFHTML "<tr>\n";
914 print DIFFHTML "<td><img src=\"$testName-$actualTag.png\" id=\"animatedImage\"></td>\n";
915 print DIFFHTML "</tr>\n";
916 print DIFFHTML "</table>\n";
917 print DIFFHTML "</body>\n";
918 print DIFFHTML "</html>\n";
923 my $dir = "$testResultsDirectory/$base";
924 $dir =~ s|/([^/]+)$|| or die "Failed to find test name from base\n";
927 writeToFile("$testResultsDirectory/$base-$errorTag.txt", $error);
930 push @{$tests{error}}, $test;
933 countFinishedTest($test, $base, $result, $isText);
935 # --reset-results does not check pass vs. fail, so exitAfterNFailures makes no sense with --reset-results.
936 if ($exitAfterNFailures && !$resetResults) {
937 my $passCount = $counts{match} || 0; # $counts{match} will be undefined if we've not yet passed a test (e.g. the first test fails).
938 my $failureCount = $count - $passCount; # "Failure" here includes new tests, timeouts, crashes, etc.
939 if ($failureCount >= $exitAfterNFailures) {
940 print "\nExiting early after $failureCount failures. $count tests run.";
946 printf "\n%0.2fs total testing time\n", (time - $overallStartTime) . "";
948 !$isDumpToolOpen || die "Failed to close $dumpToolName.\n";
950 $isHttpdOpen = !closeHTTPD();
951 closeWebSocketServer();
953 # Because multiple instances of this script are running concurrently we cannot
954 # safely delete this symlink.
955 # system "rm /tmp/LayoutTests";
957 # FIXME: Do we really want to check the image-comparison tool for leaks every time?
958 if ($isDiffToolOpen && $shouldCheckLeaks) {
959 $totalLeaks += countAndPrintLeaks("ImageDiff", $imageDiffToolPID, "$testResultsDirectory/ImageDiff-leaks.txt");
964 parseLeaksandPrintUniqueLeaks();
966 print "\nWARNING: $totalLeaks total leaks found!\n";
967 print "See above for individual leaks results.\n" if ($leaksOutputFileNumber > 2);
975 if ($report10Slowest) {
976 print "\n\nThe 10 slowest tests:\n\n";
978 for my $test (sort slowestcmp keys %durations) {
979 printf "%0.2f secs: %s\n", $durations{$test}, $test;
980 last if ++$count == 10;
986 if ($skippedOnly && $counts{"match"}) {
987 print "The following tests are in the Skipped file (" . File::Spec->abs2rel("$platformTestDirectory/Skipped", $testDirectory) . "), but succeeded:\n";
988 foreach my $test (@{$tests{"match"}}) {
993 if ($resetResults || ($counts{match} && $counts{match} == $count)) {
994 print "all $count test cases succeeded\n";
1001 mkpath $testResultsDirectory;
1003 open HTML, ">", $testResults or die "Failed to open $testResults. $!";
1004 print HTML "<html>\n";
1005 print HTML "<head>\n";
1006 print HTML "<title>Layout Test Results</title>\n";
1007 print HTML "</head>\n";
1008 print HTML "<body>\n";
1010 if ($ignoreMetrics) {
1011 print HTML "<h4>Tested with metrics ignored.</h4>";
1014 print HTML htmlForResultsSection(@{$tests{mismatch}}, "Tests where results did not match expected results", \&linksForMismatchTest);
1015 print HTML htmlForResultsSection(@{$tests{timedout}}, "Tests that timed out", \&linksForErrorTest);
1016 print HTML htmlForResultsSection(@{$tests{crash}}, "Tests that caused the DumpRenderTree tool to crash", \&linksForErrorTest);
1017 print HTML htmlForResultsSection(@{$tests{error}}, "Tests that had stderr output", \&linksForErrorTest);
1018 print HTML htmlForResultsSection(@{$tests{new}}, "Tests that had no expected results (probably new)", \&linksForNewTest);
1020 print HTML "</body>\n";
1021 print HTML "</html>\n";
1024 my @configurationArgs = argumentsForConfiguration();
1027 system "WebKitTools/Scripts/run-launcher", @configurationArgs, "file://".$testResults if $launchSafari;
1029 unshift @configurationArgs, qw(-graphicssystem raster -style windows);
1031 $testResults = "/" . toWindowsPath($testResults);
1032 $testResults =~ s/\\/\//g;
1034 system "WebKitTools/Scripts/run-launcher", @configurationArgs, "file://".$testResults if $launchSafari;
1035 } elsif (isCygwin()) {
1036 system "cygstart", $testResults if $launchSafari;
1038 system "WebKitTools/Scripts/run-safari", @configurationArgs, "-NSOpen", $testResults if $launchSafari;
1041 closeCygpaths() if isCygwin();
1045 sub countAndPrintLeaks($$$)
1047 my ($dumpToolName, $dumpToolPID, $leaksFilePath) = @_;
1049 print "\n" unless $atLineStart;
1052 # We are excluding the following reported leaks so they don't get in our way when looking for WebKit leaks:
1053 # This allows us ignore known leaks and only be alerted when new leaks occur. Some leaks are in the old
1054 # versions of the system frameworks that are being used by the leaks bots. Even though a leak has been
1055 # fixed, it will be listed here until the bot has been updated with the newer frameworks.
1057 my @typesToExclude = (
1060 my @callStacksToExclude = (
1061 "Flash_EnforceLocalSecurity" # leaks in Flash plug-in code, rdar://problem/4449747
1065 # Leak list for the version of Tiger used on the build bot.
1066 push @callStacksToExclude, (
1067 "CFRunLoopRunSpecific \\| malloc_zone_malloc", "CFRunLoopRunSpecific \\| CFAllocatorAllocate ", # leak in CFRunLoopRunSpecific, rdar://problem/4670839
1068 "CGImageSourceGetPropertiesAtIndex", # leak in ImageIO, rdar://problem/4628809
1069 "FOGetCoveredUnicodeChars", # leak in ATS, rdar://problem/3943604
1070 "GetLineDirectionPreference", "InitUnicodeUtilities", # leaks tool falsely reporting leak in CFNotificationCenterAddObserver, rdar://problem/4964790
1071 "ICCFPrefWrapper::GetPrefDictionary", # leaks in Internet Config. code, rdar://problem/4449794
1072 "NSHTTPURLProtocol setResponseHeader:", # leak in multipart/mixed-replace handling in Foundation, no Radar, but fixed in Leopard
1073 "NSURLCache cachedResponseForRequest", # leak in CFURL cache, rdar://problem/4768430
1074 "PCFragPrepareClosureFromFile", # leak in Code Fragment Manager, rdar://problem/3426998
1075 "WebCore::Selection::toRange", # bug in 'leaks', rdar://problem/4967949
1076 "WebCore::SubresourceLoader::create", # bug in 'leaks', rdar://problem/4985806
1077 "_CFPreferencesDomainDeepCopyDictionary", # leak in CFPreferences, rdar://problem/4220786
1078 "_objc_msgForward", # leak in NSSpellChecker, rdar://problem/4965278
1079 "gldGetString", # leak in OpenGL, rdar://problem/5013699
1080 "_setDefaultUserInfoFromURL", # leak in NSHTTPAuthenticator, rdar://problem/5546453
1081 "SSLHandshake", # leak in SSL, rdar://problem/5546440
1082 "SecCertificateCreateFromData", # leak in SSL code, rdar://problem/4464397
1084 push @typesToExclude, (
1085 "THRD", # bug in 'leaks', rdar://problem/3387783
1086 "DRHT", # ditto (endian little hate i)
1091 # Leak list for the version of Leopard used on the build bot.
1092 push @callStacksToExclude, (
1093 "CFHTTPMessageAppendBytes", # leak in CFNetwork, rdar://problem/5435912
1094 "sendDidReceiveDataCallback", # leak in CFNetwork, rdar://problem/5441619
1095 "_CFHTTPReadStreamReadMark", # leak in CFNetwork, rdar://problem/5441468
1096 "httpProtocolStart", # leak in CFNetwork, rdar://problem/5468837
1097 "_CFURLConnectionSendCallbacks", # leak in CFNetwork, rdar://problem/5441600
1098 "DispatchQTMsg", # leak in QuickTime, PPC only, rdar://problem/5667132
1099 "QTMovieContentView createVisualContext", # leak in QuickTime, PPC only, rdar://problem/5667132
1100 "_CopyArchitecturesForJVMVersion", # leak in Java, rdar://problem/5910823
1104 if (isSnowLeopard()) {
1105 push @callStacksToExclude, (
1106 "readMakerNoteProps", # <rdar://problem/7156432> leak in ImageIO
1107 "QTKitMovieControllerView completeUISetup", # <rdar://problem/7155156> leak in QTKit
1111 my $leaksTool = sourceDir() . "/WebKitTools/Scripts/run-leaks";
1112 my $excludeString = "--exclude-callstack '" . (join "' --exclude-callstack '", @callStacksToExclude) . "'";
1113 $excludeString .= " --exclude-type '" . (join "' --exclude-type '", @typesToExclude) . "'" if @typesToExclude;
1115 print " ? checking for leaks in $dumpToolName\n";
1116 my $leaksOutput = `$leaksTool $excludeString $dumpToolPID`;
1117 my ($count, $bytes) = $leaksOutput =~ /Process $dumpToolPID: (\d+) leaks? for (\d+) total/;
1118 my ($excluded) = $leaksOutput =~ /(\d+) leaks? excluded/;
1120 my $adjustedCount = $count;
1121 $adjustedCount -= $excluded if $excluded;
1123 if (!$adjustedCount) {
1124 print " - no leaks found\n";
1125 unlink $leaksFilePath;
1128 my $dir = $leaksFilePath;
1129 $dir =~ s|/[^/]+$|| or die;
1133 print " + $adjustedCount leaks ($bytes bytes including $excluded excluded leaks) were found, details in $leaksFilePath\n";
1135 print " + $count leaks ($bytes bytes) were found, details in $leaksFilePath\n";
1138 writeToFile($leaksFilePath, $leaksOutput);
1140 push @leaksFilenames, $leaksFilePath;
1143 return $adjustedCount;
1148 my ($filePath, $contents) = @_;
1149 open NEWFILE, ">", "$filePath" or die "Could not create $filePath. $!\n";
1150 print NEWFILE $contents;
1154 # Break up a path into the directory (with slash) and base name.
1159 my $pathSeparator = "/";
1160 my $dirname = dirname($path) . $pathSeparator;
1161 $dirname = "" if $dirname eq "." . $pathSeparator;
1163 return ($dirname, basename($path));
1166 # Sort first by directory, then by file, so all paths in one directory are grouped
1167 # rather than being interspersed with items from subdirectories.
1168 # Use numericcmp to sort directory and filenames to make order logical.
1171 my ($patha, $pathb) = @_;
1173 my ($dira, $namea) = splitpath($patha);
1174 my ($dirb, $nameb) = splitpath($pathb);
1176 return numericcmp($dira, $dirb) if $dira ne $dirb;
1177 return numericcmp($namea, $nameb);
1180 # Sort numeric parts of strings as numbers, other parts as strings.
1181 # Makes 1.33 come after 1.3, which is cool.
1186 my @a = split /(\d+)/, $aa;
1187 my @b = split /(\d+)/, $bb;
1189 # Compare one chunk at a time.
1190 # Each chunk is either all numeric digits, or all not numeric digits.
1195 # Use numeric comparison if chunks are non-equal numbers.
1196 return $a <=> $b if $a =~ /^\d/ && $b =~ /^\d/ && $a != $b;
1198 # Use string comparison if chunks are any other kind of non-equal string.
1199 return $a cmp $b if $a ne $b;
1202 # One of the two is now empty; compare lengths for result in this case.
1206 # Sort slowest tests first.
1209 my ($testa, $testb) = @_;
1211 my $dura = $durations{$testa};
1212 my $durb = $durations{$testb};
1213 return $durb <=> $dura if $dura != $durb;
1214 return pathcmp($testa, $testb);
1217 sub launchWithEnv(\@\%)
1219 my ($args, $env) = @_;
1221 # Dump the current environment as perl code and then put it in quotes so it is one parameter.
1222 my $environmentDumper = Data::Dumper->new([\%{$env}], [qw(*ENV)]);
1223 $environmentDumper->Indent(0);
1224 $environmentDumper->Purity(1);
1225 my $allEnvVars = $environmentDumper->Dump();
1226 unshift @{$args}, "\"$allEnvVars\"";
1228 my $execScript = File::Spec->catfile(sourceDir(), qw(WebKitTools Scripts execAppWithEnv));
1229 unshift @{$args}, $execScript;
1233 sub resolveAndMakeTestResultsDirectory()
1235 my $absTestResultsDirectory = File::Spec->rel2abs(glob $testResultsDirectory);
1236 mkpath $absTestResultsDirectory;
1237 return $absTestResultsDirectory;
1242 return if $isDiffToolOpen;
1243 return if !$pixelTests;
1246 $CLEAN_ENV{MallocStackLogging} = 1 if $shouldCheckLeaks;
1247 $imageDiffToolPID = open2(\*DIFFIN, \*DIFFOUT, $imageDiffTool, launchWithEnv(@diffToolArgs, %CLEAN_ENV)) or die "unable to open $imageDiffTool\n";
1248 $isDiffToolOpen = 1;
1253 return if $isDumpToolOpen;
1257 # Generic environment variables
1258 if (defined $ENV{'WEBKIT_TESTFONTS'}) {
1259 $CLEAN_ENV{WEBKIT_TESTFONTS} = $ENV{'WEBKIT_TESTFONTS'};
1262 $CLEAN_ENV{XML_CATALOG_FILES} = ""; # work around missing /etc/catalog <rdar://problem/4292995>
1264 # Platform spesifics
1266 if (defined $ENV{'DISPLAY'}) {
1267 $CLEAN_ENV{DISPLAY} = $ENV{'DISPLAY'};
1269 $CLEAN_ENV{DISPLAY} = ":1";
1271 if (defined $ENV{'XAUTHORITY'}) {
1272 $CLEAN_ENV{XAUTHORITY} = $ENV{'XAUTHORITY'};
1275 $CLEAN_ENV{HOME} = $ENV{'HOME'};
1277 if (defined $ENV{'LD_LIBRARY_PATH'}) {
1278 $CLEAN_ENV{LD_LIBRARY_PATH} = $ENV{'LD_LIBRARY_PATH'};
1280 if (defined $ENV{'DBUS_SESSION_BUS_ADDRESS'}) {
1281 $CLEAN_ENV{DBUS_SESSION_BUS_ADDRESS} = $ENV{'DBUS_SESSION_BUS_ADDRESS'};
1283 } elsif (isDarwin()) {
1284 if (defined $ENV{'DYLD_LIBRARY_PATH'}) {
1285 $CLEAN_ENV{DYLD_LIBRARY_PATH} = $ENV{'DYLD_LIBRARY_PATH'};
1288 $CLEAN_ENV{DYLD_FRAMEWORK_PATH} = $productDir;
1289 $CLEAN_ENV{DYLD_INSERT_LIBRARIES} = "/usr/lib/libgmalloc.dylib" if $guardMalloc;
1290 } elsif (isCygwin()) {
1291 $CLEAN_ENV{HOMEDRIVE} = $ENV{'HOMEDRIVE'};
1292 $CLEAN_ENV{HOMEPATH} = $ENV{'HOMEPATH'};
1294 setPathForRunningWebKitApp(\%CLEAN_ENV);
1299 $CLEAN_ENV{QTWEBKIT_PLUGIN_PATH} = productDir() . "/lib/plugins";
1302 my @args = ($dumpTool, @toolArgs);
1303 if (isAppleMacWebKit() and !isTiger()) {
1304 unshift @args, "arch", "-" . architecture();
1308 unshift @args, "valgrind", "--suppressions=$platformBaseDirectory/qt/SuppressedValgrindErrors";
1311 $CLEAN_ENV{MallocStackLogging} = 1 if $shouldCheckLeaks;
1313 $dumpToolPID = open3(\*OUT, \*IN, \*ERROR, launchWithEnv(@args, %CLEAN_ENV)) or die "Failed to start tool: $dumpTool\n";
1314 $isDumpToolOpen = 1;
1315 $dumpToolCrashed = 0;
1320 return if !$isDumpToolOpen;
1324 waitpid $dumpToolPID, 0;
1326 # check for WebCore counter leaks.
1327 if ($shouldCheckLeaks) {
1333 $isDumpToolOpen = 0;
1336 sub dumpToolDidCrash()
1338 return 1 if $dumpToolCrashed;
1339 return 0 unless $isDumpToolOpen;
1340 my $pid = waitpid(-1, WNOHANG);
1341 return 1 if ($pid == $dumpToolPID);
1343 # On Mac OS X, crashing may be significantly delayed by crash reporter.
1344 return 0 unless isAppleMacWebKit();
1346 return DumpRenderTreeSupport::processIsCrashing($dumpToolPID);
1349 sub configureAndOpenHTTPDIfNeeded()
1351 return if $isHttpdOpen;
1352 my $absTestResultsDirectory = resolveAndMakeTestResultsDirectory();
1353 my $listen = "127.0.0.1:$httpdPort";
1355 "-c", "CustomLog \"$absTestResultsDirectory/access_log.txt\" common",
1356 "-c", "ErrorLog \"$absTestResultsDirectory/error_log.txt\"",
1357 "-C", "Listen $listen"
1360 my @defaultArgs = getDefaultConfigForTestDirectory($testDirectory);
1361 @args = (@defaultArgs, @args);
1363 $isHttpdOpen = openHTTPD(@args);
1366 sub openWebSocketServerIfNeeded()
1368 return 1 if $isWebSocketServerOpen;
1369 return 0 if $failedToStartWebSocketServer;
1371 my $webSocketServerPath = "/usr/bin/python";
1372 my $webSocketPythonPath = "WebKitTools/pywebsocket";
1373 my $webSocketHandlerDir = "$testDirectory";
1374 my $webSocketHandlerScanDir = "$testDirectory/websocket/tests";
1375 my $webSocketHandlerMapFile = "$webSocketHandlerScanDir/handler_map.txt";
1376 my $sslCertificate = "$testDirectory/http/conf/webkit-httpd.pem";
1377 my $absTestResultsDirectory = resolveAndMakeTestResultsDirectory();
1378 my $logFile = "$absTestResultsDirectory/pywebsocket_log.txt";
1381 "WebKitTools/pywebsocket/mod_pywebsocket/standalone.py",
1382 "-p", "$webSocketPort",
1383 "-d", "$webSocketHandlerDir",
1384 "-s", "$webSocketHandlerScanDir",
1385 "-m", "$webSocketHandlerMapFile",
1389 # wss is disabled until all platforms support pyOpenSSL.
1390 # my @argsSecure = (
1391 # "WebKitTools/pywebsocket/mod_pywebsocket/standalone.py",
1392 # "-p", "$webSocketSecurePort",
1393 # "-d", "$webSocketHandlerDir",
1395 # "-k", "$sslCertificate",
1396 # "-c", "$sslCertificate",
1399 $ENV{"PYTHONPATH"} = $webSocketPythonPath;
1400 $webSocketServerPID = open3(\*WEBSOCKETSERVER_IN, \*WEBSOCKETSERVER_OUT, \*WEBSOCKETSERVER_ERR, $webSocketServerPath, @args);
1401 # wss is disabled until all platforms support pyOpenSSL.
1402 # $webSocketSecureServerPID = open3(\*WEBSOCKETSECURESERVER_IN, \*WEBSOCKETSECURESERVER_OUT, \*WEBSOCKETSECURESERVER_ERR, $webSocketServerPath, @argsSecure);
1403 # my @listen = ("http://127.0.0.1:$webSocketPort", "https://127.0.0.1:$webSocketSecurePort");
1404 my @listen = ("http://127.0.0.1:$webSocketPort");
1405 for (my $i = 0; $i < @listen; $i++) {
1406 my $retryCount = 10;
1407 while (system("/usr/bin/curl -k -q --silent --stderr - --output /dev/null $listen[$i]") && $retryCount) {
1411 unless ($retryCount) {
1412 print STDERR "Timed out waiting for WebSocketServer to start.\n";
1413 $failedToStartWebSocketServer = 1;
1418 $isWebSocketServerOpen = 1;
1422 sub closeWebSocketServer()
1424 return if !$isWebSocketServerOpen;
1426 close WEBSOCKETSERVER_IN;
1427 close WEBSOCKETSERVER_OUT;
1428 close WEBSOCKETSERVER_ERR;
1429 kill 15, $webSocketServerPID;
1431 # wss is disabled until all platforms support pyOpenSSL.
1432 # close WEBSOCKETSECURESERVER_IN;
1433 # close WEBSOCKETSECURESERVER_OUT;
1434 # close WEBSOCKETSECURESERVER_ERR;
1435 # kill 15, $webSocketSecureServerPID;
1437 $isWebSocketServerOpen = 0;
1440 sub fileNameWithNumber($$)
1442 my ($base, $number) = @_;
1443 return "$base$number" if ($number > 1);
1447 sub processIgnoreTests($$)
1449 my @ignoreList = split(/\s*,\s*/, shift);
1450 my $listName = shift;
1452 my $disabledSuffix = "-disabled";
1454 my $addIgnoredDirectories = sub {
1455 return () if exists $ignoredLocalDirectories{basename($File::Find::dir)};
1456 $ignoredDirectories{File::Spec->abs2rel($File::Find::dir, $testDirectory)} = 1;
1459 foreach my $item (@ignoreList) {
1460 my $path = catfile($testDirectory, $item);
1462 $ignoredDirectories{$item} = 1;
1463 find({ preprocess => $addIgnoredDirectories, wanted => sub {} }, $path);
1466 $ignoredFiles{$item} = 1;
1467 } elsif (-f $path . $disabledSuffix) {
1468 # The test is disabled, so do nothing.
1470 print "$listName list contained '$item', but no file of that name could be found\n";
1475 sub stripExtension($)
1479 $test =~ s/\.[a-zA-Z]+$//;
1483 sub isTextOnlyTest($)
1487 if ($actual =~ /^layer at/ms) {
1495 sub expectedDirectoryForTest($;$;$)
1497 my ($base, $isText, $expectedExtension) = @_;
1499 my @directories = @platformResultHierarchy;
1500 push @directories, map { catdir($platformBaseDirectory, $_) } qw(mac-snowleopard mac) if isCygwin();
1501 push @directories, $expectedDirectory;
1503 # If we already have expected results, just return their location.
1504 foreach my $directory (@directories) {
1505 return $directory if (-f "$directory/$base-$expectedTag.$expectedExtension");
1508 # For cross-platform tests, text-only results should go in the cross-platform directory,
1509 # while render tree dumps should go in the least-specific platform directory.
1510 return $isText ? $expectedDirectory : $platformResultHierarchy[$#platformResultHierarchy];
1513 sub countFinishedTest($$$$)
1515 my ($test, $base, $result, $isText) = @_;
1517 if (($count + 1) % $testsPerDumpTool == 0 || $count == $#tests) {
1518 if ($shouldCheckLeaks) {
1520 if ($testsPerDumpTool == 1) {
1521 $fileName = "$testResultsDirectory/$base-leaks.txt";
1523 $fileName = "$testResultsDirectory/" . fileNameWithNumber($dumpToolName, $leaksOutputFileNumber) . "-leaks.txt";
1525 my $leakCount = countAndPrintLeaks($dumpToolName, $dumpToolPID, $fileName);
1526 $totalLeaks += $leakCount;
1527 $leaksOutputFileNumber++ if ($leakCount);
1535 push @{$tests{$result}}, $test;
1538 sub testCrashedOrTimedOut($$$$$)
1540 my ($test, $base, $didCrash, $actual, $error) = @_;
1542 printFailureMessageForTest($test, $didCrash ? "crashed" : "timed out");
1544 sampleDumpTool() unless $didCrash;
1546 my $dir = "$testResultsDirectory/$base";
1547 $dir =~ s|/([^/]+)$|| or die "Failed to find test name from base\n";
1550 deleteExpectedAndActualResults($base);
1552 if (defined($error) && length($error)) {
1553 writeToFile("$testResultsDirectory/$base-$errorTag.txt", $error);
1556 recordActualResultsAndDiff($base, $actual);
1558 kill 9, $dumpToolPID unless $didCrash;
1563 sub printFailureMessageForTest($$)
1565 my ($test, $description) = @_;
1568 print "\n" unless $atLineStart;
1571 print "$description\n";
1577 sub openCygpathIfNeeded($)
1581 return unless isCygwin();
1582 return $cygpaths{$options} if $cygpaths{$options} && $cygpaths{$options}->{"open"};
1584 local (*CYGPATHIN, *CYGPATHOUT);
1585 my $pid = open2(\*CYGPATHIN, \*CYGPATHOUT, "cygpath -f - $options");
1589 "out" => *CYGPATHOUT,
1593 $cygpaths{$options} = $cygpath;
1600 return unless isCygwin();
1602 foreach my $cygpath (values(%cygpaths)) {
1603 close $cygpath->{"in"};
1604 close $cygpath->{"out"};
1605 waitpid($cygpath->{"pid"}, 0);
1606 $cygpath->{"open"} = 0;
1611 sub convertPathUsingCygpath($$)
1613 my ($path, $options) = @_;
1615 my $cygpath = openCygpathIfNeeded($options);
1616 local *inFH = $cygpath->{"in"};
1617 local *outFH = $cygpath->{"out"};
1618 print outFH $path . "\n";
1619 my $convertedPath = <inFH>;
1620 chomp($convertedPath) if defined $convertedPath;
1621 return $convertedPath;
1624 sub toWindowsPath($)
1627 return unless isCygwin();
1629 return convertPathUsingCygpath($path, "-w");
1636 if ($useRemoteLinksToTests) {
1637 my $relativePath = File::Spec->abs2rel($path, $testDirectory);
1639 # If the file is below the test directory then convert it into a link to the file in SVN
1640 if ($relativePath !~ /^\.\.\//) {
1641 my $revision = svnRevisionForDirectory($testDirectory);
1642 my $svnPath = pathRelativeToSVNRepositoryRootForPath($path);
1643 return "http://trac.webkit.org/export/$revision/$svnPath";
1647 return $path unless isCygwin();
1649 return "file:///" . convertPathUsingCygpath($path, "-m");
1652 sub validateSkippedArg($$;$)
1654 my ($option, $value, $value2) = @_;
1655 my %validSkippedValues = map { $_ => 1 } qw(default ignore only);
1656 $value = lc($value);
1657 die "Invalid argument '" . $value . "' for option $option" unless $validSkippedValues{$value};
1658 $treatSkipped = $value;
1661 sub htmlForResultsSection(\@$&)
1663 my ($tests, $description, $linkGetter) = @_;
1666 return join("\n", @html) unless @{$tests};
1668 push @html, "<p>$description:</p>";
1669 push @html, "<table>";
1670 foreach my $test (@{$tests}) {
1672 push @html, "<td><a href=\"" . toURL("$testDirectory/$test") . "\">$test</a></td>";
1673 foreach my $link (@{&{$linkGetter}($test)}) {
1674 push @html, "<td><a href=\"$link->{href}\">$link->{text}</a></td>";
1676 push @html, "</tr>";
1678 push @html, "</table>";
1680 return join("\n", @html);
1683 sub linksForExpectedAndActualResults($)
1689 return \@links unless -s "$testResultsDirectory/$base-$diffsTag.txt";
1691 my $expectedResultPath = $expectedResultPaths{$base};
1692 my ($expectedResultFileName, $expectedResultsDirectory, $expectedResultExtension) = fileparse($expectedResultPath, qr{\.[^.]+$});
1694 push @links, { href => "$base-$expectedTag$expectedResultExtension", text => "expected" };
1695 push @links, { href => "$base-$actualTag$expectedResultExtension", text => "actual" };
1696 push @links, { href => "$base-$diffsTag.txt", text => "diff" };
1697 push @links, { href => "$base-$prettyDiffTag.html", text => "pretty diff" };
1702 sub linksForMismatchTest
1708 my $base = stripExtension($test);
1710 push @links, @{linksForExpectedAndActualResults($base)};
1711 return \@links unless $pixelTests && $imagesPresent{$base};
1713 push @links, { href => "$base-$expectedTag.png", text => "expected image" };
1714 push @links, { href => "$base-$diffsTag.html", text => "image diffs" };
1715 push @links, { href => "$base-$diffsTag.png", text => "$imageDifferences{$base}%" };
1720 sub linksForErrorTest
1726 my $base = stripExtension($test);
1728 push @links, @{linksForExpectedAndActualResults($base)};
1729 push @links, { href => "$base-$errorTag.txt", text => "stderr" };
1740 my $base = stripExtension($test);
1742 my $expectedResultPath = $expectedResultPaths{$base};
1743 my ($expectedResultFileName, $expectedResultsDirectory, $expectedResultExtension) = fileparse($expectedResultPath, qr{\.[^.]+$});
1745 push @links, { href => "$base-$actualTag$expectedResultExtension", text => "result" };
1746 if ($pixelTests && $imagesPresent{$base}) {
1747 push @links, { href => "$base-$expectedTag.png", text => "image" };
1753 sub deleteExpectedAndActualResults($)
1757 unlink "$testResultsDirectory/$base-$actualTag.txt";
1758 unlink "$testResultsDirectory/$base-$diffsTag.txt";
1759 unlink "$testResultsDirectory/$base-$errorTag.txt";
1762 sub recordActualResultsAndDiff($$)
1764 my ($base, $actualResults) = @_;
1766 return unless defined($actualResults) && length($actualResults);
1768 my $expectedResultPath = $expectedResultPaths{$base};
1769 my ($expectedResultFileNameMinusExtension, $expectedResultDirectoryPath, $expectedResultExtension) = fileparse($expectedResultPath, qr{\.[^.]+$});
1770 my $actualResultsPath = "$testResultsDirectory/$base-$actualTag$expectedResultExtension";
1771 my $copiedExpectedResultsPath = "$testResultsDirectory/$base-$expectedTag$expectedResultExtension";
1773 mkpath(dirname($actualResultsPath));
1774 writeToFile("$actualResultsPath", $actualResults);
1776 if (-f $expectedResultPath) {
1777 copy("$expectedResultPath", "$copiedExpectedResultsPath");
1779 open EMPTY, ">$copiedExpectedResultsPath";
1783 my $diffOuputBasePath = "$testResultsDirectory/$base";
1784 my $diffOutputPath = "$diffOuputBasePath-$diffsTag.txt";
1785 system "diff -u \"$copiedExpectedResultsPath\" \"$actualResultsPath\" > \"$diffOutputPath\"";
1787 my $prettyDiffOutputPath = "$diffOuputBasePath-$prettyDiffTag.html";
1788 my $prettyPatchPath = "BugsSite/PrettyPatch/";
1789 my $prettifyPath = "$prettyPatchPath/prettify.rb";
1790 system "ruby -I \"$prettyPatchPath\" \"$prettifyPath\" \"$diffOutputPath\" > \"$prettyDiffOutputPath\"";
1793 sub buildPlatformResultHierarchy()
1795 mkpath($platformTestDirectory) if ($platform eq "undefined" && !-d "$platformTestDirectory");
1798 if ($platform =~ /^mac-/) {
1800 for ($i = 0; $i < @macPlatforms; $i++) {
1801 last if $macPlatforms[$i] eq $platform;
1803 for (; $i < @macPlatforms; $i++) {
1804 push @platforms, $macPlatforms[$i];
1806 } elsif ($platform =~ /^qt-/) {
1807 push @platforms, $platform;
1808 push @platforms, "qt";
1810 @platforms = $platform;
1814 for (my $i = 0; $i < @platforms; $i++) {
1815 my $scoped = catdir($platformBaseDirectory, $platforms[$i]);
1816 push(@hierarchy, $scoped) if (-d $scoped);
1822 sub buildPlatformTestHierarchy(@)
1824 my (@platformHierarchy) = @_;
1825 return @platformHierarchy if (@platformHierarchy < 2);
1827 return ($platformHierarchy[0], $platformHierarchy[$#platformHierarchy]);
1830 sub epiloguesAndPrologues($$)
1832 my ($lastDirectory, $directory) = @_;
1833 my @lastComponents = split('/', $lastDirectory);
1834 my @components = split('/', $directory);
1836 while (@lastComponents) {
1837 if (!defined($components[0]) || $lastComponents[0] ne $components[0]) {
1841 shift @lastComponents;
1845 my $leaving = $lastDirectory;
1846 foreach (@lastComponents) {
1847 my $epilogue = $leaving . "/resources/run-webkit-tests-epilogue.html";
1848 foreach (@platformResultHierarchy) {
1849 push @result, catdir($_, $epilogue) if (stat(catdir($_, $epilogue)));
1851 push @result, catdir($testDirectory, $epilogue) if (stat(catdir($testDirectory, $epilogue)));
1852 $leaving =~ s|(^\|/)[^/]+$||;
1855 my $entering = $leaving;
1856 foreach (@components) {
1857 $entering .= '/' . $_;
1858 my $prologue = $entering . "/resources/run-webkit-tests-prologue.html";
1859 push @result, catdir($testDirectory, $prologue) if (stat(catdir($testDirectory, $prologue)));
1860 foreach (reverse @platformResultHierarchy) {
1861 push @result, catdir($_, $prologue) if (stat(catdir($_, $prologue)));
1867 sub parseLeaksandPrintUniqueLeaks()
1869 return unless @leaksFilenames;
1871 my $mergedFilenames = join " ", @leaksFilenames;
1872 my $parseMallocHistoryTool = sourceDir() . "/WebKitTools/Scripts/parse-malloc-history";
1874 open MERGED_LEAKS, "cat $mergedFilenames | $parseMallocHistoryTool --merge-depth $mergeDepth - |" ;
1875 my @leakLines = <MERGED_LEAKS>;
1878 my $uniqueLeakCount = 0;
1880 foreach my $line (@leakLines) {
1881 ++$uniqueLeakCount if ($line =~ /^(\d*)\scalls/);
1882 $totalBytes = $1 if $line =~ /^total\:\s(.*)\s\(/;
1885 print "\nWARNING: $totalLeaks total leaks found for a total of $totalBytes!\n";
1886 print "WARNING: $uniqueLeakCount unique leaks found!\n";
1887 print "See above for individual leaks results.\n" if ($leaksOutputFileNumber > 2);
1891 sub extensionForMimeType($)
1893 my ($mimeType) = @_;
1895 if ($mimeType eq "application/x-webarchive") {
1896 return "webarchive";
1897 } elsif ($mimeType eq "application/pdf") {
1903 # Read up to the first #EOF (the content block of the test), or until detecting crashes or timeouts.
1904 sub readFromDumpToolWithTimer(**)
1906 my ($fhIn, $fhError) = @_;
1908 setFileHandleNonBlocking($fhIn, 1);
1909 setFileHandleNonBlocking($fhError, 1);
1911 my $maximumSecondsWithoutOutput = $timeoutSeconds;
1912 $maximumSecondsWithoutOutput *= 10 if $guardMalloc;
1913 my $microsecondsToWaitBeforeReadingAgain = 1000;
1915 my $timeOfLastSuccessfulRead = time;
1919 my $status = "success";
1920 my $mimeType = "text/plain";
1921 # We don't have a very good way to know when the "headers" stop
1922 # and the content starts, so we use this as a hack:
1923 my $haveSeenContentType = 0;
1924 my $haveSeenEofIn = 0;
1925 my $haveSeenEofError = 0;
1928 if (time - $timeOfLastSuccessfulRead > $maximumSecondsWithoutOutput) {
1929 $status = dumpToolDidCrash() ? "crashed" : "timedOut";
1933 # Once we've seen the EOF, we must not read anymore.
1934 my $lineIn = readline($fhIn) unless $haveSeenEofIn;
1935 my $lineError = readline($fhError) unless $haveSeenEofError;
1936 if (!defined($lineIn) && !defined($lineError)) {
1937 last if ($haveSeenEofIn && $haveSeenEofError);
1940 $status = "crashed";
1945 usleep($microsecondsToWaitBeforeReadingAgain);
1949 $timeOfLastSuccessfulRead = time;
1951 if (defined($lineIn)) {
1952 if (!$haveSeenContentType && $lineIn =~ /^Content-Type: (\S+)$/) {
1954 $haveSeenContentType = 1;
1955 } elsif ($lineIn =~ /#EOF/) {
1958 push @output, $lineIn;
1961 if (defined($lineError)) {
1962 if ($lineError =~ /#EOF/) {
1963 $haveSeenEofError = 1;
1965 push @error, $lineError;
1970 setFileHandleNonBlocking($fhIn, 0);
1971 setFileHandleNonBlocking($fhError, 0);
1973 output => join("", @output),
1974 error => join("", @error),
1976 mimeType => $mimeType,
1977 extension => extensionForMimeType($mimeType)
1981 sub setFileHandleNonBlocking(*$)
1983 my ($fh, $nonBlocking) = @_;
1985 my $flags = fcntl($fh, F_GETFL, 0) or die "Couldn't get filehandle flags";
1988 $flags |= O_NONBLOCK;
1990 $flags &= ~O_NONBLOCK;
1993 fcntl($fh, F_SETFL, $flags) or die "Couldn't set filehandle flags";
1998 sub sampleDumpTool()
2000 return unless isAppleMacWebKit();
2001 return unless $runSample;
2003 my $outputDirectory = "$ENV{HOME}/Library/Logs/DumpRenderTree";
2004 -d $outputDirectory or mkdir $outputDirectory;
2006 my $outputFile = "$outputDirectory/HangReport.txt";
2007 system "/usr/bin/sample", $dumpToolPID, qw(10 10 -file), $outputFile;
2010 sub stripMetrics($$)
2012 my ($actual, $expected) = @_;
2014 foreach my $result ($actual, $expected) {
2015 $result =~ s/at \(-?[0-9]+,-?[0-9]+\) *//g;
2016 $result =~ s/size -?[0-9]+x-?[0-9]+ *//g;
2017 $result =~ s/text run width -?[0-9]+: //g;
2018 $result =~ s/text run width -?[0-9]+ [a-zA-Z ]+: //g;
2019 $result =~ s/RenderButton {BUTTON} .*/RenderButton {BUTTON}/g;
2020 $result =~ s/RenderImage {INPUT} .*/RenderImage {INPUT}/g;
2021 $result =~ s/RenderBlock {INPUT} .*/RenderBlock {INPUT}/g;
2022 $result =~ s/RenderTextControl {INPUT} .*/RenderTextControl {INPUT}/g;
2023 $result =~ s/\([0-9]+px/px/g;
2024 $result =~ s/ *" *\n +" */ /g;
2025 $result =~ s/" +$/"/g;
2027 $result =~ s/- /-/g;
2028 $result =~ s/\n( *)"\s+/\n$1"/g;
2029 $result =~ s/\s+"\n/"\n/g;
2030 $result =~ s/scrollWidth [0-9]+/scrollWidth/g;
2031 $result =~ s/scrollHeight [0-9]+/scrollHeight/g;
2034 return ($actual, $expected);
2037 sub fileShouldBeIgnored
2039 my ($filePath) = @_;
2040 foreach my $ignoredDir (keys %ignoredDirectories) {
2041 if ($filePath =~ m/^$ignoredDir/) {
2048 sub readSkippedFiles($)
2050 my ($constraintPath) = @_;
2052 foreach my $level (@platformTestHierarchy) {
2053 if (open SKIPPED, "<", "$level/Skipped") {
2055 my ($dir, $name) = splitpath($level);
2056 print "Skipped tests in $name:\n";
2062 $skipped =~ s/^[ \n\r]+//;
2063 $skipped =~ s/[ \n\r]+$//;
2064 if ($skipped && $skipped !~ /^#/) {
2066 if (!fileShouldBeIgnored($skipped)) {
2067 if (!$constraintPath) {
2068 # Always add $skipped since no constraint path was specified on the command line.
2069 push(@ARGV, $skipped);
2070 } elsif ($skipped =~ /^($constraintPath)/) {
2071 # Add $skipped only if it matches the current path constraint, e.g.,
2072 # "--skipped=only dir1" with "dir1/file1.html" on the skipped list.
2073 push(@ARGV, $skipped);
2074 } elsif ($constraintPath =~ /^($skipped)/) {
2075 # Add current path constraint if it is more specific than the skip list entry,
2076 # e.g., "--skipped=only dir1/dir2/dir3" with "dir1" on the skipped list.
2077 push(@ARGV, $constraintPath);
2079 } elsif ($verbose) {
2080 print " $skipped\n";
2084 print " $skipped\n";
2086 processIgnoreTests($skipped, "Skipped");
2099 return () if exists $ignoredLocalDirectories{basename($File::Find::dir)};
2100 return () if exists $ignoredDirectories{File::Spec->abs2rel($File::Find::dir, $testDirectory)};
2107 if ($filename =~ /\.([^.]+)$/) {
2108 if (exists $supportedFileExtensions{$1}) {
2109 my $path = File::Spec->abs2rel(catfile($File::Find::dir, $filename), $testDirectory);
2110 push @testsToRun, $path if !exists $ignoredFiles{$path};
2119 for my $test (@ARGV) {
2120 $test =~ s/^($layoutTestsName|$testDirectory)\///;
2121 my $fullPath = catfile($testDirectory, $test);
2122 if (file_name_is_absolute($test)) {
2123 print "can't run test $test outside $testDirectory\n";
2124 } elsif (-f $fullPath) {
2125 my ($filename, $pathname, $fileExtension) = fileparse($test, qr{\.[^.]+$});
2126 if (!exists $supportedFileExtensions{substr($fileExtension, 1)}) {
2127 print "test $test does not have a supported extension\n";
2128 } elsif ($testHTTP || $pathname !~ /^http\//) {
2129 push @testsToRun, $test;
2131 } elsif (-d $fullPath) {
2132 find({ preprocess => \&directoryFilter, wanted => \&fileFilter }, $fullPath);
2133 for my $level (@platformTestHierarchy) {
2134 my $platformPath = catfile($level, $test);
2135 find({ preprocess => \&directoryFilter, wanted => \&fileFilter }, $platformPath) if (-d $platformPath);
2138 print "test $test not found\n";
2142 if (!scalar @ARGV) {
2143 find({ preprocess => \&directoryFilter, wanted => \&fileFilter }, $testDirectory);
2144 for my $level (@platformTestHierarchy) {
2145 find({ preprocess => \&directoryFilter, wanted => \&fileFilter }, $level);
2149 # Remove duplicate tests
2150 @testsToRun = keys %{{ map { $_ => 1 } @testsToRun }};
2152 @testsToRun = sort pathcmp @testsToRun;
2154 # We need to minimize the time when Apache and WebSocketServer is locked by tests
2155 # so run them last if no explicit order was specified in the argument list.
2156 if (!scalar @ARGV) {
2160 foreach my $test (@testsToRun) {
2161 if ($test =~ /^http\//) {
2162 push(@httpTests, $test);
2163 } elsif ($test =~ /^websocket\//) {
2164 push(@websocketTests, $test);
2166 push(@otherTests, $test);
2169 @testsToRun = (@otherTests, @httpTests, @websocketTests);
2173 @testsToRun = reverse @testsToRun if $reverseTests;
2176 @testsToRun = shuffle(@testsToRun) if $randomizeTests;
2184 match => "succeeded",
2185 mismatch => "had incorrect layout",
2187 timedout => "timed out",
2189 error => "had stderr output"
2192 for my $type ("match", "mismatch", "new", "timedout", "crash", "error") {
2193 my $typeCount = $counts{$type};
2194 next unless $typeCount;
2195 my $typeText = $text{$type};
2197 if ($typeCount == 1) {
2198 $typeText =~ s/were/was/;
2199 $message = sprintf "1 test case (%d%%) %s\n", 1 * 100 / $count, $typeText;
2201 $message = sprintf "%d test cases (%d%%) %s\n", $typeCount, $typeCount * 100 / $count, $typeText;
2203 $message =~ s-\(0%\)-(<1%)-;