[Zope3-checkins]
SVN: zope.testing/trunk/src/zope/testing/testrunner/
- merge benji-parallelize-subprocesses branch:
Benji York
benji at zope.com
Thu Jul 10 22:29:28 EDT 2008
Log message for revision 88225:
- merge benji-parallelize-subprocesses branch:
- adds -j option that causes subprocesses to be run in parallel (first test
is not run in a subprocess, so isn't parallelized)
- also fixes bug that causes the unit test layer to be skipped if run in a
subprocess
Changed:
U zope.testing/trunk/src/zope/testing/testrunner/filter.py
U zope.testing/trunk/src/zope/testing/testrunner/options.py
U zope.testing/trunk/src/zope/testing/testrunner/runner.py
U zope.testing/trunk/src/zope/testing/testrunner/testrunner-layers.txt
U zope.testing/trunk/src/zope/testing/testrunner/testrunner-profiling-cprofiler.txt
U zope.testing/trunk/src/zope/testing/testrunner/testrunner-profiling.txt
U zope.testing/trunk/src/zope/testing/testrunner/testrunner-test-selection.txt
-=-
Modified: zope.testing/trunk/src/zope/testing/testrunner/filter.py
===================================================================
--- zope.testing/trunk/src/zope/testing/testrunner/filter.py 2008-07-11 02:14:34 UTC (rev 88224)
+++ zope.testing/trunk/src/zope/testing/testrunner/filter.py 2008-07-11 02:29:26 UTC (rev 88225)
@@ -36,7 +36,7 @@
# We start out assuming unit tests should run and look for reasons
# why they shouldn't be run.
should_run = True
- if (not options.non_unit) and not options.resume_layer:
+ if (not options.non_unit):
if options.layer:
should_run = False
for pat in options.layer:
Modified: zope.testing/trunk/src/zope/testing/testrunner/options.py
===================================================================
--- zope.testing/trunk/src/zope/testing/testrunner/options.py 2008-07-11 02:14:34 UTC (rev 88224)
+++ zope.testing/trunk/src/zope/testing/testrunner/options.py 2008-07-11 02:29:26 UTC (rev 88225)
@@ -413,6 +413,13 @@
""")
other.add_option(
+ '-j', action="store", type="int", dest='processes',
+ help="""\
+Use up to given number of parallel processes to execute tests. May decrease
+test run time substantially. Defaults to %default.
+""")
+
+other.add_option(
'--keepbytecode', '-k', action="store_true", dest='keepbytecode',
help="""\
Normally, the test runner scans the test paths and the test
@@ -450,6 +457,7 @@
suite_name='test_suite',
list_tests=False,
slow_test_threshold=10,
+ processes=1,
)
Modified: zope.testing/trunk/src/zope/testing/testrunner/runner.py
===================================================================
--- zope.testing/trunk/src/zope/testing/testrunner/runner.py 2008-07-11 02:14:34 UTC (rev 88224)
+++ zope.testing/trunk/src/zope/testing/testrunner/runner.py 2008-07-11 02:29:26 UTC (rev 88225)
@@ -16,12 +16,12 @@
$Id: __init__.py 86232 2008-05-03 15:09:33Z ctheune $
"""
-import re
import cStringIO
import gc
import glob
import os
import pdb
+import re
import sys
import tempfile
import threading
@@ -205,8 +205,10 @@
"""
setup_layers = {}
layers_to_run = list(self.ordered_layers())
+ should_resume = False
- for layer_name, layer, tests in layers_to_run:
+ while layers_to_run:
+ layer_name, layer, tests = layers_to_run[0]
for feature in self.features:
feature.layer_setup(layer)
try:
@@ -216,12 +218,21 @@
self.failed = True
return
except CanNotTearDown:
- setup_layers = None
if not self.options.resume_layer:
- self.ran += resume_tests(self.options, layer_name, layers_to_run,
- self.failures, self.errors)
+ should_resume = True
break
+ layers_to_run.pop(0)
+ if self.options.processes > 1:
+ should_resume = True
+ break
+
+ if should_resume:
+ setup_layers = None
+ if layers_to_run:
+ self.ran += resume_tests(self.options, self.features,
+ layers_to_run, self.failures, self.errors)
+
if setup_layers:
if self.options.resume_layer == None:
self.options.output.info("Tearing down left over layers:")
@@ -358,18 +369,13 @@
def runTest(self):
"Layer set up failure."
-def resume_tests(options, layer_name, layers, failures, errors):
- output = options.output
- layers = [l for (l, _, _) in layers]
- layers = layers[layers.index(layer_name):]
- rantotal = 0
- resume_number = 0
- for layer_name in layers:
+def spawn_layer_in_subprocess(result, options, features, layer_name, layer,
+ failures, errors, resume_number):
+ try:
args = [sys.executable,
sys.argv[0],
'--resume-layer', layer_name, str(resume_number),
]
- resume_number += 1
for d in options.testrunner_defaults:
args.extend(['--default', d])
@@ -386,19 +392,22 @@
for a in args[1:]
])
+ for feature in features:
+ feature.layer_setup(layer)
+
subin, subout, suberr = os.popen3(args)
while True:
try:
- for l in subout:
- sys.stdout.write(l)
+ for line in subout:
+ result.stdout.append(line)
except IOError, e:
if e.errno == errno.EINTR:
- # If the subprocess dies before we finish reading its
- # output, a SIGCHLD signal can interrupt the reading.
- # The correct thing to to in that case is to retry.
+ # If the reading the subprocess input is interruped (as
+ # be caused by recieving SIGCHLD), then retry.
continue
- output.error("Error reading subprocess output for %s" % layer_name)
- output.info(str(e))
+ options.output.error(
+ "Error reading subprocess output for %s" % layer_name)
+ options.output.info(str(e))
else:
break
@@ -413,7 +422,7 @@
'No subprocess summary found', repr(whole_suberr))
try:
- ran, nfail, nerr = map(int, line.strip().split())
+ result.num_ran, nfail, nerr = map(int, line.strip().split())
break
except KeyboardInterrupt:
raise
@@ -427,11 +436,60 @@
nerr -= 1
errors.append((suberr.readline().strip(), None))
- rantotal += ran
+ finally:
+ result.done = True
- return rantotal
+class SubprocessResult(object):
+ def __init__(self):
+ self.num_ran = 0
+ self.stdout = []
+ self.done = False
+
+def resume_tests(options, features, layers, failures, errors):
+ results = []
+ resume_number = int(options.processes > 1)
+ ready_threads = []
+ for layer_name, layer, tests in layers:
+ result = SubprocessResult()
+ results.append(result)
+ ready_threads.append(threading.Thread(
+ target=spawn_layer_in_subprocess,
+ args=(result, options, features, layer_name, layer, failures,
+ errors, resume_number)))
+ resume_number += 1
+
+ # Now start a few threads at a time.
+ running_threads = []
+ results_iter = iter(results)
+ current_result = results_iter.next()
+ while ready_threads or running_threads:
+ while len(running_threads) < options.processes and ready_threads:
+ thread = ready_threads.pop(0)
+ thread.start()
+ running_threads.append(thread)
+
+ for index, thread in reversed(list(enumerate(running_threads))):
+ if not thread.isAlive():
+ del running_threads[index]
+
+ # We want to display results in the order they would have been
+ # displayed, had the work not been done in parallel.
+ while current_result and current_result.done:
+ map(sys.stdout.write, current_result.stdout)
+
+ try:
+ current_result = results_iter.next()
+ except StopIteration:
+ current_result = None
+
+ time.sleep(0.01) # Keep the loop from being too tight.
+
+ # Return the total number of tests run.
+ return sum(r.num_ran for r in results)
+
+
def tear_down_unneeded(options, needed, setup_layers, optional=False):
# Tear down any layers not needed for these tests. The unneeded
# layers might interfere.
Modified: zope.testing/trunk/src/zope/testing/testrunner/testrunner-layers.txt
===================================================================
--- zope.testing/trunk/src/zope/testing/testrunner/testrunner-layers.txt 2008-07-11 02:14:34 UTC (rev 88224)
+++ zope.testing/trunk/src/zope/testing/testrunner/testrunner-layers.txt 2008-07-11 02:29:26 UTC (rev 88225)
@@ -124,3 +124,52 @@
Total: 405 tests, 0 failures, 0 errors in N.NNN seconds.
False
+It is possible to force the layers to run in subprocesses and parallelize them.
+
+ >>> sys.argv = [testrunner_script, '-j2']
+ >>> testrunner.run(defaults)
+ Running samplelayers.Layer1 tests:
+ Set up samplelayers.Layer1 in N.NNN seconds.
+ Ran 9 tests with 0 failures and 0 errors in N.NNN seconds.
+ Running samplelayers.Layer11 tests:
+ Running in a subprocess.
+ Set up samplelayers.Layer1 in N.NNN seconds.
+ Set up samplelayers.Layer11 in N.NNN seconds.
+ Ran 34 tests with 0 failures and 0 errors in N.NNN seconds.
+ Running samplelayers.Layer111 tests:
+ Running in a subprocess.
+ Set up samplelayers.Layerx in N.NNN seconds.
+ Set up samplelayers.Layer1 in N.NNN seconds.
+ Set up samplelayers.Layer11 in N.NNN seconds.
+ Set up samplelayers.Layer111 in N.NNN seconds.
+ Ran 34 tests with 0 failures and 0 errors in N.NNN seconds.
+ Running samplelayers.Layer112 tests:
+ Running in a subprocess.
+ Set up samplelayers.Layerx in N.NNN seconds.
+ Set up samplelayers.Layer1 in N.NNN seconds.
+ Set up samplelayers.Layer11 in N.NNN seconds.
+ Set up samplelayers.Layer112 in N.NNN seconds.
+ Ran 34 tests with 0 failures and 0 errors in N.NNN seconds.
+ Running samplelayers.Layer12 tests:
+ Running in a subprocess.
+ Set up samplelayers.Layer1 in N.NNN seconds.
+ Set up samplelayers.Layer12 in N.NNN seconds.
+ Ran 34 tests with 0 failures and 0 errors in N.NNN seconds.
+ Running samplelayers.Layer121 tests:
+ Running in a subprocess.
+ Set up samplelayers.Layer1 in N.NNN seconds.
+ Set up samplelayers.Layer12 in N.NNN seconds.
+ Set up samplelayers.Layer121 in N.NNN seconds.
+ Ran 34 tests with 0 failures and 0 errors in N.NNN seconds.
+ Running samplelayers.Layer122 tests:
+ Running in a subprocess.
+ Set up samplelayers.Layer1 in N.NNN seconds.
+ Set up samplelayers.Layer12 in N.NNN seconds.
+ Set up samplelayers.Layer122 in N.NNN seconds.
+ Ran 34 tests with 0 failures and 0 errors in N.NNN seconds.
+ Running zope.testing.testrunner.layer.UnitTests tests:
+ Running in a subprocess.
+ Set up zope.testing.testrunner.layer.UnitTests in N.NNN seconds.
+ Ran 192 tests with 0 failures and 0 errors in N.NNN seconds.
+ Total: 405 tests, 0 failures, 0 errors in N.NNN seconds.
+ False
Modified: zope.testing/trunk/src/zope/testing/testrunner/testrunner-profiling-cprofiler.txt
===================================================================
--- zope.testing/trunk/src/zope/testing/testrunner/testrunner-profiling-cprofiler.txt 2008-07-11 02:14:34 UTC (rev 88224)
+++ zope.testing/trunk/src/zope/testing/testrunner/testrunner-profiling-cprofiler.txt 2008-07-11 02:29:26 UTC (rev 88225)
@@ -26,7 +26,7 @@
Profiling also works across layers::
- >>> sys.argv = [testrunner_script, '-ssample2', '--profile=cProfile',
+ >>> sys.argv = [testrunner_script, '-ssample2', '--profile=cProfile',
... '--tests-pattern', 'sampletests_ntd']
>>> testrunner.run(defaults)
Running...
Modified: zope.testing/trunk/src/zope/testing/testrunner/testrunner-profiling.txt
===================================================================
--- zope.testing/trunk/src/zope/testing/testrunner/testrunner-profiling.txt 2008-07-11 02:14:34 UTC (rev 88224)
+++ zope.testing/trunk/src/zope/testing/testrunner/testrunner-profiling.txt 2008-07-11 02:29:26 UTC (rev 88225)
@@ -32,7 +32,7 @@
Profiling also works across layers.
- >>> sys.argv = [testrunner_script, '-ssample2', '--profile=hotshot',
+ >>> sys.argv = [testrunner_script, '-ssample2', '--profile=hotshot',
... '--tests-pattern', 'sampletests_ntd']
>>> testrunner.run(defaults)
Running...
Modified: zope.testing/trunk/src/zope/testing/testrunner/testrunner-test-selection.txt
===================================================================
--- zope.testing/trunk/src/zope/testing/testrunner/testrunner-test-selection.txt 2008-07-11 02:14:34 UTC (rev 88224)
+++ zope.testing/trunk/src/zope/testing/testrunner/testrunner-test-selection.txt 2008-07-11 02:29:26 UTC (rev 88225)
@@ -43,7 +43,7 @@
You can specify multiple packages:
>>> sys.argv = 'test -u -vv -ssample1 -ssample2'.split()
- >>> testrunner.run(defaults)
+ >>> testrunner.run(defaults)
Running tests at level 1
Running zope.testing.testrunner.layer.UnitTests tests:
Set up zope.testing.testrunner.layer.UnitTests in N.NNN seconds.
@@ -274,7 +274,7 @@
match the regular expression are selected:
>>> sys.argv = 'test -u -vv -ssample1 -m!sample1[.]sample1'.split()
- >>> testrunner.run(defaults)
+ >>> testrunner.run(defaults)
Running tests at level 1
Running zope.testing.testrunner.layer.UnitTests tests:
Set up zope.testing.testrunner.layer.UnitTests in N.NNN seconds.
@@ -328,7 +328,7 @@
>>> sys.argv = 'test -u -vv -ssample1 !sample1[.]sample1'.split()
- >>> testrunner.run(defaults)
+ >>> testrunner.run(defaults)
Running tests at level 1
Running zope.testing.testrunner.layer.UnitTests tests:
Set up zope.testing.testrunner.layer.UnitTests in N.NNN seconds.
More information about the Zope3-Checkins
mailing list