Enable parallel test execution in console runner

Adds a `-j`/`--workers-count` parameter to `tools/packaging/test262.py`, defaulting to `[number of cores] - 1`.

Speeds up running the test suite by about ~3x on my 4-core machine, with the SpiderMonkey shell. This could certainly be optimized more by just appending test results to per-thread lists and merging them at the end, but it's better than nothing.
This commit is contained in:
Till Schneidereit 2016-02-08 17:36:15 +01:00
parent 26aeed1428
commit 7ae29d49ae
2 changed files with 78 additions and 15 deletions

View File

@ -40,6 +40,7 @@ Name | Action
-----|------- -----|-------
-h, --help | displays a brief help message -h, --help | displays a brief help message
--command=COMMAND | **required** command which invokes javascript engine to be tested --command=COMMAND | **required** command which invokes javascript engine to be tested
-j, --workers-count | Number of tests to run in parallel (defaults to number of cores - 1)
--tests=TESTS | path to the test suite; default is current directory --tests=TESTS | path to the test suite; default is current directory
--cat | don't execute tests, just print code that would be run --cat | don't execute tests, just print code that would be run
--summary | generate a summary at end of execution --summary | generate a summary at end of execution

View File

@ -18,6 +18,7 @@ import subprocess
import sys import sys
import tempfile import tempfile
import time import time
import threading
import xml.dom.minidom import xml.dom.minidom
import datetime import datetime
import shutil import shutil
@ -54,6 +55,8 @@ EXCLUDE_LIST = [x.getAttribute("id") for x in EXCLUDE_LIST]
def BuildOptions(): def BuildOptions():
result = optparse.OptionParser() result = optparse.OptionParser()
result.add_option("--command", default=None, help="The command-line to run") result.add_option("--command", default=None, help="The command-line to run")
result.add_option("-j", "--workers-count", type=int, default=max(1, GetCPUCount() - 1),
help="Number of tests to run in parallel (default %default)")
result.add_option("--tests", default=path.abspath('.'), result.add_option("--tests", default=path.abspath('.'),
help="Path to the tests") help="Path to the tests")
result.add_option("--cat", default=False, action="store_true", result.add_option("--cat", default=False, action="store_true",
@ -92,6 +95,35 @@ def IsWindows():
p = platform.system() p = platform.system()
return (p == 'Windows') or (p == 'Microsoft') return (p == 'Windows') or (p == 'Microsoft')
def GetCPUCount():
"""
Guess at a reasonable parallelism count to set as the default for the
current machine and run.
"""
# Python 2.6+
try:
import multiprocessing
return multiprocessing.cpu_count()
except (ImportError, NotImplementedError):
pass
# POSIX
try:
res = int(os.sysconf('SC_NPROCESSORS_ONLN'))
if res > 0:
return res
except (AttributeError, ValueError):
pass
# Windows
try:
res = int(os.environ['NUMBER_OF_PROCESSORS'])
if res > 0:
return res
except (KeyError, ValueError):
pass
return 1
class TempFile(object): class TempFile(object):
@ -526,7 +558,7 @@ class TestSuite(object):
print print
result.ReportOutcome(False) result.ReportOutcome(False)
def Run(self, command_template, tests, print_summary, full_summary, logname, junitfile): def Run(self, command_template, tests, print_summary, full_summary, logname, junitfile, workers_count):
if not "{{path}}" in command_template: if not "{{path}}" in command_template:
command_template += " {{path}}" command_template += " {{path}}"
cases = self.EnumerateTests(tests) cases = self.EnumerateTests(tests)
@ -551,16 +583,41 @@ class TestSuite(object):
SkipCaseElement.append(SkipElement) SkipCaseElement.append(SkipElement)
TestSuiteElement.append(SkipCaseElement) TestSuiteElement.append(SkipCaseElement)
if workers_count > 1:
pool_sem = threading.Semaphore(workers_count)
log_lock = threading.Lock()
else:
log_lock = None
for case in cases: for case in cases:
result = case.Run(command_template) def exec_case():
if junitfile: result = case.Run(command_template)
TestCaseElement = result.XmlAssemble(result)
TestSuiteElement.append(TestCaseElement) try:
if case == cases[len(cases)-1]: if workers_count > 1:
xmlj.ElementTree(TestSuitesElement).write(junitfile, "UTF-8") log_lock.acquire()
if logname:
self.WriteLog(result) if junitfile:
progress.HasRun(result) TestCaseElement = result.XmlAssemble(result)
TestSuiteElement.append(TestCaseElement)
if case == cases[len(cases)-1]:
xmlj.ElementTree(TestSuitesElement).write(junitfile, "UTF-8")
if logname:
self.WriteLog(result)
finally:
if workers_count > 1:
log_lock.release()
progress.HasRun(result)
if workers_count == 1:
exec_case()
else:
pool_sem.acquire()
threading.Thread(target=exec_case).start()
pool_sem.release()
if workers_count > 1:
log_lock.acquire()
if print_summary: if print_summary:
self.PrintSummary(progress, logname) self.PrintSummary(progress, logname)
@ -570,6 +627,10 @@ class TestSuite(object):
print print
print "Use --full-summary to see output from failed tests" print "Use --full-summary to see output from failed tests"
print print
if workers_count > 1:
log_lock.release()
return progress.failed return progress.failed
def WriteLog(self, result): def WriteLog(self, result):
@ -634,7 +695,8 @@ def Main():
options.summary or options.full_summary, options.summary or options.full_summary,
options.full_summary, options.full_summary,
options.logname, options.logname,
options.junitname) options.junitname,
options.workers_count)
return code return code
if __name__ == '__main__': if __name__ == '__main__':