mirror of https://github.com/django/django.git
Fixed #27301 -- Prevented exceptions that fail unpickling from crashing the parallel test runner.
This commit is contained in:
parent
9cfd060b1c
commit
52188a5ca6
|
@ -89,6 +89,14 @@ class RemoteTestResult(object):
|
||||||
def test_index(self):
|
def test_index(self):
|
||||||
return self.testsRun - 1
|
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):
|
def _print_unpicklable_subtest(self, test, subtest, pickle_exc):
|
||||||
print("""
|
print("""
|
||||||
Subtest failed:
|
Subtest failed:
|
||||||
|
@ -113,7 +121,7 @@ with a cleaner failure message.
|
||||||
# with the multiprocessing module. Since we're in a forked process,
|
# with the multiprocessing module. Since we're in a forked process,
|
||||||
# our best chance to communicate with them is to print to stdout.
|
# our best chance to communicate with them is to print to stdout.
|
||||||
try:
|
try:
|
||||||
pickle.dumps(err)
|
self._confirm_picklable(err)
|
||||||
except Exception as exc:
|
except Exception as exc:
|
||||||
original_exc_txt = repr(err[1])
|
original_exc_txt = repr(err[1])
|
||||||
original_exc_txt = textwrap.fill(original_exc_txt, 75, initial_indent=' ', subsequent_indent=' ')
|
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):
|
def check_subtest_picklable(self, test, subtest):
|
||||||
try:
|
try:
|
||||||
pickle.dumps(subtest)
|
self._confirm_picklable(subtest)
|
||||||
except Exception as exc:
|
except Exception as exc:
|
||||||
self._print_unpicklable_subtest(test, subtest, exc)
|
self._print_unpicklable_subtest(test, subtest, exc)
|
||||||
raise
|
raise
|
||||||
|
|
|
@ -10,6 +10,15 @@ except ImportError:
|
||||||
tblib = None
|
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):
|
class ParallelTestRunnerTest(SimpleTestCase):
|
||||||
"""
|
"""
|
||||||
End-to-end tests of the parallel test runner.
|
End-to-end tests of the parallel test runner.
|
||||||
|
@ -44,6 +53,19 @@ class SampleFailingSubtest(SimpleTestCase):
|
||||||
|
|
||||||
class RemoteTestResultTest(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')
|
@unittest.skipUnless(six.PY3 and tblib is not None, 'requires tblib to be installed')
|
||||||
def test_add_failing_subtests(self):
|
def test_add_failing_subtests(self):
|
||||||
"""
|
"""
|
||||||
|
|
Loading…
Reference in New Issue