mirror of
https://github.com/django/django.git
synced 2025-05-07 23:46:30 +00:00
Fixed #32114 -- Fixed parallel test crash on non-picklable objects in subtests.
This commit is contained in:
parent
a269d8d1d8
commit
c09e8f5fd8
@ -1,6 +1,7 @@
|
|||||||
import difflib
|
import difflib
|
||||||
import json
|
import json
|
||||||
import logging
|
import logging
|
||||||
|
import pickle
|
||||||
import posixpath
|
import posixpath
|
||||||
import sys
|
import sys
|
||||||
import threading
|
import threading
|
||||||
@ -92,6 +93,18 @@ def to_list(value):
|
|||||||
return 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):
|
def assert_and_parse_html(self, html, user_msg, msg):
|
||||||
try:
|
try:
|
||||||
dom = parse_html(html)
|
dom = parse_html(html)
|
||||||
@ -303,6 +316,23 @@ class SimpleTestCase(unittest.TestCase):
|
|||||||
"""
|
"""
|
||||||
self._setup_and_call(result)
|
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):
|
def debug(self):
|
||||||
"""Perform the same as __call__(), without catching the exception."""
|
"""Perform the same as __call__(), without catching the exception."""
|
||||||
debug_result = _DebugResult()
|
debug_result = _DebugResult()
|
||||||
|
@ -51,6 +51,13 @@ class SampleFailingSubtest(SimpleTestCase):
|
|||||||
with self.subTest(index=i):
|
with self.subTest(index=i):
|
||||||
self.assertEqual(i, 1)
|
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):
|
class RemoteTestResultTest(SimpleTestCase):
|
||||||
def _test_error_exc_info(self):
|
def _test_error_exc_info(self):
|
||||||
@ -106,6 +113,16 @@ class RemoteTestResultTest(SimpleTestCase):
|
|||||||
with self.assertRaisesMessage(TypeError, msg):
|
with self.assertRaisesMessage(TypeError, msg):
|
||||||
result._confirm_picklable(not_unpicklable_error)
|
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")
|
@unittest.skipUnless(tblib is not None, "requires tblib to be installed")
|
||||||
def test_add_failing_subtests(self):
|
def test_add_failing_subtests(self):
|
||||||
"""
|
"""
|
||||||
|
@ -1,12 +1,20 @@
|
|||||||
|
import pickle
|
||||||
from functools import wraps
|
from functools import wraps
|
||||||
|
|
||||||
from django.db import IntegrityError, connections, transaction
|
from django.db import IntegrityError, connections, transaction
|
||||||
from django.test import TestCase, skipUnlessDBFeature
|
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
|
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):
|
class TestTestCase(TestCase):
|
||||||
@skipUnlessDBFeature("can_defer_constraint_checks")
|
@skipUnlessDBFeature("can_defer_constraint_checks")
|
||||||
@skipUnlessDBFeature("supports_foreign_keys")
|
@skipUnlessDBFeature("supports_foreign_keys")
|
||||||
|
Loading…
x
Reference in New Issue
Block a user