3 # Copyright (C) 2005 Apple Computer, Inc. All rights reserved.
5 # Redistribution and use in source and binary forms, with or without
6 # modification, are permitted provided that the following conditions
9 # 1. Redistributions of source code must retain the above copyright
10 # notice, this list of conditions and the following disclaimer.
11 # 2. Redistributions in binary form must reproduce the above copyright
12 # notice, this list of conditions and the following disclaimer in the
13 # documentation and/or other materials provided with the distribution.
14 # 3. Neither the name of Apple Computer, Inc. ("Apple") nor the names of
15 # its contributors may be used to endorse or promote products derived
16 # from this software without specific prior written permission.
18 # THIS SOFTWARE IS PROVIDED BY APPLE AND ITS CONTRIBUTORS "AS IS" AND ANY
19 # EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
20 # WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
21 # DISCLAIMED. IN NO EVENT SHALL APPLE OR ITS CONTRIBUTORS BE LIABLE FOR ANY
22 # DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
23 # (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
24 # LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
25 # ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
26 # (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
27 # THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
29 # Script to run the Web Kit Open Source Project layout tests.
37 use lib $FindBin::Bin;
40 # Run all the tests passed in on the command line.
41 # If no tests are passed, find all the .html, .xml, .xhtml (and svg) files in the test directory.
44 # Compare against the existing file xxx-expected.txt.
45 # If there is a mismatch, generate xxx-actual.txt and xxx-diffs.txt.
48 # the number of tests that got the expected results
49 # the number of tests that ran, but did not get the expected results
50 # the number of tests that failed to run
51 # the number of tests that were run but had no expected results to compare against
54 my $productDir = productDir();
69 GetOptions('svg' => \$testSVGs,
70 'pixel-tests|p' => \$pixelTests,
71 'leaks|l' => \$checkLeaks,
72 'guard-malloc|g' => \$guardMalloc,
73 'max-width|w' => \$maxWidth,
74 'max-height|h' => \$maxHeight,
75 'verbose|v' => \$verbose,
77 'singly|1' => \$singly);
79 my $dumpToolName = "DumpRenderTree";
80 my $result = system "WebKitTools/Scripts/build-dumprendertree", @ARGV;
81 exit $result if $result;
83 my $result = system "WebKitTools/Scripts/build-dumpkcanvastree", @ARGV;
84 exit $result if $result;
85 $dumpToolName = "DumpKCanvasTree";
86 $pixelTests = 1; # Pixel tests are always on for SVG.
89 my $tool = "$productDir/$dumpToolName";
90 my $imageDiffTool = "$productDir/ImageDiff";
91 die "can't find executable $dumpToolName (looked in $productDir)\n" if !-x $tool;
92 die "can't find executable $imageDiffTool (looked in $productDir)\n" if $pixelTests && !-x $imageDiffTool;
95 checkWebCoreSVGSupport() if $testSVGs;
97 my $layoutTestsName = "LayoutTests";
99 $layoutTestsName = "SVGSupport/layout-tests";
102 my $workingDir = getcwd();
103 my $testDirectory = "$workingDir/$layoutTestsName";
104 my $testResultsDirectory = "/tmp/layout-test-results";
105 my $testResults = "$testResultsDirectory/results.html";
109 my $findArguments = "\\( -name resources \\! -prune \\) -or -name '*.html' -or -name '*.xml' -or -name '*.xhtml'";
111 $findArguments = "\\( -name resources \\! -prune \\) -or -name '*.svg'";
113 my $foundTestName = 0;
114 for my $test (@ARGV) {
115 next if $test =~ /^-/;
117 $test =~ s/^$testDirectory\///;
118 if ($test =~ /^\//) {
119 print "can't run test outside $testDirectory\n";
120 } elsif (-f "$testDirectory/$test") {
121 if ($test !~ /\.(html|xml|xhtml|svg)$/) {
122 print "test $test does not have a supported extension\n";
126 } elsif (-d "$testDirectory/$test") {
127 push @tests, sort pathcmp map { chomp; s-^$testDirectory/--; $_; } `find -Ls $testDirectory/$test $findArguments`;
129 print "test $test not found\n";
132 if (!$foundTestName) {
133 @tests = sort pathcmp map { chomp; s-^$testDirectory/--; $_; } `find -Ls $testDirectory $findArguments`;
136 die "no tests to run\n" if !@tests;
146 push @toolArgs, "--pixel-tests";
147 push @toolArgs, ("--width", $maxWidth) if $maxWidth;
148 push @toolArgs, ("--height", $maxHeight) if $maxHeight;
155 my $imageDiffToolPID;
157 $imageDiffToolPID = open2(\*DIFFIN, \*DIFFOUT, $imageDiffTool, "") or die "unable to open $imageDiffTool\n";
164 my $lastDirectory = "";
166 for my $test (@tests) {
167 next if $test eq 'results.html';
171 $ENV{DYLD_FRAMEWORK_PATH} = $productDir;
172 $ENV{MallocStackLogging} = 1 if $checkLeaks;
173 $ENV{DYLD_INSERT_LIBRARIES} = "/usr/lib/libgmalloc.dylib" if $guardMalloc;
174 $dumpToolPID = open2(\*IN, \*OUT, $tool, @toolArgs) or die;
179 $base =~ s/\.(html|xml|xhtml|svg)$//;
181 if ($verbose || $singly) {
182 print "running $test -> ";
187 if ($dir ne $lastDirectory) {
188 print "\n" unless $atLineStart;
190 $lastDirectory = $dir;
198 print OUT "$testDirectory/$test\n";
207 if (open EXPECTED, "<", "$testDirectory/$base-expected.txt") {
215 if ($checkLeaks && $singly) {
219 my $textDumpMatches = $expected && ($actual eq $expected);
221 my $expectedHash = "";
222 my $hashMatches = "";
224 my $actualPNGSize = 0;
225 my $expectedPNG = "";
226 my $expectedPNGSize = 0;
228 my $diffPercentage = "";
229 my $diffResult = "passed";
234 if (/ActualHash: ([a-f0-9]{32})/) {
236 } elsif (/BaselineHash: ([a-f0-9]{32})/) {
238 } elsif (/Content-length: (\d+)\s*/) {
240 read(IN, $actualPNG, $actualPNGSize);
244 if ($hashMatches = ($expectedHash eq $actualHash)) {
245 $diffResult = "passed";
248 if (!$hashMatches && -f "$testDirectory/$base-expected.png") {
249 $expectedPNGSize = -s "$testDirectory/$base-expected.png";
250 open EXPECTEDPNG, "$testDirectory/$base-expected.png";
251 read(EXPECTEDPNG, $expectedPNG, $expectedPNGSize);
253 print DIFFOUT "Content-length: $actualPNGSize\n";
254 print DIFFOUT $actualPNG;
256 print DIFFOUT "Content-length: $expectedPNGSize\n";
257 print DIFFOUT $expectedPNG;
260 last if /^error/ || /^diff:/;
261 if (/Content-length: (\d+)\s*/) {
262 read(DIFFIN, $diffPNG, $1);
266 if (/^diff: (.+)% (passed|failed)/) {
267 $diffPercentage = $1;
274 if ($actualPNGSize != 0 && ! -f "$testDirectory/$base-expected.png") {
275 open EXPECTED, ">", "$testDirectory/$base-expected.png" or die "could not create $testDirectory/$base-expected.png\n";
276 print EXPECTED $actualPNG;
280 # update the expected hash if the image diff said that there was no difference
281 if ($actualHash ne "" && ! -f "$testDirectory/$base-expected.checksum") {
282 open EXPECTED, ">", "$testDirectory/$base-expected.checksum" or die "could not create $testDirectory/$base-expected.checksum\n";
283 print EXPECTED $actualHash;
288 if (!defined $expected) {
289 if ($verbose || $singly) {
294 open EXPECTED, ">", "$testDirectory/$base-expected.txt" or die "could not create $testDirectory/$base-expected.txt\n";
295 print EXPECTED $actual;
297 unlink "$testResultsDirectory/$base-actual.txt";
298 unlink "$testResultsDirectory/$base-diffs.txt";
299 } elsif ($textDumpMatches && (!$pixelTests || ($pixelTests && $diffResult eq "passed"))) {
300 if ($verbose || $singly) {
305 unlink "$testResultsDirectory/$base-actual.txt";
306 unlink "$testResultsDirectory/$base-diffs.txt";
307 } elsif (!$textDumpMatches || ($pixelTests && $diffResult ne "passed")) {
308 unless ($verbose || $singly) {
309 print "\n" unless $atLineStart;
315 $result = "mismatch";
317 my $dir = "$testResultsDirectory/$base";
318 $dir =~ s|/[^/]+$|| or die;
321 open ACTUAL, ">", "$testResultsDirectory/$base-actual.txt" or die;
322 print ACTUAL $actual;
325 system "diff -u \"$testDirectory/$base-expected.txt\" \"$testResultsDirectory/$base-actual.txt\" > \"$testResultsDirectory/$base-diffs.txt\"";
327 if ($pixelTests && $diffPNG && $diffPNG ne "") {
328 $imagesPresent{$base} = 1;
330 open ACTUAL, ">", "$testResultsDirectory/$base-actual.png" or die;
331 print ACTUAL $actualPNG;
334 open DIFF, ">", "$testResultsDirectory/$base-diffs.png" or die;
338 open DIFFHTML, ">$testResultsDirectory/$base-diffs.html" or die;
339 print DIFFHTML "<html>\n";
340 print DIFFHTML "<head>\n";
341 print DIFFHTML "<title>$base Image Compare</title>\n";
342 print DIFFHTML "<script language=\"Javascript\" type=\"text/javascript\">\n";
343 print DIFFHTML "var currentImage = 0;\n";
344 print DIFFHTML "var imageNames = new Array(\"Actual\", \"Expected\");\n";
345 print DIFFHTML "var imagePaths = new Array(\"$testResultsDirectory/$base-actual.png\", \"$testDirectory/$base-expected.png\");\n";
346 if (-f "$testDirectory/$base-w3c.png") {
347 print DIFFHTML "imageNames.push(\"W3C\");\n";
348 print DIFFHTML "imagePaths.push(\"$testDirectory/$base-w3c.png\");\n";
350 print DIFFHTML "function animateImage() {\n";
351 print DIFFHTML " var image = document.getElementById(\"animatedImage\");\n";
352 print DIFFHTML " var imageText = document.getElementById(\"imageText\");\n";
353 print DIFFHTML " image.src = imagePaths[currentImage];\n";
354 print DIFFHTML " imageText.innerHTML = imageNames[currentImage] + \" Image\";\n";
355 print DIFFHTML " currentImage = (currentImage + 1) % imageNames.length;\n";
356 print DIFFHTML " setTimeout('animateImage()',2000);\n";
357 print DIFFHTML "}\n";
358 print DIFFHTML "</script>\n";
359 print DIFFHTML "</head>\n";
360 print DIFFHTML "<body onLoad=\"animateImage();\">\n";
361 print DIFFHTML "<table>\n";
362 if ($diffPercentage) {
363 print DIFFHTML "<tr>\n";
364 print DIFFHTML "<td>Difference between images: <a href=\"$testResultsDirectory/$base-diffs.png\">$diffPercentage%</a></td>\n";
365 print DIFFHTML "</tr>\n";
367 print DIFFHTML "<tr>\n";
368 print DIFFHTML "<td id=\"imageText\" style=\"text-weight: bold;\">Actual Image</td>\n";
369 print DIFFHTML "</tr>\n";
370 print DIFFHTML "<tr>\n";
371 print DIFFHTML "<td><img src=\"$testResultsDirectory/$base-actual.png\" id=\"animatedImage\"></td>\n";
372 print DIFFHTML "</tr>\n";
373 print DIFFHTML "</table>\n";
374 print DIFFHTML "</body>\n";
375 print DIFFHTML "</html>\n";
379 print "\n" unless $atLineStart;
380 print "$test -> crashed?\n";
388 if ($checkLeaks && $singly && $toolOpen) {
389 printLeaks($dumpToolName, $dumpToolPID, "$testResultsDirectory/$base-leaks.txt");
392 if ($singly && $toolOpen) {
395 waitpid $dumpToolPID, 0;
400 $counts{$result} += 1;
401 push @{$tests{$result}}, $test;
404 if ($checkLeaks && !$singly && $toolOpen) {
405 printLeaks($dumpToolName, $dumpToolPID, "$testResultsDirectory/$dumpToolName-leaks.txt");
408 # FIXME: Do we really want to check the image-comparison tool for leaks?
409 if ($checkLeaks && $pixelTests) {
410 printLeaks("ImageDiff", $imageDiffToolPID, "$testResultsDirectory/ImageDiff-leaks.txt");
417 match => "succeeded",
418 mismatch => "had incorrect layout",
420 fail => "failed (tool did not execute successfully)",
425 if ($counts{match} && $counts{match} == $count) {
426 print "all $count test cases succeeded\n";
429 for my $type ("match", "mismatch", "new", "fail") {
430 my $c = $counts{$type};
432 my $t = $text{$type};
436 $message = sprintf "1 test case (%d%%) %s\n", 1 * 100 / $count, $t;
438 $message = sprintf "%d test cases (%d%%) %s\n", $c, $c * 100 / $count, $t;
440 $message =~ s-\(0%\)-(<1%)-;
445 mkpath $testResultsDirectory;
447 open HTML, ">", $testResults or die;
448 print HTML "<html>\n";
449 print HTML "<head>\n";
450 print HTML "<title>Layout Test Results</title>\n";
451 print HTML "</head>\n";
452 print HTML "<body>\n";
454 if ($counts{mismatch}) {
455 print HTML "<p>Tests where results did not match expected results:</p>\n";
456 print HTML "<table>\n";
457 for my $test (@{$tests{mismatch}}) {
459 $base =~ s/\.(html|xml|xhtml|svg)$//;
461 print HTML "<td><a href=\"$testDirectory/$test\">$base</a></td>\n";
462 if (-s "$testResultsDirectory/$base-diffs.txt") {
463 print HTML "<td><a href=\"$testDirectory/$base-expected.txt\">expected</a></td>\n";
464 print HTML "<td><a href=\"$base-actual.txt\">actual</a></td>\n";
465 print HTML "<td><a href=\"$base-diffs.txt\">diffs</a></td>\n";
467 print HTML "<td></td><td></td><td></td>\n";
470 if ($imagesPresent{$base}) {
471 print HTML "<td><a href=\"$testDirectory/$base-expected.png\">expected image</a></td>\n";
472 print HTML "<td><a href=\"$base-diffs.html\">image diffs</a></td>\n";
474 print HTML "<td></td><td></td>\n";
477 print HTML "</tr>\n";
479 print HTML "</table>\n";
483 print HTML "<p>Tests that caused the DumpRenderTree tool to fail:</p>\n";
484 print HTML "<table>\n";
485 for my $test (@{$tests{fail}}) {
487 $base =~ s/\.(html|xml|xhtml|svg)$//;
489 print HTML "<td><a href=\"$testDirectory/$test\">$base</a></td>\n";
490 print HTML "</tr>\n";
492 print HTML "</table>\n";
496 print HTML "<p>Tests that had no expected results (probably new):</p>\n";
497 print HTML "<table>\n";
498 for my $test (@{$tests{new}}) {
500 $base =~ s/\.(html|xml|xhtml|svg)$//;
502 print HTML "<td><a href=\"$testDirectory/$test\">$base</a></td>\n";
503 print HTML "<td><a href=\"$testDirectory/$base-expected.txt\">results</a></td>\n";
504 if ($pixelTests && -f "$testDirectory/$base-expected.png") {
505 print HTML "<td><a href=\"$testDirectory/$base-expected.png\">image</a></td>\n";
507 print HTML "</tr>\n";
509 print HTML "</table>\n";
512 print HTML "</body>\n";
513 print HTML "</html>\n";
516 system "WebKitTools/Scripts/run-safari", $testResults;
521 my ($toolName, $toolPID, $leaksFilePath) = @_;
523 print "\n" unless $atLineStart;
526 # We are excluding the following reported leaks so they don't get in our way when looking for WebKit leaks:
528 # pthread_create: false positive leak of 'THRD', Radar 3387783
529 # _CFPreferencesDomainDeepCopyDictionary: leak apparently in CFPreferences, Radar 4220786
531 # Note that this exclusion doesn't quite work right; sometimes a leak of 'THRD' with no stack trace will
532 # still appear in the leaks output.
534 print " ? checking for leaks in $toolName\n";
535 my $leaksOutput = `leaks -exclude pthread_create -exclude _CFPreferencesDomainDeepCopyDictionary $toolPID`;
536 my ($count, $bytes) = $leaksOutput =~ /Process $toolPID: (\d+) leaks? for (\d+) total/;
537 my ($excluded) = $leaksOutput =~ /(\d+) leaks? excluded/;
539 if ($count == 0 || ($excluded && $count <= $excluded)) {
540 print " - no leaks found\n";
541 unlink $leaksFilePath;
543 my $dir = $leaksFilePath;
544 $dir =~ s|/[^/]+$|| or die;
547 print " + $count leaks ($bytes bytes) were found, details in $leaksFilePath\n";
548 open LEAKS, ">", $leaksFilePath or die;
549 print LEAKS $leaksOutput;
554 # Break up a path into the directory (with slash) and base name.
559 return ($1, $2) if $path =~ m|^(.*/)([^/]+)$|;
563 # Sort first by directory, then by file, so all paths in one directory are grouped
564 # rather than being interspersed with items from subdirectories.
565 # Use numericcmp to sort directory and filenames to make order logical.
568 my ($patha, $pathb) = @_;
570 my ($dira, $namea) = splitpath($patha);
571 my ($dirb, $nameb) = splitpath($pathb);
573 return numericcmp($dira, $dirb) if $dira ne $dirb;
574 return numericcmp($namea, $nameb);
577 # Sort numeric parts of strings as numbers, other parts as strings.
578 # Makes 1.33 come before 1.3, which is cool.
583 my @a = split /(\d+)/, $aa;
584 my @b = split /(\d+)/, $bb;
586 # Compare one chunk at a time.
587 # Each chunk is either all numeric digits, or all not numeric digits.
592 # Use numeric comparison if chunks are non-equal numbers.
593 return $a <=> $b if $a =~ /^\d/ && $b =~ /^\d/ && $a != $b;
595 # Use string comparison if chunks are any other kind of non-equal string.
596 return $a cmp $b if $a ne $b;
599 # One of the two is now empty; compare lengths for result in this case.