浏览代码

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

Adam Wróbel 8 年之前
父节点
当前提交
52188a5ca6
共有 2 个文件被更改,包括 32 次插入2 次删除
  1. 10 2
      django/test/runner.py
  2. 22 0
      tests/test_runner/test_parallel.py

+ 10 - 2
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

+ 22 - 0
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):
         """