https://github.com/python/cpython/commit/8ebda57bb17c542b696eb4e60e14ef2399ba1c25 commit: 8ebda57bb17c542b696eb4e60e14ef2399ba1c25 branch: 3.13 author: Miss Islington (bot) <[email protected]> committer: kumaraditya303 <[email protected]> date: 2025-12-15T18:22:11+05:30 summary:
[3.13] gh-142651: make `Mock.call_count` thread-safe (GH-142656) (#142744) 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 6cec61ff35ccd8..1dd7735d150ebe 100644 --- a/Lib/unittest/mock.py +++ b/Lib/unittest/mock.py @@ -1174,7 +1174,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 @@ -1182,6 +1181,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]
