#!/usr/bin/perl -w # Copyright (C) 2005-2017 Apple Inc. All rights reserved. # Copyright (C) 2007 Eric Seidel # # Redistribution and use in source and binary forms, with or without # modification, are permitted provided that the following conditions # are met: # # 1. Redistributions of source code must retain the above copyright # notice, this list of conditions and the following disclaimer. # 2. Redistributions in binary form must reproduce the above copyright # notice, this list of conditions and the following disclaimer in the # documentation and/or other materials provided with the distribution. # 3. Neither the name of Apple Inc. ("Apple") nor the names of # its contributors may be used to endorse or promote products derived # from this software without specific prior written permission. # # THIS SOFTWARE IS PROVIDED BY APPLE AND ITS CONTRIBUTORS "AS IS" AND ANY # EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED # WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE # DISCLAIMED. IN NO EVENT SHALL APPLE OR ITS CONTRIBUTORS BE LIABLE FOR ANY # DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES # (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; # LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND # ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT # (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF # THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. # Script to run the WebKit Open Source Project JavaScriptCore tests (adapted from Mozilla), # as well as other tests: testapi on Mac and LayoutTests/js. use strict; use File::Spec; use FindBin; use Getopt::Long qw(:config pass_through); use JSON::PP; use lib $FindBin::Bin; use List::Util qw(min max); use POSIX; use webkitdirs; # determine configuration setConfiguration(); my $configuration = configuration(); # These variables are intentionally left undefined. my $root; my $showHelp; my @extraTests = (); my $jsDriverArgs; my $childProcesses; my $shellRunner; my $makeRunner; my $rubyRunner; my $testWriter; my $memoryLimited; my $buildJSC = 1; use constant { ENV_VAR_SAYS_DO_RUN => 4, ENV_VAR_SAYS_DONT_RUN => 3, RUN_IF_NO_TESTS_SPECIFIED => 2, DO_RUN => 1, DONT_RUN => 0, }; my $runTestMasm = RUN_IF_NO_TESTS_SPECIFIED; my $runTestAir = RUN_IF_NO_TESTS_SPECIFIED; my $runTestB3 = RUN_IF_NO_TESTS_SPECIFIED; my $runTestAPI = RUN_IF_NO_TESTS_SPECIFIED; my $runJSCStress = RUN_IF_NO_TESTS_SPECIFIED; my $runMozillaTests = RUN_IF_NO_TESTS_SPECIFIED; # $runJITStressTests is special because it is not for enabling a different set of tests. # Instead it is only meaningful for when we want to disable using JIT test configurations # on the JSC stress test or mozilla tests. my $runJITStressTests = 1; my $runQuickMode = 0; my $forceCollectContinuously = 0; my $envVars = ""; my $gmallocPath = undef; my $gmallocDefaultPath = "/usr/lib/libgmalloc.dylib"; my $createTarball = 0; my $remoteHost = 0; my $failFast = 1; my %jsonData = (); my $remoteConfigFile; my $jsonFileName; if ($ENV{RUN_JAVASCRIPTCORE_TESTS_TESTMASM}) { if ($ENV{RUN_JAVASCRIPTCORE_TESTS_TESTMASM} eq "true") { $runTestMasm = ENV_VAR_SAYS_DO_RUN; } elsif ($ENV{RUN_JAVASCRIPTCORE_TESTS_TESTMASM} eq "false") { $runTestMasm = ENV_VAR_SAYS_DONT_RUN; } else { print "Don't recognize value for RUN_JAVASCRIPTCORE_TESTS_TESTMASM environment variable: '" . $ENV{RUN_JAVASCRIPTCORE_TESTS_TESTMASM} . "'. Should be set to 'true' or 'false'.\n"; } } if ($ENV{RUN_JAVASCRIPTCORE_TESTS_TESTAIR}) { if ($ENV{RUN_JAVASCRIPTCORE_TESTS_TESTAIR} eq "true") { $runTestAir = ENV_VAR_SAYS_DO_RUN; } elsif ($ENV{RUN_JAVASCRIPTCORE_TESTS_TESTAIR} eq "false") { $runTestAir = ENV_VAR_SAYS_DONT_RUN; } else { print "Don't recognize value for RUN_JAVASCRIPTCORE_TESTS_TESTAIR environment variable: '" . $ENV{RUN_JAVASCRIPTCORE_TESTS_TESTAIR} . "'. Should be set to 'true' or 'false'.\n"; } } if ($ENV{RUN_JAVASCRIPTCORE_TESTS_TESTB3}) { if ($ENV{RUN_JAVASCRIPTCORE_TESTS_TESTB3} eq "true") { $runTestB3 = ENV_VAR_SAYS_DO_RUN; } elsif ($ENV{RUN_JAVASCRIPTCORE_TESTS_TESTB3} eq "false") { $runTestB3 = ENV_VAR_SAYS_DONT_RUN; } else { print "Don't recognize value for RUN_JAVASCRIPTCORE_TESTS_TESTB3 environment variable: '" . $ENV{RUN_JAVASCRIPTCORE_TESTS_TESTB3} . "'. Should be set to 'true' or 'false'.\n"; } } if ($ENV{RUN_JAVASCRIPTCORE_TESTS_TESTAPI}) { if ($ENV{RUN_JAVASCRIPTCORE_TESTS_TESTAPI} eq "true") { $runTestAPI = ENV_VAR_SAYS_DO_RUN; } elsif ($ENV{RUN_JAVASCRIPTCORE_TESTS_TESTAPI} eq "false") { $runTestAPI = ENV_VAR_SAYS_DONT_RUN; } else { print "Don't recognize value for RUN_JAVASCRIPTCORE_TESTS_TESTAPI environment variable: '" . $ENV{RUN_JAVASCRIPTCORE_TESTS_TESTAPI} . "'. Should be set to 'true' or 'false'.\n"; } } if ($ENV{RUN_JAVASCRIPTCORE_TESTS_BUILD}) { if ($ENV{RUN_JAVASCRIPTCORE_TESTS_BUILD} eq "true") { $buildJSC = 1; } elsif ($ENV{RUN_JAVASCRIPTCORE_TESTS_BUILD} eq "false") { $buildJSC = 0; } else { print "Don't recognize value for RUN_JAVASCRIPTCORE_TESTS_BUILD environment variable: '" . $ENV{RUN_JAVASCRIPTCORE_TESTS_BUILD} . "'. Should be set to 'true' or 'false'.\n"; } } if ($ENV{RUN_JAVASCRIPTCORE_TESTS_EXTRA_TESTS}) { push @extraTests, $ENV{RUN_JAVASCRIPTCORE_TESTS_EXTRA_TESTS}; } sub defaultStringForTestState { my ($state) = @_; if ($state == ENV_VAR_SAYS_DONT_RUN) { return "will not run due to environment variable"; } elsif ($state == DONT_RUN) { return "will not run"; } else { return "will run"; } } my $programName = basename($0); my $buildJSCDefault = $buildJSC ? "will check" : "will not check"; my $testmasmDefault = defaultStringForTestState($runTestMasm); my $testairDefault = defaultStringForTestState($runTestAir); my $testb3Default = defaultStringForTestState($runTestB3); my $testapiDefault = defaultStringForTestState($runTestAPI); my $jscStressDefault = defaultStringForTestState($runJSCStress); my $mozillaTestsDefault = defaultStringForTestState($runMozillaTests); my $jitStressTestsDefault = $runJITStressTests ? "will run" : " will not run"; my $quickModeDefault = $runQuickMode ? "some" : "all"; my $failFastDefault = $failFast ? "fail fast" : "don't fail fast"; my $filter; my $usage = < options are specified, only those tests will run. For example, the following only runs testmasm and testapi: \$ run-javascriptcore-tests --testmasm --testapi Otherwise, all tests will run unless the test is disabled using --no- or an environment variable. EOF GetOptions( 'root=s' => \$root, 'extra-tests=s' => \@extraTests, 'build!' => \$buildJSC, 'testmasm!' => \$runTestMasm, 'testair!' => \$runTestAir, 'testb3!' => \$runTestB3, 'testapi!' => \$runTestAPI, 'jsc-stress!' => \$runJSCStress, 'mozilla-tests!' => \$runMozillaTests, 'jit-stress-tests!' => \$runJITStressTests, 'quick!' => \$runQuickMode, 'fail-fast!' => \$failFast, 'force-collectContinuously!' => \$forceCollectContinuously, 'json-output=s' => \$jsonFileName, 'tarball!' => \$createTarball, 'remote=s' => \$remoteHost, 'remote-config-file=s' => \$remoteConfigFile, 'child-processes=s' => \$childProcesses, 'shell-runner' => \$shellRunner, 'make-runner' => \$makeRunner, 'ruby-runner' => \$rubyRunner, 'test-writer=s' => \$testWriter, 'memory-limited' => \$memoryLimited, 'filter=s' => \$filter, 'help' => \$showHelp, 'env-vars=s' => \$envVars, 'gmalloc:s' => \$gmallocPath, ); my $specificTestsSpecified = 0; if ($runTestMasm == DO_RUN || $runTestAir == DO_RUN || $runTestB3 == DO_RUN || $runTestAPI == DO_RUN || $runJSCStress == DO_RUN || $runMozillaTests == DO_RUN) { $specificTestsSpecified = 1; } sub enableTestOrNot { my ($state) = @_; if ($state == RUN_IF_NO_TESTS_SPECIFIED || $state == ENV_VAR_SAYS_DO_RUN) { return $specificTestsSpecified ? DONT_RUN : DO_RUN; } elsif ($state == ENV_VAR_SAYS_DONT_RUN) { return DONT_RUN; } return $state; } $runTestMasm = enableTestOrNot($runTestMasm); $runTestAir = enableTestOrNot($runTestAir); $runTestB3 = enableTestOrNot($runTestB3); $runTestAPI = enableTestOrNot($runTestAPI); $runJSCStress = enableTestOrNot($runJSCStress); $runMozillaTests = enableTestOrNot($runMozillaTests); # Assume any arguments left over from GetOptions are assumed to be build arguments my @buildArgs = @ARGV; if ($showHelp) { print STDERR $usage; exit 1; } setConfigurationProductDir(Cwd::abs_path($root)) if (defined($root)); if (defined($jsonFileName)) { $jsonFileName = File::Spec->rel2abs($jsonFileName); } if (!defined($root) && $buildJSC) { chdirWebKit(); push(@buildArgs, argumentsForConfiguration()); print "Running: build-jsc " . join(" ", @buildArgs) . "\n"; my $buildResult = system "perl", File::Spec->catfile("Tools", "Scripts", "build-jsc"), @buildArgs; if ($buildResult) { print STDERR "Compiling jsc failed!\n"; exit exitStatus($buildResult); } } if (defined($gmallocPath)) { if ($gmallocPath eq "") { $envVars .= " DYLD_INSERT_LIBRARIES=" . $gmallocDefaultPath; } else { $envVars .= " DYLD_INSERT_LIBRARIES=" . $gmallocPath; } } my $productDir = jscProductDir(); $ENV{DYLD_FRAMEWORK_PATH} = $productDir; $ENV{JSCTEST_timeout} = 120 unless $ENV{JSCTEST_timeout}; # Set a 120 second timeout on all jsc tests (if environment variable not defined already). $ENV{TZ}="US/Pacific"; # Some tests fail if the time zone is not set to US/Pacific () setPathForRunningWebKitApp(\%ENV) if isCygwin(); sub testPath { my ($productDir, $testName) = @_; $testName .= "_debug" if configuration() eq "Debug_All"; return File::Spec->catfile($productDir, $testName); } sub runTest { my ($testName, $jsonTestStatusName) = @_; chdirWebKit(); chdir($productDir) or die "Failed to switch directory to '$productDir'\n"; my @command = (testPath($productDir, $testName)); unshift @command, ("xcrun", "-sdk", xcodeSDK(), "sim") if willUseIOSSimulatorSDK(); unshift @command, wrapperPrefixIfNeeded() if shouldUseJhbuild(); if ($envVars ne "") { foreach my $var (split(/\s+/, $envVars)) { if ($var =~ /([^=]*)=(.*)/) { $ENV{$1} = $2; } } } # Use an "indirect object" so that system() won't get confused if the path # contains spaces (see perldoc -f exec). my $testResult = system { $command[0] } @command; my $exitStatus = exitStatus($testResult); print "$testName completed with rc=$testResult ($exitStatus)\n\n"; if (defined($jsonFileName)) { my $testStatus = ($exitStatus == 0)? JSON::PP::true: JSON::PP::false; $jsonData{$jsonTestStatusName} = $testStatus; } if ($testResult && $failFast) { writeJsonDataIfApplicable(); exit exitStatus($testResult); } } if ($runTestMasm) { runTest("testmasm", "allMasmTestsPassed") } if ($runTestAir) { runTest("testair", "allAirTestsPassed") } if ($runTestB3) { runTest("testb3", "allB3TestsPassed") } if ($runTestAPI) { runTest("testapi", "allApiTestsPassed") } # Find JavaScriptCore directory chdirWebKit(); runJSCStressTests(); sub runJSCStressTests { my $jscStressResultsDir = $productDir . "/jsc-stress-results"; my $hasTestsToRun = 0; my @testList; if ($runJSCStress) { @testList = ( "PerformanceTests/SunSpider/tests/sunspider-1.0", "PerformanceTests/JetStream/cdjs/cdjs-tests.yaml", "PerformanceTests/ARES-6/Air/airjs-tests.yaml", "PerformanceTests/ARES-6/Basic/basic-tests.yaml", "Tools/WebGPUShadingLanguageRI/wsl-tests.yaml", "JSTests/executableAllocationFuzz.yaml", "JSTests/exceptionFuzz.yaml", "PerformanceTests/SunSpider/no-architecture-specific-optimizations.yaml", "PerformanceTests/SunSpider/shadow-chicken.yaml", "PerformanceTests/SunSpider/tests/v8-v6", "JSTests/stress", "JSTests/microbenchmarks", "JSTests/slowMicrobenchmarks.yaml", "PerformanceTests/SunSpider/profiler-test.yaml", "LayoutTests/jsc-layout-tests.yaml", "JSTests/typeProfiler.yaml", "JSTests/controlFlowProfiler.yaml", "JSTests/es6.yaml", "JSTests/modules.yaml", "JSTests/ChakraCore.yaml", "JSTests/wasm.yaml"); $hasTestsToRun = 1; } if ($runMozillaTests) { push(@testList, "JSTests/mozilla/mozilla-tests.yaml"); $hasTestsToRun = 1; } # Set LANG environment variable so the stress tests will work with newer ruby () $ENV{LANG}="en_US.UTF-8"; my @jscStressDriverCmd = ( "/usr/bin/env", "ruby", "Tools/Scripts/run-jsc-stress-tests", "-j", jscPath($productDir), "-o", $jscStressResultsDir); push(@jscStressDriverCmd, @testList); if (isWindows() && !isCygwin()) { shift @jscStressDriverCmd; # Remove /usr/bin/env } if (configuration() eq "Debug") { push(@jscStressDriverCmd, "--debug"); } if ($forceCollectContinuously) { push(@jscStressDriverCmd, "--force-collectContinuously"); } if ($envVars ne "") { push(@jscStressDriverCmd, "--env-vars"); push(@jscStressDriverCmd, $envVars); } if ($runQuickMode) { push(@jscStressDriverCmd, "--quick"); } if (!$runJITStressTests) { push(@jscStressDriverCmd, "--no-jit"); } if ($createTarball) { push(@jscStressDriverCmd, "--tarball"); } if ($remoteHost) { push(@jscStressDriverCmd, "--remote"); push(@jscStressDriverCmd, $remoteHost); } if ($remoteConfigFile) { push(@jscStressDriverCmd, "--remote-config-file"); push(@jscStressDriverCmd, $remoteConfigFile); } if ($childProcesses) { push(@jscStressDriverCmd, "--child-processes"); push(@jscStressDriverCmd, $childProcesses); } if ($shellRunner) { push(@jscStressDriverCmd, "--shell-runner"); } if ($makeRunner) { push(@jscStressDriverCmd, "--make-runner"); } if ($rubyRunner) { push(@jscStressDriverCmd, "--ruby-runner"); } if ($testWriter) { push(@jscStressDriverCmd, "--test-writer"); push(@jscStressDriverCmd, $testWriter); } if ($memoryLimited) { push(@jscStressDriverCmd, "--memory-limited"); } if ($filter) { push(@jscStressDriverCmd, "--filter"); push(@jscStressDriverCmd, $filter); } unshift @jscStressDriverCmd, wrapperPrefixIfNeeded() if shouldUseJhbuild(); # End option processing, the rest of the arguments are tests push(@jscStressDriverCmd, "--"); for my $testSuite (@extraTests) { push(@jscStressDriverCmd, $testSuite); $hasTestsToRun = 1; } if (defined($ENV{"EXTRA_JSC_TESTS"})) { push(@jscStressDriverCmd, $ENV{"EXTRA_JSC_TESTS"}); $hasTestsToRun = 1; } if (!$hasTestsToRun) { exit(0); } print "Running: " . join(" ", @jscStressDriverCmd) . "\n"; my $result = system(@jscStressDriverCmd); exit exitStatus($result) if $result; my @jscStressFailList = readAllLines($jscStressResultsDir . "/failed"); @jscStressFailList = sort @jscStressFailList; my $numJSCStressFailures = @jscStressFailList; if ($numJSCStressFailures) { print "\n** The following JSC stress test failures have been introduced:\n"; foreach my $testFailure (@jscStressFailList) { print "\t$testFailure\n"; } } print "\n"; print "Results for JSC stress tests:\n"; printThingsFound($numJSCStressFailures, "failure", "failures", "found"); print " OK.\n" if $numJSCStressFailures == 0; print "\n"; if (defined($jsonFileName)) { $jsonData{'stressTestFailures'} = \@jscStressFailList; } writeJsonDataIfApplicable(); exit(1) if $numJSCStressFailures; } sub readAllLines { my ($filename) = @_; my @array = (); eval { open FILE, $filename or die; while () { chomp; push @array, $_; } close FILE; }; return @array; } sub printThingsFound { my ($number, $label, $pluralLabel, $verb) = @_; print " $number "; if ($number == 1) { print $label; } else { print $pluralLabel; } print " $verb.\n"; } sub writeJsonDataIfApplicable { if (defined($jsonFileName)) { open(my $fileHandler, ">", $jsonFileName) or die; print $fileHandler "${\encode_json(\%jsonData)}\n"; close($fileHandler); } }