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.)
This commit is contained in:
Aymeric Augustin 2015-06-05 16:44:12 +02:00
parent b799a50c8e
commit 073ea9e852
2 changed files with 65 additions and 0 deletions

View File

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

View File

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