2de3b81ffed089d2dad738899e4faf6989acb992
[WebKit-https.git] / WebKitTools / Scripts / run-webkit-tests
1 #!/usr/bin/perl -w
2
3 # Copyright (C) 2005 Apple Computer, Inc.  All rights reserved.
4 #
5 # Redistribution and use in source and binary forms, with or without
6 # modification, are permitted provided that the following conditions
7 # are met:
8 #
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. 
17 #
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.
28
29 # Script to run the Web Kit Open Source Project layout tests.
30
31 use strict;
32 use IPC::Open2;
33 use Getopt::Long;
34 use File::Path;
35 use FindBin;
36 use Cwd;
37 use lib $FindBin::Bin;
38 use webkitdirs;
39
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.
42
43 # Run each text.
44 # Compare against the existing file xxx-expected.txt.
45 # If there is a mismatch, generate xxx-actual.txt and xxx-diffs.txt.
46
47 # At the end, report:
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
52
53 setConfiguration();
54 my $productDir = productDir();
55
56 chdirWebKit();
57
58 # Argument handling
59 my $testSVGs = '';
60 my $pixelTests = '';
61 my $checkLeaks = '';
62 my $guardMalloc = '';
63 my $maxWidth = '';
64 my $maxHeight = '';
65 my $verbose = 0;
66 my $quiet = '';
67 my $singly = 0;
68
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,
76     'quiet|q' => \$quiet,
77     'singly|1' => \$singly);
78
79 my $dumpToolName = "DumpRenderTree";
80 my $result = system "WebKitTools/Scripts/build-dumprendertree", @ARGV;
81 exit $result if $result;
82 if ($testSVGs) {
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.
87 }
88
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;
93
94 checkFrameworks();
95 checkWebCoreSVGSupport() if $testSVGs;
96
97 my $layoutTestsName = "layout-tests";
98 if ($testSVGs) {
99     $layoutTestsName = "svg-tests";
100 }
101
102 my $workingDir = getcwd();
103 my $WebCoreDirectory = "$workingDir/WebCore";
104 my $testDirectory = "$WebCoreDirectory/$layoutTestsName";
105 my $testResultsDirectory = "/tmp/layout-test-results";
106 my $testResults = "$testResultsDirectory/results.html";
107
108 my @tests = ();
109
110 my $findArguments = "\\( -name resources \\! -prune \\) -or -name '*.html' -or -name '*.xml' -or -name '*.xhtml'";
111 if ($testSVGs) {
112     $findArguments = "\\( -name resources \\! -prune \\) -or -name '*.svg'";
113 }
114 my $foundTestName = 0;
115 for my $test (@ARGV) {
116     next if $test =~ /^-/;
117     $foundTestName = 1;
118     $test =~ s/^$testDirectory\///;
119     if ($test =~ /^\//) {
120         print "can't run test outside $testDirectory\n";
121     } elsif (-f "$testDirectory/$test") {
122         if ($test !~ /\.(html|xml|xhtml|svg)$/) {
123             print "test $test does not have a supported extension\n";
124         } else {
125             push @tests, $test;
126         }
127     } elsif (-d "$testDirectory/$test") {
128         push @tests, sort pathcmp map { chomp; s-^$testDirectory/--; $_; } `find -Ls $testDirectory/$test $findArguments`;
129     } else {
130         print "test $test not found\n";
131     }
132 }
133 if (!$foundTestName) {
134     @tests = sort pathcmp map { chomp; s-^$testDirectory/--; $_; } `find -Ls $testDirectory $findArguments`;
135 }
136
137 die "no tests to run\n" if !@tests;
138
139 my %counts;
140 my %tests;
141 my %imagesPresent;
142 my $count = 0;
143
144 my @toolArgs = ();
145
146 if ($pixelTests) {
147     push @toolArgs, "--pixel-tests";
148     push @toolArgs, ("--width", $maxWidth) if $maxWidth;
149     push @toolArgs, ("--height", $maxHeight) if $maxHeight;
150 }
151
152 push @toolArgs, "-";
153
154 $| = 1;
155
156 my $imageDiffToolPID;
157 if ($pixelTests) {
158     $imageDiffToolPID = open2(\*DIFFIN, \*DIFFOUT, $imageDiffTool, "") or die "unable to open $imageDiffTool\n";
159 }
160
161 my $dumpToolPID;
162 my $toolOpen = 0;
163
164 my $atLineStart = 1;
165 my $lastDirectory = "";
166
167 for my $test (@tests) {
168     next if $test eq 'results.html';
169
170     if (!$toolOpen) {
171         local %ENV;
172         $ENV{DYLD_FRAMEWORK_PATH} = $productDir;
173         $ENV{MallocStackLogging} = 1 if $checkLeaks;
174         $ENV{DYLD_INSERT_LIBRARIES} = "/usr/lib/libgmalloc.dylib" if $guardMalloc;
175         $dumpToolPID = open2(\*IN, \*OUT, $tool, @toolArgs) or die;
176         $toolOpen = 1;
177     }
178
179     my $base = $test;
180     $base =~ s/\.(html|xml|xhtml|svg)$//;
181     
182     if ($verbose || $singly) {
183         print "running $test -> ";
184         $atLineStart = 0;
185     } elsif (!$quiet) {
186         my $dir = $base;
187         $dir =~ s|/[^/]+$||;
188         if ($dir ne $lastDirectory) {
189             print "\n" unless $atLineStart;
190             print "$dir ";
191             $lastDirectory = $dir;
192         }
193         print ".";
194         $atLineStart = 0;
195     }
196
197     my $result;
198
199     print OUT "$testDirectory/$test\n";
200
201     my $actual = "";
202     while (<IN>) {
203         last if /#EOF/;
204         $actual .= $_;
205     }
206
207     my $expected;
208     if (open EXPECTED, "<", "$testDirectory/$base-expected.txt") {
209         $expected = "";
210         while (<EXPECTED>) {
211             $expected .= $_;
212         }
213         close EXPECTED;
214     }
215
216     if ($checkLeaks && $singly) {
217         print "        $test -> ";
218     }
219
220     my $textDumpMatches = $expected && ($actual eq $expected);
221     my $actualHash = "";
222     my $expectedHash = "";
223     my $hashMatches = "";
224     my $actualPNG = "";
225     my $actualPNGSize = 0;
226     my $expectedPNG = "";
227     my $expectedPNGSize = 0;
228     my $diffPNG = "";
229     my $diffPercentage = "";
230     my $diffResult = "passed";
231     
232     if ($pixelTests) {
233         while (<IN>) {
234             last if /#EOF/;
235             if (/ActualHash: ([a-f0-9]{32})/) {
236                 $actualHash = $1;
237             } elsif (/BaselineHash: ([a-f0-9]{32})/) {
238                 $expectedHash = $1;
239             } elsif (/Content-length: (\d+)\s*/) {
240                 $actualPNGSize = $1;
241                 read(IN, $actualPNG, $actualPNGSize);
242             }
243         }
244
245         if ($hashMatches = ($expectedHash eq $actualHash)) {
246             $diffResult = "passed";
247         }
248
249         if (!$hashMatches && -f "$testDirectory/$base-expected.png") {
250             $expectedPNGSize = -s "$testDirectory/$base-expected.png";
251             open EXPECTEDPNG, "$testDirectory/$base-expected.png";
252             read(EXPECTEDPNG, $expectedPNG, $expectedPNGSize);
253
254             print DIFFOUT "Content-length: $actualPNGSize\n";
255             print DIFFOUT $actualPNG;
256
257             print DIFFOUT "Content-length: $expectedPNGSize\n";
258             print DIFFOUT $expectedPNG;
259
260             while (<DIFFIN>) {
261                 last if /^error/ || /^diff:/;
262                 if (/Content-length: (\d+)\s*/) {
263                     read(DIFFIN, $diffPNG, $1);
264                 }
265             }
266
267             if (/^diff: (.+)% (passed|failed)/) {
268                 $diffPercentage = $1;
269                 $diffResult = $2;
270             }
271         }
272     }
273
274     if ($pixelTests) {
275         if ($actualPNGSize != 0 && ! -f "$testDirectory/$base-expected.png") {
276             open EXPECTED, ">", "$testDirectory/$base-expected.png" or die "could not create $testDirectory/$base-expected.png\n";
277             print EXPECTED $actualPNG;
278             close EXPECTED;
279         }
280
281         # update the expected hash if the image diff said that there was no difference
282         if ($actualHash ne "" && ! -f "$testDirectory/$base-expected.checksum") {
283             open EXPECTED, ">", "$testDirectory/$base-expected.checksum" or die "could not create $testDirectory/$base-expected.checksum\n";
284             print EXPECTED $actualHash;
285             close EXPECTED;
286         }
287     }
288
289     if (!defined $expected) {
290         if ($verbose || $singly) {
291             print "new test\n";
292             $atLineStart = 1;
293         }
294         $result = "new";
295         open EXPECTED, ">", "$testDirectory/$base-expected.txt" or die "could not create $testDirectory/$base-expected.txt\n";
296         print EXPECTED $actual;
297         close EXPECTED;
298         unlink "$testResultsDirectory/$base-actual.txt";
299         unlink "$testResultsDirectory/$base-diffs.txt";
300     } elsif ($textDumpMatches && (!$pixelTests || ($pixelTests && $diffResult eq "passed"))) {
301         if ($verbose || $singly) {
302             print "succeeded\n";
303             $atLineStart = 1;
304         }
305         $result = "match";
306         unlink "$testResultsDirectory/$base-actual.txt";
307         unlink "$testResultsDirectory/$base-diffs.txt";
308     } elsif (!$textDumpMatches || ($pixelTests && $diffResult ne "passed")) {
309         unless ($verbose || $singly) {
310             print "\n" unless $atLineStart;
311             print "$test -> ";
312         }
313         print "failed\n";
314         $atLineStart = 1;
315
316         $result = "mismatch";
317
318         my $dir = "$testResultsDirectory/$base";
319         $dir =~ s|/[^/]+$|| or die;
320         mkpath $dir;
321
322         open ACTUAL, ">", "$testResultsDirectory/$base-actual.txt" or die;
323         print ACTUAL $actual;
324         close ACTUAL;
325
326         system "diff -u \"$testDirectory/$base-expected.txt\" \"$testResultsDirectory/$base-actual.txt\" > \"$testResultsDirectory/$base-diffs.txt\"";
327
328         if ($pixelTests && $diffPNG && $diffPNG ne "") {
329             $imagesPresent{$base} = 1;
330
331             open ACTUAL, ">", "$testResultsDirectory/$base-actual.png" or die;
332             print ACTUAL $actualPNG;
333             close ACTUAL;
334
335             open DIFF, ">", "$testResultsDirectory/$base-diffs.png" or die;
336             print DIFF $diffPNG;
337             close DIFF;
338
339             open DIFFHTML, ">$testResultsDirectory/$base-diffs.html" or die;
340             print DIFFHTML "<html>\n";
341             print DIFFHTML "<head>\n";
342             print DIFFHTML "<title>$base Image Compare</title>\n";
343             print DIFFHTML "<script language=\"Javascript\" type=\"text/javascript\">\n";
344             print DIFFHTML "var currentImage = 0;\n";
345             print DIFFHTML "var imageNames = new Array(\"Actual\", \"Expected\");\n";
346             print DIFFHTML "var imagePaths = new Array(\"$testResultsDirectory/$base-actual.png\", \"$testDirectory/$base-expected.png\");\n";
347             if (-f "$testDirectory/$base-w3c.png") {
348                 print DIFFHTML "imageNames.push(\"W3C\");\n";
349                 print DIFFHTML "imagePaths.push(\"$testDirectory/$base-w3c.png\");\n";
350             }
351             print DIFFHTML "function animateImage() {\n";
352             print DIFFHTML "    var image = document.getElementById(\"animatedImage\");\n";
353             print DIFFHTML "    var imageText = document.getElementById(\"imageText\");\n";
354             print DIFFHTML "    image.src = imagePaths[currentImage];\n";
355             print DIFFHTML "    imageText.innerHTML = imageNames[currentImage] + \" Image\";\n";
356             print DIFFHTML "    currentImage = (currentImage + 1) % imageNames.length;\n";
357             print DIFFHTML "    setTimeout('animateImage()',2000);\n";
358             print DIFFHTML "}\n";
359             print DIFFHTML "</script>\n";
360             print DIFFHTML "</head>\n";
361             print DIFFHTML "<body onLoad=\"animateImage();\">\n";
362             print DIFFHTML "<table>\n";
363             if ($diffPercentage) {
364                 print DIFFHTML "<tr>\n";
365                 print DIFFHTML "<td>Difference between images: <a href=\"$testResultsDirectory/$base-diffs.png\">$diffPercentage%</a></td>\n";
366                 print DIFFHTML "</tr>\n";
367             }
368             print DIFFHTML "<tr>\n";
369             print DIFFHTML "<td id=\"imageText\" style=\"text-weight: bold;\">Actual Image</td>\n";
370             print DIFFHTML "</tr>\n";
371             print DIFFHTML "<tr>\n";
372             print DIFFHTML "<td><img src=\"$testResultsDirectory/$base-actual.png\" id=\"animatedImage\"></td>\n";
373             print DIFFHTML "</tr>\n";
374             print DIFFHTML "</table>\n";
375             print DIFFHTML "</body>\n";
376             print DIFFHTML "</html>\n";
377         }
378     } else {
379         $result = "fail";
380         print "\n" unless $atLineStart;
381         print "$test -> crashed?\n";
382         $atLineStart = 1;
383
384         close IN;
385         close OUT;
386         $toolOpen = 0;
387     }
388
389     if ($checkLeaks && $singly && $toolOpen) {
390         printLeaks($dumpToolName, $dumpToolPID, "$testResultsDirectory/$base-leaks.txt");
391     }
392
393     if ($singly && $toolOpen) {
394         close IN;
395         close OUT;
396         waitpid $dumpToolPID, 0;
397         $toolOpen = 0;
398     }
399
400     $count += 1;
401     $counts{$result} += 1;
402     push @{$tests{$result}}, $test;
403 }
404
405 if ($checkLeaks && !$singly && $toolOpen) {
406     printLeaks($dumpToolName, $dumpToolPID, "$testResultsDirectory/$dumpToolName-leaks.txt");
407 }
408
409 # FIXME: Do we really want to check the image-comparison tool for leaks?
410 if ($checkLeaks && $pixelTests) {
411     printLeaks("ImageDiff", $imageDiffToolPID, "$testResultsDirectory/ImageDiff-leaks.txt");
412 }
413
414 close IN;
415 close OUT;
416
417 my %text = (
418     match => "succeeded",
419     mismatch => "had incorrect layout",
420     new => "were new",
421     fail => "failed (tool did not execute successfully)",
422 );
423
424 print "\n";
425
426 if ($counts{match} && $counts{match} == $count) {
427     print "all $count test cases succeeded\n";
428     unlink $testResults;
429 } else {
430     for my $type ("match", "mismatch", "new", "fail") {
431         my $c = $counts{$type};
432         if ($c) {
433             my $t = $text{$type};
434             my $message;
435             if ($c == 1) {
436                 $t =~ s/were/was/;
437                 $message = sprintf "1 test case (%d%%) %s\n", 1 * 100 / $count, $t;
438             } else {
439                 $message = sprintf "%d test cases (%d%%) %s\n", $c, $c * 100 / $count, $t;
440             }
441             $message =~ s-\(0%\)-(<1%)-;
442             print $message;
443         }
444     }
445     
446     mkpath $testResultsDirectory;
447
448     open HTML, ">", $testResults or die;
449     print HTML "<html>\n";
450     print HTML "<head>\n";
451     print HTML "<title>Layout Test Results</title>\n";
452     print HTML "</head>\n";
453     print HTML "<body>\n";
454
455     if ($counts{mismatch}) {
456         print HTML "<p>Tests where results did not match expected results:</p>\n";
457         print HTML "<table>\n";
458         for my $test (@{$tests{mismatch}}) {
459             my $base = $test;
460             $base =~ s/\.(html|xml|xhtml|svg)$//;
461             print HTML "<tr>\n";
462             print HTML "<td><a href=\"$testDirectory/$test\">$base</a></td>\n";
463             if (-s "$testResultsDirectory/$base-diffs.txt") {
464                 print HTML "<td><a href=\"$testDirectory/$base-expected.txt\">expected</a></td>\n";
465                 print HTML "<td><a href=\"$base-actual.txt\">actual</a></td>\n";
466                 print HTML "<td><a href=\"$base-diffs.txt\">diffs</a></td>\n";
467             } else {
468                 print HTML "<td></td><td></td><td></td>\n";
469             }
470             if ($pixelTests) {
471                 if ($imagesPresent{$base}) {
472                     print HTML "<td><a href=\"$testDirectory/$base-expected.png\">expected image</a></td>\n";
473                     print HTML "<td><a href=\"$base-diffs.html\">image diffs</a></td>\n";
474                 } else {
475                     print HTML "<td></td><td></td>\n";
476                 }            
477             }
478             print HTML "</tr>\n";
479         }
480         print HTML "</table>\n";
481     }
482
483     if ($counts{fail}) {
484         print HTML "<p>Tests that caused the DumpRenderTree tool to fail:</p>\n";
485         print HTML "<table>\n";
486         for my $test (@{$tests{fail}}) {
487             my $base = $test;
488             $base =~ s/\.(html|xml|xhtml|svg)$//;
489             print HTML "<tr>\n";
490             print HTML "<td><a href=\"$testDirectory/$test\">$base</a></td>\n";
491             print HTML "</tr>\n";
492         }
493         print HTML "</table>\n";
494     }
495
496     if ($counts{new}) {
497         print HTML "<p>Tests that had no expected results (probably new):</p>\n";
498         print HTML "<table>\n";
499         for my $test (@{$tests{new}}) {
500             my $base = $test;
501             $base =~ s/\.(html|xml|xhtml|svg)$//;
502             print HTML "<tr>\n";
503             print HTML "<td><a href=\"$testDirectory/$test\">$base</a></td>\n";
504             print HTML "<td><a href=\"$testDirectory/$base-expected.txt\">results</a></td>\n";
505             if ($pixelTests && -f "$testDirectory/$base-expected.png") {
506                 print HTML "<td><a href=\"$testDirectory/$base-expected.png\">image</a></td>\n";
507             }
508             print HTML "</tr>\n";
509         }
510         print HTML "</table>\n";
511     }
512
513     print HTML "</body>\n";
514     print HTML "</html>\n";
515     close HTML;
516     
517     system "WebKitTools/Scripts/run-safari", $testResults;
518 }
519
520 sub printLeaks
521 {
522     my ($toolName, $toolPID, $leaksFilePath) = @_;
523
524     print "\n" unless $atLineStart;
525     $atLineStart = 1;
526
527     # We are excluding the following reported leaks so they don't get in our way when looking for WebKit leaks:
528     #
529     #     pthread_create: false positive leak of 'THRD', Radar 3387783
530     #     _CFPreferencesDomainDeepCopyDictionary: leak apparently in CFPreferences, Radar 4220786
531     #
532     # Note that this exclusion doesn't quite work right; sometimes a leak of 'THRD' with no stack trace will
533     # still appear in the leaks output.
534
535     print " ? checking for leaks in $toolName\n";
536     my $leaksOutput = `leaks -exclude pthread_create -exclude _CFPreferencesDomainDeepCopyDictionary $toolPID`;
537     my ($count, $bytes) = $leaksOutput =~ /Process $toolPID: (\d+) leaks? for (\d+) total/;
538     my ($excluded) = $leaksOutput =~ /(\d+) leaks? excluded/;
539
540     if ($count == 0 || ($excluded && $count <= $excluded)) {
541         print " - no leaks found\n";
542         unlink $leaksFilePath;
543     } else {
544         my $dir = $leaksFilePath;
545         $dir =~ s|/[^/]+$|| or die;
546         mkpath $dir;
547
548         print " + $count leaks ($bytes bytes) were found, details in $leaksFilePath\n";
549         open LEAKS, ">", $leaksFilePath or die;
550         print LEAKS $leaksOutput;
551         close LEAKS;
552     }
553 }
554
555 # Break up a path into the directory (with slash) and base name.
556 sub splitpath($)
557 {
558     my ($path) = @_;
559
560     return ($1, $2) if $path =~ m|^(.*/)([^/]+)$|;
561     return ("", $path);
562 }
563
564 # Sort first by directory, then by file, so all paths in one directory are grouped
565 # rather than being interspersed with items from subdirectories.
566 # Use numericcmp to sort directory and filenames to make order logical.
567 sub pathcmp($$)
568 {
569     my ($patha, $pathb) = @_;
570
571     my ($dira, $namea) = splitpath($patha);
572     my ($dirb, $nameb) = splitpath($pathb);
573
574     return numericcmp($dira, $dirb) if $dira ne $dirb;
575     return numericcmp($namea, $nameb);
576 }
577
578 # Sort numeric parts of strings as numbers, other parts as strings.
579 # Makes 1.33 come before 1.3, which is cool.
580 sub numericcmp($$)
581 {
582     my ($aa, $bb) = @_;
583
584     my @a = split /(\d+)/, $aa;
585     my @b = split /(\d+)/, $bb;
586
587     # Compare one chunk at a time.
588     # Each chunk is either all numeric digits, or all not numeric digits.
589     while (@a && @b) {
590         my $a = shift @a;
591         my $b = shift @b;
592         
593         # Use numeric comparison if chunks are non-equal numbers.
594         return $a <=> $b if $a =~ /^\d/ && $b =~ /^\d/ && $a != $b;
595
596         # Use string comparison if chunks are any other kind of non-equal string.
597         return $a cmp $b if $a ne $b;
598     }
599     
600     # One of the two is now empty; compare lengths for result in this case.
601     return @a <=> @b;
602 }