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