userver: en/testsuite/plugins/testpoint.py Source File
Loading...
Searching...
No Matches
testpoint.py
2import typing
3
4import pytest
5
6from testsuite import types
7from testsuite.mockserver import server
8from testsuite.utils import callinfo, http
9
10TestpointHandler = typing.Callable[
11 [types.JsonAnyOptional],
12 types.MaybeAsyncResult[types.JsonAnyOptional],
13]
14TestpointDecorator = typing.Callable[
15 [TestpointHandler],
16 callinfo.AsyncCallQueue,
17]
18
19
20class TestpointFixture(collections.abc.MutableMapping):
21 """Testpoint control object."""
22
23 def __init__(self, *, checker_factory) -> None:
24 self._handlers: dict[str, callinfo.AsyncCallQueue] = {}
25 self._checker_factory = checker_factory
26
27 def __getitem__(self, name: str) -> callinfo.AsyncCallQueue:
28 return self._handlers[name]
29
30 def __setitem__(self, key: str, value: callinfo.AsyncCallQueue):
31 self._handlers[key] = value
32
33 def __delitem__(self, key):
34 if isinstance(key, callinfo.AsyncCallQueue):
35 names = [
36 name for name, value in self._handlers.items() if value == key
37 ]
38 if not names:
39 raise KeyError(f'{key!r}')
40 for name in names:
41 del self._handlers[name]
42 else:
43 del self._handlers[key]
44
45 def __len__(self):
46 return len(self._handlers)
47
48 def __iter__(self):
49 return iter(self._handlers)
50
51 def __call__(self, name: str) -> TestpointDecorator:
52 """Returns decorator for registering testpoint called ``name``.
53
54 After decoration function is wrapped with `AsyncCallQueue`_.
55 """
56
57 checker = self._checker_factory(name)
58
59 def decorator(func) -> callinfo.AsyncCallQueue:
60 wrapped = callinfo.acallqueue(func, checker=checker)
61 self[name] = wrapped
62 return wrapped
63
64 return decorator
65
66
67@pytest.fixture(scope='session')
69 """Testpoint checker factory fixture.
70
71 Can be used to control whether or not testpoint is valid.
72 Feel free to override, e.g.:
73
74 @code
75 @pytest.fixture
76 def testpoint_checker_factory(testpoint_enabled)
77 def create_checker(name):
78 def checker(opname):
79 if testpoint_enabled(name):
80 return
81 pytest.fail(
82 f'{opname}() called on disabled testpoint {name}'
83 )
84 return create_checker
85 @endcode
86
87 @ingroup userver_testsuite_fixtures
88 Part of the [yandex-taxi-testsuite](https://github.com/yandex/yandex-taxi-testsuite/blob/develop/testsuite/plugins/testpoint.py#L68)
89 """
90
91 def create_checker(name):
92 return None
93
94 return create_checker
95
96
97@pytest.fixture
98async def testpoint(
99 mockserver: server.MockserverFixture,
100 testpoint_checker_factory,
101) -> TestpointFixture:
102 """Testpoint fixture returns testpoint session instance that works
103 as decorator that registers testpoint handler. Original function is
104 wrapped with @ref AsyncCallQueue
105
106 @param name testpoint name
107 @returns decorator
108
109 @code
110 def test_foo(testpoint):
111 @testpoint('foo'):
112 def testpoint_handler(data):
113 pass
114
115 ...
116 # testpoint_handler is AsyncCallQueue instance, e.g.:
117 assert testpoint_handler.has_calls
118 assert testpoint_handler.next_call == {...}
119 aseert testpoint_handler.wait_call() == {...}
120 @endcode
121
122 @ingroup userver_testsuite_fixtures
123 Part of the [yandex-taxi-testsuite](https://github.com/yandex/yandex-taxi-testsuite/blob/develop/testsuite/plugins/testpoint.py#L95)
124 """
125
126 session = TestpointFixture(checker_factory=testpoint_checker_factory)
127
128 @mockserver.json_handler('/testpoint')
129 async def _handler(request: http.Request):
130 body = request.json
131 handler = session.get(body['name'])
132 if handler is None:
133 return {'data': None, 'handled': False}
134 data = await handler(body['data'])
135 return {'data': data, 'handled': True}
136
137 return session