https://github.com/python/cpython/commit/f17281741c623eefe364ae4d7fd643c44bffe208
commit: f17281741c623eefe364ae4d7fd643c44bffe208
branch: 3.14
author: Miss Islington (bot) <[email protected]>
committer: kumaraditya303 <[email protected]>
date: 2025-12-15T18:21:52+05:30
summary:

[3.14] gh-142651: make `Mock.call_count` thread-safe (GH-142656) (#142743)

gh-142651: make `Mock.call_count` thread-safe (GH-142656)
(cherry picked from commit 850f95f6f64a55920cbb91b022b70b736bd20ed8)

Co-authored-by: chaope <[email protected]>

files:
A Misc/NEWS.d/next/Library/2025-12-13-06-17-44.gh-issue-142651.ZRtBu4.rst
M Lib/test/test_unittest/testmock/testthreadingmock.py
M Lib/unittest/mock.py

diff --git a/Lib/test/test_unittest/testmock/testthreadingmock.py 
b/Lib/test/test_unittest/testmock/testthreadingmock.py
index a02b532ed447cd..3603995b090a6c 100644
--- a/Lib/test/test_unittest/testmock/testthreadingmock.py
+++ b/Lib/test/test_unittest/testmock/testthreadingmock.py
@@ -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 @@ def test_reset_mock_resets_wait(self):
         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()
diff --git a/Lib/unittest/mock.py b/Lib/unittest/mock.py
index e370aa48b7c703..feb9730d6e980e 100644
--- a/Lib/unittest/mock.py
+++ b/Lib/unittest/mock.py
@@ -1180,7 +1180,6 @@ def _mock_call(self, /, *args, **kwargs):
 
     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 @@ def _increment_mock_call(self, /, *args, **kwargs):
         _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
index 00000000000000..236900bac5d6f6
--- /dev/null
+++ b/Misc/NEWS.d/next/Library/2025-12-13-06-17-44.gh-issue-142651.ZRtBu4.rst
@@ -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.

_______________________________________________
Python-checkins mailing list -- [email protected]
To unsubscribe send an email to [email protected]
https://mail.python.org/mailman3//lists/python-checkins.python.org
Member address: [email protected]

Reply via email to