Source code for testsuite.plugins.testpoint
import collections.abc
import typing
import pytest
from testsuite import annotations
from testsuite.mockserver import server
from testsuite.utils import callinfo
from testsuite.utils import http
TestpointHandler = typing.Callable[
[annotations.JsonAnyOptional],
annotations.MaybeAsyncResult[annotations.JsonAnyOptional],
]
TestpointDecorator = typing.Callable[
[TestpointHandler],
callinfo.AsyncCallQueue,
]
class TestpointFixture(collections.abc.MutableMapping):
"""Testpoint control object."""
def __init__(self, *, checker_factory) -> None:
self._handlers: typing.Dict[str, callinfo.AsyncCallQueue] = {}
self._checker_factory = checker_factory
def __getitem__(self, name: str) -> callinfo.AsyncCallQueue:
return self._handlers[name]
def __setitem__(self, key: str, value: callinfo.AsyncCallQueue):
self._handlers[key] = value
def __delitem__(self, key):
if isinstance(key, callinfo.AsyncCallQueue):
names = [
name for name, value in self._handlers.items() if value == key
]
if not names:
raise KeyError(f'{key!r}')
for name in names:
del self._handlers[name]
else:
del self._handlers[key]
def __len__(self):
return len(self._handlers)
def __iter__(self):
return iter(self._handlers)
def __call__(self, name: str) -> TestpointDecorator:
"""Returns decorator for registering testpoint called ``name``.
After decoration function is wrapped with `AsyncCallQueue`_.
"""
checker = self._checker_factory(name)
def decorator(func) -> callinfo.AsyncCallQueue:
wrapped = callinfo.acallqueue(func, checker=checker)
self[name] = wrapped
return wrapped
return decorator
@pytest.fixture(scope='session')
def testpoint_checker_factory():
"""Testpoint checker factory fixture.
Can be used to control whether or not testpoint is valid.
Feel free to override, e.g.:
.. code-block::
@pytest.fixture
def testpoint_checker_factory(testpoint_enabled)
def create_checker(name):
def checker(opname):
if testpoint_enabled(name):
return
pytest.fail(
f'{opname}() called on disabled testpoint {name}'
)
return create_checker
"""
def create_checker(name):
return None
return create_checker
[docs]@pytest.fixture
async def testpoint(
mockserver: server.MockserverFixture,
testpoint_checker_factory,
) -> TestpointFixture:
"""Testpoint fixture returns testpoint session instance that works
as decorator that registers testpoint handler. Original function is
wrapped with :ref:`AsyncCallQueue`
:param name: testpoint name
:returns: decorator
.. code-block::
def test_foo(testpoint):
@testpoint('foo'):
def testpoint_handler(data):
pass
...
# testpoint_handler is AsyncCallQueue instance, e.g.:
assert testpoint_handler.has_calls
assert testpoint_handler.next_call == {...}
aseert testpoint_handler.wait_call() == {...}
"""
session = TestpointFixture(checker_factory=testpoint_checker_factory)
@mockserver.json_handler('/testpoint')
async def _handler(request: http.Request):
body = request.json
handler = session.get(body['name'])
if handler is None:
return {'data': None, 'handled': False}
data = await handler(body['data'])
return {'data': data, 'handled': True}
return session