Fixed #26942 -- Added support for subtests during parallel testing.
This commit is contained in:
parent
a02b5848ae
commit
42dcceba61
|
@ -77,6 +77,9 @@ class RemoteTestResult(object):
|
|||
"""
|
||||
|
||||
def __init__(self):
|
||||
if tblib is not None:
|
||||
tblib.pickling_support.install()
|
||||
|
||||
self.events = []
|
||||
self.failfast = False
|
||||
self.shouldStop = False
|
||||
|
@ -86,6 +89,22 @@ class RemoteTestResult(object):
|
|||
def test_index(self):
|
||||
return self.testsRun - 1
|
||||
|
||||
def _print_unpicklable_subtest(self, test, subtest, pickle_exc):
|
||||
print("""
|
||||
Subtest failed:
|
||||
|
||||
test: {}
|
||||
subtest: {}
|
||||
|
||||
Unfortunately, the subtest that failed cannot be pickled, so the parallel
|
||||
test runner cannot handle it cleanly. Here is the pickling error:
|
||||
|
||||
> {}
|
||||
|
||||
You should re-run this test with --parallel=1 to reproduce the failure
|
||||
with a cleaner failure message.
|
||||
""".format(test, subtest, pickle_exc))
|
||||
|
||||
def check_picklable(self, test, err):
|
||||
# Ensure that sys.exc_info() tuples are picklable. This displays a
|
||||
# clear multiprocessing.pool.RemoteTraceback generated in the child
|
||||
|
@ -133,6 +152,13 @@ failure and get a correct traceback.
|
|||
""".format(test, original_exc_txt, pickle_exc_txt))
|
||||
raise
|
||||
|
||||
def check_subtest_picklable(self, test, subtest):
|
||||
try:
|
||||
pickle.dumps(subtest)
|
||||
except Exception as exc:
|
||||
self._print_unpicklable_subtest(test, subtest, exc)
|
||||
raise
|
||||
|
||||
def stop_if_failfast(self):
|
||||
if self.failfast:
|
||||
self.stop()
|
||||
|
@ -164,7 +190,15 @@ failure and get a correct traceback.
|
|||
self.stop_if_failfast()
|
||||
|
||||
def addSubTest(self, test, subtest, err):
|
||||
raise NotImplementedError("subtests aren't supported at this time")
|
||||
# Follow Python 3.5's implementation of unittest.TestResult.addSubTest()
|
||||
# by not doing anything when a subtest is successful.
|
||||
if err is not None:
|
||||
# Call check_picklable() before check_subtest_picklable() since
|
||||
# check_picklable() performs the tblib check.
|
||||
self.check_picklable(test, err)
|
||||
self.check_subtest_picklable(test, subtest)
|
||||
self.events.append(('addSubTest', self.test_index, subtest, err))
|
||||
self.stop_if_failfast()
|
||||
|
||||
def addSuccess(self, test):
|
||||
self.events.append(('addSuccess', self.test_index))
|
||||
|
@ -307,9 +341,6 @@ class ParallelTestSuite(unittest.TestSuite):
|
|||
Even with tblib, errors may still occur for dynamically created
|
||||
exception classes such Model.DoesNotExist which cannot be unpickled.
|
||||
"""
|
||||
if tblib is not None:
|
||||
tblib.pickling_support.install()
|
||||
|
||||
counter = multiprocessing.Value(ctypes.c_int, 0)
|
||||
pool = multiprocessing.Pool(
|
||||
processes=self.processes,
|
||||
|
|
|
@ -313,6 +313,9 @@ Tests
|
|||
``django.test.runner``) and :func:`~django.test.utils.teardown_databases`
|
||||
functions make it easier to build custom test runners.
|
||||
|
||||
* Added support for :meth:`python:unittest.TestCase.subTest`’s when using the
|
||||
:option:`test --parallel` option.
|
||||
|
||||
URLs
|
||||
~~~~
|
||||
|
||||
|
|
|
@ -0,0 +1,67 @@
|
|||
import unittest
|
||||
|
||||
from django.test import SimpleTestCase
|
||||
from django.test.runner import RemoteTestResult
|
||||
from django.utils import six
|
||||
|
||||
try:
|
||||
import tblib
|
||||
except ImportError:
|
||||
tblib = None
|
||||
|
||||
|
||||
class ParallelTestRunnerTest(SimpleTestCase):
|
||||
"""
|
||||
End-to-end tests of the parallel test runner.
|
||||
|
||||
These tests are only meaningful when running tests in parallel using
|
||||
the --parallel option, though it doesn't hurt to run them not in
|
||||
parallel.
|
||||
"""
|
||||
|
||||
@unittest.skipUnless(six.PY3, 'subtests were added in Python 3.4')
|
||||
def test_subtest(self):
|
||||
"""
|
||||
Check that passing subtests work.
|
||||
"""
|
||||
for i in range(2):
|
||||
with self.subTest(index=i):
|
||||
self.assertEqual(i, i)
|
||||
|
||||
|
||||
class SampleFailingSubtest(SimpleTestCase):
|
||||
|
||||
# This method name doesn't begin with "test" to prevent test discovery
|
||||
# from seeing it.
|
||||
def dummy_test(self):
|
||||
"""
|
||||
A dummy test for testing subTest failures.
|
||||
"""
|
||||
for i in range(3):
|
||||
with self.subTest(index=i):
|
||||
self.assertEqual(i, 1)
|
||||
|
||||
|
||||
class RemoteTestResultTest(SimpleTestCase):
|
||||
|
||||
@unittest.skipUnless(six.PY3 and tblib is not None, 'requires tblib to be installed')
|
||||
def test_add_failing_subtests(self):
|
||||
"""
|
||||
Failing subtests are added correctly using addSubTest().
|
||||
"""
|
||||
# Manually run a test with failing subtests to prevent the failures
|
||||
# from affecting the actual test run.
|
||||
result = RemoteTestResult()
|
||||
subtest_test = SampleFailingSubtest(methodName='dummy_test')
|
||||
subtest_test.run(result=result)
|
||||
|
||||
events = result.events
|
||||
self.assertEqual(len(events), 4)
|
||||
|
||||
event = events[1]
|
||||
self.assertEqual(event[0], 'addSubTest')
|
||||
self.assertEqual(str(event[2]), 'dummy_test (test_runner.test_parallel.SampleFailingSubtest) (index=0)')
|
||||
self.assertEqual(repr(event[3][1]), "AssertionError('0 != 1',)")
|
||||
|
||||
event = events[2]
|
||||
self.assertEqual(repr(event[3][1]), "AssertionError('2 != 1',)")
|
Loading…
Reference in New Issue