summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorAymeric Augustin <aymeric.augustin@m4x.org>2015-06-05 16:44:12 +0200
committerAymeric Augustin <aymeric.augustin@m4x.org>2015-09-09 23:03:44 +0200
commit073ea9e8522e7d685864fd9e283bd1fcb9fa5243 (patch)
tree098b8223566c0b09aee53aa9ad4b593a710e6a78
parentb799a50c8e4461121b52659970c21a58b4067651 (diff)
Acknoweldeged a limitation of the parallel test runner.
Notably it will fail to report a Model.DoesNotExist exceptions because the class itself isn't pickleable. (Django has specific code to make its instances pickleable.)
-rw-r--r--django/test/runner.py54
-rw-r--r--docs/ref/django-admin.txt11
2 files changed, 65 insertions, 0 deletions
diff --git a/django/test/runner.py b/django/test/runner.py
index cd12d6203f..0d01b9b61d 100644
--- a/django/test/runner.py
+++ b/django/test/runner.py
@@ -4,6 +4,8 @@ import itertools
import logging
import multiprocessing
import os
+import pickle
+import textwrap
import unittest
from importlib import import_module
@@ -82,6 +84,55 @@ class RemoteTestResult(object):
def test_index(self):
return self.testsRun - 1
+ def check_pickleable(self, test, err):
+ # Ensure that sys.exc_info() tuples are picklable. This displays a
+ # clear multiprocessing.pool.RemoteTraceback generated in the child
+ # process instead of a multiprocessing.pool.MaybeEncodingError, making
+ # the root cause easier to figure out for users who aren't familiar
+ # 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)
+ except Exception as exc:
+ original_exc_txt = repr(err[1])
+ original_exc_txt = textwrap.fill(original_exc_txt, 75)
+ original_exc_txt = textwrap.indent(original_exc_txt, ' ')
+ pickle_exc_txt = repr(exc)
+ pickle_exc_txt = textwrap.fill(pickle_exc_txt, 75)
+ pickle_exc_txt = textwrap.indent(pickle_exc_txt, ' ')
+ if tblib is None:
+ print("""
+
+{} failed:
+
+{}
+
+Unfortunately, tracebacks cannot be pickled, making it impossible for the
+parallel test runner to handle this exception cleanly.
+
+In order to see the traceback, you should install tblib:
+
+ pip install tblib
+""".format(test, original_exc_txt))
+ else:
+ print("""
+
+{} failed:
+
+{}
+
+Unfortunately, the exception it raised cannot be pickled, making it impossible
+for the parallel test runner to handle it cleanly.
+
+Here's the error encountered while trying to pickle the exception:
+
+{}
+
+You should re-run this test without the --parallel option to reproduce the
+failure and get a correct traceback.
+""".format(test, original_exc_txt, pickle_exc_txt))
+ raise
+
def stop_if_failfast(self):
if self.failfast:
self.stop()
@@ -103,10 +154,12 @@ class RemoteTestResult(object):
self.events.append(('stopTest', self.test_index))
def addError(self, test, err):
+ self.check_pickleable(test, err)
self.events.append(('addError', self.test_index, err))
self.stop_if_failfast()
def addFailure(self, test, err):
+ self.check_pickleable(test, err)
self.events.append(('addFailure', self.test_index, err))
self.stop_if_failfast()
@@ -120,6 +173,7 @@ class RemoteTestResult(object):
self.events.append(('addSkip', self.test_index, reason))
def addExpectedFailure(self, test, err):
+ self.check_pickleable(test, err)
self.events.append(('addExpectedFailure', self.test_index, err))
def addUnexpectedSuccess(self, test):
diff --git a/docs/ref/django-admin.txt b/docs/ref/django-admin.txt
index a98b60b45f..0c88b5aadb 100644
--- a/docs/ref/django-admin.txt
+++ b/docs/ref/django-admin.txt
@@ -1285,6 +1285,17 @@ correctly:
$ pip install tblib
+.. warning::
+
+ When test parallelization is enabled and a test fails, Django may be
+ unable to display the exception traceback. This can make debugging
+ difficult. If you encounter this problem, run the affected test without
+ parallelization to see the traceback of the failure.
+
+ This is a known limitation. It arises from the need to serialize objects
+ in order to exchange them between processes. See
+ :ref:`python:pickle-picklable` for details.
+
testserver <fixture fixture ...>
--------------------------------