mirror of
				https://github.com/django/django.git
				synced 2025-10-31 09:41:08 +00:00 
			
		
		
		
	Fixed #32114 -- Fixed parallel test crash on non-picklable objects in subtests.
This commit is contained in:
		
				
					committed by
					
						 Mariusz Felisiak
						Mariusz Felisiak
					
				
			
			
				
	
			
			
			
						parent
						
							a269d8d1d8
						
					
				
				
					commit
					c09e8f5fd8
				
			| @@ -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() | ||||
|   | ||||
| @@ -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): | ||||
|         """ | ||||
|   | ||||
| @@ -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") | ||||
|   | ||||
		Reference in New Issue
	
	Block a user