Fixed #27301 -- Prevented exceptions that fail unpickling from crashing the parallel test runner.

This commit is contained in:
Adam Wróbel 2016-10-06 21:54:38 +02:00 committed by Tim Graham
parent 9cfd060b1c
commit 52188a5ca6
2 changed files with 32 additions and 2 deletions

View File

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

View File

@ -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):
"""