mirror of
https://github.com/python/cpython.git
synced 2026-04-16 00:31:03 +00:00
299 lines
10 KiB
Python
299 lines
10 KiB
Python
import sys
|
|
import time
|
|
import unittest
|
|
import threading
|
|
import concurrent.futures
|
|
|
|
from test.support import setswitchinterval, threading_helper
|
|
from unittest.mock import patch, ThreadingMock
|
|
|
|
|
|
threading_helper.requires_working_threading(module=True)
|
|
|
|
VERY_SHORT_TIMEOUT = 0.1
|
|
|
|
|
|
class Something:
|
|
def method_1(self):
|
|
pass # pragma: no cover
|
|
|
|
def method_2(self):
|
|
pass # pragma: no cover
|
|
|
|
|
|
class TestThreadingMock(unittest.TestCase):
|
|
def _call_after_delay(self, func, /, *args, **kwargs):
|
|
time.sleep(kwargs.pop("delay"))
|
|
func(*args, **kwargs)
|
|
|
|
def setUp(self):
|
|
self._executor = concurrent.futures.ThreadPoolExecutor(max_workers=5)
|
|
|
|
def tearDown(self):
|
|
self._executor.shutdown()
|
|
|
|
def run_async(self, func, /, *args, delay=0, **kwargs):
|
|
self._executor.submit(
|
|
self._call_after_delay, func, *args, **kwargs, delay=delay
|
|
)
|
|
|
|
def _make_mock(self, *args, **kwargs):
|
|
return ThreadingMock(*args, **kwargs)
|
|
|
|
def test_spec(self):
|
|
waitable_mock = self._make_mock(spec=Something)
|
|
|
|
with patch(f"{__name__}.Something", waitable_mock) as m:
|
|
something = m()
|
|
|
|
self.assertIsInstance(something.method_1, ThreadingMock)
|
|
self.assertIsInstance(something.method_1().method_2(), ThreadingMock)
|
|
|
|
with self.assertRaises(AttributeError):
|
|
m.test
|
|
|
|
def test_side_effect(self):
|
|
waitable_mock = self._make_mock()
|
|
|
|
with patch(f"{__name__}.Something", waitable_mock):
|
|
something = Something()
|
|
something.method_1.side_effect = [1]
|
|
|
|
self.assertEqual(something.method_1(), 1)
|
|
|
|
def test_instance_check(self):
|
|
waitable_mock = self._make_mock()
|
|
|
|
with patch(f"{__name__}.Something", waitable_mock):
|
|
something = Something()
|
|
|
|
self.assertIsInstance(something.method_1, ThreadingMock)
|
|
self.assertIsInstance(something.method_1().method_2(), ThreadingMock)
|
|
|
|
def test_dynamic_child_mocks_are_threading_mocks(self):
|
|
waitable_mock = self._make_mock()
|
|
self.assertIsInstance(waitable_mock.child, ThreadingMock)
|
|
|
|
def test_dynamic_child_mocks_inherit_timeout(self):
|
|
mock1 = self._make_mock()
|
|
self.assertIs(mock1._mock_wait_timeout, None)
|
|
mock2 = self._make_mock(timeout=2)
|
|
self.assertEqual(mock2._mock_wait_timeout, 2)
|
|
mock3 = self._make_mock(timeout=3)
|
|
self.assertEqual(mock3._mock_wait_timeout, 3)
|
|
|
|
self.assertIs(mock1.child._mock_wait_timeout, None)
|
|
self.assertEqual(mock2.child._mock_wait_timeout, 2)
|
|
self.assertEqual(mock3.child._mock_wait_timeout, 3)
|
|
|
|
self.assertEqual(mock2.really().__mul__().complex._mock_wait_timeout, 2)
|
|
|
|
def test_no_name_clash(self):
|
|
waitable_mock = self._make_mock()
|
|
waitable_mock._event = "myevent"
|
|
waitable_mock.event = "myevent"
|
|
waitable_mock.timeout = "mytimeout"
|
|
waitable_mock("works")
|
|
waitable_mock.wait_until_called()
|
|
waitable_mock.wait_until_any_call_with("works")
|
|
|
|
def test_patch(self):
|
|
waitable_mock = self._make_mock(spec=Something)
|
|
|
|
with patch(f"{__name__}.Something", waitable_mock):
|
|
something = Something()
|
|
something.method_1()
|
|
something.method_1.wait_until_called()
|
|
|
|
def test_wait_already_called_success(self):
|
|
waitable_mock = self._make_mock(spec=Something)
|
|
waitable_mock.method_1()
|
|
waitable_mock.method_1.wait_until_called()
|
|
waitable_mock.method_1.wait_until_any_call_with()
|
|
waitable_mock.method_1.assert_called()
|
|
|
|
def test_wait_until_called_success(self):
|
|
waitable_mock = self._make_mock(spec=Something)
|
|
self.run_async(waitable_mock.method_1, delay=VERY_SHORT_TIMEOUT)
|
|
waitable_mock.method_1.wait_until_called()
|
|
|
|
def test_wait_until_called_method_timeout(self):
|
|
waitable_mock = self._make_mock(spec=Something)
|
|
with self.assertRaises(AssertionError):
|
|
waitable_mock.method_1.wait_until_called(timeout=VERY_SHORT_TIMEOUT)
|
|
|
|
def test_wait_until_called_instance_timeout(self):
|
|
waitable_mock = self._make_mock(spec=Something, timeout=VERY_SHORT_TIMEOUT)
|
|
with self.assertRaises(AssertionError):
|
|
waitable_mock.method_1.wait_until_called()
|
|
|
|
def test_wait_until_called_global_timeout(self):
|
|
with patch.object(ThreadingMock, "DEFAULT_TIMEOUT"):
|
|
ThreadingMock.DEFAULT_TIMEOUT = VERY_SHORT_TIMEOUT
|
|
waitable_mock = self._make_mock(spec=Something)
|
|
with self.assertRaises(AssertionError):
|
|
waitable_mock.method_1.wait_until_called()
|
|
|
|
def test_wait_until_any_call_with_success(self):
|
|
waitable_mock = self._make_mock()
|
|
self.run_async(waitable_mock, delay=VERY_SHORT_TIMEOUT)
|
|
waitable_mock.wait_until_any_call_with()
|
|
|
|
def test_wait_until_any_call_with_instance_timeout(self):
|
|
waitable_mock = self._make_mock(timeout=VERY_SHORT_TIMEOUT)
|
|
with self.assertRaises(AssertionError):
|
|
waitable_mock.wait_until_any_call_with()
|
|
|
|
def test_wait_until_any_call_global_timeout(self):
|
|
with patch.object(ThreadingMock, "DEFAULT_TIMEOUT"):
|
|
ThreadingMock.DEFAULT_TIMEOUT = VERY_SHORT_TIMEOUT
|
|
waitable_mock = self._make_mock()
|
|
with self.assertRaises(AssertionError):
|
|
waitable_mock.wait_until_any_call_with()
|
|
|
|
def test_wait_until_any_call_positional(self):
|
|
waitable_mock = self._make_mock(timeout=VERY_SHORT_TIMEOUT)
|
|
waitable_mock.method_1(1, 2, 3)
|
|
waitable_mock.method_1.wait_until_any_call_with(1, 2, 3)
|
|
with self.assertRaises(AssertionError):
|
|
waitable_mock.method_1.wait_until_any_call_with(2, 3, 1)
|
|
with self.assertRaises(AssertionError):
|
|
waitable_mock.method_1.wait_until_any_call_with()
|
|
|
|
def test_wait_until_any_call_kw(self):
|
|
waitable_mock = self._make_mock(timeout=VERY_SHORT_TIMEOUT)
|
|
waitable_mock.method_1(a=1, b=2)
|
|
waitable_mock.method_1.wait_until_any_call_with(a=1, b=2)
|
|
with self.assertRaises(AssertionError):
|
|
waitable_mock.method_1.wait_until_any_call_with(a=2, b=1)
|
|
with self.assertRaises(AssertionError):
|
|
waitable_mock.method_1.wait_until_any_call_with()
|
|
|
|
def test_magic_methods_success(self):
|
|
waitable_mock = self._make_mock()
|
|
str(waitable_mock)
|
|
waitable_mock.__str__.wait_until_called()
|
|
waitable_mock.__str__.assert_called()
|
|
|
|
def test_reset_mock_resets_wait(self):
|
|
m = self._make_mock(timeout=VERY_SHORT_TIMEOUT)
|
|
|
|
with self.assertRaises(AssertionError):
|
|
m.wait_until_called()
|
|
with self.assertRaises(AssertionError):
|
|
m.wait_until_any_call_with()
|
|
m()
|
|
m.wait_until_called()
|
|
m.wait_until_any_call_with()
|
|
m.assert_called_once()
|
|
|
|
m.reset_mock()
|
|
|
|
with self.assertRaises(AssertionError):
|
|
m.wait_until_called()
|
|
with self.assertRaises(AssertionError):
|
|
m.wait_until_any_call_with()
|
|
m()
|
|
m.wait_until_called()
|
|
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)
|
|
|
|
|
|
def test_call_args_thread_safe(self):
|
|
m = ThreadingMock()
|
|
LOOPS = 100
|
|
THREADS = 10
|
|
def test_function(thread_id):
|
|
for i in range(LOOPS):
|
|
m(thread_id, i)
|
|
|
|
oldswitchinterval = sys.getswitchinterval()
|
|
setswitchinterval(1e-6)
|
|
try:
|
|
threads = [
|
|
threading.Thread(target=test_function, args=(thread_id,))
|
|
for thread_id in range(THREADS)
|
|
]
|
|
with threading_helper.start_threads(threads):
|
|
pass
|
|
finally:
|
|
sys.setswitchinterval(oldswitchinterval)
|
|
expected_calls = {
|
|
(thread_id, i)
|
|
for thread_id in range(THREADS)
|
|
for i in range(LOOPS)
|
|
}
|
|
self.assertSetEqual({call.args for call in m.call_args_list}, expected_calls)
|
|
|
|
def test_method_calls_thread_safe(self):
|
|
m = ThreadingMock()
|
|
LOOPS = 100
|
|
THREADS = 10
|
|
def test_function(thread_id):
|
|
for i in range(LOOPS):
|
|
getattr(m, f"method_{thread_id}")(i)
|
|
|
|
oldswitchinterval = sys.getswitchinterval()
|
|
setswitchinterval(1e-6)
|
|
try:
|
|
threads = [
|
|
threading.Thread(target=test_function, args=(thread_id,))
|
|
for thread_id in range(THREADS)
|
|
]
|
|
with threading_helper.start_threads(threads):
|
|
pass
|
|
finally:
|
|
sys.setswitchinterval(oldswitchinterval)
|
|
for thread_id in range(THREADS):
|
|
self.assertEqual(getattr(m, f"method_{thread_id}").call_count, LOOPS)
|
|
self.assertEqual({call.args for call in getattr(m, f"method_{thread_id}").call_args_list},
|
|
{(i,) for i in range(LOOPS)})
|
|
|
|
def test_mock_calls_thread_safe(self):
|
|
m = ThreadingMock()
|
|
LOOPS = 100
|
|
THREADS = 10
|
|
def test_function(thread_id):
|
|
for i in range(LOOPS):
|
|
m(thread_id, i)
|
|
|
|
oldswitchinterval = sys.getswitchinterval()
|
|
setswitchinterval(1e-6)
|
|
try:
|
|
threads = [
|
|
threading.Thread(target=test_function, args=(thread_id,))
|
|
for thread_id in range(THREADS)
|
|
]
|
|
with threading_helper.start_threads(threads):
|
|
pass
|
|
finally:
|
|
sys.setswitchinterval(oldswitchinterval)
|
|
expected_calls = {
|
|
(thread_id, i)
|
|
for thread_id in range(THREADS)
|
|
for i in range(LOOPS)
|
|
}
|
|
self.assertSetEqual({call.args for call in m.mock_calls}, expected_calls)
|
|
|
|
if __name__ == "__main__":
|
|
unittest.main()
|