https://github.com/python/cpython/commit/850f95f6f64a55920cbb91b022b70b736bd20ed8
commit: 850f95f6f64a55920cbb91b022b70b736bd20ed8
branch: main
author: chaope <[email protected]>
committer: kumaraditya303 <[email protected]>
date: 2025-12-15T17:13:15+05:30
summary:
gh-142651: make `Mock.call_count` thread-safe (#142656)
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 0bb6750655380d..34fd49bf56fbb6 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]