Separate jsc stress test script writer from run-jsc-stress-tests
authorcommit-queue@webkit.org <commit-queue@webkit.org@268f45cc-cd09-0410-ab3c-d52691b4dbfc>
Wed, 9 Aug 2017 00:18:08 +0000 (00:18 +0000)
committercommit-queue@webkit.org <commit-queue@webkit.org@268f45cc-cd09-0410-ab3c-d52691b4dbfc>
Wed, 9 Aug 2017 00:18:08 +0000 (00:18 +0000)
https://bugs.webkit.org/show_bug.cgi?id=175216

Patch by Stephan Szabo <stephan.szabo@sony.com> on 2017-08-08
Reviewed by Mark Lam.

* Scripts/run-jsc-stress-tests:
* Scripts/webkitruby/jsc-stress-test-writer-default.rb: Added.

git-svn-id: https://svn.webkit.org/repository/webkit/trunk@220431 268f45cc-cd09-0410-ab3c-d52691b4dbfc

Tools/ChangeLog
Tools/Scripts/run-jsc-stress-tests
Tools/Scripts/webkitruby/jsc-stress-test-writer-default.rb [new file with mode: 0644]

index 06baf6769b1bf53928c564ab81436c683aca0820..fd657c0111f52ebde2f652fb262d5a240f7b8b98 100644 (file)
@@ -1,3 +1,13 @@
+2017-08-08  Stephan Szabo  <stephan.szabo@sony.com>
+
+        Separate jsc stress test script writer from run-jsc-stress-tests
+        https://bugs.webkit.org/show_bug.cgi?id=175216
+
+        Reviewed by Mark Lam.
+
+        * Scripts/run-jsc-stress-tests:
+        * Scripts/webkitruby/jsc-stress-test-writer-default.rb: Added.
+
 2017-08-08  Michael Catanzaro  <mcatanzaro@igalia.com>
 
         Unreviewed, fix Ubuntu LTS build
index c60839deaeddfb8a41abfecffa8d5a489f939f36..e610c97d85dcb307e18bb1785a8c0b724eaebb0d 100755 (executable)
@@ -434,6 +434,8 @@ NO_CJIT_OPTIONS = ["--useConcurrentJIT=false", "--thresholdForJITAfterWarmUp=100
 B3O1_OPTIONS = ["--defaultB3OptLevel=1"]
 FTL_OPTIONS = ["--useFTLJIT=true"]
 
+require_relative "webkitruby/jsc-stress-test-writer-default"
+
 def shouldCollectContinuously?
     $buildType == "release" or $forceCollectContinuously
 end
@@ -471,274 +473,8 @@ def pathToHelpers
     pathToBundleResourceFromBenchmarkDirectory(".helpers")
 end
 
-def prefixCommand(prefix)
-    "awk " + Shellwords.shellescape("{ printf #{(prefix + ': ').inspect}; print }")
-end
-
-def redirectAndPrefixCommand(prefix)
-    prefixCommand(prefix) + " 2>&1"
-end
-
-def pipeAndPrefixCommand(outputFilename, prefix)
-    "tee " + Shellwords.shellescape(outputFilename.to_s) + " | " + prefixCommand(prefix)
-end
-
-# Output handler for tests that are expected to be silent.
-def silentOutputHandler
-    Proc.new {
-        | name |
-        " | " + pipeAndPrefixCommand((Pathname("..") + (name + ".out")).to_s, name)
-    }
-end
-
-# Output handler for tests that are expected to produce meaningful output.
-def noisyOutputHandler
-    Proc.new {
-        | name |
-        " | cat > " + Shellwords.shellescape((Pathname("..") + (name + ".out")).to_s)
-    }
-end
-
-# Error handler for tests that fail exactly when they return non-zero exit status.
-# This is useful when a test is expected to fail.
-def simpleErrorHandler
-    Proc.new {
-        | outp, plan |
-        outp.puts "if test -e #{plan.failFile}"
-        outp.puts "then"
-        outp.puts "    (echo ERROR: Unexpected exit code: `cat #{plan.failFile}`) | " + redirectAndPrefixCommand(plan.name)
-        outp.puts "    " + plan.failCommand
-        outp.puts "else"
-        outp.puts "    " + plan.successCommand
-        outp.puts "fi"
-    }
-end
-
-# Error handler for tests that fail exactly when they return zero exit status.
-def expectedFailErrorHandler
-    Proc.new {
-        | outp, plan |
-        outp.puts "if test -e #{plan.failFile}"
-        outp.puts "then"
-        outp.puts "    " + plan.successCommand
-        outp.puts "else"
-        outp.puts "    (echo ERROR: Unexpected exit code: 0) | " + redirectAndPrefixCommand(plan.name)
-        outp.puts "    " + plan.failCommand
-        outp.puts "fi"
-    }
-end
-
-# Error handler for tests that fail exactly when they return non-zero exit status and produce
-# lots of spew. This will echo that spew when the test fails.
-def noisyErrorHandler
-    Proc.new {
-        | outp, plan |
-        outputFilename = Shellwords.shellescape((Pathname("..") + (plan.name + ".out")).to_s)
-    
-        outp.puts "if test -e #{plan.failFile}"
-        outp.puts "then"
-        outp.puts "    (cat #{outputFilename} && echo ERROR: Unexpected exit code: `cat #{plan.failFile}`) | " + redirectAndPrefixCommand(plan.name)
-        outp.puts "    " + plan.failCommand
-        outp.puts "else"
-        outp.puts "    " + plan.successCommand
-        outp.puts "fi"
-    }
-end
-
-# Error handler for tests that diff their output with some expectation.
-def diffErrorHandler(expectedFilename)
-    Proc.new {
-        | outp, plan |
-        outputFilename = Shellwords.shellescape((Pathname("..") + (plan.name + ".out")).to_s)
-        diffFilename = Shellwords.shellescape((Pathname("..") + (plan.name + ".diff")).to_s)
-        
-        outp.puts "if test -e #{plan.failFile}"
-        outp.puts "then"
-        outp.puts "    (cat #{outputFilename} && echo ERROR: Unexpected exit code: `cat #{plan.failFile}`) | " + redirectAndPrefixCommand(plan.name)
-        outp.puts "    " + plan.failCommand
-        outp.puts "elif test -e ../#{Shellwords.shellescape(expectedFilename)}"
-        outp.puts "then"
-        outp.puts "    diff --strip-trailing-cr -u ../#{Shellwords.shellescape(expectedFilename)} #{outputFilename} > #{diffFilename}"
-        outp.puts "    if [ $? -eq 0 ]"
-        outp.puts "    then"
-        outp.puts "    " + plan.successCommand
-        outp.puts "    else"
-        outp.puts "        (echo \"DIFF FAILURE!\" && cat #{diffFilename}) | " + redirectAndPrefixCommand(plan.name)
-        outp.puts "        " + plan.failCommand
-        outp.puts "    fi"
-        outp.puts "else"
-        outp.puts "    (echo \"NO EXPECTATION!\" && cat #{outputFilename}) | " + redirectAndPrefixCommand(plan.name)
-        outp.puts "    " + plan.failCommand
-        outp.puts "fi"
-    }
-end
-
-# Error handler for tests that report error by saying "failed!". This is used by Mozilla
-# tests.
-def mozillaErrorHandler
-    Proc.new {
-        | outp, plan |
-        outputFilename = Shellwords.shellescape((Pathname("..") + (plan.name + ".out")).to_s)
-
-        outp.puts "if test -e #{plan.failFile}"
-        outp.puts "then"
-        outp.puts "    (cat #{outputFilename} && echo ERROR: Unexpected exit code: `cat #{plan.failFile}`) | " + redirectAndPrefixCommand(plan.name)
-        outp.puts "    " + plan.failCommand
-        outp.puts "elif grep -i -q failed! #{outputFilename}"
-        outp.puts "then"
-        outp.puts "    (echo Detected failures: && cat #{outputFilename}) | " + redirectAndPrefixCommand(plan.name)
-        outp.puts "    " + plan.failCommand
-        outp.puts "else"
-        outp.puts "    " + plan.successCommand
-        outp.puts "fi"
-    }
-end
-
-# Error handler for tests that report error by saying "failed!", and are expected to
-# fail. This is used by Mozilla tests.
-def mozillaFailErrorHandler
-    Proc.new {
-        | outp, plan |
-        outputFilename = Shellwords.shellescape((Pathname("..") + (plan.name + ".out")).to_s)
-
-        outp.puts "if test -e #{plan.failFile}"
-        outp.puts "then"
-        outp.puts "    " + plan.successCommand
-        outp.puts "elif grep -i -q failed! #{outputFilename}"
-        outp.puts "then"
-        outp.puts "    " + plan.successCommand
-        outp.puts "else"
-        outp.puts "    (echo NOTICE: You made this test pass, but it was expected to fail) | " + redirectAndPrefixCommand(plan.name)
-        outp.puts "    " + plan.failCommand
-        outp.puts "fi"
-    }
-end
-
-# Error handler for tests that report error by saying "failed!", and are expected to have
-# an exit code of 3.
-def mozillaExit3ErrorHandler
-    Proc.new {
-        | outp, plan |
-        outputFilename = Shellwords.shellescape((Pathname("..") + (plan.name + ".out")).to_s)
-
-        outp.puts "if test -e #{plan.failFile}"
-        outp.puts "then"
-        outp.puts "    if [ `cat #{plan.failFile}` -eq 3 ]"
-        outp.puts "    then"
-        outp.puts "        if grep -i -q failed! #{outputFilename}"
-        outp.puts "        then"
-        outp.puts "            (echo Detected failures: && cat #{outputFilename}) | " + redirectAndPrefixCommand(plan.name)
-        outp.puts "            " + plan.failCommand
-        outp.puts "        else"
-        outp.puts "            " + plan.successCommand
-        outp.puts "        fi"
-        outp.puts "    else"
-        outp.puts "        (cat #{outputFilename} && echo ERROR: Unexpected exit code: `cat #{plan.failFile}`) | " + redirectAndPrefixCommand(plan.name)
-        outp.puts "        " + plan.failCommand
-        outp.puts "    fi"
-        outp.puts "else"
-        outp.puts "    (cat #{outputFilename} && echo ERROR: Test expected to fail, but returned successfully) | " + redirectAndPrefixCommand(plan.name)
-        outp.puts "    " + plan.failCommand
-        outp.puts "fi"
-    }
-end
-
-# Error handler for tests that report success by saying "Passed" or error by saying "FAILED".
-# This is used by Chakra tests.
-def chakraPassFailErrorHandler
-    Proc.new {
-        | outp, plan |
-        outputFilename = Shellwords.shellescape((Pathname("..") + (plan.name + ".out")).to_s)
-
-        outp.puts "if test -e #{plan.failFile}"
-        outp.puts "then"
-        outp.puts "    (cat #{outputFilename} && echo ERROR: Unexpected exit code: `cat #{plan.failFile}`) | " + redirectAndPrefixCommand(plan.name)
-        outp.puts "    " + plan.failCommand
-        outp.puts "elif grep -i -q FAILED #{outputFilename}"
-        outp.puts "then"
-        outp.puts "    (echo Detected failures: && cat #{outputFilename}) | " + redirectAndPrefixCommand(plan.name)
-        outp.puts "    " + plan.failCommand
-        outp.puts "else"
-        outp.puts "    " + plan.successCommand
-        outp.puts "fi"
-    }
-end
-
 $runCommandOptions = {}
 
-class Plan
-    attr_reader :directory, :arguments, :family, :name, :outputHandler, :errorHandler
-    attr_accessor :index
-    
-    def initialize(directory, arguments, family, name, outputHandler, errorHandler)
-        @directory = directory
-        @arguments = arguments
-        @family = family
-        @name = name
-        @outputHandler = outputHandler
-        @errorHandler = errorHandler
-        @isSlow = !!$runCommandOptions[:isSlow]
-    end
-    
-    def shellCommand
-        # It's important to remember that the test is actually run in a subshell, so if we change directory
-        # in the subshell when we return we will be in our original directory. This is nice because we don't
-        # have to bend over backwards to do things relative to the root.
-        script = "(cd ../#{Shellwords.shellescape(@directory.to_s)} && ("
-        $envVars.each { |var| script += "export " << var << "; " }
-        script += "\"$@\" " + escapeAll(@arguments) + "))"
-        return script
-    end
-    
-    def reproScriptCommand
-        # We have to find our way back to the .runner directory since that's where all of the relative
-        # paths assume they start out from.
-        script = "CURRENT_DIR=\"$( cd \"$( dirname \"${BASH_SOURCE[0]}\" )\" && pwd )\"\n"
-        script += "cd $CURRENT_DIR\n"
-        Pathname.new(@name).dirname.each_filename {
-            | pathComponent |
-            script += "cd ..\n"
-        }
-        script += "cd .runner\n"
-
-        script += "export DYLD_FRAMEWORK_PATH=$(cd #{$testingFrameworkPath.dirname}; pwd)\n"
-        script += "export JSCTEST_timeout=#{Shellwords.shellescape(ENV['JSCTEST_timeout'])}\n"
-        $envVars.each { |var| script += "export " << var << "\n" }
-        script += "#{shellCommand} || exit 1"
-        "echo #{Shellwords.shellescape(script)} > #{Shellwords.shellescape((Pathname.new("..") + @name).to_s)}"
-    end
-    
-    def failCommand
-        "echo FAIL: #{Shellwords.shellescape(@name)} ; touch #{failFile} ; " + reproScriptCommand
-    end
-    
-    def successCommand
-        if $progressMeter or $verbosity >= 2
-            "rm -f #{failFile} ; echo PASS: #{Shellwords.shellescape(@name)}"
-        else
-            "rm -f #{failFile}"
-        end
-    end
-    
-    def failFile
-        "test_fail_#{@index}"
-    end
-    
-    def writeRunScript(filename)
-        File.open(filename, "w") {
-            | outp |
-            outp.puts "echo Running #{Shellwords.shellescape(@name)}"
-            cmd  = "(" + shellCommand + " || (echo $? > #{failFile})) 2>&1 "
-            cmd += @outputHandler.call(@name)
-            if $verbosity >= 3
-                outp.puts "echo #{Shellwords.shellescape(cmd)}"
-            end
-            outp.puts cmd
-            @errorHandler.call(outp, self)
-        }
-    end
-end
-
 $uniqueFilenameCounter = 0
 def uniqueFilename(extension)
     payloadDir = $outputDir + "_payload"
@@ -1895,63 +1631,6 @@ def prepareTestRunner
     end
 end
 
-def prepareShellTestRunner
-    FileUtils.cp SCRIPTS_PATH + "jsc-stress-test-helpers" + "shell-runner.sh", $runnerDir + "runscript"
-end
-
-def prepareMakeTestRunner
-    # The goals of our parallel test runner are scalability and simplicity. The
-    # simplicity part is particularly important. We don't want to have to have
-    # a full-time contributor just philosophising about parallel testing.
-    #
-    # As such, we just pass off all of the hard work to 'make'. This creates a
-    # dummy directory ("$outputDir/.runner") in which we create a dummy
-    # Makefile. The Makefile has an 'all' rule that depends on all of the tests.
-    # That is, for each test we know we will run, there is a rule in the
-    # Makefile and 'all' depends on it. Running 'make -j <whatever>' on this
-    # Makefile results in 'make' doing all of the hard work:
-    #
-    # - Load balancing just works. Most systems have a great load balancer in
-    #   'make'. If your system doesn't then just install a real 'make'.
-    #
-    # - Interruptions just work. For example Ctrl-C handling in 'make' is
-    #   exactly right. You don't have to worry about zombie processes.
-    #
-    # We then do some tricks to make failure detection work and to make this
-    # totally sound. If a test fails, we don't want the whole 'make' job to
-    # stop. We also don't have any facility for makefile-escaping of path names.
-    # We do have such a thing for shell-escaping, though. We fix both problems
-    # by having the actual work for each of the test rules be done in a shell
-    # script on the side. There is one such script per test. The script responds
-    # to failure by printing something on the console and then touching a
-    # failure file for that test, but then still returns 0. This makes 'make'
-    # continue past that failure and complete all the tests anyway.
-    #
-    # In the end, this script collects all of the failures by searching for
-    # files in the .runner directory whose name matches /^test_fail_/, where
-    # the thing after the 'fail_' is the test index. Those are the files that
-    # would be created by the test scripts if they detect failure. We're
-    # basically using the filesystem as a concurrent database of test failures.
-    # Even if two tests fail at the same time, since they're touching different
-    # files we won't miss any failures.
-    runIndices = []
-    $runlist.each {
-        | plan |
-        runIndices << plan.index
-    }
-    
-    File.open($runnerDir + "Makefile", "w") {
-        | outp |
-        outp.puts("all: " + runIndices.map{|v| "test_done_#{v}"}.join(' '))
-        runIndices.each {
-            | index |
-            plan = $runlist[index]
-            outp.puts "test_done_#{index}:"
-            outp.puts "\tsh test_script_#{plan.index}"
-        }
-    }
-end
-
 def cleanRunnerDirectory
     raise unless $bundle
     Dir.foreach($runnerDir) {
@@ -2077,15 +1756,6 @@ def runAndMonitorTestRunnerCommand(*cmd)
 end
 
 def runTestRunner
-    case $testRunnerType
-    when :shell
-        testRunnerCommand = "sh runscript"
-    when :make
-        testRunnerCommand = "make -j #{$numChildProcesses.to_s} -s -f Makefile"
-    else
-        raise "Unknown test runner type: #{$testRunnerType.to_s}"
-    end
-
     if $remote
         if !$remoteDirectory
             $remoteDirectory = JSON::parse(sshRead("cat ~/.bencher"))["tempPath"]
diff --git a/Tools/Scripts/webkitruby/jsc-stress-test-writer-default.rb b/Tools/Scripts/webkitruby/jsc-stress-test-writer-default.rb
new file mode 100644 (file)
index 0000000..3111830
--- /dev/null
@@ -0,0 +1,357 @@
+# Copyright (C) 2013-2016 Apple Inc. All rights reserved.
+#
+# 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. 
+#
+# 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.
+
+def prefixCommand(prefix)
+    "awk " + Shellwords.shellescape("{ printf #{(prefix + ': ').inspect}; print }")
+end
+
+def redirectAndPrefixCommand(prefix)
+    prefixCommand(prefix) + " 2>&1"
+end
+
+def pipeAndPrefixCommand(outputFilename, prefix)
+    "tee " + Shellwords.shellescape(outputFilename.to_s) + " | " + prefixCommand(prefix)
+end
+
+# Output handler for tests that are expected to be silent.
+def silentOutputHandler
+    Proc.new {
+        | name |
+        " | " + pipeAndPrefixCommand((Pathname("..") + (name + ".out")).to_s, name)
+    }
+end
+
+# Output handler for tests that are expected to produce meaningful output.
+def noisyOutputHandler
+    Proc.new {
+        | name |
+        " | cat > " + Shellwords.shellescape((Pathname("..") + (name + ".out")).to_s)
+    }
+end
+
+# Error handler for tests that fail exactly when they return non-zero exit status.
+# This is useful when a test is expected to fail.
+def simpleErrorHandler
+    Proc.new {
+        | outp, plan |
+        outp.puts "if test -e #{plan.failFile}"
+        outp.puts "then"
+        outp.puts "    (echo ERROR: Unexpected exit code: `cat #{plan.failFile}`) | " + redirectAndPrefixCommand(plan.name)
+        outp.puts "    " + plan.failCommand
+        outp.puts "else"
+        outp.puts "    " + plan.successCommand
+        outp.puts "fi"
+    }
+end
+
+# Error handler for tests that fail exactly when they return zero exit status.
+def expectedFailErrorHandler
+    Proc.new {
+        | outp, plan |
+        outp.puts "if test -e #{plan.failFile}"
+        outp.puts "then"
+        outp.puts "    " + plan.successCommand
+        outp.puts "else"
+        outp.puts "    (echo ERROR: Unexpected exit code: 0) | " + redirectAndPrefixCommand(plan.name)
+        outp.puts "    " + plan.failCommand
+        outp.puts "fi"
+    }
+end
+
+# Error handler for tests that fail exactly when they return non-zero exit status and produce
+# lots of spew. This will echo that spew when the test fails.
+def noisyErrorHandler
+    Proc.new {
+        | outp, plan |
+        outputFilename = Shellwords.shellescape((Pathname("..") + (plan.name + ".out")).to_s)
+    
+        outp.puts "if test -e #{plan.failFile}"
+        outp.puts "then"
+        outp.puts "    (cat #{outputFilename} && echo ERROR: Unexpected exit code: `cat #{plan.failFile}`) | " + redirectAndPrefixCommand(plan.name)
+        outp.puts "    " + plan.failCommand
+        outp.puts "else"
+        outp.puts "    " + plan.successCommand
+        outp.puts "fi"
+    }
+end
+
+# Error handler for tests that diff their output with some expectation.
+def diffErrorHandler(expectedFilename)
+    Proc.new {
+        | outp, plan |
+        outputFilename = Shellwords.shellescape((Pathname("..") + (plan.name + ".out")).to_s)
+        diffFilename = Shellwords.shellescape((Pathname("..") + (plan.name + ".diff")).to_s)
+        
+        outp.puts "if test -e #{plan.failFile}"
+        outp.puts "then"
+        outp.puts "    (cat #{outputFilename} && echo ERROR: Unexpected exit code: `cat #{plan.failFile}`) | " + redirectAndPrefixCommand(plan.name)
+        outp.puts "    " + plan.failCommand
+        outp.puts "elif test -e ../#{Shellwords.shellescape(expectedFilename)}"
+        outp.puts "then"
+        outp.puts "    diff --strip-trailing-cr -u ../#{Shellwords.shellescape(expectedFilename)} #{outputFilename} > #{diffFilename}"
+        outp.puts "    if [ $? -eq 0 ]"
+        outp.puts "    then"
+        outp.puts "    " + plan.successCommand
+        outp.puts "    else"
+        outp.puts "        (echo \"DIFF FAILURE!\" && cat #{diffFilename}) | " + redirectAndPrefixCommand(plan.name)
+        outp.puts "        " + plan.failCommand
+        outp.puts "    fi"
+        outp.puts "else"
+        outp.puts "    (echo \"NO EXPECTATION!\" && cat #{outputFilename}) | " + redirectAndPrefixCommand(plan.name)
+        outp.puts "    " + plan.failCommand
+        outp.puts "fi"
+    }
+end
+
+# Error handler for tests that report error by saying "failed!". This is used by Mozilla
+# tests.
+def mozillaErrorHandler
+    Proc.new {
+        | outp, plan |
+        outputFilename = Shellwords.shellescape((Pathname("..") + (plan.name + ".out")).to_s)
+
+        outp.puts "if test -e #{plan.failFile}"
+        outp.puts "then"
+        outp.puts "    (cat #{outputFilename} && echo ERROR: Unexpected exit code: `cat #{plan.failFile}`) | " + redirectAndPrefixCommand(plan.name)
+        outp.puts "    " + plan.failCommand
+        outp.puts "elif grep -i -q failed! #{outputFilename}"
+        outp.puts "then"
+        outp.puts "    (echo Detected failures: && cat #{outputFilename}) | " + redirectAndPrefixCommand(plan.name)
+        outp.puts "    " + plan.failCommand
+        outp.puts "else"
+        outp.puts "    " + plan.successCommand
+        outp.puts "fi"
+    }
+end
+
+# Error handler for tests that report error by saying "failed!", and are expected to
+# fail. This is used by Mozilla tests.
+def mozillaFailErrorHandler
+    Proc.new {
+        | outp, plan |
+        outputFilename = Shellwords.shellescape((Pathname("..") + (plan.name + ".out")).to_s)
+
+        outp.puts "if test -e #{plan.failFile}"
+        outp.puts "then"
+        outp.puts "    " + plan.successCommand
+        outp.puts "elif grep -i -q failed! #{outputFilename}"
+        outp.puts "then"
+        outp.puts "    " + plan.successCommand
+        outp.puts "else"
+        outp.puts "    (echo NOTICE: You made this test pass, but it was expected to fail) | " + redirectAndPrefixCommand(plan.name)
+        outp.puts "    " + plan.failCommand
+        outp.puts "fi"
+    }
+end
+
+# Error handler for tests that report error by saying "failed!", and are expected to have
+# an exit code of 3.
+def mozillaExit3ErrorHandler
+    Proc.new {
+        | outp, plan |
+        outputFilename = Shellwords.shellescape((Pathname("..") + (plan.name + ".out")).to_s)
+
+        outp.puts "if test -e #{plan.failFile}"
+        outp.puts "then"
+        outp.puts "    if [ `cat #{plan.failFile}` -eq 3 ]"
+        outp.puts "    then"
+        outp.puts "        if grep -i -q failed! #{outputFilename}"
+        outp.puts "        then"
+        outp.puts "            (echo Detected failures: && cat #{outputFilename}) | " + redirectAndPrefixCommand(plan.name)
+        outp.puts "            " + plan.failCommand
+        outp.puts "        else"
+        outp.puts "            " + plan.successCommand
+        outp.puts "        fi"
+        outp.puts "    else"
+        outp.puts "        (cat #{outputFilename} && echo ERROR: Unexpected exit code: `cat #{plan.failFile}`) | " + redirectAndPrefixCommand(plan.name)
+        outp.puts "        " + plan.failCommand
+        outp.puts "    fi"
+        outp.puts "else"
+        outp.puts "    (cat #{outputFilename} && echo ERROR: Test expected to fail, but returned successfully) | " + redirectAndPrefixCommand(plan.name)
+        outp.puts "    " + plan.failCommand
+        outp.puts "fi"
+    }
+end
+
+# Error handler for tests that report success by saying "Passed" or error by saying "FAILED".
+# This is used by Chakra tests.
+def chakraPassFailErrorHandler
+    Proc.new {
+        | outp, plan |
+        outputFilename = Shellwords.shellescape((Pathname("..") + (plan.name + ".out")).to_s)
+
+        outp.puts "if test -e #{plan.failFile}"
+        outp.puts "then"
+        outp.puts "    (cat #{outputFilename} && echo ERROR: Unexpected exit code: `cat #{plan.failFile}`) | " + redirectAndPrefixCommand(plan.name)
+        outp.puts "    " + plan.failCommand
+        outp.puts "elif grep -i -q FAILED #{outputFilename}"
+        outp.puts "then"
+        outp.puts "    (echo Detected failures: && cat #{outputFilename}) | " + redirectAndPrefixCommand(plan.name)
+        outp.puts "    " + plan.failCommand
+        outp.puts "else"
+        outp.puts "    " + plan.successCommand
+        outp.puts "fi"
+    }
+end
+
+class Plan
+    attr_reader :directory, :arguments, :family, :name, :outputHandler, :errorHandler
+    attr_accessor :index
+    
+    def initialize(directory, arguments, family, name, outputHandler, errorHandler)
+        @directory = directory
+        @arguments = arguments
+        @family = family
+        @name = name
+        @outputHandler = outputHandler
+        @errorHandler = errorHandler
+        @isSlow = !!$runCommandOptions[:isSlow]
+    end
+    
+    def shellCommand
+        # It's important to remember that the test is actually run in a subshell, so if we change directory
+        # in the subshell when we return we will be in our original directory. This is nice because we don't
+        # have to bend over backwards to do things relative to the root.
+        script = "(cd ../#{Shellwords.shellescape(@directory.to_s)} && ("
+        $envVars.each { |var| script += "export " << var << "; " }
+        script += "\"$@\" " + escapeAll(@arguments) + "))"
+        return script
+    end
+    
+    def reproScriptCommand
+        # We have to find our way back to the .runner directory since that's where all of the relative
+        # paths assume they start out from.
+        script = "CURRENT_DIR=\"$( cd \"$( dirname \"${BASH_SOURCE[0]}\" )\" && pwd )\"\n"
+        script += "cd $CURRENT_DIR\n"
+        Pathname.new(@name).dirname.each_filename {
+            | pathComponent |
+            script += "cd ..\n"
+        }
+        script += "cd .runner\n"
+
+        script += "export DYLD_FRAMEWORK_PATH=$(cd #{$testingFrameworkPath.dirname}; pwd)\n"
+        script += "export JSCTEST_timeout=#{Shellwords.shellescape(ENV['JSCTEST_timeout'])}\n"
+        $envVars.each { |var| script += "export " << var << "\n" }
+        script += "#{shellCommand} || exit 1"
+        "echo #{Shellwords.shellescape(script)} > #{Shellwords.shellescape((Pathname.new("..") + @name).to_s)}"
+    end
+    
+    def failCommand
+        "echo FAIL: #{Shellwords.shellescape(@name)} ; touch #{failFile} ; " + reproScriptCommand
+    end
+    
+    def successCommand
+        if $progressMeter or $verbosity >= 2
+            "rm -f #{failFile} ; echo PASS: #{Shellwords.shellescape(@name)}"
+        else
+            "rm -f #{failFile}"
+        end
+    end
+    
+    def failFile
+        "test_fail_#{@index}"
+    end
+    
+    def writeRunScript(filename)
+        File.open(filename, "w") {
+            | outp |
+            outp.puts "echo Running #{Shellwords.shellescape(@name)}"
+            cmd  = "(" + shellCommand + " || (echo $? > #{failFile})) 2>&1 "
+            cmd += @outputHandler.call(@name)
+            if $verbosity >= 3
+                outp.puts "echo #{Shellwords.shellescape(cmd)}"
+            end
+            outp.puts cmd
+            @errorHandler.call(outp, self)
+        }
+    end
+end
+
+def prepareShellTestRunner
+    FileUtils.cp SCRIPTS_PATH + "jsc-stress-test-helpers" + "shell-runner.sh", $runnerDir + "runscript"
+end
+
+def prepareMakeTestRunner
+    # The goals of our parallel test runner are scalability and simplicity. The
+    # simplicity part is particularly important. We don't want to have to have
+    # a full-time contributor just philosophising about parallel testing.
+    #
+    # As such, we just pass off all of the hard work to 'make'. This creates a
+    # dummy directory ("$outputDir/.runner") in which we create a dummy
+    # Makefile. The Makefile has an 'all' rule that depends on all of the tests.
+    # That is, for each test we know we will run, there is a rule in the
+    # Makefile and 'all' depends on it. Running 'make -j <whatever>' on this
+    # Makefile results in 'make' doing all of the hard work:
+    #
+    # - Load balancing just works. Most systems have a great load balancer in
+    #   'make'. If your system doesn't then just install a real 'make'.
+    #
+    # - Interruptions just work. For example Ctrl-C handling in 'make' is
+    #   exactly right. You don't have to worry about zombie processes.
+    #
+    # We then do some tricks to make failure detection work and to make this
+    # totally sound. If a test fails, we don't want the whole 'make' job to
+    # stop. We also don't have any facility for makefile-escaping of path names.
+    # We do have such a thing for shell-escaping, though. We fix both problems
+    # by having the actual work for each of the test rules be done in a shell
+    # script on the side. There is one such script per test. The script responds
+    # to failure by printing something on the console and then touching a
+    # failure file for that test, but then still returns 0. This makes 'make'
+    # continue past that failure and complete all the tests anyway.
+    #
+    # In the end, this script collects all of the failures by searching for
+    # files in the .runner directory whose name matches /^test_fail_/, where
+    # the thing after the 'fail_' is the test index. Those are the files that
+    # would be created by the test scripts if they detect failure. We're
+    # basically using the filesystem as a concurrent database of test failures.
+    # Even if two tests fail at the same time, since they're touching different
+    # files we won't miss any failures.
+    runIndices = []
+    $runlist.each {
+        | plan |
+        runIndices << plan.index
+    }
+    
+    File.open($runnerDir + "Makefile", "w") {
+        | outp |
+        outp.puts("all: " + runIndices.map{|v| "test_done_#{v}"}.join(' '))
+        runIndices.each {
+            | index |
+            plan = $runlist[index]
+            outp.puts "test_done_#{index}:"
+            outp.puts "\tsh test_script_#{plan.index}"
+        }
+    }
+end
+
+def testRunnerCommand
+    case $testRunnerType
+    when :shell
+        command = "sh runscript"
+    when :make
+        command = "make -j #{$numChildProcesses.to_s} -s -f Makefile"
+    else
+        raise "Unknown test runner type: #{$testRunnerType.to_s}"
+    end
+    return command
+end