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

View File

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