summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorPetter Friberg <petter_friberg@hotmail.com>2022-01-04 23:06:46 +0100
committerMariusz Felisiak <felisiak.mariusz@gmail.com>2022-01-06 06:38:17 +0100
commitbc174e6ea0ce676c5a7f467bda9739e6ef6b6186 (patch)
tree68ae81b376e74e74ce3e8736592f02b3f3ada5ec
parent806efe912b846c1fde250c9321d8334b7517cd56 (diff)
Fixed #33410 -- Fixed recursive capturing of callbacks by TestCase.captureOnCommitCallbacks().
Regression in d89f976bddb49fb168334960acc8979c3de991fa.
-rw-r--r--django/test/testcases.py12
-rw-r--r--docs/releases/4.0.2.txt3
-rw-r--r--tests/test_utils/tests.py52
3 files changed, 59 insertions, 8 deletions
diff --git a/django/test/testcases.py b/django/test/testcases.py
index 685cbe2bd4..9759680f0f 100644
--- a/django/test/testcases.py
+++ b/django/test/testcases.py
@@ -1253,18 +1253,16 @@ class TestCase(TransactionTestCase):
try:
yield callbacks
finally:
- callback_count = len(connections[using].run_on_commit)
while True:
- run_on_commit = connections[using].run_on_commit[start_count:]
- callbacks[:] = [func for sids, func in run_on_commit]
- if execute:
- for callback in callbacks:
+ callback_count = len(connections[using].run_on_commit)
+ for _, callback in connections[using].run_on_commit[start_count:]:
+ callbacks.append(callback)
+ if execute:
callback()
if callback_count == len(connections[using].run_on_commit):
break
- start_count = callback_count - 1
- callback_count = len(connections[using].run_on_commit)
+ start_count = callback_count
class CheckCondition:
diff --git a/docs/releases/4.0.2.txt b/docs/releases/4.0.2.txt
index 002e891017..b1f1fb9c76 100644
--- a/docs/releases/4.0.2.txt
+++ b/docs/releases/4.0.2.txt
@@ -9,4 +9,5 @@ Django 4.0.2 fixes several bugs in 4.0.1.
Bugfixes
========
-* ...
+* Fixed a bug in Django 4.0 where ``TestCase.captureOnCommitCallbacks()`` could
+ execute callbacks multiple times (:ticket:`33410`).
diff --git a/tests/test_utils/tests.py b/tests/test_utils/tests.py
index 2addc1fdde..3380a1c67e 100644
--- a/tests/test_utils/tests.py
+++ b/tests/test_utils/tests.py
@@ -1829,6 +1829,58 @@ class CaptureOnCommitCallbacksTests(TestCase):
self.assertEqual(len(callbacks), 2)
self.assertIs(self.callback_called, True)
+ def test_execute_tree(self):
+ """
+ A visualisation of the callback tree tested. Each node is expected to
+ be visited only once:
+
+ └─branch_1
+ ├─branch_2
+ │ ├─leaf_1
+ │ └─leaf_2
+ └─leaf_3
+ """
+ branch_1_call_counter = 0
+ branch_2_call_counter = 0
+ leaf_1_call_counter = 0
+ leaf_2_call_counter = 0
+ leaf_3_call_counter = 0
+
+ def leaf_1():
+ nonlocal leaf_1_call_counter
+ leaf_1_call_counter += 1
+
+ def leaf_2():
+ nonlocal leaf_2_call_counter
+ leaf_2_call_counter += 1
+
+ def leaf_3():
+ nonlocal leaf_3_call_counter
+ leaf_3_call_counter += 1
+
+ def branch_1():
+ nonlocal branch_1_call_counter
+ branch_1_call_counter += 1
+ transaction.on_commit(branch_2)
+ transaction.on_commit(leaf_3)
+
+ def branch_2():
+ nonlocal branch_2_call_counter
+ branch_2_call_counter += 1
+ transaction.on_commit(leaf_1)
+ transaction.on_commit(leaf_2)
+
+ with self.captureOnCommitCallbacks(execute=True) as callbacks:
+ transaction.on_commit(branch_1)
+
+ self.assertEqual(branch_1_call_counter, 1)
+ self.assertEqual(branch_2_call_counter, 1)
+ self.assertEqual(leaf_1_call_counter, 1)
+ self.assertEqual(leaf_2_call_counter, 1)
+ self.assertEqual(leaf_3_call_counter, 1)
+
+ self.assertEqual(callbacks, [branch_1, branch_2, leaf_3, leaf_1, leaf_2])
+
class DisallowedDatabaseQueriesTests(SimpleTestCase):
def test_disallowed_database_connections(self):