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:
parent
b799a50c8e
commit
073ea9e852
|
@ -4,6 +4,8 @@ import itertools
|
||||||
import logging
|
import logging
|
||||||
import multiprocessing
|
import multiprocessing
|
||||||
import os
|
import os
|
||||||
|
import pickle
|
||||||
|
import textwrap
|
||||||
import unittest
|
import unittest
|
||||||
from importlib import import_module
|
from importlib import import_module
|
||||||
|
|
||||||
|
@ -82,6 +84,55 @@ class RemoteTestResult(object):
|
||||||
def test_index(self):
|
def test_index(self):
|
||||||
return self.testsRun - 1
|
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):
|
def stop_if_failfast(self):
|
||||||
if self.failfast:
|
if self.failfast:
|
||||||
self.stop()
|
self.stop()
|
||||||
|
@ -103,10 +154,12 @@ class RemoteTestResult(object):
|
||||||
self.events.append(('stopTest', self.test_index))
|
self.events.append(('stopTest', self.test_index))
|
||||||
|
|
||||||
def addError(self, test, err):
|
def addError(self, test, err):
|
||||||
|
self.check_pickleable(test, err)
|
||||||
self.events.append(('addError', self.test_index, err))
|
self.events.append(('addError', self.test_index, err))
|
||||||
self.stop_if_failfast()
|
self.stop_if_failfast()
|
||||||
|
|
||||||
def addFailure(self, test, err):
|
def addFailure(self, test, err):
|
||||||
|
self.check_pickleable(test, err)
|
||||||
self.events.append(('addFailure', self.test_index, err))
|
self.events.append(('addFailure', self.test_index, err))
|
||||||
self.stop_if_failfast()
|
self.stop_if_failfast()
|
||||||
|
|
||||||
|
@ -120,6 +173,7 @@ class RemoteTestResult(object):
|
||||||
self.events.append(('addSkip', self.test_index, reason))
|
self.events.append(('addSkip', self.test_index, reason))
|
||||||
|
|
||||||
def addExpectedFailure(self, test, err):
|
def addExpectedFailure(self, test, err):
|
||||||
|
self.check_pickleable(test, err)
|
||||||
self.events.append(('addExpectedFailure', self.test_index, err))
|
self.events.append(('addExpectedFailure', self.test_index, err))
|
||||||
|
|
||||||
def addUnexpectedSuccess(self, test):
|
def addUnexpectedSuccess(self, test):
|
||||||
|
|
|
@ -1285,6 +1285,17 @@ correctly:
|
||||||
|
|
||||||
$ pip install tblib
|
$ 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 ...>
|
testserver <fixture fixture ...>
|
||||||
--------------------------------
|
--------------------------------
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue