#37459 new Uncategorized

Parallel test runner crashes with MaybeEncodingError when a subTest fails in a test case with unpicklable attributes

Reported by: Oscar van Leusen Owned by:
Component: Testing framework Version: 6.1
Severity: Normal Keywords:
Cc: Oscar van Leusen Triage Stage: Unreviewed
Has patch: no Needs documentation: no
Needs tests: no Patch needs improvement: no
Easy pickings: no UI/UX: no

Description

Summary

With --parallel, a failing subTest in a test case that has any unpicklable instance attribute (for example, a MagicMock or a test Client assigned in setUp) aborts the whole run with multiprocessing.pool.MaybeEncodingError: Error sending result, instead of reporting the failure.

RemoteTestResult.check_subtest_picklable() runs while the test is executing. At that point SimpleTestCase.__getstate__ filters out unpicklable attributes, because self._outcome is set. The events are pickled again when the worker sends its results, after the test has finished. By then unittest has reset _outcome to None, so __getstate__ returns the full __dict__ and pickling fails. The check passes on different state from what is actually sent.

Storing mocks or extra clients on self in setUp is common, so any subtest failure in such a test crashes the parallel run. The built-in self.client is unaffected only because _pre_setup() assigns it on the class.

Minimal Reproduction

Reproduced on 6.1.2 and 5.2.18, Python 3.12, with both the spawn and fork start methods. Requires tblib (pip install django tblib)

settings.py:

SECRET_KEY = "x"
DATABASES = {"default": {"ENGINE": "django.db.backends.sqlite3", "NAME": ":memory:"}}

tests.py:

from unittest.mock import MagicMock

from django.test import SimpleTestCase


class SubTestWithMock(SimpleTestCase):
    def setUp(self):
        self.mock = MagicMock()

    def test_failing_subtest(self):
        with self.subTest(i=1):
            self.fail("expected failure")


class Other(SimpleTestCase):  # A second class so the run actually uses workers
    def test_ok(self):
        pass

Run with: DJANGO_SETTINGS_MODULE=settings PYTHONPATH=. python -m django test tests --parallel 2

Expected: FAILED (failures=1).

Actual: the run aborts with

multiprocessing.pool.MaybeEncodingError: Error sending result: ...
Reason: 'PicklingError("Can't pickle <class 'unittest.mock.MagicMock'>: it's not the same object as unittest.mock.MagicMock")'

Workaround

With --parallel 1, or without including an un-pickleable attribute, the failure is reported normally.

Change History (0)

Note: See TracTickets for help on using tickets.
Back to Top