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