Skip to content

Commit

Permalink
Fixed #27301 -- Prevented exceptions that fail unpickling from crashi…
Browse files Browse the repository at this point in the history
…ng the parallel test runner.
  • Loading branch information
amw authored and timgraham committed Oct 6, 2016
1 parent 9cfd060 commit 52188a5
Show file tree
Hide file tree
Showing 2 changed files with 32 additions and 2 deletions.
12 changes: 10 additions & 2 deletions django/test/runner.py
Expand Up @@ -89,6 +89,14 @@ def __init__(self):
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:
Expand All @@ -113,7 +121,7 @@ def check_picklable(self, test, err):
# 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=' ')
Expand Down Expand Up @@ -154,7 +162,7 @@ def check_picklable(self, test, err):

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
Expand Down
22 changes: 22 additions & 0 deletions tests/test_runner/test_parallel.py
Expand Up @@ -10,6 +10,15 @@
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.
Expand Down Expand Up @@ -44,6 +53,19 @@ def dummy_test(self):

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):
"""
Expand Down

0 comments on commit 52188a5

Please sign in to comment.