]> git.ipfire.org Git - thirdparty/Python/cpython.git/commitdiff
gh-142651: make `Mock.call_count` thread-safe (#142656)
authorchaope <pengchaoandy@gmail.com>
Mon, 15 Dec 2025 11:43:15 +0000 (06:43 -0500)
committerGitHub <noreply@github.com>
Mon, 15 Dec 2025 11:43:15 +0000 (17:13 +0530)
Lib/test/test_unittest/testmock/testthreadingmock.py
Lib/unittest/mock.py
Misc/NEWS.d/next/Library/2025-12-13-06-17-44.gh-issue-142651.ZRtBu4.rst [new file with mode: 0644]

index a02b532ed447cd572c4d5648d609817b7e603fe6..3603995b090a6c9d48977e101352f35d4b0b48fa 100644 (file)
@@ -1,8 +1,10 @@
+import sys
 import time
 import unittest
+import threading
 import concurrent.futures
 
-from test.support import threading_helper
+from test.support import setswitchinterval, threading_helper
 from unittest.mock import patch, ThreadingMock
 
 
@@ -196,6 +198,26 @@ class TestThreadingMock(unittest.TestCase):
         m.wait_until_any_call_with()
         m.assert_called_once()
 
+    def test_call_count_thread_safe(self):
+        # See https://github.com/python/cpython/issues/142651.
+        m = ThreadingMock()
+        LOOPS = 100
+        THREADS = 10
+        def test_function():
+            for _ in range(LOOPS):
+                m()
+
+        oldswitchinterval = sys.getswitchinterval()
+        setswitchinterval(1e-6)
+        try:
+            threads = [threading.Thread(target=test_function) for _ in range(THREADS)]
+            with threading_helper.start_threads(threads):
+                pass
+        finally:
+            sys.setswitchinterval(oldswitchinterval)
+
+        self.assertEqual(m.call_count, LOOPS * THREADS)
+
 
 if __name__ == "__main__":
     unittest.main()
index 0bb6750655380de6b27745bc372ff109628ab52e..34fd49bf56fbb6a1e333beec3348add1aa03d857 100644 (file)
@@ -1180,7 +1180,6 @@ class CallableMixin(Base):
 
     def _increment_mock_call(self, /, *args, **kwargs):
         self.called = True
-        self.call_count += 1
 
         # handle call_args
         # needs to be set here so assertions on call arguments pass before
@@ -1188,6 +1187,7 @@ class CallableMixin(Base):
         _call = _Call((args, kwargs), two=True)
         self.call_args = _call
         self.call_args_list.append(_call)
+        self.call_count = len(self.call_args_list)
 
         # initial stuff for method_calls:
         do_method_calls = self._mock_parent is not None
diff --git a/Misc/NEWS.d/next/Library/2025-12-13-06-17-44.gh-issue-142651.ZRtBu4.rst b/Misc/NEWS.d/next/Library/2025-12-13-06-17-44.gh-issue-142651.ZRtBu4.rst
new file mode 100644 (file)
index 0000000..236900b
--- /dev/null
@@ -0,0 +1,3 @@
+:mod:`unittest.mock`: fix a thread safety issue where :attr:`Mock.call_count
+<unittest.mock.Mock.call_count>` may return inaccurate values when the mock
+is called concurrently from multiple threads.