3 # Copyright (C) 2005, 2006, 2007, 2008, 2009, 2010 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, .xhtmlmp, .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;
66 use Time::HiRes qw(time usleep);
68 use List::Util 'shuffle';
70 use lib $FindBin::Bin;
71 use webkitperl::features;
72 use webkitperl::httpd;
77 sub buildPlatformResultHierarchy();
78 sub buildPlatformTestHierarchy(@);
79 sub checkPythonVersion();
82 sub closeWebSocketServer();
83 sub configureAndOpenHTTPDIfNeeded();
84 sub countAndPrintLeaks($$$);
85 sub countFinishedTest($$$$);
86 sub deleteExpectedAndActualResults($);
87 sub dumpToolDidCrash();
88 sub epiloguesAndPrologues($$);
89 sub expectedDirectoryForTest($;$;$);
90 sub fileNameWithNumber($$);
91 sub htmlForResultsSection(\@$&);
92 sub isTextOnlyTest($);
93 sub launchWithEnv(\@\%);
94 sub resolveAndMakeTestResultsDirectory();
99 sub parseLeaksandPrintUniqueLeaks();
100 sub openWebSocketServerIfNeeded();
102 sub printFailureMessageForTest($$);
103 sub processIgnoreTests($$);
104 sub readFromDumpToolWithTimer(**);
105 sub readSkippedFiles($);
106 sub recordActualResultsAndDiff($$);
107 sub sampleDumpTool();
108 sub setFileHandleNonBlocking(*$);
109 sub setUpWindowsCrashLogSaving();
112 sub stopRunningTestsEarlyIfNeeded();
113 sub stripExtension($);
114 sub stripMetrics($$);
115 sub testCrashedOrTimedOut($$$$$);
118 sub toWindowsPath($);
119 sub validateSkippedArg($$;$);
123 my $addPlatformExceptions = 0;
125 my $exitAfterNFailures = 0;
126 my $exitAfterNCrashesOrTimeouts = 0;
127 my $generateNewResults = isAppleMacWebKit() ? 1 : 0;
128 my $guardMalloc = '';
129 # FIXME: Dynamic HTTP-port configuration in this file is wrong. The various
130 # apache config files in LayoutTests/http/config govern the port numbers.
131 # Dynamic configuration as-written will also cause random failures in
132 # an IPv6 environment. See https://bugs.webkit.org/show_bug.cgi?id=37104.
133 my $httpdPort = 8000;
134 my $httpdSSLPort = 8443;
135 my $ignoreMetrics = 0;
136 my $webSocketPort = 8880;
137 # wss is disabled until all platforms support pyOpenSSL.
138 # my $webSocketSecurePort = 9323;
139 my $ignoreTests = '';
141 my $launchSafari = 1;
146 my $randomizeTests = 0;
148 my $report10Slowest = 0;
149 my $resetResults = 0;
150 my $reverseTests = 0;
153 my $shouldCheckLeaks = 0;
155 my $stripEditingCallbacks;
157 my $testWebSocket = 1;
160 my $testResultsDirectory = File::Spec->catfile($tmpDir, "layout-test-results");
161 my $testsPerDumpTool = 1000;
163 my $html5treebuilder = 0;
164 # DumpRenderTree has an internal timeout of 30 seconds, so this must be > 30.
165 my $timeoutSeconds = 35;
167 my $treatSkipped = "default";
168 my $useRemoteLinksToTests = 0;
171 my $shouldWaitForHTTPD = 0;
172 my $useWebKitTestRunner = 0;
176 if (isWindows() || isMsys()) {
177 print "This script has to be run under Cygwin to function correctly.\n";
181 # Default to --no-http for wx for now.
182 $testHTTP = 0 if (isWx());
184 my $expectedTag = "expected";
185 my $actualTag = "actual";
186 my $prettyDiffTag = "pretty-diff";
187 my $diffsTag = "diffs";
188 my $errorTag = "stderr";
190 # These are defined here instead of closer to where they are used so that they
191 # will always be accessible from the END block that uses them, even if the user
192 # presses Ctrl-C before Perl has finished evaluating this whole file.
193 my $windowsPostMortemDebuggerKey = "/HKLM/SOFTWARE/Microsoft/Windows NT/CurrentVersion/AeDebug";
194 my %previousWindowsPostMortemDebuggerValues;
198 my @macPlatforms = ("mac-tiger", "mac-leopard", "mac-snowleopard", "mac");
199 my @winPlatforms = ("win-xp", "win-vista", "win-7", "win");
201 if (isAppleMacWebKit()) {
203 $platform = "mac-tiger";
205 } elsif (isLeopard()) {
206 $platform = "mac-leopard";
208 } elsif (isSnowLeopard()) {
209 $platform = "mac-snowleopard";
216 $platform = "qt-mac";
217 } elsif (isLinux()) {
218 $platform = "qt-linux";
219 } elsif (isWindows() || isCygwin()) {
220 $platform = "qt-win";
228 } elsif (isCygwin()) {
230 $platform = "win-xp";
231 } elsif (isWindowsVista()) {
232 $platform = "win-vista";
233 } elsif (isWindows7()) {
240 if (isQt() || isGtk() || isCygwin()) {
241 my $testfontPath = $ENV{"WEBKIT_TESTFONTS"};
242 if (!$testfontPath || !-d "$testfontPath") {
243 print "The WEBKIT_TESTFONTS environment variable is not defined or not set properly\n";
244 print "You must set it before running the tests.\n";
245 print "Use git to grab the actual fonts from http://gitorious.org/qtwebkit/testfonts\n";
250 if (!defined($platform)) {
251 print "WARNING: Your platform is not recognized. Any platform-specific results will be generated in platform/undefined.\n";
252 $platform = "undefined";
255 if (!checkPythonVersion()) {
256 print "WARNING: Your platform does not have Python 2.5+, which is required to run websocket server, so disabling websocket/tests.\n";
260 my $programName = basename($0);
261 my $launchSafariDefault = $launchSafari ? "launch" : "do not launch";
262 my $httpDefault = $testHTTP ? "run" : "do not run";
263 my $sampleDefault = $runSample ? "run" : "do not run";
266 Usage: $programName [options] [testdir|testpath ...]
267 --add-platform-exceptions Put new results for non-platform-specific failing tests into the platform-specific results directory
268 --complex-text Use the complex text code path for all text (Mac OS X and Windows only)
269 -c|--configuration config Set DumpRenderTree build configuration
270 -g|--guard-malloc Enable malloc guard
271 --exit-after-n-failures N Exit after the first N failures (includes crashes) instead of running all tests
272 --exit-after-n-crashes-or-timeouts N
273 Exit after the first N crashes instead of running all tests
274 -h|--help Show this help message
275 --[no-]http Run (or do not run) http tests (default: $httpDefault)
276 --[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)
277 -i|--ignore-tests Comma-separated list of directories or tests to ignore
278 --iterations n Number of times to run the set of tests (e.g. ABCABCABC)
279 --[no-]launch-safari Launch (or do not launch) Safari to display test results (default: $launchSafariDefault)
280 -l|--leaks Enable leaks checking
281 --[no-]new-test-results Generate results for new tests
282 --nthly n Restart DumpRenderTree every n tests (default: $testsPerDumpTool)
283 -p|--pixel-tests Enable pixel tests
284 --tolerance t Ignore image differences less than this percentage (default: $tolerance)
285 --platform Override the detected platform to use for tests and results (default: $platform)
286 --port Web server port to use with http tests
287 -q|--quiet Less verbose output
288 --reset-results Reset ALL results (including pixel tests if --pixel-tests is set)
289 -o|--results-directory Output results directory (default: $testResultsDirectory)
290 --random Run the tests in a random order
291 --repeat-each n Number of times to run each test (e.g. AAABBBCCC)
292 --reverse Run the tests in reverse alphabetical order
293 --root Path to root tools build
294 --[no-]sample-on-timeout Run sample on timeout (default: $sampleDefault) (Mac OS X only)
295 -1|--singly Isolate each test case run (implies --nthly 1 --verbose)
296 --skipped=[default|ignore|only] Specifies how to treat the Skipped file
297 default: Tests/directories listed in the Skipped file are not tested
298 ignore: The Skipped file is ignored
299 only: Only those tests/directories listed in the Skipped file will be run
300 --slowest Report the 10 slowest tests
301 --ignore-metrics Ignore metrics in tests
302 --[no-]strip-editing-callbacks Remove editing callbacks from expected results
303 -t|--threaded Run a concurrent JavaScript thead with each test
304 --html5-treebuilder Run the tests using the HTML5 tree builder
305 --timeout t Sets the number of seconds before a test times out (default: $timeoutSeconds)
306 --valgrind Run DumpRenderTree inside valgrind (Qt/Linux only)
307 -v|--verbose More verbose output (overrides --quiet)
308 -m|--merge-leak-depth arg Merges leak callStacks and prints the number of unique leaks beneath a callstack depth of arg. Defaults to 5.
309 --use-remote-links-to-tests Link to test files within the SVN repository in the results.
310 -2|--webkit-test-runner Use WebKitTestRunner rather than DumpRenderTree.
315 my $getOptionsResult = GetOptions(
316 'add-platform-exceptions' => \$addPlatformExceptions,
317 'complex-text' => \$complexText,
318 'exit-after-n-failures=i' => \$exitAfterNFailures,
319 'exit-after-n-crashes-or-timeouts=i' => \$exitAfterNCrashesOrTimeouts,
320 'guard-malloc|g' => \$guardMalloc,
321 'help|h' => \$showHelp,
322 'http!' => \$testHTTP,
323 'wait-for-httpd!' => \$shouldWaitForHTTPD,
324 'ignore-metrics!' => \$ignoreMetrics,
325 'ignore-tests|i=s' => \$ignoreTests,
326 'iterations=i' => \$iterations,
327 'launch-safari!' => \$launchSafari,
328 'leaks|l' => \$shouldCheckLeaks,
329 'merge-leak-depth|m:5' => \$mergeDepth,
330 'new-test-results!' => \$generateNewResults,
331 'nthly=i' => \$testsPerDumpTool,
332 'pixel-tests|p' => \$pixelTests,
333 'platform=s' => \$platform,
334 'port=i' => \$httpdPort,
335 'quiet|q' => \$quiet,
336 'random' => \$randomizeTests,
337 'repeat-each=i' => \$repeatEach,
338 'reset-results' => \$resetResults,
339 'results-directory|o=s' => \$testResultsDirectory,
340 'reverse' => \$reverseTests,
342 'sample-on-timeout!' => \$runSample,
343 'singly|1' => sub { $testsPerDumpTool = 1; },
344 'skipped=s' => \&validateSkippedArg,
345 'slowest' => \$report10Slowest,
346 'strip-editing-callbacks!' => \$stripEditingCallbacks,
347 'threaded|t' => \$threaded,
348 'html5-treebuilder' => \$html5treebuilder,
349 'timeout=i' => \$timeoutSeconds,
350 'tolerance=f' => \$tolerance,
351 'use-remote-links-to-tests' => \$useRemoteLinksToTests,
352 'valgrind' => \$useValgrind,
353 'verbose|v' => \$verbose,
354 'webkit-test-runner|2' => \$useWebKitTestRunner,
357 if (!$getOptionsResult || $showHelp) {
362 if ($useWebKitTestRunner) {
363 if (isAppleMacWebKit()) {
364 $realPlatform = $platform;
365 $platform = "mac-wk2";
366 } elsif (isAppleWinWebKit()) {
367 $stripEditingCallbacks = 0 unless defined $stripEditingCallbacks;
368 $realPlatform = $platform;
369 $platform = "win-wk2";
373 $stripEditingCallbacks = isCygwin() unless defined $stripEditingCallbacks;
375 my $ignoreSkipped = $treatSkipped eq "ignore";
376 my $skippedOnly = $treatSkipped eq "only";
378 my $configuration = configuration();
380 # We need an environment variable to be able to enable the feature per-slave
381 $shouldWaitForHTTPD = $ENV{"WEBKIT_WAIT_FOR_HTTPD"} unless ($shouldWaitForHTTPD);
382 $verbose = 1 if $testsPerDumpTool == 1;
384 if ($shouldCheckLeaks && $testsPerDumpTool > 1000) {
385 print STDERR "\nWARNING: Running more than 1000 tests at a time with MallocStackLogging enabled may cause a crash.\n\n";
388 # Stack logging does not play well with QuickTime on Tiger (rdar://problem/5537157)
389 $testMedia = 0 if $shouldCheckLeaks && isTiger();
391 # Generating remote links causes a lot of unnecessary spew on GTK build bot
392 $useRemoteLinksToTests = 0 if isGtk();
394 setUpWindowsCrashLogSaving() if isCygwin();
396 setConfigurationProductDir(Cwd::abs_path($root)) if (defined($root));
397 my $productDir = productDir();
398 $productDir .= "/bin" if isQt();
399 $productDir .= "/Programs" if isGtk();
403 if (!defined($root)) {
404 # FIXME: We build both DumpRenderTree and WebKitTestRunner for
405 # WebKitTestRunner runs becuase DumpRenderTree still includes
406 # the DumpRenderTreeSupport module and the TestNetscapePlugin.
407 # These two projects should be factored out into their own
409 buildDumpTool("DumpRenderTree");
410 buildDumpTool("WebKitTestRunner") if $useWebKitTestRunner;
413 my $dumpToolName = $useWebKitTestRunner ? "WebKitTestRunner" : "DumpRenderTree";
415 $dumpToolName .= "_debug" if isCygwin() && configurationForVisualStudio() !~ /^Release|Debug_Internal$/;
416 my $dumpTool = "$productDir/$dumpToolName";
417 die "can't find executable $dumpToolName (looked in $productDir)\n" unless -x $dumpTool;
419 my $imageDiffTool = "$productDir/ImageDiff";
420 $imageDiffTool .= "_debug" if isCygwin() && configurationForVisualStudio() !~ /^Release|Debug_Internal$/;
421 die "can't find executable $imageDiffTool (looked in $productDir)\n" if $pixelTests && !-x $imageDiffTool;
423 checkFrameworks() unless isCygwin();
425 if (isAppleMacWebKit()) {
426 push @INC, $productDir;
427 require DumpRenderTreeSupport;
430 my $layoutTestsName = "LayoutTests";
431 my $testDirectory = File::Spec->rel2abs($layoutTestsName);
432 my $expectedDirectory = $testDirectory;
433 my $platformBaseDirectory = catdir($testDirectory, "platform");
434 my $platformTestDirectory = catdir($platformBaseDirectory, $platform);
435 my @platformResultHierarchy = buildPlatformResultHierarchy();
436 my @platformTestHierarchy = buildPlatformTestHierarchy(@platformResultHierarchy);
438 $expectedDirectory = $ENV{"WebKitExpectedTestResultsDirectory"} if $ENV{"WebKitExpectedTestResultsDirectory"};
440 $testResultsDirectory = File::Spec->rel2abs($testResultsDirectory);
441 my $testResults = File::Spec->catfile($testResultsDirectory, "results.html");
443 if (isAppleMacWebKit()) {
444 print STDERR "Compiling Java tests\n";
445 my $javaTestsDirectory = catdir($testDirectory, "java");
447 if (system("/usr/bin/make", "-C", "$javaTestsDirectory")) {
453 print "Running tests from $testDirectory\n";
455 print "Enabling pixel tests with a tolerance of $tolerance%\n";
457 print "WARNING: Temporarily changing the main display color profile:\n";
458 print "\tThe colors on your screen will change for the duration of the testing.\n";
459 print "\tThis allows the pixel tests to have consistent color values across all machines.\n";
461 if (isPerianInstalled()) {
462 print "WARNING: Perian's QuickTime component is installed and this may affect pixel test results!\n";
463 print "\tYou should avoid generating new pixel results in this environment.\n";
464 print "\tSee https://bugs.webkit.org/show_bug.cgi?id=22615 for details.\n";
469 system "ln", "-s", $testDirectory, "/tmp/LayoutTests" unless -x "/tmp/LayoutTests";
471 my %ignoredFiles = ( "results.html" => 1 );
472 my %ignoredDirectories = map { $_ => 1 } qw(platform);
473 my %ignoredLocalDirectories = map { $_ => 1 } qw(.svn _svn resources script-tests);
474 my %supportedFileExtensions = map { $_ => 1 } qw(html shtml xml xhtml xhtmlmp pl php);
476 if (!checkWebCoreFeatureSupport("MathML", 0)) {
477 $ignoredDirectories{'mathml'} = 1;
480 # FIXME: We should fix webkitperl/features.pm:hasFeature() to do the correct feature detection for Cygwin.
481 if (checkWebCoreFeatureSupport("SVG", 0)) {
482 $supportedFileExtensions{'svg'} = 1;
483 } elsif (isCygwin()) {
484 $supportedFileExtensions{'svg'} = 1;
486 $ignoredLocalDirectories{'svg'} = 1;
490 $ignoredDirectories{'http'} = 1;
491 $ignoredDirectories{'websocket'} = 1;
493 if (!$testWebSocket) {
494 $ignoredDirectories{'websocket'} = 1;
498 $ignoredDirectories{'media'} = 1;
499 $ignoredDirectories{'http/tests/media'} = 1;
502 my $supportedFeaturesResult = "";
505 # Collect supported features list
506 setPathForRunningWebKitApp(\%ENV);
507 my $supportedFeaturesCommand = $dumpTool . " --print-supported-features 2>&1";
508 $supportedFeaturesResult = `$supportedFeaturesCommand 2>&1`;
511 my $hasAcceleratedCompositing = 0;
512 my $has3DRendering = 0;
515 $hasAcceleratedCompositing = $supportedFeaturesResult =~ /AcceleratedCompositing/;
516 $has3DRendering = $supportedFeaturesResult =~ /3DRendering/;
518 $hasAcceleratedCompositing = checkWebCoreFeatureSupport("Accelerated Compositing", 0);
519 $has3DRendering = checkWebCoreFeatureSupport("3D Rendering", 0);
522 if (!$hasAcceleratedCompositing) {
523 $ignoredDirectories{'compositing'} = 1;
526 if (!$has3DRendering) {
527 $ignoredDirectories{'animations/3d'} = 1;
528 $ignoredDirectories{'transforms/3d'} = 1;
531 if (!checkWebCoreFeatureSupport("3D Canvas", 0)) {
532 $ignoredDirectories{'fast/canvas/webgl'} = 1;
533 $ignoredDirectories{'compositing/webgl'} = 1;
536 if (checkWebCoreFeatureSupport("WML", 0)) {
537 $supportedFileExtensions{'wml'} = 1;
539 $ignoredDirectories{'http/tests/wml'} = 1;
540 $ignoredDirectories{'fast/wml'} = 1;
541 $ignoredDirectories{'wml'} = 1;
544 if (!checkWebCoreFeatureSupport("WCSS", 0)) {
545 $ignoredDirectories{'fast/wcss'} = 1;
548 if (!checkWebCoreFeatureSupport("XHTMLMP", 0)) {
549 $ignoredDirectories{'fast/xhtmlmp'} = 1;
552 processIgnoreTests($ignoreTests, "ignore-tests") if $ignoreTests;
553 if (!$ignoreSkipped) {
554 if (!$skippedOnly || @ARGV == 0) {
555 readSkippedFiles("");
557 # Since readSkippedFiles() appends to @ARGV, we must use a foreach
558 # loop so that we only iterate over the original argument list.
559 foreach my $argnum (0 .. $#ARGV) {
560 readSkippedFiles(shift @ARGV);
565 my @tests = findTestsToRun();
567 die "no tests to run\n" if !@tests;
572 my %imageDifferences;
575 my $leaksOutputFileNumber = 1;
579 push @toolArgs, "--pixel-tests" if $pixelTests;
580 push @toolArgs, "--threaded" if $threaded;
581 push @toolArgs, "--html5-treebuilder" if $html5treebuilder;
582 push @toolArgs, "--complex-text" if $complexText;
585 my @diffToolArgs = ();
586 push @diffToolArgs, "--tolerance", $tolerance;
591 my $isDumpToolOpen = 0;
592 my $dumpToolCrashed = 0;
593 my $imageDiffToolPID;
594 my $isDiffToolOpen = 0;
597 my $lastDirectory = "";
600 my $isWebSocketServerOpen = 0;
601 my $webSocketServerPidFile = 0;
602 my $failedToStartWebSocketServer = 0;
603 # wss is disabled until all platforms support pyOpenSSL.
604 # my $webSocketSecureServerPID = 0;
606 sub catch_pipe { $dumpToolCrashed = 1; }
607 $SIG{"PIPE"} = "catch_pipe";
609 print "Testing ", scalar @tests, " test cases";
610 print " $iterations times" if ($iterations > 1);
611 print ", repeating each test $repeatEach times" if ($repeatEach > 1);
614 my $overallStartTime = time;
616 my %expectedResultPaths;
618 my @originalTests = @tests;
619 # Add individual test repetitions
620 if ($repeatEach > 1) {
622 foreach my $test (@originalTests) {
623 for (my $i = 0; $i < $repeatEach; $i++) {
628 # Add test set repetitions
629 for (my $i = 1; $i < $iterations; $i++) {
630 push(@tests, @originalTests);
633 for my $test (@tests) {
634 my $newDumpTool = not $isDumpToolOpen;
637 my $base = stripExtension($test);
638 my $expectedExtension = ".txt";
643 if ($newDumpTool || $dir ne $lastDirectory) {
644 foreach my $logue (epiloguesAndPrologues($newDumpTool ? "" : $lastDirectory, $dir)) {
646 $logue = toWindowsPath($logue);
648 $logue = canonpath($logue);
651 print "running epilogue or prologue $logue\n";
653 print OUT "$logue\n";
654 # Throw away output from DumpRenderTree.
655 # Once for the test output and once for pixel results (empty)
666 print "running $test -> ";
669 if ($dir ne $lastDirectory) {
670 print "\n" unless $atLineStart;
677 $lastDirectory = $dir;
681 my $startTime = time if $report10Slowest;
683 # Try to read expected hash file for pixel tests
684 my $suffixExpectedHash = "";
685 if ($pixelTests && !$resetResults) {
686 my $expectedPixelDir = expectedDirectoryForTest($base, 0, "png");
687 if (open EXPECTEDHASH, "$expectedPixelDir/$base-$expectedTag.checksum") {
688 my $expectedHash = <EXPECTEDHASH>;
689 chomp($expectedHash);
692 # Format expected hash into a suffix string that is appended to the path / URL passed to DRT
693 $suffixExpectedHash = "'$expectedHash";
697 if ($test =~ /^http\//) {
698 configureAndOpenHTTPDIfNeeded();
699 if ($test !~ /^http\/tests\/local\// && $test !~ /^http\/tests\/ssl\// && $test !~ /^http\/tests\/wml\// && $test !~ /^http\/tests\/media\//) {
700 my $path = canonpath($test);
701 $path =~ s/^http\/tests\///;
702 print OUT "http://127.0.0.1:$httpdPort/$path$suffixExpectedHash\n";
703 } elsif ($test =~ /^http\/tests\/ssl\//) {
704 my $path = canonpath($test);
705 $path =~ s/^http\/tests\///;
706 print OUT "https://127.0.0.1:$httpdSSLPort/$path$suffixExpectedHash\n";
708 my $testPath = "$testDirectory/$test";
710 $testPath = toWindowsPath($testPath);
712 $testPath = canonpath($testPath);
714 print OUT "$testPath$suffixExpectedHash\n";
716 } elsif ($test =~ /^websocket\//) {
717 if ($test =~ /^websocket\/tests\/local\//) {
718 my $testPath = "$testDirectory/$test";
720 $testPath = toWindowsPath($testPath);
722 $testPath = canonpath($testPath);
724 print OUT "$testPath\n";
726 if (openWebSocketServerIfNeeded()) {
727 my $path = canonpath($test);
728 if ($test =~ /^websocket\/tests\/ssl\//) {
729 # wss is disabled until all platforms support pyOpenSSL.
730 print STDERR "Error: wss is disabled until all platforms support pyOpenSSL.";
731 # print OUT "https://127.0.0.1:$webSocketSecurePort/$path\n";
733 print OUT "http://127.0.0.1:$webSocketPort/$path\n";
736 # We failed to launch the WebSocket server. Display a useful error message rather than attempting
737 # to run tests that expect the server to be available.
738 my $errorMessagePath = "$testDirectory/websocket/resources/server-failed-to-start.html";
739 $errorMessagePath = isCygwin() ? toWindowsPath($errorMessagePath) : canonpath($errorMessagePath);
740 print OUT "$errorMessagePath\n";
744 my $testPath = "$testDirectory/$test";
746 $testPath = toWindowsPath($testPath);
748 $testPath = canonpath($testPath);
750 print OUT "$testPath$suffixExpectedHash\n" if defined $testPath;
753 # DumpRenderTree is expected to dump two "blocks" to stdout for each test.
754 # Each block is terminated by a #EOF on a line by itself.
755 # The first block is the output of the test (in text, RenderTree or other formats).
756 # The second block is for optional pixel data in PNG format, and may be empty if
757 # pixel tests are not being run, or the test does not dump pixels (e.g. text tests).
758 my $readResults = readFromDumpToolWithTimer(IN, ERROR);
760 my $actual = $readResults->{output};
761 my $error = $readResults->{error};
763 $expectedExtension = $readResults->{extension};
764 my $expectedFileName = "$base-$expectedTag.$expectedExtension";
766 my $isText = isTextOnlyTest($actual);
768 my $expectedDir = expectedDirectoryForTest($base, $isText, $expectedExtension);
769 $expectedResultPaths{$base} = "$expectedDir/$expectedFileName";
771 unless ($readResults->{status} eq "success") {
772 my $crashed = $readResults->{status} eq "crashed";
773 testCrashedOrTimedOut($test, $base, $crashed, $actual, $error);
774 countFinishedTest($test, $base, $crashed ? "crash" : "timedout", 0);
775 last if stopRunningTestsEarlyIfNeeded();
779 $durations{$test} = time - $startTime if $report10Slowest;
783 if (!$resetResults && open EXPECTED, "<", "$expectedDir/$expectedFileName") {
786 next if $stripEditingCallbacks && $_ =~ /^EDITING DELEGATE:/;
792 if ($ignoreMetrics && !$isText && defined $expected) {
793 ($actual, $expected) = stripMetrics($actual, $expected);
796 if ($shouldCheckLeaks && $testsPerDumpTool == 1) {
802 my $diffPercentage = 0;
803 my $diffResult = "passed";
806 my $expectedHash = "";
807 my $actualPNGSize = 0;
811 if (/ActualHash: ([a-f0-9]{32})/) {
813 } elsif (/ExpectedHash: ([a-f0-9]{32})/) {
815 } elsif (/Content-Length: (\d+)\s*/) {
817 read(IN, $actualPNG, $actualPNGSize);
821 if ($verbose && $pixelTests && !$resetResults && $actualPNGSize) {
822 if ($actualHash eq "" && $expectedHash eq "") {
823 printFailureMessageForTest($test, "WARNING: actual & expected pixel hashes are missing!");
824 } elsif ($actualHash eq "") {
825 printFailureMessageForTest($test, "WARNING: actual pixel hash is missing!");
826 } elsif ($expectedHash eq "") {
827 printFailureMessageForTest($test, "WARNING: expected pixel hash is missing!");
831 if ($actualPNGSize > 0) {
832 my $expectedPixelDir = expectedDirectoryForTest($base, 0, "png");
834 if (!$resetResults && ($expectedHash ne $actualHash || ($actualHash eq "" && $expectedHash eq ""))) {
835 if (-f "$expectedPixelDir/$base-$expectedTag.png") {
836 my $expectedPNGSize = -s "$expectedPixelDir/$base-$expectedTag.png";
837 my $expectedPNG = "";
838 open EXPECTEDPNG, "$expectedPixelDir/$base-$expectedTag.png";
839 read(EXPECTEDPNG, $expectedPNG, $expectedPNGSize);
842 print DIFFOUT "Content-Length: $actualPNGSize\n";
843 print DIFFOUT $actualPNG;
845 print DIFFOUT "Content-Length: $expectedPNGSize\n";
846 print DIFFOUT $expectedPNG;
849 last if /^error/ || /^diff:/;
850 if (/Content-Length: (\d+)\s*/) {
851 read(DIFFIN, $diffPNG, $1);
855 if (/^diff: (.+)% (passed|failed)/) {
856 $diffPercentage = $1 + 0;
857 $imageDifferences{$base} = $diffPercentage;
861 if (!$diffPercentage) {
862 printFailureMessageForTest($test, "pixel hash failed (but pixel test still passes)");
865 printFailureMessageForTest($test, "WARNING: expected image is missing!");
869 if ($resetResults || !-f "$expectedPixelDir/$base-$expectedTag.png") {
870 mkpath catfile($expectedPixelDir, dirname($base)) if $testDirectory ne $expectedPixelDir;
871 writeToFile("$expectedPixelDir/$base-$expectedTag.png", $actualPNG);
874 if ($actualHash ne "" && ($resetResults || !-f "$expectedPixelDir/$base-$expectedTag.checksum")) {
875 writeToFile("$expectedPixelDir/$base-$expectedTag.checksum", $actualHash);
879 if (dumpToolDidCrash()) {
881 testCrashedOrTimedOut($test, $base, 1, $actual, $error);
882 } elsif (!defined $expected) {
884 print "new " . ($resetResults ? "result" : "test") ."\n";
889 if ($generateNewResults || $resetResults) {
890 mkpath catfile($expectedDir, dirname($base)) if $testDirectory ne $expectedDir;
891 writeToFile("$expectedDir/$expectedFileName", $actual);
893 deleteExpectedAndActualResults($base);
894 recordActualResultsAndDiff($base, $actual);
895 if (!$resetResults) {
896 # Always print the file name for new tests, as they will probably need some manual inspection.
897 # in verbose mode we already printed the test case, so no need to do it again.
899 print "\n" unless $atLineStart;
902 my $resultsDir = catdir($expectedDir, dirname($base));
903 if ($generateNewResults) {
904 print "new (results generated in $resultsDir)\n";
910 } elsif ($actual eq $expected && $diffResult eq "passed") {
916 deleteExpectedAndActualResults($base);
918 $result = "mismatch";
920 my $pixelTestFailed = $pixelTests && $diffPNG && $diffPNG ne "";
921 my $testFailed = $actual ne $expected;
923 my $message = !$testFailed ? "pixel test failed" : "failed";
925 if (($testFailed || $pixelTestFailed) && $addPlatformExceptions) {
926 my $testBase = catfile($testDirectory, $base);
927 my $expectedBase = catfile($expectedDir, $base);
928 my $testIsMaximallyPlatformSpecific = $testBase =~ m|^\Q$platformTestDirectory\E/|;
929 my $expectedResultIsMaximallyPlatformSpecific = $expectedBase =~ m|^\Q$platformTestDirectory\E/|;
930 if (!$testIsMaximallyPlatformSpecific && !$expectedResultIsMaximallyPlatformSpecific) {
931 mkpath catfile($platformTestDirectory, dirname($base));
933 my $expectedFile = catfile($platformTestDirectory, "$expectedFileName");
934 writeToFile("$expectedFile", $actual);
936 if ($pixelTestFailed) {
937 my $expectedFile = catfile($platformTestDirectory, "$base-$expectedTag.checksum");
938 writeToFile("$expectedFile", $actualHash);
940 $expectedFile = catfile($platformTestDirectory, "$base-$expectedTag.png");
941 writeToFile("$expectedFile", $actualPNG);
943 $message .= " (results generated in $platformTestDirectory)";
947 printFailureMessageForTest($test, $message);
949 my $dir = "$testResultsDirectory/$base";
950 $dir =~ s|/([^/]+)$|| or die "Failed to find test name from base\n";
954 deleteExpectedAndActualResults($base);
955 recordActualResultsAndDiff($base, $actual);
957 if ($pixelTestFailed) {
958 $imagesPresent{$base} = 1;
960 writeToFile("$testResultsDirectory/$base-$actualTag.png", $actualPNG);
961 writeToFile("$testResultsDirectory/$base-$diffsTag.png", $diffPNG);
963 my $expectedPixelDir = expectedDirectoryForTest($base, 0, "png");
964 copy("$expectedPixelDir/$base-$expectedTag.png", "$testResultsDirectory/$base-$expectedTag.png");
966 open DIFFHTML, ">$testResultsDirectory/$base-$diffsTag.html" or die;
967 print DIFFHTML "<html>\n";
968 print DIFFHTML "<head>\n";
969 print DIFFHTML "<title>$base Image Compare</title>\n";
970 print DIFFHTML "<script language=\"Javascript\" type=\"text/javascript\">\n";
971 print DIFFHTML "var currentImage = 0;\n";
972 print DIFFHTML "var imageNames = new Array(\"Actual\", \"Expected\");\n";
973 print DIFFHTML "var imagePaths = new Array(\"$testName-$actualTag.png\", \"$testName-$expectedTag.png\");\n";
974 if (-f "$testDirectory/$base-w3c.png") {
975 copy("$testDirectory/$base-w3c.png", "$testResultsDirectory/$base-w3c.png");
976 print DIFFHTML "imageNames.push(\"W3C\");\n";
977 print DIFFHTML "imagePaths.push(\"$testName-w3c.png\");\n";
979 print DIFFHTML "function animateImage() {\n";
980 print DIFFHTML " var image = document.getElementById(\"animatedImage\");\n";
981 print DIFFHTML " var imageText = document.getElementById(\"imageText\");\n";
982 print DIFFHTML " image.src = imagePaths[currentImage];\n";
983 print DIFFHTML " imageText.innerHTML = imageNames[currentImage] + \" Image\";\n";
984 print DIFFHTML " currentImage = (currentImage + 1) % imageNames.length;\n";
985 print DIFFHTML " setTimeout('animateImage()',2000);\n";
986 print DIFFHTML "}\n";
987 print DIFFHTML "</script>\n";
988 print DIFFHTML "</head>\n";
989 print DIFFHTML "<body onLoad=\"animateImage();\">\n";
990 print DIFFHTML "<table>\n";
991 if ($diffPercentage) {
992 print DIFFHTML "<tr>\n";
993 print DIFFHTML "<td>Difference between images: <a href=\"$testName-$diffsTag.png\">$diffPercentage%</a></td>\n";
994 print DIFFHTML "</tr>\n";
996 print DIFFHTML "<tr>\n";
997 print DIFFHTML "<td><a href=\"" . toURL("$testDirectory/$test") . "\">test file</a></td>\n";
998 print DIFFHTML "</tr>\n";
999 print DIFFHTML "<tr>\n";
1000 print DIFFHTML "<td id=\"imageText\" style=\"text-weight: bold;\">Actual Image</td>\n";
1001 print DIFFHTML "</tr>\n";
1002 print DIFFHTML "<tr>\n";
1003 print DIFFHTML "<td><img src=\"$testName-$actualTag.png\" id=\"animatedImage\"></td>\n";
1004 print DIFFHTML "</tr>\n";
1005 print DIFFHTML "</table>\n";
1006 print DIFFHTML "</body>\n";
1007 print DIFFHTML "</html>\n";
1012 my $dir = "$testResultsDirectory/$base";
1013 $dir =~ s|/([^/]+)$|| or die "Failed to find test name from base\n";
1016 writeToFile("$testResultsDirectory/$base-$errorTag.txt", $error);
1019 push @{$tests{error}}, $test;
1022 countFinishedTest($test, $base, $result, $isText);
1023 last if stopRunningTestsEarlyIfNeeded();
1026 my $totalTestingTime = time - $overallStartTime;
1027 my $waitTime = getWaitTime();
1028 if ($waitTime > 0.1) {
1029 my $normalizedTestingTime = $totalTestingTime - $waitTime;
1030 printf "\n%0.2fs HTTPD waiting time\n", $waitTime . "";
1031 printf "%0.2fs normalized testing time", $normalizedTestingTime . "";
1033 printf "\n%0.2fs total testing time\n", $totalTestingTime . "";
1035 !$isDumpToolOpen || die "Failed to close $dumpToolName.\n";
1037 $isHttpdOpen = !closeHTTPD();
1038 closeWebSocketServer();
1040 # Because multiple instances of this script are running concurrently we cannot
1041 # safely delete this symlink.
1042 # system "rm /tmp/LayoutTests";
1044 # FIXME: Do we really want to check the image-comparison tool for leaks every time?
1045 if ($isDiffToolOpen && $shouldCheckLeaks) {
1046 $totalLeaks += countAndPrintLeaks("ImageDiff", $imageDiffToolPID, "$testResultsDirectory/ImageDiff-leaks.txt");
1051 parseLeaksandPrintUniqueLeaks();
1053 print "\nWARNING: $totalLeaks total leaks found!\n";
1054 print "See above for individual leaks results.\n" if ($leaksOutputFileNumber > 2);
1062 if ($report10Slowest) {
1063 print "\n\nThe 10 slowest tests:\n\n";
1065 for my $test (sort slowestcmp keys %durations) {
1066 printf "%0.2f secs: %s\n", $durations{$test}, $test;
1067 last if ++$count == 10;
1073 if ($skippedOnly && $counts{"match"}) {
1074 print "The following tests are in the Skipped file (" . File::Spec->abs2rel("$platformTestDirectory/Skipped", $testDirectory) . "), but succeeded:\n";
1075 foreach my $test (@{$tests{"match"}}) {
1080 if ($resetResults || ($counts{match} && $counts{match} == $count)) {
1081 print "all $count test cases succeeded\n";
1082 unlink $testResults;
1088 mkpath $testResultsDirectory;
1090 open HTML, ">", $testResults or die "Failed to open $testResults. $!";
1091 print HTML "<html>\n";
1092 print HTML "<head>\n";
1093 print HTML "<title>Layout Test Results</title>\n";
1094 print HTML "</head>\n";
1095 print HTML "<body>\n";
1097 if ($ignoreMetrics) {
1098 print HTML "<h4>Tested with metrics ignored.</h4>";
1101 print HTML htmlForResultsSection(@{$tests{mismatch}}, "Tests where results did not match expected results", \&linksForMismatchTest);
1102 print HTML htmlForResultsSection(@{$tests{timedout}}, "Tests that timed out", \&linksForErrorTest);
1103 print HTML htmlForResultsSection(@{$tests{crash}}, "Tests that caused the DumpRenderTree tool to crash", \&linksForErrorTest);
1104 print HTML htmlForResultsSection(@{$tests{error}}, "Tests that had stderr output", \&linksForErrorTest);
1105 print HTML htmlForResultsSection(@{$tests{new}}, "Tests that had no expected results (probably new)", \&linksForNewTest);
1107 print HTML "</body>\n";
1108 print HTML "</html>\n";
1111 my @configurationArgs = argumentsForConfiguration();
1114 system "WebKitTools/Scripts/run-launcher", @configurationArgs, "file://".$testResults if $launchSafari;
1116 unshift @configurationArgs, qw(-graphicssystem raster -style windows);
1118 $testResults = "/" . toWindowsPath($testResults);
1119 $testResults =~ s/\\/\//g;
1121 system "WebKitTools/Scripts/run-launcher", @configurationArgs, "file://".$testResults if $launchSafari;
1122 } elsif (isCygwin()) {
1123 system "cygstart", $testResults if $launchSafari;
1125 system "WebKitTools/Scripts/run-safari", @configurationArgs, "-NSOpen", $testResults if $launchSafari;
1128 closeCygpaths() if isCygwin();
1132 sub countAndPrintLeaks($$$)
1134 my ($dumpToolName, $dumpToolPID, $leaksFilePath) = @_;
1136 print "\n" unless $atLineStart;
1139 # We are excluding the following reported leaks so they don't get in our way when looking for WebKit leaks:
1140 # This allows us ignore known leaks and only be alerted when new leaks occur. Some leaks are in the old
1141 # versions of the system frameworks that are being used by the leaks bots. Even though a leak has been
1142 # fixed, it will be listed here until the bot has been updated with the newer frameworks.
1144 my @typesToExclude = (
1147 my @callStacksToExclude = (
1148 "Flash_EnforceLocalSecurity" # leaks in Flash plug-in code, rdar://problem/4449747
1152 # Leak list for the version of Tiger used on the build bot.
1153 push @callStacksToExclude, (
1154 "CFRunLoopRunSpecific \\| malloc_zone_malloc", "CFRunLoopRunSpecific \\| CFAllocatorAllocate ", # leak in CFRunLoopRunSpecific, rdar://problem/4670839
1155 "CGImageSourceGetPropertiesAtIndex", # leak in ImageIO, rdar://problem/4628809
1156 "FOGetCoveredUnicodeChars", # leak in ATS, rdar://problem/3943604
1157 "GetLineDirectionPreference", "InitUnicodeUtilities", # leaks tool falsely reporting leak in CFNotificationCenterAddObserver, rdar://problem/4964790
1158 "ICCFPrefWrapper::GetPrefDictionary", # leaks in Internet Config. code, rdar://problem/4449794
1159 "NSHTTPURLProtocol setResponseHeader:", # leak in multipart/mixed-replace handling in Foundation, no Radar, but fixed in Leopard
1160 "NSURLCache cachedResponseForRequest", # leak in CFURL cache, rdar://problem/4768430
1161 "PCFragPrepareClosureFromFile", # leak in Code Fragment Manager, rdar://problem/3426998
1162 "WebCore::Selection::toRange", # bug in 'leaks', rdar://problem/4967949
1163 "WebCore::SubresourceLoader::create", # bug in 'leaks', rdar://problem/4985806
1164 "_CFPreferencesDomainDeepCopyDictionary", # leak in CFPreferences, rdar://problem/4220786
1165 "_objc_msgForward", # leak in NSSpellChecker, rdar://problem/4965278
1166 "gldGetString", # leak in OpenGL, rdar://problem/5013699
1167 "_setDefaultUserInfoFromURL", # leak in NSHTTPAuthenticator, rdar://problem/5546453
1168 "SSLHandshake", # leak in SSL, rdar://problem/5546440
1169 "SecCertificateCreateFromData", # leak in SSL code, rdar://problem/4464397
1171 push @typesToExclude, (
1172 "THRD", # bug in 'leaks', rdar://problem/3387783
1173 "DRHT", # ditto (endian little hate i)
1178 # Leak list for the version of Leopard used on the build bot.
1179 push @callStacksToExclude, (
1180 "CFHTTPMessageAppendBytes", # leak in CFNetwork, rdar://problem/5435912
1181 "sendDidReceiveDataCallback", # leak in CFNetwork, rdar://problem/5441619
1182 "_CFHTTPReadStreamReadMark", # leak in CFNetwork, rdar://problem/5441468
1183 "httpProtocolStart", # leak in CFNetwork, rdar://problem/5468837
1184 "_CFURLConnectionSendCallbacks", # leak in CFNetwork, rdar://problem/5441600
1185 "DispatchQTMsg", # leak in QuickTime, PPC only, rdar://problem/5667132
1186 "QTMovieContentView createVisualContext", # leak in QuickTime, PPC only, rdar://problem/5667132
1187 "_CopyArchitecturesForJVMVersion", # leak in Java, rdar://problem/5910823
1191 if (isSnowLeopard()) {
1192 push @callStacksToExclude, (
1193 "readMakerNoteProps", # <rdar://problem/7156432> leak in ImageIO
1194 "QTKitMovieControllerView completeUISetup", # <rdar://problem/7155156> leak in QTKit
1195 "getVMInitArgs", # <rdar://problem/7714444> leak in Java
1196 "Java_java_lang_System_initProperties", # <rdar://problem/7714465> leak in Java
1197 "glrCompExecuteKernel", # <rdar://problem/7815391> leak in graphics driver while using OpenGL
1201 if (isDarwin() && !isTiger() && !isLeopard() && !isSnowLeopard()) {
1202 push @callStacksToExclude, (
1203 "CGGradientCreateWithColorComponents", # leak in CoreGraphics, <rdar://problem/7888492>
1207 my $leaksTool = sourceDir() . "/WebKitTools/Scripts/run-leaks";
1208 my $excludeString = "--exclude-callstack '" . (join "' --exclude-callstack '", @callStacksToExclude) . "'";
1209 $excludeString .= " --exclude-type '" . (join "' --exclude-type '", @typesToExclude) . "'" if @typesToExclude;
1211 print " ? checking for leaks in $dumpToolName\n";
1212 my $leaksOutput = `$leaksTool $excludeString $dumpToolPID`;
1213 my ($count, $bytes) = $leaksOutput =~ /Process $dumpToolPID: (\d+) leaks? for (\d+) total/;
1214 my ($excluded) = $leaksOutput =~ /(\d+) leaks? excluded/;
1216 my $adjustedCount = $count;
1217 $adjustedCount -= $excluded if $excluded;
1219 if (!$adjustedCount) {
1220 print " - no leaks found\n";
1221 unlink $leaksFilePath;
1224 my $dir = $leaksFilePath;
1225 $dir =~ s|/[^/]+$|| or die;
1229 print " + $adjustedCount leaks ($bytes bytes including $excluded excluded leaks) were found, details in $leaksFilePath\n";
1231 print " + $count leaks ($bytes bytes) were found, details in $leaksFilePath\n";
1234 writeToFile($leaksFilePath, $leaksOutput);
1236 push @leaksFilenames, $leaksFilePath;
1239 return $adjustedCount;
1244 my ($filePath, $contents) = @_;
1245 open NEWFILE, ">", "$filePath" or die "Could not create $filePath. $!\n";
1246 print NEWFILE $contents;
1250 # Break up a path into the directory (with slash) and base name.
1255 my $pathSeparator = "/";
1256 my $dirname = dirname($path) . $pathSeparator;
1257 $dirname = "" if $dirname eq "." . $pathSeparator;
1259 return ($dirname, basename($path));
1262 # Sort first by directory, then by file, so all paths in one directory are grouped
1263 # rather than being interspersed with items from subdirectories.
1264 # Use numericcmp to sort directory and filenames to make order logical.
1267 my ($patha, $pathb) = @_;
1269 my ($dira, $namea) = splitpath($patha);
1270 my ($dirb, $nameb) = splitpath($pathb);
1272 return numericcmp($dira, $dirb) if $dira ne $dirb;
1273 return numericcmp($namea, $nameb);
1276 # Sort numeric parts of strings as numbers, other parts as strings.
1277 # Makes 1.33 come after 1.3, which is cool.
1282 my @a = split /(\d+)/, $aa;
1283 my @b = split /(\d+)/, $bb;
1285 # Compare one chunk at a time.
1286 # Each chunk is either all numeric digits, or all not numeric digits.
1291 # Use numeric comparison if chunks are non-equal numbers.
1292 return $a <=> $b if $a =~ /^\d/ && $b =~ /^\d/ && $a != $b;
1294 # Use string comparison if chunks are any other kind of non-equal string.
1295 return $a cmp $b if $a ne $b;
1298 # One of the two is now empty; compare lengths for result in this case.
1302 # Sort slowest tests first.
1305 my ($testa, $testb) = @_;
1307 my $dura = $durations{$testa};
1308 my $durb = $durations{$testb};
1309 return $durb <=> $dura if $dura != $durb;
1310 return pathcmp($testa, $testb);
1313 sub launchWithEnv(\@\%)
1315 my ($args, $env) = @_;
1317 # Dump the current environment as perl code and then put it in quotes so it is one parameter.
1318 my $environmentDumper = Data::Dumper->new([\%{$env}], [qw(*ENV)]);
1319 $environmentDumper->Indent(0);
1320 $environmentDumper->Purity(1);
1321 my $allEnvVars = $environmentDumper->Dump();
1322 unshift @{$args}, "\"$allEnvVars\"";
1324 my $execScript = File::Spec->catfile(sourceDir(), qw(WebKitTools Scripts execAppWithEnv));
1325 unshift @{$args}, $execScript;
1329 sub resolveAndMakeTestResultsDirectory()
1331 my $absTestResultsDirectory = File::Spec->rel2abs(glob $testResultsDirectory);
1332 mkpath $absTestResultsDirectory;
1333 return $absTestResultsDirectory;
1338 return if $isDiffToolOpen;
1339 return if !$pixelTests;
1342 $CLEAN_ENV{MallocStackLogging} = 1 if $shouldCheckLeaks;
1343 $imageDiffToolPID = open2(\*DIFFIN, \*DIFFOUT, $imageDiffTool, launchWithEnv(@diffToolArgs, %CLEAN_ENV)) or die "unable to open $imageDiffTool\n";
1344 $isDiffToolOpen = 1;
1347 sub buildDumpTool($)
1349 my ($dumpToolName) = @_;
1351 my $dumpToolBuildScript = "build-" . lc($dumpToolName);
1352 print STDERR "Running $dumpToolBuildScript\n";
1355 my ($childIn, $childOut, $childErr);
1357 open(DEVNULL, ">", File::Spec->devnull()) or die "Failed to open /dev/null";
1358 $childOut = ">&DEVNULL";
1359 $childErr = ">&DEVNULL";
1361 # When not quiet, let the child use our stdout/stderr.
1362 $childOut = ">&STDOUT";
1363 $childErr = ">&STDERR";
1366 my @args = argumentsForConfiguration();
1367 my $buildProcess = open3($childIn, $childOut, $childErr, "WebKitTools/Scripts/$dumpToolBuildScript", @args) or die "Failed to run build-dumprendertree";
1369 waitpid $buildProcess, 0;
1370 my $buildResult = $?;
1374 close DEVNULL if ($quiet);
1377 print STDERR "Compiling $dumpToolName failed!\n";
1378 exit exitStatus($buildResult);
1384 return if $isDumpToolOpen;
1388 # Generic environment variables
1389 if (defined $ENV{'WEBKIT_TESTFONTS'}) {
1390 $CLEAN_ENV{WEBKIT_TESTFONTS} = $ENV{'WEBKIT_TESTFONTS'};
1393 # unique temporary directory for each DumpRendertree - needed for running more DumpRenderTree in parallel
1394 $CLEAN_ENV{DUMPRENDERTREE_TEMP} = File::Temp::tempdir('DumpRenderTree-XXXXXX', TMPDIR => 1, CLEANUP => 1);
1395 $CLEAN_ENV{XML_CATALOG_FILES} = ""; # work around missing /etc/catalog <rdar://problem/4292995>
1397 # Platform spesifics
1399 if (defined $ENV{'DISPLAY'}) {
1400 $CLEAN_ENV{DISPLAY} = $ENV{'DISPLAY'};
1402 $CLEAN_ENV{DISPLAY} = ":1";
1404 if (defined $ENV{'XAUTHORITY'}) {
1405 $CLEAN_ENV{XAUTHORITY} = $ENV{'XAUTHORITY'};
1408 $CLEAN_ENV{HOME} = $ENV{'HOME'};
1410 if (defined $ENV{'LD_LIBRARY_PATH'}) {
1411 $CLEAN_ENV{LD_LIBRARY_PATH} = $ENV{'LD_LIBRARY_PATH'};
1413 if (defined $ENV{'DBUS_SESSION_BUS_ADDRESS'}) {
1414 $CLEAN_ENV{DBUS_SESSION_BUS_ADDRESS} = $ENV{'DBUS_SESSION_BUS_ADDRESS'};
1416 } elsif (isDarwin()) {
1417 if (defined $ENV{'DYLD_LIBRARY_PATH'}) {
1418 $CLEAN_ENV{DYLD_LIBRARY_PATH} = $ENV{'DYLD_LIBRARY_PATH'};
1421 $CLEAN_ENV{DYLD_FRAMEWORK_PATH} = $productDir;
1422 $CLEAN_ENV{DYLD_INSERT_LIBRARIES} = "/usr/lib/libgmalloc.dylib" if $guardMalloc;
1423 } elsif (isCygwin()) {
1424 $CLEAN_ENV{HOMEDRIVE} = $ENV{'HOMEDRIVE'};
1425 $CLEAN_ENV{HOMEPATH} = $ENV{'HOMEPATH'};
1426 $CLEAN_ENV{_NT_SYMBOL_PATH} = $ENV{_NT_SYMBOL_PATH};
1428 setPathForRunningWebKitApp(\%CLEAN_ENV);
1433 $CLEAN_ENV{GTK_MODULES} = "gail";
1434 $CLEAN_ENV{WEBKIT_INSPECTOR_PATH} = "$productDir/resources/inspector";
1438 $CLEAN_ENV{QTWEBKIT_PLUGIN_PATH} = productDir() . "/lib/plugins";
1439 $CLEAN_ENV{QT_DRT_WEBVIEW_MODE} = $ENV{"QT_DRT_WEBVIEW_MODE"};
1442 my @args = ($dumpTool, @toolArgs);
1443 if (isAppleMacWebKit() and !isTiger()) {
1444 unshift @args, "arch", "-" . architecture();
1448 unshift @args, "valgrind", "--suppressions=$platformBaseDirectory/qt/SuppressedValgrindErrors";
1451 $CLEAN_ENV{MallocStackLogging} = 1 if $shouldCheckLeaks;
1453 $dumpToolPID = open3(\*OUT, \*IN, \*ERROR, launchWithEnv(@args, %CLEAN_ENV)) or die "Failed to start tool: $dumpTool\n";
1454 $isDumpToolOpen = 1;
1455 $dumpToolCrashed = 0;
1460 return if !$isDumpToolOpen;
1464 waitpid $dumpToolPID, 0;
1466 # check for WebCore counter leaks.
1467 if ($shouldCheckLeaks) {
1473 $isDumpToolOpen = 0;
1476 sub dumpToolDidCrash()
1478 return 1 if $dumpToolCrashed;
1479 return 0 unless $isDumpToolOpen;
1480 my $pid = waitpid(-1, WNOHANG);
1481 return 1 if ($pid == $dumpToolPID);
1483 # On Mac OS X, crashing may be significantly delayed by crash reporter.
1484 return 0 unless isAppleMacWebKit();
1486 return DumpRenderTreeSupport::processIsCrashing($dumpToolPID);
1489 sub configureAndOpenHTTPDIfNeeded()
1491 return if $isHttpdOpen;
1492 my $absTestResultsDirectory = resolveAndMakeTestResultsDirectory();
1493 my $listen = "127.0.0.1:$httpdPort";
1495 "-c", "CustomLog \"$absTestResultsDirectory/access_log.txt\" common",
1496 "-c", "ErrorLog \"$absTestResultsDirectory/error_log.txt\"",
1497 "-C", "Listen $listen"
1500 my @defaultArgs = getDefaultConfigForTestDirectory($testDirectory);
1501 @args = (@defaultArgs, @args);
1503 waitForHTTPDLock() if $shouldWaitForHTTPD;
1504 $isHttpdOpen = openHTTPD(@args);
1507 sub checkPythonVersion()
1509 # we have not chdir to sourceDir yet.
1510 system sourceDir() . "/WebKitTools/Scripts/ensure-valid-python", "--check-only";
1511 return exitStatus($?) == 0;
1514 sub openWebSocketServerIfNeeded()
1516 return 1 if $isWebSocketServerOpen;
1517 return 0 if $failedToStartWebSocketServer;
1519 my $webSocketHandlerDir = "$testDirectory";
1520 my $absTestResultsDirectory = resolveAndMakeTestResultsDirectory();
1521 $webSocketServerPidFile = "$absTestResultsDirectory/websocket.pid";
1524 "WebKitTools/Scripts/new-run-webkit-websocketserver",
1525 "--server", "start",
1526 "--port", "$webSocketPort",
1527 "--root", "$webSocketHandlerDir",
1528 "--output-dir", "$absTestResultsDirectory",
1529 "--pidfile", "$webSocketServerPidFile"
1531 system "/usr/bin/python", @args;
1533 $isWebSocketServerOpen = 1;
1537 sub closeWebSocketServer()
1539 return if !$isWebSocketServerOpen;
1542 "WebKitTools/Scripts/new-run-webkit-websocketserver",
1544 "--pidfile", "$webSocketServerPidFile"
1546 system "/usr/bin/python", @args;
1547 unlink "$webSocketServerPidFile";
1549 # wss is disabled until all platforms support pyOpenSSL.
1550 $isWebSocketServerOpen = 0;
1553 sub fileNameWithNumber($$)
1555 my ($base, $number) = @_;
1556 return "$base$number" if ($number > 1);
1560 sub processIgnoreTests($$)
1562 my @ignoreList = split(/\s*,\s*/, shift);
1563 my $listName = shift;
1565 my $disabledSuffix = "-disabled";
1567 my $addIgnoredDirectories = sub {
1568 return () if exists $ignoredLocalDirectories{basename($File::Find::dir)};
1569 $ignoredDirectories{File::Spec->abs2rel($File::Find::dir, $testDirectory)} = 1;
1572 foreach my $item (@ignoreList) {
1573 my $path = catfile($testDirectory, $item);
1575 $ignoredDirectories{$item} = 1;
1576 find({ preprocess => $addIgnoredDirectories, wanted => sub {} }, $path);
1579 $ignoredFiles{$item} = 1;
1580 } elsif (-f $path . $disabledSuffix) {
1581 # The test is disabled, so do nothing.
1583 print "$listName list contained '$item', but no file of that name could be found\n";
1588 sub stripExtension($)
1592 $test =~ s/\.[a-zA-Z]+$//;
1596 sub isTextOnlyTest($)
1600 if ($actual =~ /^layer at/ms) {
1608 sub expectedDirectoryForTest($;$;$)
1610 my ($base, $isText, $expectedExtension) = @_;
1612 my @directories = @platformResultHierarchy;
1613 push @directories, map { catdir($platformBaseDirectory, $_) } qw(mac-snowleopard mac) if isCygwin();
1614 push @directories, $expectedDirectory;
1616 # If we already have expected results, just return their location.
1617 foreach my $directory (@directories) {
1618 return $directory if (-f "$directory/$base-$expectedTag.$expectedExtension");
1621 # For cross-platform tests, text-only results should go in the cross-platform directory,
1622 # while render tree dumps should go in the least-specific platform directory.
1623 return $isText ? $expectedDirectory : $platformResultHierarchy[$#platformResultHierarchy];
1626 sub countFinishedTest($$$$)
1628 my ($test, $base, $result, $isText) = @_;
1630 if (($count + 1) % $testsPerDumpTool == 0 || $count == $#tests) {
1631 if ($shouldCheckLeaks) {
1633 if ($testsPerDumpTool == 1) {
1634 $fileName = "$testResultsDirectory/$base-leaks.txt";
1636 $fileName = "$testResultsDirectory/" . fileNameWithNumber($dumpToolName, $leaksOutputFileNumber) . "-leaks.txt";
1638 my $leakCount = countAndPrintLeaks($dumpToolName, $dumpToolPID, $fileName);
1639 $totalLeaks += $leakCount;
1640 $leaksOutputFileNumber++ if ($leakCount);
1648 push @{$tests{$result}}, $test;
1651 sub testCrashedOrTimedOut($$$$$)
1653 my ($test, $base, $didCrash, $actual, $error) = @_;
1655 printFailureMessageForTest($test, $didCrash ? "crashed" : "timed out");
1657 sampleDumpTool() unless $didCrash;
1659 my $dir = "$testResultsDirectory/$base";
1660 $dir =~ s|/([^/]+)$|| or die "Failed to find test name from base\n";
1663 deleteExpectedAndActualResults($base);
1665 if (defined($error) && length($error)) {
1666 writeToFile("$testResultsDirectory/$base-$errorTag.txt", $error);
1669 recordActualResultsAndDiff($base, $actual);
1671 kill 9, $dumpToolPID unless $didCrash;
1675 return unless isCygwin() && !$didCrash && $base =~ /^http/;
1676 # On Cygwin, http tests timing out can be a symptom of a non-responsive httpd.
1677 # If we timed out running an http test, try restarting httpd.
1678 $isHttpdOpen = !closeHTTPD();
1679 configureAndOpenHTTPDIfNeeded();
1682 sub printFailureMessageForTest($$)
1684 my ($test, $description) = @_;
1687 print "\n" unless $atLineStart;
1690 print "$description\n";
1696 sub openCygpathIfNeeded($)
1700 return unless isCygwin();
1701 return $cygpaths{$options} if $cygpaths{$options} && $cygpaths{$options}->{"open"};
1703 local (*CYGPATHIN, *CYGPATHOUT);
1704 my $pid = open2(\*CYGPATHIN, \*CYGPATHOUT, "cygpath -f - $options");
1708 "out" => *CYGPATHOUT,
1712 $cygpaths{$options} = $cygpath;
1719 return unless isCygwin();
1721 foreach my $cygpath (values(%cygpaths)) {
1722 close $cygpath->{"in"};
1723 close $cygpath->{"out"};
1724 waitpid($cygpath->{"pid"}, 0);
1725 $cygpath->{"open"} = 0;
1730 sub convertPathUsingCygpath($$)
1732 my ($path, $options) = @_;
1734 # cygpath -f (at least in Cygwin 1.7) converts spaces into newlines. We remove spaces here and
1735 # add them back in after conversion to work around this.
1736 my $spaceSubstitute = "__NOTASPACE__";
1737 $path =~ s/ /\Q$spaceSubstitute\E/g;
1739 my $cygpath = openCygpathIfNeeded($options);
1740 local *inFH = $cygpath->{"in"};
1741 local *outFH = $cygpath->{"out"};
1742 print outFH $path . "\n";
1743 my $convertedPath = <inFH>;
1744 chomp($convertedPath) if defined $convertedPath;
1746 $convertedPath =~ s/\Q$spaceSubstitute\E/ /g;
1747 return $convertedPath;
1753 return unless isCygwin();
1755 return convertPathUsingCygpath($path, "-u");
1758 sub toWindowsPath($)
1761 return unless isCygwin();
1763 return convertPathUsingCygpath($path, "-w");
1770 if ($useRemoteLinksToTests) {
1771 my $relativePath = File::Spec->abs2rel($path, $testDirectory);
1773 # If the file is below the test directory then convert it into a link to the file in SVN
1774 if ($relativePath !~ /^\.\.\//) {
1775 my $revision = svnRevisionForDirectory($testDirectory);
1776 my $svnPath = pathRelativeToSVNRepositoryRootForPath($path);
1777 return "http://trac.webkit.org/export/$revision/$svnPath";
1781 return $path unless isCygwin();
1783 return "file:///" . convertPathUsingCygpath($path, "-m");
1786 sub validateSkippedArg($$;$)
1788 my ($option, $value, $value2) = @_;
1789 my %validSkippedValues = map { $_ => 1 } qw(default ignore only);
1790 $value = lc($value);
1791 die "Invalid argument '" . $value . "' for option $option" unless $validSkippedValues{$value};
1792 $treatSkipped = $value;
1795 sub htmlForResultsSection(\@$&)
1797 my ($tests, $description, $linkGetter) = @_;
1800 return join("\n", @html) unless @{$tests};
1802 push @html, "<p>$description:</p>";
1803 push @html, "<table>";
1804 foreach my $test (@{$tests}) {
1806 push @html, "<td><a href=\"" . toURL("$testDirectory/$test") . "\">$test</a></td>";
1807 foreach my $link (@{&{$linkGetter}($test)}) {
1808 push @html, "<td><a href=\"$link->{href}\">$link->{text}</a></td>";
1810 push @html, "</tr>";
1812 push @html, "</table>";
1814 return join("\n", @html);
1817 sub linksForExpectedAndActualResults($)
1823 return \@links unless -s "$testResultsDirectory/$base-$diffsTag.txt";
1825 my $expectedResultPath = $expectedResultPaths{$base};
1826 my ($expectedResultFileName, $expectedResultsDirectory, $expectedResultExtension) = fileparse($expectedResultPath, qr{\.[^.]+$});
1828 push @links, { href => "$base-$expectedTag$expectedResultExtension", text => "expected" };
1829 push @links, { href => "$base-$actualTag$expectedResultExtension", text => "actual" };
1830 push @links, { href => "$base-$diffsTag.txt", text => "diff" };
1831 push @links, { href => "$base-$prettyDiffTag.html", text => "pretty diff" };
1836 sub linksForMismatchTest
1842 my $base = stripExtension($test);
1844 push @links, @{linksForExpectedAndActualResults($base)};
1845 return \@links unless $pixelTests && $imagesPresent{$base};
1847 push @links, { href => "$base-$expectedTag.png", text => "expected image" };
1848 push @links, { href => "$base-$diffsTag.html", text => "image diffs" };
1849 push @links, { href => "$base-$diffsTag.png", text => "$imageDifferences{$base}%" };
1854 sub linksForErrorTest
1860 my $base = stripExtension($test);
1862 push @links, @{linksForExpectedAndActualResults($base)};
1863 push @links, { href => "$base-$errorTag.txt", text => "stderr" };
1874 my $base = stripExtension($test);
1876 my $expectedResultPath = $expectedResultPaths{$base};
1877 my ($expectedResultFileName, $expectedResultsDirectory, $expectedResultExtension) = fileparse($expectedResultPath, qr{\.[^.]+$});
1879 push @links, { href => "$base-$actualTag$expectedResultExtension", text => "result" };
1880 if ($pixelTests && $imagesPresent{$base}) {
1881 push @links, { href => "$base-$expectedTag.png", text => "image" };
1887 sub deleteExpectedAndActualResults($)
1891 unlink "$testResultsDirectory/$base-$actualTag.txt";
1892 unlink "$testResultsDirectory/$base-$diffsTag.txt";
1893 unlink "$testResultsDirectory/$base-$errorTag.txt";
1896 sub recordActualResultsAndDiff($$)
1898 my ($base, $actualResults) = @_;
1900 return unless defined($actualResults) && length($actualResults);
1902 my $expectedResultPath = $expectedResultPaths{$base};
1903 my ($expectedResultFileNameMinusExtension, $expectedResultDirectoryPath, $expectedResultExtension) = fileparse($expectedResultPath, qr{\.[^.]+$});
1904 my $actualResultsPath = "$testResultsDirectory/$base-$actualTag$expectedResultExtension";
1905 my $copiedExpectedResultsPath = "$testResultsDirectory/$base-$expectedTag$expectedResultExtension";
1907 mkpath(dirname($actualResultsPath));
1908 writeToFile("$actualResultsPath", $actualResults);
1910 if (-f $expectedResultPath) {
1911 copy("$expectedResultPath", "$copiedExpectedResultsPath");
1913 open EMPTY, ">$copiedExpectedResultsPath";
1917 my $diffOuputBasePath = "$testResultsDirectory/$base";
1918 my $diffOutputPath = "$diffOuputBasePath-$diffsTag.txt";
1919 system "diff -u \"$copiedExpectedResultsPath\" \"$actualResultsPath\" > \"$diffOutputPath\"";
1921 my $prettyDiffOutputPath = "$diffOuputBasePath-$prettyDiffTag.html";
1922 my $prettyPatchPath = "BugsSite/PrettyPatch/";
1923 my $prettifyPath = "$prettyPatchPath/prettify.rb";
1924 system "ruby -I \"$prettyPatchPath\" \"$prettifyPath\" \"$diffOutputPath\" > \"$prettyDiffOutputPath\"";
1927 sub buildPlatformResultHierarchy()
1929 mkpath($platformTestDirectory) if ($platform eq "undefined" && !-d "$platformTestDirectory");
1933 my $isMac = $platform =~ /^mac/;
1934 my $isWin = $platform =~ /^win/;
1935 if ($isMac || $isWin) {
1936 my $effectivePlatform = $platform;
1937 if ($platform eq "mac-wk2" || $platform eq "win-wk2") {
1938 push @platforms, $platform;
1939 $effectivePlatform = $realPlatform;
1942 my @platformList = $isMac ? @macPlatforms : @winPlatforms;
1944 for ($i = 0; $i < @platformList; $i++) {
1945 last if $platformList[$i] eq $effectivePlatform;
1947 for (; $i < @platformList; $i++) {
1948 push @platforms, $platformList[$i];
1950 } elsif ($platform =~ /^qt-/) {
1951 push @platforms, $platform;
1952 push @platforms, "qt";
1954 @platforms = $platform;
1958 for (my $i = 0; $i < @platforms; $i++) {
1959 my $scoped = catdir($platformBaseDirectory, $platforms[$i]);
1960 push(@hierarchy, $scoped) if (-d $scoped);
1966 sub buildPlatformTestHierarchy(@)
1968 my (@platformHierarchy) = @_;
1969 return @platformHierarchy if (@platformHierarchy < 2);
1970 if ($platformHierarchy[0] =~ /mac-wk2/) {
1971 return ($platformHierarchy[0], $platformHierarchy[1], $platformHierarchy[$#platformHierarchy]);
1973 return ($platformHierarchy[0], $platformHierarchy[$#platformHierarchy]);
1976 sub epiloguesAndPrologues($$)
1978 my ($lastDirectory, $directory) = @_;
1979 my @lastComponents = split('/', $lastDirectory);
1980 my @components = split('/', $directory);
1982 while (@lastComponents) {
1983 if (!defined($components[0]) || $lastComponents[0] ne $components[0]) {
1987 shift @lastComponents;
1991 my $leaving = $lastDirectory;
1992 foreach (@lastComponents) {
1993 my $epilogue = $leaving . "/resources/run-webkit-tests-epilogue.html";
1994 foreach (@platformResultHierarchy) {
1995 push @result, catdir($_, $epilogue) if (stat(catdir($_, $epilogue)));
1997 push @result, catdir($testDirectory, $epilogue) if (stat(catdir($testDirectory, $epilogue)));
1998 $leaving =~ s|(^\|/)[^/]+$||;
2001 my $entering = $leaving;
2002 foreach (@components) {
2003 $entering .= '/' . $_;
2004 my $prologue = $entering . "/resources/run-webkit-tests-prologue.html";
2005 push @result, catdir($testDirectory, $prologue) if (stat(catdir($testDirectory, $prologue)));
2006 foreach (reverse @platformResultHierarchy) {
2007 push @result, catdir($_, $prologue) if (stat(catdir($_, $prologue)));
2013 sub parseLeaksandPrintUniqueLeaks()
2015 return unless @leaksFilenames;
2017 my $mergedFilenames = join " ", @leaksFilenames;
2018 my $parseMallocHistoryTool = sourceDir() . "/WebKitTools/Scripts/parse-malloc-history";
2020 open MERGED_LEAKS, "cat $mergedFilenames | $parseMallocHistoryTool --merge-depth $mergeDepth - |" ;
2021 my @leakLines = <MERGED_LEAKS>;
2024 my $uniqueLeakCount = 0;
2026 foreach my $line (@leakLines) {
2027 ++$uniqueLeakCount if ($line =~ /^(\d*)\scalls/);
2028 $totalBytes = $1 if $line =~ /^total\:\s(.*)\s\(/;
2031 print "\nWARNING: $totalLeaks total leaks found for a total of $totalBytes!\n";
2032 print "WARNING: $uniqueLeakCount unique leaks found!\n";
2033 print "See above for individual leaks results.\n" if ($leaksOutputFileNumber > 2);
2037 sub extensionForMimeType($)
2039 my ($mimeType) = @_;
2041 if ($mimeType eq "application/x-webarchive") {
2042 return "webarchive";
2043 } elsif ($mimeType eq "application/pdf") {
2049 # Read up to the first #EOF (the content block of the test), or until detecting crashes or timeouts.
2050 sub readFromDumpToolWithTimer(**)
2052 my ($fhIn, $fhError) = @_;
2054 setFileHandleNonBlocking($fhIn, 1);
2055 setFileHandleNonBlocking($fhError, 1);
2057 my $maximumSecondsWithoutOutput = $timeoutSeconds;
2058 $maximumSecondsWithoutOutput *= 10 if $guardMalloc;
2059 my $microsecondsToWaitBeforeReadingAgain = 1000;
2061 my $timeOfLastSuccessfulRead = time;
2065 my $status = "success";
2066 my $mimeType = "text/plain";
2067 # We don't have a very good way to know when the "headers" stop
2068 # and the content starts, so we use this as a hack:
2069 my $haveSeenContentType = 0;
2070 my $haveSeenEofIn = 0;
2071 my $haveSeenEofError = 0;
2074 if (time - $timeOfLastSuccessfulRead > $maximumSecondsWithoutOutput) {
2075 $status = dumpToolDidCrash() ? "crashed" : "timedOut";
2079 # Once we've seen the EOF, we must not read anymore.
2080 my $lineIn = readline($fhIn) unless $haveSeenEofIn;
2081 my $lineError = readline($fhError) unless $haveSeenEofError;
2082 if (!defined($lineIn) && !defined($lineError)) {
2083 last if ($haveSeenEofIn && $haveSeenEofError);
2086 $status = "crashed";
2091 usleep($microsecondsToWaitBeforeReadingAgain);
2095 $timeOfLastSuccessfulRead = time;
2097 if (defined($lineIn)) {
2098 if (!$haveSeenContentType && $lineIn =~ /^Content-Type: (\S+)$/) {
2100 $haveSeenContentType = 1;
2101 } elsif ($lineIn =~ /#EOF/) {
2104 push @output, $lineIn;
2107 if (defined($lineError)) {
2108 if ($lineError =~ /#CRASHED/) {
2109 $status = "crashed";
2112 if ($lineError =~ /#EOF/) {
2113 $haveSeenEofError = 1;
2115 push @error, $lineError;
2120 setFileHandleNonBlocking($fhIn, 0);
2121 setFileHandleNonBlocking($fhError, 0);
2123 output => join("", @output),
2124 error => join("", @error),
2126 mimeType => $mimeType,
2127 extension => extensionForMimeType($mimeType)
2131 sub setFileHandleNonBlocking(*$)
2133 my ($fh, $nonBlocking) = @_;
2135 my $flags = fcntl($fh, F_GETFL, 0) or die "Couldn't get filehandle flags";
2138 $flags |= O_NONBLOCK;
2140 $flags &= ~O_NONBLOCK;
2143 fcntl($fh, F_SETFL, $flags) or die "Couldn't set filehandle flags";
2148 sub sampleDumpTool()
2150 return unless isAppleMacWebKit();
2151 return unless $runSample;
2153 my $outputDirectory = "$ENV{HOME}/Library/Logs/DumpRenderTree";
2154 -d $outputDirectory or mkdir $outputDirectory;
2156 my $outputFile = "$outputDirectory/HangReport.txt";
2157 system "/usr/bin/sample", $dumpToolPID, qw(10 10 -file), $outputFile;
2160 sub stripMetrics($$)
2162 my ($actual, $expected) = @_;
2164 foreach my $result ($actual, $expected) {
2165 $result =~ s/at \(-?[0-9]+,-?[0-9]+\) *//g;
2166 $result =~ s/size -?[0-9]+x-?[0-9]+ *//g;
2167 $result =~ s/text run width -?[0-9]+: //g;
2168 $result =~ s/text run width -?[0-9]+ [a-zA-Z ]+: //g;
2169 $result =~ s/RenderButton {BUTTON} .*/RenderButton {BUTTON}/g;
2170 $result =~ s/RenderImage {INPUT} .*/RenderImage {INPUT}/g;
2171 $result =~ s/RenderBlock {INPUT} .*/RenderBlock {INPUT}/g;
2172 $result =~ s/RenderTextControl {INPUT} .*/RenderTextControl {INPUT}/g;
2173 $result =~ s/\([0-9]+px/px/g;
2174 $result =~ s/ *" *\n +" */ /g;
2175 $result =~ s/" +$/"/g;
2177 $result =~ s/- /-/g;
2178 $result =~ s/\n( *)"\s+/\n$1"/g;
2179 $result =~ s/\s+"\n/"\n/g;
2180 $result =~ s/scrollWidth [0-9]+/scrollWidth/g;
2181 $result =~ s/scrollHeight [0-9]+/scrollHeight/g;
2184 return ($actual, $expected);
2187 sub fileShouldBeIgnored
2189 my ($filePath) = @_;
2190 foreach my $ignoredDir (keys %ignoredDirectories) {
2191 if ($filePath =~ m/^$ignoredDir/) {
2198 sub readSkippedFiles($)
2200 my ($constraintPath) = @_;
2202 my @skippedFileDirectories = @platformTestHierarchy;
2204 # Because nearly all of the skipped tests for WebKit 2 on Mac are due to
2205 # cross-platform issues, Windows will use both the Mac and Windows skipped
2206 # lists to avoid maintaining separate lists.
2207 push(@skippedFileDirectories, catdir($platformBaseDirectory, "mac-wk2")) if $platform eq "win-wk2";
2209 foreach my $level (@skippedFileDirectories) {
2210 if (open SKIPPED, "<", "$level/Skipped") {
2212 my ($dir, $name) = splitpath($level);
2213 print "Skipped tests in $name:\n";
2219 $skipped =~ s/^[ \n\r]+//;
2220 $skipped =~ s/[ \n\r]+$//;
2221 if ($skipped && $skipped !~ /^#/) {
2223 if (!fileShouldBeIgnored($skipped)) {
2224 if (!$constraintPath) {
2225 # Always add $skipped since no constraint path was specified on the command line.
2226 push(@ARGV, $skipped);
2227 } elsif ($skipped =~ /^($constraintPath)/) {
2228 # Add $skipped only if it matches the current path constraint, e.g.,
2229 # "--skipped=only dir1" with "dir1/file1.html" on the skipped list.
2230 push(@ARGV, $skipped);
2231 } elsif ($constraintPath =~ /^($skipped)/) {
2232 # Add current path constraint if it is more specific than the skip list entry,
2233 # e.g., "--skipped=only dir1/dir2/dir3" with "dir1" on the skipped list.
2234 push(@ARGV, $constraintPath);
2236 } elsif ($verbose) {
2237 print " $skipped\n";
2241 print " $skipped\n";
2243 processIgnoreTests($skipped, "Skipped");
2256 return () if exists $ignoredLocalDirectories{basename($File::Find::dir)};
2257 return () if exists $ignoredDirectories{File::Spec->abs2rel($File::Find::dir, $testDirectory)};
2264 if ($filename =~ /\.([^.]+)$/) {
2265 if (exists $supportedFileExtensions{$1}) {
2266 my $path = File::Spec->abs2rel(catfile($File::Find::dir, $filename), $testDirectory);
2267 push @testsFound, $path if !exists $ignoredFiles{$path};
2274 my @testsToRun = ();
2276 for my $test (@ARGV) {
2277 $test =~ s/^($layoutTestsName|$testDirectory)\///;
2278 my $fullPath = catfile($testDirectory, $test);
2279 if (file_name_is_absolute($test)) {
2280 print "can't run test $test outside $testDirectory\n";
2281 } elsif (-f $fullPath) {
2282 my ($filename, $pathname, $fileExtension) = fileparse($test, qr{\.[^.]+$});
2283 if (!exists $supportedFileExtensions{substr($fileExtension, 1)}) {
2284 print "test $test does not have a supported extension\n";
2285 } elsif ($testHTTP || $pathname !~ /^http\//) {
2286 push @testsToRun, $test;
2288 } elsif (-d $fullPath) {
2290 find({ preprocess => \&directoryFilter, wanted => \&fileFilter }, $fullPath);
2291 for my $level (@platformTestHierarchy) {
2292 my $platformPath = catfile($level, $test);
2293 find({ preprocess => \&directoryFilter, wanted => \&fileFilter }, $platformPath) if (-d $platformPath);
2295 push @testsToRun, sort pathcmp @testsFound;
2298 print "test $test not found\n";
2302 if (!scalar @ARGV) {
2304 find({ preprocess => \&directoryFilter, wanted => \&fileFilter }, $testDirectory);
2305 for my $level (@platformTestHierarchy) {
2306 find({ preprocess => \&directoryFilter, wanted => \&fileFilter }, $level);
2308 push @testsToRun, sort pathcmp @testsFound;
2311 # We need to minimize the time when Apache and WebSocketServer is locked by tests
2312 # so run them last if no explicit order was specified in the argument list.
2316 foreach my $test (@testsToRun) {
2317 if ($test =~ /^http\//) {
2318 push(@httpTests, $test);
2319 } elsif ($test =~ /^websocket\//) {
2320 push(@websocketTests, $test);
2322 push(@otherTests, $test);
2325 @testsToRun = (@otherTests, @httpTests, @websocketTests);
2329 @testsToRun = reverse @testsToRun if $reverseTests;
2332 @testsToRun = shuffle(@testsToRun) if $randomizeTests;
2340 match => "succeeded",
2341 mismatch => "had incorrect layout",
2343 timedout => "timed out",
2345 error => "had stderr output"
2348 for my $type ("match", "mismatch", "new", "timedout", "crash", "error") {
2349 my $typeCount = $counts{$type};
2350 next unless $typeCount;
2351 my $typeText = $text{$type};
2353 if ($typeCount == 1) {
2354 $typeText =~ s/were/was/;
2355 $message = sprintf "1 test case (%d%%) %s\n", 1 * 100 / $count, $typeText;
2357 $message = sprintf "%d test cases (%d%%) %s\n", $typeCount, $typeCount * 100 / $count, $typeText;
2359 $message =~ s-\(0%\)-(<1%)-;
2364 sub stopRunningTestsEarlyIfNeeded()
2366 # --reset-results does not check pass vs. fail, so exitAfterNFailures makes no sense with --reset-results.
2367 return 0 if $resetResults;
2369 my $passCount = $counts{match} || 0; # $counts{match} will be undefined if we've not yet passed a test (e.g. the first test fails).
2370 my $newCount = $counts{new} || 0;
2371 my $failureCount = $count - $passCount - $newCount; # "Failure" here includes timeouts, crashes, etc.
2372 if ($exitAfterNFailures && $failureCount >= $exitAfterNFailures) {
2373 print "\nExiting early after $failureCount failures. $count tests run.";
2378 my $crashCount = $counts{crash} || 0;
2379 my $timeoutCount = $counts{timedout} || 0;
2380 if ($exitAfterNCrashesOrTimeouts && $crashCount + $timeoutCount >= $exitAfterNCrashesOrTimeouts) {
2381 print "\nExiting early after $crashCount crashes and $timeoutCount timeouts. $count tests run.";
2389 sub setUpWindowsCrashLogSaving()
2391 return unless isCygwin();
2393 unless (defined $ENV{_NT_SYMBOL_PATH}) {
2394 print "The _NT_SYMBOL_PATH environment variable is not set. Crash logs will not be saved.\nSee <http://trac.webkit.org/wiki/BuildingOnWindows#GettingCrashLogs>.\n";
2398 my $ntsdPath = File::Spec->catfile(toCygwinPath($ENV{PROGRAMFILES}), "Debugging Tools for Windows (x86)", "ntsd.exe");
2399 unless (-f $ntsdPath) {
2400 $ntsdPath = File::Spec->catfile(toCygwinPath($ENV{SYSTEMROOT}), "system32", "ntsd.exe");
2401 unless (-f $ntsdPath) {
2402 print STDERR "Can't find ntsd.exe. Crash logs will not be saved.\nSee <http://trac.webkit.org/wiki/BuildingOnWindows#GettingCrashLogs>.\n";
2408 Debugger => '"' . toWindowsPath($ntsdPath) . '" -p %ld -e %ld -g -lines -c ".logopen /t \"' . toWindowsPath($testResultsDirectory) . '\CrashLog.txt\";!analyze -vv;~*kpn;q"',
2412 foreach my $value (keys %values) {
2413 chomp($previousWindowsPostMortemDebuggerValues{$value} = `regtool get "$windowsPostMortemDebuggerKey/$value"`);
2414 my $result = system "regtool", "set", "-s", "$windowsPostMortemDebuggerKey/$value", $values{$value};
2415 next unless $result;
2417 print "Failed to set \"$windowsPostMortemDebuggerKey/$value\". Crash logs will not be saved.\nSee <http://trac.webkit.org/wiki/BuildingOnWindows#GettingCrashLogs>.\n";
2421 print "Crash logs will be saved to $testResultsDirectory.\n";
2425 return unless isCygwin();
2427 foreach my $value (keys %previousWindowsPostMortemDebuggerValues) {
2428 my $result = system "regtool", "set", "-s", "$windowsPostMortemDebuggerKey/$value", $previousWindowsPostMortemDebuggerValues{$value};
2429 !$result or print "Failed to restore \"$windowsPostMortemDebuggerKey/$value\" to its previous value \"$previousWindowsPostMortemDebuggerValues{$value}\"\n.";