67cd8ba9006ce73d4ce5c1ed6f4aae7e9b1cf030
[WebKit-https.git] / Tools / Scripts / test262 / Runner.pm
1 #!/usr/bin/env perl
2
3 # Copyright (C) 2018 Bocoup LLC. 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
10 #    copyright notice, this list of conditions and the following
11 #    disclaimer.
12 # 2. Redistributions in binary form must reproduce the above
13 #    copyright notice, this list of conditions and the following
14 #    disclaimer in the documentation and/or other materials
15 #    provided with the distribution.
16 #
17 # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDER "AS IS" AND ANY
18 # EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
19 # IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
20 # PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER BE
21 # LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY,
22 # OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
23 # PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
24 # PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
25 # THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR
26 # TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF
27 # THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
28 # SUCH DAMAGE.
29
30 use strict;
31 use warnings;
32 use 5.8.8;
33 package Test262::Runner;
34
35 use File::Find;
36 use File::Temp qw(tempfile tempdir);
37 use File::Spec::Functions qw(abs2rel);
38 use File::Basename qw(dirname);
39 use Cwd qw(abs_path);
40 use FindBin;
41 use Env qw(DYLD_FRAMEWORK_PATH);
42 my $Bin;
43
44 use Config;
45 use Encode;
46
47 BEGIN {
48     $ENV{DBIC_OVERWRITE_HELPER_METHODS_OK} = 1;
49
50     $Bin = $ENV{T262_EXEC_BIN} || $FindBin::Bin;
51
52     unshift @INC, "$Bin";
53     unshift @INC, "$Bin/lib";
54     unshift @INC, "$Bin/local/lib/perl5";
55     unshift @INC, "$Bin/local/lib/perl5/$Config{archname}";
56
57     $ENV{LOAD_ROUTES} = 1;
58 }
59
60 use YAML qw(Load LoadFile Dump DumpFile Bless);
61 use Parallel::ForkManager;
62 use Getopt::Long qw(GetOptions);
63 use Pod::Usage;
64
65 # Commandline settings
66 my $max_process;
67 my @cliTestDirs;
68 my $verbose;
69 my $JSC;
70 my $test262Dir;
71 my $harnessDir;
72 my %filterFeatures;
73 my $ignoreConfig;
74 my $config;
75 my %configSkipHash;
76 my $expect;
77 my $saveExpectations;
78 my $failingOnly;
79 my $latestImport;
80 my $runningAllTests;
81
82 processCLI();
83
84 my @results;
85 my @files;
86
87 my $expectationsFile = abs_path("$Bin/expectations.yaml");
88 my $configFile = abs_path("$Bin/config.yaml");
89 my $resultsFile = abs_path("$Bin/results.yaml");
90 my $summaryTxtFile = abs_path("$Bin/results-summary.txt");
91 my $summaryFile = abs_path("$Bin/results-summary.yaml");
92
93 my $tempdir = tempdir();
94
95 my @default_harnesses = (
96     "$harnessDir/sta.js",
97     "$harnessDir/assert.js",
98     "$harnessDir/doneprintHandle.js",
99     "$Bin/agent.js"
100 );
101
102 my ($deffh, $deffile) = getTempFile();
103 print $deffh getHarness(<@default_harnesses>);
104
105 my $startTime = time();
106
107 main();
108
109 sub processCLI {
110     my $help = 0;
111     my $debug;
112     my $ignoreExpectations;
113     my @features;
114     my $stats;
115
116     # If adding a new commandline argument, you must update the POD
117     # documentation at the end of the file.
118     GetOptions(
119         'j|jsc=s' => \$JSC,
120         't|t262=s' => \$test262Dir,
121         'o|test-only=s@' => \@cliTestDirs,
122         'p|child-processes=i' => \$max_process,
123         'h|help' => \$help,
124         'd|debug' => \$debug,
125         'v|verbose' => \$verbose,
126         'f|features=s@' => \@features,
127         'c|config=s' => \$configFile,
128         'i|ignore-config' => \$ignoreConfig,
129         's|save' => \$saveExpectations,
130         'x|ignore-expectations' => \$ignoreExpectations,
131         'failing-files' => \$failingOnly,
132         'l|latest-import' => \$latestImport,
133         'stats' => \$stats,
134     );
135
136     if ($help) {
137         pod2usage(-exitstatus => 0, -verbose => 2, -input => __FILE__);
138     }
139
140     if ($stats) {
141         summarizeResults();
142         exit;
143     }
144
145     if ($JSC) {
146         $JSC = abs_path($JSC);
147         # Make sure the path and file jsc exist
148         if (! ($JSC && -e $JSC)) {
149             die "Error: --jsc path does not exist.";
150         }
151
152         # For custom JSC paths, Sets only if not yet defined
153         if (not defined $DYLD_FRAMEWORK_PATH) {
154             $DYLD_FRAMEWORK_PATH = dirname($JSC);
155         }
156     } else {
157         $JSC = getBuildPath($debug);
158     }
159
160     if ($latestImport) {
161         # Does not allow custom $test262Dir
162         $test262Dir = '';
163     }
164
165     if (! $test262Dir) {
166         $test262Dir = abs_path("$Bin/../../../JSTests/test262");
167     } else {
168         $test262Dir = abs_path($test262Dir);
169     }
170     $harnessDir = "$test262Dir/harness";
171
172     if (! $ignoreConfig) {
173         if ($configFile and not -e $configFile) {
174             die "Config file $configFile does not exist!";
175         }
176
177         $config = LoadFile($configFile) or die $!;
178         if ($config->{skip} && $config->{skip}->{files}) {
179             %configSkipHash = map { $_ => 1 } @{$config->{skip}->{files}};
180         }
181     }
182
183     if (! $ignoreExpectations) {
184         # If expectations file doesn't exist yet, just run tests, UNLESS
185         # --failures-only option supplied.
186         if ( $failingOnly && ! -e $expectationsFile ) {
187             print "Error: Cannot run failing tests if test262-expectation.yaml file does not exist.\n";
188             die;
189         } elsif (-e $expectationsFile) {
190             $expect = LoadFile($expectationsFile) or die $!;
191         }
192     }
193
194     if (@features) {
195         %filterFeatures = map { $_ => 1 } @features;
196     }
197
198     $max_process ||= getProcesses();
199
200     print "\n-------------------------Settings------------------------\n"
201         . "Test262 Dir: $test262Dir\n"
202         . "JSC: $JSC\n"
203         . "DYLD_FRAMEWORK_PATH: $DYLD_FRAMEWORK_PATH\n"
204         . "Child Processes: $max_process\n";
205
206     print "Features to include: " . join(', ', @features) . "\n" if @features;
207     print "Paths:  " . join(', ', @cliTestDirs) . "\n" if @cliTestDirs;
208     print "Config file: $configFile\n" if $config;
209     print "Expectations file: $expectationsFile\n" if $expect;
210
211     print "Running only the latest imported files\n" if $latestImport;
212
213     print "Verbose mode\n" if $verbose;
214
215     print "--------------------------------------------------------\n\n";
216 }
217
218 sub main {
219
220     # If not commandline test path supplied, use the root directory of all tests.
221     push(@cliTestDirs, 'test') if not @cliTestDirs;
222
223     if ($latestImport) {
224         @files = loadImportFile();
225     } elsif ($failingOnly) {
226         # If we only want to re-run failure, only run tests in expectation file
227         @files = map { qq($test262Dir/$_) } keys %{$expect};
228     } else {
229         $runningAllTests = 1;
230         # Otherwise, get all files from directory
231         foreach my $testsDir (@cliTestDirs) {
232             find(
233                 { wanted => \&wanted, bydepth => 1 },
234                 qq($test262Dir/$testsDir)
235                 );
236             sub wanted {
237                 /(?<!_FIXTURE)\.[jJ][sS]$/s && push(@files, $File::Find::name);
238             }
239         }
240     }
241
242
243     # If we are processing many files, fork process
244     if (scalar @files > $max_process * 5) {
245
246         # Make temporary files to record results
247         my @resultsfhs;
248         for (my $i = 0; $i <= $max_process-1; $i++) {
249             my ($fh, $filename) = getTempFile();
250             $resultsfhs[$i] = $fh;
251         }
252
253         my $pm = Parallel::ForkManager->new($max_process);
254         my $filesperprocess = int(scalar @files / $max_process);
255
256         FILES:
257         for (my $i = 0; $i <= $max_process-1; $i++) {
258             $pm->start and next FILES; # do the fork
259             srand(time ^ $$); # Creates a new seed for each fork
260
261             my $first = $filesperprocess * $i;
262             my $last = $i == $max_process-1 ? scalar @files : $filesperprocess * ($i+1);
263
264             for (my $j = $first; $j < $last; $j++) {
265                 processFile($files[$j], $resultsfhs[$i]);
266             };
267
268             $pm->finish; # do the exit in the child process
269         };
270
271         $pm->wait_all_children;
272
273         # Read results from file into @results and close
274         for (my $i = 0; $i <= $max_process-1; $i++) {
275             seek($resultsfhs[$i], 0, 0);
276             push @results, LoadFile($resultsfhs[$i]);
277             close $resultsfhs[$i];
278         }
279     }
280     # Otherwising, running sequentially is fine
281     else {
282         my ($resfh, $resfilename) = getTempFile();
283         foreach my $file (@files) {
284             processFile($file, $resfh);
285         };
286         seek($resfh, 0, 0);
287         @results = LoadFile($resfh);
288         close $resfh;
289     }
290
291     close $deffh;
292
293     @results = sort { "$a->{path} . $a->{mode}" cmp "$b->{path} . $b->{mode}" } @results;
294
295     my %failed;
296     my $failcount = 0;
297     my $newfailcount = 0;
298     my $newpasscount = 0;
299     my $skipfilecount = 0;
300
301     # Create expectation file and calculate results
302     foreach my $test (@results) {
303
304         my $expectedFailure = 0;
305         if ($expect && $expect->{$test->{path}}) {
306             $expectedFailure = $expect->{$test->{path}}->{$test->{mode}}
307         }
308
309         if ($test->{result} eq 'FAIL') {
310             $failcount++;
311
312             # Record this round of failures
313             if ( $failed{$test->{path}} ) {
314                 $failed{$test->{path}}->{$test->{mode}} =  $test->{error};
315             }
316             else {
317                 $failed{$test->{path}} = {
318                     $test->{mode} => $test->{error}
319                 };
320             }
321
322             # If an unexpected failure
323             $newfailcount++ if !$expectedFailure || ($expectedFailure ne $test->{error});
324
325         }
326         elsif ($test->{result} eq 'PASS') {
327             # If this is an newly passing test
328             $newpasscount++ if $expectedFailure;
329         }
330         elsif ($test->{result} eq 'SKIP') {
331             $skipfilecount++;
332         }
333     }
334
335     if ($saveExpectations) {
336         DumpFile($expectationsFile, \%failed);
337         print "\nSaved results in: $expectationsFile\n";
338     } else {
339         print "\nRun with --save to save a new expectations file\n";
340     }
341
342     if ($runningAllTests) {
343         DumpFile($resultsFile, \@results);
344         print "Saved all the results in $resultsFile\n";
345         summarizeResults();
346     }
347
348     my $total = scalar @results - $skipfilecount;
349     print "\n" . $total . " tests ran\n";
350
351     if ( !$expect ) {
352         print $failcount . " tests failed\n";
353     } else {
354         print $failcount . " expected tests failed\n";
355         print $newfailcount . " tests newly fail\n";
356         print $newpasscount . " tests newly pass\n";
357     }
358
359     print $skipfilecount . " test files skipped\n";
360
361     my $endTime = time();
362     my $totalTime = $endTime - $startTime;
363     print "Done in $totalTime seconds!\n";
364
365     exit $newfailcount ? 1 : 0;
366 }
367
368 sub loadImportFile {
369     my $importFile = abs_path("$Bin/../../../JSTests/test262/latest-changes-summary.txt");
370     die "Import file not found at $importFile.\n" if ! -e $importFile;
371
372     open(my $fh, "<", $importFile) or die $!;
373
374     my @files = grep { $_ =~ /^[AM]\s*test\// } <$fh>;
375
376     return map { $_ =~ s/^\w\s(\w*)/$test262Dir\/$1/; chomp $_; $_ } @files;
377 }
378
379 sub getProcesses {
380     my $cores;
381     my $uname = qx(which uname >> /dev/null && uname);
382     chomp $uname;
383
384     if ($uname eq 'Darwin') {
385         # sysctl should be available
386         $cores = qx/sysctl -n hw.ncpu/;
387     } elsif ($uname eq 'Linux') {
388         $cores = qx(which getconf >> /dev/null && getconf _NPROCESSORS_ONLN);
389         if (!$cores) {
390             $cores = qx(which lscpu >> /dev/null && lscpu -p | egrep -v '^#' | wc -l);
391         }
392     }
393
394     chomp $cores;
395
396     if (!$cores) {
397         $cores = 1;
398     }
399
400     return $cores * 8;
401 }
402
403 sub parseError {
404     my $error = shift;
405
406     if ($error =~ /^Exception: ([\w\d]+: .*)/m) {
407         return $1;
408     } else {
409         # Unusual error format. Save the first line instead.
410         my @errors = split("\n", $error);
411         return $errors[0];
412     }
413 }
414
415 sub getBuildPath {
416     my $debug = shift;
417
418     # Try to find JSC for user, if not supplied
419     my $cmd = abs_path("$Bin/../webkit-build-directory");
420     if (! -e $cmd) {
421         die 'Error: cannot find webkit-build-directory, specify with JSC with --jsc <path>.';
422     }
423
424     if ($debug) {
425         $cmd .= ' --debug';
426     } else {
427         $cmd .= ' --release';
428     }
429     $cmd .= ' --executablePath';
430     my $jscDir = qx($cmd);
431     chomp $jscDir;
432
433     my $jsc;
434     $jsc = $jscDir . '/jsc';
435
436     $jsc = $jscDir . '/JavaScriptCore.framework/Resources/jsc' if (! -e $jsc);
437     $jsc = $jscDir . '/bin/jsc' if (! -e $jsc);
438     if (! -e $jsc) {
439         die 'Error: cannot find jsc, specify with --jsc <path>.';
440     }
441
442     # Sets the Env DYLD_FRAMEWORK_PATH
443     $DYLD_FRAMEWORK_PATH = dirname($jsc);
444
445     return $jsc;
446 }
447
448 sub processFile {
449     my ($filename, $resultsfh) = @_;
450     my $contents = getContents($filename);
451     my $data = parseData($contents, $filename);
452     my $resultsdata;
453
454     # Check test against filters in config file
455     my $file = abs2rel( $filename, $test262Dir );
456     if (shouldSkip($file, $data)) {
457         $resultsdata = processResult($filename, $data, "skip");
458         DumpFile($resultsfh, $resultsdata);
459         return;
460     }
461
462     my @scenarios = getScenarios(@{ $data->{flags} });
463
464     my $includes = $data->{includes};
465     my ($includesfh, $includesfile);
466
467     ($includesfh, $includesfile) = compileTest($includes) if defined $includes;
468
469     foreach my $scenario (@scenarios) {
470         my $result = runTest($includesfile, $filename, $scenario, $data);
471
472         $resultsdata = processResult($filename, $data, $scenario, $result);
473         DumpFile($resultsfh, $resultsdata);
474     }
475
476     close $includesfh if defined $includesfh;
477 }
478
479 sub shouldSkip {
480     my ($filename, $data) = @_;
481
482     if (exists $config->{skip}) {
483         # Filter by file
484         if( $configSkipHash{$filename} ) {
485             return 1;
486         }
487
488         # Filter by paths
489         my @skipPaths;
490         @skipPaths = @{ $config->{skip}->{paths} } if defined $config->{skip}->{paths};
491         return 1 if (grep {$filename =~ $_} @skipPaths);
492
493         my @skipFeatures;
494         @skipFeatures = @{ $config->{skip}->{features} } if defined $config->{skip}->{features};
495
496         my $skip = 0;
497         my $keep = 0;
498         my @features = @{ $data->{features} } if $data->{features};
499         # Filter by features, loop over file features to for less iterations
500         foreach my $feature (@features) {
501             $skip = (grep {$_ eq $feature} @skipFeatures) ? 1 : 0;
502
503             # keep the test if the config skips the feature but it was also request
504             # through the CLI --features
505             return 1 if $skip && !$filterFeatures{$feature};
506
507             $keep = 1 if $filterFeatures{$feature};
508         }
509
510         # filter tests that do not contain the --features features
511         return 1 if (%filterFeatures and not $keep);
512     }
513
514     return 0;
515 }
516
517 sub getScenarios {
518     my @flags = @_;
519     my @scenarios;
520     my $nonStrict = 'default';
521     my $strictMode = 'strict mode';
522
523     if (grep $_ eq 'raw', @flags) {
524         push @scenarios, 'raw';
525     } elsif (grep $_ eq 'noStrict', @flags) {
526         push @scenarios, $nonStrict;
527     } elsif (grep $_ eq 'onlyStrict', @flags) {
528         push @scenarios, $strictMode;
529     } elsif (grep $_ eq 'module', @flags) {
530         push @scenarios, 'module';
531     } else {
532         # Add 2 default scenarios
533         push @scenarios, $strictMode;
534         push @scenarios, $nonStrict;
535     };
536
537     return @scenarios;
538 }
539
540 sub compileTest {
541     my $includes = shift;
542     my ($tfh, $tfname) = getTempFile();
543
544     my $includesContent = getHarness(map { "$harnessDir/$_" } @{ $includes });
545     print $tfh $includesContent;
546
547     return ($tfh, $tfname);
548 }
549
550 sub runTest {
551     my ($includesfile, $filename, $scenario, $data) = @_;
552     $includesfile ||= '';
553
554     my $args = '';
555
556     if (exists $data->{negative}) {
557         my $type = $data->{negative}->{type};
558         $args .=  " --exception=$type ";
559     }
560
561     if (exists $data->{flags}) {
562         my @flags = $data->{flags};
563         if (grep $_ eq 'async', @flags) {
564             $args .= ' --test262-async ';
565         }
566     }
567
568     my $prefixFile = '';
569
570     if ($scenario eq 'module') {
571         $prefixFile='--module-file=';
572     } elsif ($scenario eq 'strict mode') {
573         $prefixFile='--strict-file=';
574     }
575
576     # Raw tests should not include the default harness
577     my $defaultHarness = '';
578     $defaultHarness = $deffile if $scenario ne 'raw';
579
580     my $result = qx/$JSC $args $defaultHarness $includesfile '$prefixFile$filename'/;
581
582     chomp $result;
583
584     return $result if ($?);
585 }
586
587 sub processResult {
588     my ($path, $data, $scenario, $result) = @_;
589
590     # Report a relative path
591     my $file = abs2rel( $path, $test262Dir );
592     my %resultdata;
593     $resultdata{path} = $file;
594     $resultdata{mode} = $scenario;
595
596     my $currentfailure = parseError($result) if $result;
597     my $expectedfailure = $expect
598         && $expect->{$file}
599         && $expect->{$file}->{$scenario};
600
601     if ($scenario ne 'skip' && $currentfailure) {
602
603         # We have a new failure if we have loaded an expectation file
604         # AND (there is no expected failure OR the failure has changed).
605         my $isnewfailure = $expect
606             && (!$expectedfailure || $expectedfailure ne $currentfailure);
607
608         # Print the failure if we haven't loaded an expectation file
609         # or the failure is new.
610         my $printfailure = !$expect || $isnewfailure;
611
612         print "! NEW " if $isnewfailure;
613         print "FAIL $file ($scenario)\n" if $printfailure;
614         if ($verbose) {
615             print $result;
616             print "\nFeatures: " . join(', ', @{ $data->{features} }) if $data->{features};
617             print "\n\n";
618         }
619
620         $resultdata{result} = 'FAIL';
621         $resultdata{error} = $currentfailure;
622     } elsif ($scenario ne 'skip' && !$currentfailure) {
623         if ($expectedfailure) {
624             print "NEW PASS $file ($scenario)\n";
625             print "\n" if $verbose;
626         }
627
628         $resultdata{result} = 'PASS';
629     } else {
630         $resultdata{result} = 'SKIP';
631     }
632
633     $resultdata{features} = $data->{features} if $data->{features};
634
635     return \%resultdata;
636 }
637
638 sub getTempFile {
639     my ($tfh, $tfname) = tempfile(DIR => $tempdir);
640
641     return ($tfh, $tfname);
642 }
643
644 sub getContents {
645     my $filename = shift;
646
647     open(my $fh, '<', $filename) or die $!;
648     my $contents = join('', <$fh>);
649     close $fh;
650
651     return $contents;
652 }
653
654 sub parseData {
655     my ($contents, $filename) = @_;
656
657     my $parsed;
658     my $found = '';
659     if ($contents =~ /\/\*(---\n[\S\s]*)\n---\*\//m) {
660         $found = $1;
661     };
662
663     eval {
664         $parsed = Load($found);
665     };
666     if ($@) {
667         print "\nError parsing YAML data on file $filename.\n";
668         print "$@\n";
669     };
670     return $parsed;
671 }
672
673 sub getHarness {
674     my @files = @_;
675     my $content;
676     for (@files) {
677         my $file = $_;
678
679         open(my $harness_file, '<', $file)
680             or die "$!, '$file'";
681
682         $content .= join('', <$harness_file>);
683
684         close $harness_file;
685     };
686
687     return $content;
688 }
689
690 sub summarizeResults {
691     print "Summarizing results...\n";
692
693     if (not @results) {
694         my @rawresults = LoadFile($resultsFile) or die $!;
695         @results = @{$rawresults[0]};
696     }
697     my %byfeature;
698     my %bypath;
699
700     foreach my $test (@results) {
701         my $result = $test->{result};
702
703         if ($test->{features}) {
704             foreach my $feature (@{$test->{features}}) {
705
706                 if (not exists $byfeature{$feature}) {
707                     $byfeature{$feature} = [0, 0, 0]
708                 }
709
710                 if ($result eq 'PASS') {
711                     $byfeature{$feature}->[0]++;
712                 }
713                 if ($result eq 'FAIL') {
714                     $byfeature{$feature}->[1]++;
715                 }
716                 if ($result eq 'SKIP') {
717                     $byfeature{$feature}->[2]++;
718                 }
719             }
720         }
721         my @paths = split('/', $test->{path});
722         @paths = @paths[ 1 ... scalar @paths-2 ];
723         foreach my $i (0..scalar @paths-1) {
724             my $partialpath = join("/", @paths[0...$i]);
725
726             if (not exists $bypath{$partialpath}) {
727                 $bypath{$partialpath} = [0, 0, 0];
728             }
729
730             if ($result eq 'PASS') {
731                 $bypath{$partialpath}->[0]++;
732             }
733             if ($result eq 'FAIL') {
734                 $bypath{$partialpath}->[1]++;
735             }
736             if ($result eq 'SKIP') {
737                 $bypath{$partialpath}->[2]++;
738             }
739         }
740
741     }
742
743     open(my $sfh, '>', $summaryTxtFile) or die $!;
744
745     print $sfh sprintf("%-6s %-6s %-6s %-6s %s\n", '%PASS', 'PASS', 'FAIL', 'SKIP', 'FOLDER');
746     foreach my $key (sort keys %bypath) {
747         my $per = ($bypath{$key}->[0] / (
748             $bypath{$key}->[0]
749             + $bypath{$key}->[1]
750             + $bypath{$key}->[2])) * 100;
751
752         $per = sprintf("%.0f", $per) . "%";
753
754         print $sfh sprintf("%-6s %-6d %-6d %-6d %s \n", $per,
755                            $bypath{$key}->[0],
756                            $bypath{$key}->[1],
757                            $bypath{$key}->[2], $key,);
758     }
759
760     print $sfh "\n\n";
761     print $sfh sprintf("%-6s %-6s %-6s %-6s %s\n", '%PASS', 'PASS', 'FAIL', 'SKIP', 'FEATURE');
762
763     foreach my $key (sort keys %byfeature) {
764         my $per = ($byfeature{$key}->[0] / (
765             $byfeature{$key}->[0]
766             + $byfeature{$key}->[1]
767             + $byfeature{$key}->[2])) * 100;
768
769         $per = sprintf("%.0f", $per) . "%";
770
771         print $sfh sprintf("%-6s %-6d %-6d %-6d %s\n", $per,
772                            $byfeature{$key}->[0],
773                            $byfeature{$key}->[1],
774                            $byfeature{$key}->[2], $key);
775     }
776
777     close($sfh);
778
779     my %resultsyaml = (
780         byFolder => \%bypath,
781         byFeature => \%byfeature,
782     );
783
784     DumpFile($summaryFile, \%resultsyaml);
785
786     print "See summarized results in $summaryTxtFile\n";
787 }
788
789 __END__
790
791 =head1 DESCRIPTION
792
793 This program will run all Test262 tests. If you edit, make sure your changes are Perl 5.8.8 compatible.
794
795 =head1 SYNOPSIS
796
797 Run using native Perl:
798
799 =over 8
800
801 test262-runner -j $jsc-dir
802
803 =back
804
805 Run using carton (recommended for testing on Perl 5.8.8):
806
807 =over 8
808
809 carton exec 'test262-runner -j $jsc-dir'
810
811 =back
812
813 =head1 OPTIONS
814
815 =over 8
816
817 =item B<--help, -h>
818
819 Print a brief help message and exits.
820
821 =item B<--child-processes, -p>
822
823 Specify number of child processes.
824
825 =item B<--t262, -t>
826
827 Specify root test262 directory.
828
829 =item B<--jsc, -j>
830
831 Specify JSC location. If not provided, script will attempt to look up JSC.
832
833 =item B<--debug, -d>
834
835 Use debug build of JSC. Can only use if --jsc <path> is not provided. Release build of JSC is used by default.
836
837 =item B<--verbose, -v>
838
839 Verbose output for test results. Includes error message for test.
840
841 =item B<--config, -c>
842
843 Specify a config file. If not provided, script will load local test262-config.yaml
844
845 =item B<--ignore-config, -i>
846
847 Ignores config file if supplied or findable in directory. Will still filter based on commandline arguments.
848
849 =item B<--features, -f>
850
851 Filter test on list of features (only runs tests in feature list).
852
853 =item B<--test-only, -o>
854
855 Specify one or more specific test262 directory of test to run, relative to the root test262 directory. For example, --test-only 'test/built-ins/Number/prototype'
856
857 =item B<--save, -s>
858
859 Overwrites the test262-expectations.yaml file with the current list of test262 files and test results.
860
861 =item B<--ignore-expectations, -x>
862
863 Ignores the test262-expectations.yaml file and outputs all failures, instead of only unexpected failures.
864
865 =item B<--failing-files>
866
867 Runs all test files that expect to fail according to the expectation file. This option will run the rests in both strict and non-strict modes, even if the test only fails in one of the two modes.
868
869 =item B<--latest-import, -l>
870
871 Runs the test files listed in the last import (./JSTests/test262/latest-changes-summary.txt).
872
873 =item B<--stats>
874
875 Calculate conformance statistics from JSTests/test262-results.yaml file. Saves results in JSTests/test262/results-summary.txt and JSTests/test262/results-summary.yaml.
876
877 =back
878
879 =cut