From 64420ea54f169cc9078ae1d9673a35b88a7bd67d Mon Sep 17 00:00:00 2001 From: SaJH Date: Tue, 26 Aug 2025 02:58:44 +0900 Subject: [PATCH] Fixed #35548 -- Fixed data leakage when setUpTestData() fails without transaction support. Signed-off-by: SaJH --- django/test/testcases.py | 7 ++++++- tests/test_utils/test_testcase.py | 33 +++++++++++++++++++++++++++++++ 2 files changed, 39 insertions(+), 1 deletion(-) diff --git a/django/test/testcases.py b/django/test/testcases.py index 5f0c819815..cd6480ed6d 100644 --- a/django/test/testcases.py +++ b/django/test/testcases.py @@ -7,7 +7,7 @@ import sys import threading import unittest from collections import Counter -from contextlib import contextmanager +from contextlib import contextmanager, suppress from copy import copy, deepcopy from difflib import get_close_matches from functools import wraps @@ -1153,6 +1153,11 @@ class TransactionTestCase(SimpleTestCase): try: cls._fixture_setup() except Exception: + # Attempt to teardown fixtures on exception during setup as + # _post_teardown won't be triggered to cleanup state when an + # an exception is surfaced to SimpleTestCase._pre_setup. + with suppress(Exception): + cls("setUp")._fixture_teardown() if cls.available_apps is not None: apps.unset_available_apps() setting_changed.send( diff --git a/tests/test_utils/test_testcase.py b/tests/test_utils/test_testcase.py index 866e0dccc6..fcbd8c6da6 100644 --- a/tests/test_utils/test_testcase.py +++ b/tests/test_utils/test_testcase.py @@ -1,5 +1,6 @@ import pickle from functools import wraps +from unittest.mock import patch from django.db import IntegrityError, connections, transaction from django.test import TestCase, skipUnlessDBFeature @@ -168,3 +169,35 @@ class SetupTestDataIsolationTests(TestCase): self.assertEqual(self.car.name, "Volkswagen Beetle") self.car.name = "Volkswagen Coccinelle" self.car.save() + + +class SetupTestDataExceptionTest(TestCase): + def test_cleanup_on_setup_exception_without_transactions(self): + with patch.object( + connections["default"].features, "supports_transactions", False + ): + + class FailingSetupTestCase(TestCase): + @classmethod + def setUpTestData(cls): + Car.objects.create(name="Should be cleaned up") + raise ValueError("Simulated exception in setUpTestData") + + def test_dummy(self): + pass + + test_instance = FailingSetupTestCase("test_dummy") + + initial_count = Car.objects.count() + + with self.assertRaises(ValueError): + test_instance._pre_setup() + + final_count = Car.objects.count() + + self.assertEqual( + initial_count, + final_count, + "Data was not cleaned up after setUpTestData exception. " + f"Cars before: {initial_count}, after: {final_count}", + )