Fixed #32114 -- Fixed parallel test crash on non-picklable objects in subtests.

This commit is contained in:
David Wobrock 2023-12-27 17:02:27 +01:00 committed by Mariusz Felisiak
parent a269d8d1d8
commit c09e8f5fd8
3 changed files with 56 additions and 1 deletions

View File

@ -1,6 +1,7 @@
import difflib
import json
import logging
import pickle
import posixpath
import sys
import threading
@ -92,6 +93,18 @@ def to_list(value):
return value
def is_pickable(obj):
"""
Returns true if the object can be dumped and loaded through the pickle
module.
"""
try:
pickle.loads(pickle.dumps(obj))
except (AttributeError, TypeError):
return False
return True
def assert_and_parse_html(self, html, user_msg, msg):
try:
dom = parse_html(html)
@ -303,6 +316,23 @@ class SimpleTestCase(unittest.TestCase):
"""
self._setup_and_call(result)
def __getstate__(self):
"""
Make SimpleTestCase picklable for parallel tests using subtests.
"""
state = super().__dict__
# _outcome and _subtest cannot be tested on picklability, since they
# contain the TestCase itself, leading to an infinite recursion.
if state["_outcome"]:
pickable_state = {"_outcome": None, "_subtest": None}
for key, value in state.items():
if key in pickable_state or not is_pickable(value):
continue
pickable_state[key] = value
return pickable_state
return state
def debug(self):
"""Perform the same as __call__(), without catching the exception."""
debug_result = _DebugResult()

View File

@ -51,6 +51,13 @@ class SampleFailingSubtest(SimpleTestCase):
with self.subTest(index=i):
self.assertEqual(i, 1)
# This method name doesn't begin with "test" to prevent test discovery
# from seeing it.
def pickle_error_test(self):
with self.subTest("TypeError: cannot pickle memoryview object"):
self.x = memoryview(b"")
self.fail("expected failure")
class RemoteTestResultTest(SimpleTestCase):
def _test_error_exc_info(self):
@ -106,6 +113,16 @@ class RemoteTestResultTest(SimpleTestCase):
with self.assertRaisesMessage(TypeError, msg):
result._confirm_picklable(not_unpicklable_error)
def test_unpicklable_subtest(self):
result = RemoteTestResult()
subtest_test = SampleFailingSubtest(methodName="pickle_error_test")
subtest_test.run(result=result)
events = result.events
subtest_event = events[1]
assertion_error = subtest_event[3]
self.assertEqual(str(assertion_error[1]), "expected failure")
@unittest.skipUnless(tblib is not None, "requires tblib to be installed")
def test_add_failing_subtests(self):
"""

View File

@ -1,12 +1,20 @@
import pickle
from functools import wraps
from django.db import IntegrityError, connections, transaction
from django.test import TestCase, skipUnlessDBFeature
from django.test.testcases import DatabaseOperationForbidden, TestData
from django.test.testcases import DatabaseOperationForbidden, SimpleTestCase, TestData
from .models import Car, Person, PossessedCar
class TestSimpleTestCase(SimpleTestCase):
def test_is_picklable_with_non_picklable_properties(self):
"""ParallelTestSuite requires that all TestCases are picklable."""
self.non_picklable = lambda: 0
self.assertEqual(self, pickle.loads(pickle.dumps(self)))
class TestTestCase(TestCase):
@skipUnlessDBFeature("can_defer_constraint_checks")
@skipUnlessDBFeature("supports_foreign_keys")