0
0
mirror of https://github.com/django/django.git synced 2024-11-29 06:03:25 +01:00

Fixed #26942 -- Added support for subtests during parallel testing.

This commit is contained in:
Chris Jerdonek 2016-08-11 03:47:12 -07:00 committed by Tim Graham
parent a02b5848ae
commit 42dcceba61
3 changed files with 105 additions and 4 deletions

View File

@ -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,

View File

@ -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
~~~~

View File

@ -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',)")