From 073ea9e8522e7d685864fd9e283bd1fcb9fa5243 Mon Sep 17 00:00:00 2001 From: Aymeric Augustin Date: Fri, 5 Jun 2015 16:44:12 +0200 Subject: [PATCH] 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.) --- django/test/runner.py | 54 +++++++++++++++++++++++++++++++++++++++ docs/ref/django-admin.txt | 11 ++++++++ 2 files changed, 65 insertions(+) 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 --------------------------------