summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorChris Jerdonek <chris.jerdonek@gmail.com>2016-08-11 03:47:12 -0700
committerTim Graham <timograham@gmail.com>2016-08-26 14:24:27 -0400
commit42dcceba614081966dfb5f236ae1b6fe78f31683 (patch)
treee928f493f89befd48da83178fa3b1cf6ad86d209
parenta02b5848ae6025ad3ef12cf6646ffd3e82a66067 (diff)
Fixed #26942 -- Added support for subtests during parallel testing.
-rw-r--r--django/test/runner.py39
-rw-r--r--docs/releases/1.11.txt3
-rw-r--r--tests/test_runner/test_parallel.py67
3 files changed, 105 insertions, 4 deletions
diff --git a/django/test/runner.py b/django/test/runner.py
index 02a4c826ac..0610d303f8 100644
--- a/django/test/runner.py
+++ b/django/test/runner.py
@@ -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,
diff --git a/docs/releases/1.11.txt b/docs/releases/1.11.txt
index 498b3ee09f..c8282f70d6 100644
--- a/docs/releases/1.11.txt
+++ b/docs/releases/1.11.txt
@@ -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
~~~~
diff --git a/tests/test_runner/test_parallel.py b/tests/test_runner/test_parallel.py
new file mode 100644
index 0000000000..529fe22092
--- /dev/null
+++ b/tests/test_runner/test_parallel.py
@@ -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',)")