summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorAdam Wróbel <adam@adamwrobel.com>2016-10-06 21:54:38 +0200
committerTim Graham <timograham@gmail.com>2016-10-06 15:54:38 -0400
commit52188a5ca6bafea0a66f17baacb315d61c7b99cd (patch)
tree1a612c81a9203902faeeecc41a0ff10698f8cda3
parent9cfd060b1c0c3664d77e4ab30a6c351822f0a4d6 (diff)
Fixed #27301 -- Prevented exceptions that fail unpickling from crashing the parallel test runner.
-rw-r--r--django/test/runner.py12
-rw-r--r--tests/test_runner/test_parallel.py22
2 files changed, 32 insertions, 2 deletions
diff --git a/django/test/runner.py b/django/test/runner.py
index 0610d303f8..8b4c608c08 100644
--- a/django/test/runner.py
+++ b/django/test/runner.py
@@ -89,6 +89,14 @@ class RemoteTestResult(object):
def test_index(self):
return self.testsRun - 1
+ def _confirm_picklable(self, obj):
+ """
+ Confirm that obj can be pickled and unpickled as multiprocessing will
+ need to pickle the exception in the child process and unpickle it in
+ the parent process. Let the exception rise, if not.
+ """
+ pickle.loads(pickle.dumps(obj))
+
def _print_unpicklable_subtest(self, test, subtest, pickle_exc):
print("""
Subtest failed:
@@ -113,7 +121,7 @@ with a cleaner failure message.
# with the multiprocessing module. Since we're in a forked process,
# our best chance to communicate with them is to print to stdout.
try:
- pickle.dumps(err)
+ self._confirm_picklable(err)
except Exception as exc:
original_exc_txt = repr(err[1])
original_exc_txt = textwrap.fill(original_exc_txt, 75, initial_indent=' ', subsequent_indent=' ')
@@ -154,7 +162,7 @@ failure and get a correct traceback.
def check_subtest_picklable(self, test, subtest):
try:
- pickle.dumps(subtest)
+ self._confirm_picklable(subtest)
except Exception as exc:
self._print_unpicklable_subtest(test, subtest, exc)
raise
diff --git a/tests/test_runner/test_parallel.py b/tests/test_runner/test_parallel.py
index 529fe22092..8118734994 100644
--- a/tests/test_runner/test_parallel.py
+++ b/tests/test_runner/test_parallel.py
@@ -10,6 +10,15 @@ except ImportError:
tblib = None
+class ExceptionThatFailsUnpickling(Exception):
+ """
+ After pickling, this class fails unpickling with an error about incorrect
+ arguments passed to __init__().
+ """
+ def __init__(self, arg):
+ super(ExceptionThatFailsUnpickling, self).__init__()
+
+
class ParallelTestRunnerTest(SimpleTestCase):
"""
End-to-end tests of the parallel test runner.
@@ -44,6 +53,19 @@ class SampleFailingSubtest(SimpleTestCase):
class RemoteTestResultTest(SimpleTestCase):
+ def test_pickle_errors_detection(self):
+ picklable_error = RuntimeError('This is fine')
+ not_unpicklable_error = ExceptionThatFailsUnpickling('arg')
+
+ result = RemoteTestResult()
+ result._confirm_picklable(picklable_error)
+
+ msg = '__init__() missing 1 required positional argument'
+ if six.PY2:
+ msg = '__init__() takes exactly 2 arguments (1 given)'
+ with self.assertRaisesMessage(TypeError, msg):
+ result._confirm_picklable(not_unpicklable_error)
+
@unittest.skipUnless(six.PY3 and tblib is not None, 'requires tblib to be installed')
def test_add_failing_subtests(self):
"""