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