Skip to content

Commit

Permalink
mockserver: better asyncexc handling (#108)
Browse files Browse the repository at this point in the history
  • Loading branch information
vitek authored Oct 18, 2024
1 parent 7af5bd1 commit 1375933
Show file tree
Hide file tree
Showing 13 changed files with 165 additions and 133 deletions.
14 changes: 14 additions & 0 deletions tests/plugins/mockserver/conftest.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
import pytest


@pytest.fixture
def mockserver_errors_pop(_asyncexc):
def pop():
return _asyncexc.pop()

return pop


@pytest.fixture
def mockserver_errors_list(_asyncexc):
return _asyncexc
5 changes: 3 additions & 2 deletions tests/plugins/mockserver/test_invalid_request.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
async def test_mockserver_raises_on_get_with_content(
mockserver,
mockserver_client,
mockserver_errors_pop,
chunked: bool,
):
@mockserver.handler('/foo')
Expand All @@ -19,6 +20,6 @@ def _foo_handler(request):
chunked=chunked,
)
assert response.status_code == 500
# pylint: disable=protected-access
error = mockserver._session._errors.pop()

error = mockserver_errors_pop()
assert isinstance(error, http.InvalidRequestError)
14 changes: 8 additions & 6 deletions tests/plugins/mockserver/test_mockserver.py
Original file line number Diff line number Diff line change
Expand Up @@ -81,6 +81,8 @@ def _foo_handler(request):
async def test_user_error(
mockserver: fixture_types.MockserverFixture,
mockserver_client: Client,
mockserver_errors_list,
mockserver_errors_pop,
):
@mockserver.json_handler('/foo')
def _foo_handler(request):
Expand All @@ -89,25 +91,25 @@ def _foo_handler(request):
response = await mockserver_client.get('/foo')
assert response.status == 500

session = mockserver._session
assert len(session._errors) == 1
assert len(mockserver_errors_list) == 1

error = session._errors.pop()
error = mockserver_errors_pop()
assert isinstance(error, UserError)


async def test_nohandler(
mockserver: fixture_types.MockserverFixture,
mockserver_client: Client,
mockserver_errors_list,
mockserver_errors_pop,
):
response = await mockserver_client.get(
'/foo123',
headers={mockserver.trace_id_header: mockserver.trace_id},
)
assert response.status == 500

session = mockserver._session
assert len(session._errors) == 1
assert len(mockserver_errors_list) == 1

error = session._errors.pop()
error = mockserver_errors_pop()
assert isinstance(error, exceptions.HandlerNotFoundError)
30 changes: 22 additions & 8 deletions tests/plugins/mockserver/test_session.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,16 @@
from testsuite.mockserver import server


def test_session():
session = server.Session()
@pytest.fixture
def create_session(asyncexc_append):
def create_session():
return server.Session(asyncexc_append=asyncexc_append)

return create_session


def test_session(create_session):
session = create_session()

def handler(request):
pass
Expand All @@ -27,15 +35,15 @@ def handler2(request):
session.get_handler('/bar')


def test_installer_base_url():
session = server.Session()
def test_installer_base_url(create_session):
session = create_session()
dummy_server = _create_server()
installer = server.MockserverFixture(dummy_server, session)
assert installer.base_url == dummy_server.server_info.base_url


async def test_installer_handlers():
session = server.Session()
async def test_installer_handlers(create_session):
session = create_session()
dummy_server = _create_server()
installer = server.MockserverFixture(dummy_server, session)

Expand Down Expand Up @@ -65,8 +73,14 @@ def handler2(request):
('http://foo/', '/bar', True, 'http://foo/bar'),
],
)
def test_mockserver_new(base_prefix, prefix, http_proxy_enabled, expected):
session = server.Session()
def test_mockserver_new(
create_session,
base_prefix,
prefix,
http_proxy_enabled,
expected,
):
session = create_session()
dummy_server = _create_server(http_proxy_enabled=http_proxy_enabled)
installer = server.MockserverFixture(
dummy_server,
Expand Down
20 changes: 7 additions & 13 deletions tests/plugins/mockserver/test_setup_errors.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,17 +9,11 @@ async def inject_setup_error(mockserver_client, mockserver):
assert response.status_code == 500


def test_setup_errors_basic(inject_setup_error, mockserver):
assert len(mockserver._session._errors) == 1
error = mockserver._session._errors.pop()
def test_setup_errors_basic(
inject_setup_error,
mockserver_errors_pop,
mockserver_errors_list,
):
assert len(mockserver_errors_list) == 1
error = mockserver_errors_pop()
assert isinstance(error, exceptions.HandlerNotFoundError)


@pytest.mark.mockserver_nosetup_errors
def test_setup_errors_basic(inject_setup_error, mockserver):
pass


@pytest.fixture
def mockserver_nosetup_errors():
return False
7 changes: 4 additions & 3 deletions tests/plugins/mockserver/test_tracing_disabled.py
Original file line number Diff line number Diff line change
Expand Up @@ -46,15 +46,16 @@ async def test_mockserver_raises_on_unhandled_request_from_other_sources(
mockserver_info,
tracing_enabled=False,
)
with mockserver.new_session() as session:
errors = []
with mockserver.new_session(asyncexc_append=errors.append) as session:
request = _make_mocked_request(
'POST',
'/arbitrary/path',
headers=http_headers,
)
await mockserver._handle_request(request)
assert len(session._errors) == 1
error = session._errors.pop()
assert len(errors) == 1
error = errors.pop()
assert isinstance(error, exceptions.HandlerNotFoundError)


Expand Down
4 changes: 2 additions & 2 deletions tests/plugins/mockserver/test_tracing_enabled.py
Original file line number Diff line number Diff line change
Expand Up @@ -92,10 +92,10 @@ async def test_mockserver_responds_500_on_unhandled_request_from_other_sources(
mockserver,
http_headers,
create_service_client,
mockserver_errors_list,
):
client = create_service_client(mockserver.base_url, headers=http_headers)
response = await client.post('arbitrary/path')
assert response.status_code == 500

session = mockserver._session
assert len(session._errors) == 0
assert len(mockserver_errors_list) == 0
17 changes: 8 additions & 9 deletions tests/plugins/mockserver/test_unix_mockserver.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,18 +11,19 @@

@pytest.fixture
def unix_mockserver(
asyncexc_append,
_unix_mockserver: server.Server,
_mockserver_trace_id: str,
):
with _unix_mockserver.new_session(_mockserver_trace_id) as session:
with _unix_mockserver.new_session(
asyncexc_append=asyncexc_append,
trace_id=_mockserver_trace_id,
) as session:
yield server.MockserverFixture(_unix_mockserver, session)


@pytest.fixture(scope='session')
async def _unix_mockserver(
pytestconfig,
tmp_path_factory,
):
async def _unix_mockserver(pytestconfig, tmp_path_factory):
async with server.create_unix_server(
tmp_path_factory.mktemp('mockserver') / 'mockserver.socket',
loop=None,
Expand All @@ -45,7 +46,7 @@ async def unix_mockserver_client(
service_client_options: Dict[str, Any],
) -> service_client.Client:
async with aiohttp.UnixConnector(
path=unix_mockserver_info.socket_path
path=unix_mockserver_info.socket_path,
) as conn:
async with aiohttp.ClientSession(connector=conn) as session:
unix_service_client_options = {
Expand All @@ -55,9 +56,7 @@ async def unix_mockserver_client(

yield service_client.Client(
unix_mockserver.base_url,
headers={
'host': str(unix_mockserver_info.socket_path),
},
headers={'host': str(unix_mockserver_info.socket_path)},
**unix_service_client_options,
)

Expand Down
21 changes: 21 additions & 0 deletions tests/plugins/test_asyncexc.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
import pytest

from testsuite.plugins import asyncexc


def test_check(asyncexc_append, asyncexc_check, _asyncexc):
asyncexc_check()

try:
raise ValueError('oops')
except ValueError as exc:
asyncexc_append(exc)

assert len(_asyncexc) == 1

with pytest.raises(asyncexc.BackgroundExceptionError):
asyncexc_check()

assert len(_asyncexc) == 0

asyncexc_check()
76 changes: 6 additions & 70 deletions testsuite/mockserver/pytest_plugin.py
Original file line number Diff line number Diff line change
Expand Up @@ -26,23 +26,6 @@
"""


class MockserverPlugin:
def __init__(self):
self._invalidators = set()

def pytest_runtest_call(self, item):
for invalidator in self._invalidators:
invalidator()

@contextlib.contextmanager
def register_invalidator(self, invalidator):
self._invalidators.add(invalidator)
try:
yield
finally:
self._invalidators.discard(invalidator)


def pytest_addoption(parser):
group = parser.getgroup('mockserver')
group.addoption(
Expand Down Expand Up @@ -133,14 +116,6 @@ def pytest_addoption(parser):
)


def pytest_configure(config):
config.pluginmanager.register(MockserverPlugin(), 'mockserver_plugin')
config.addinivalue_line(
'markers',
'mockserver_nosetup_errors: do not fail on mockserver setup errors',
)


def pytest_register_object_hooks():
return {
'$mockserver': {'$fixture': '_mockserver_hook'},
Expand All @@ -150,15 +125,17 @@ def pytest_register_object_hooks():

@pytest.fixture(name='_mockserver_create_session')
def fixture_mockserver_create_session(
asyncexc_append,
_mockserver_trace_id: str,
_mockserver_errors_clear,
):
@contextlib.contextmanager
def create_session(mockserver):
__tracebackhide__ = True
with mockserver.new_session(_mockserver_trace_id) as session:
with _mockserver_errors_clear(session):
yield server.MockserverFixture(mockserver, session)
with mockserver.new_session(
asyncexc_append=asyncexc_append,
trace_id=_mockserver_trace_id,
) as session:
yield server.MockserverFixture(mockserver, session)

return create_session

Expand Down Expand Up @@ -319,47 +296,6 @@ def wrapper(doc: dict):
return wrapper


@pytest.fixture(name='_mockserver_errors_clear')
def fixture_mockserver_errors_clear(
_mockserver_plugin: MockserverPlugin,
request,
mockserver_nosetup_errors,
):
"""
Clear mockserver errors at startup.
Required for backward compatibility with older testsuite versions.
"""
marker = request.node.get_closest_marker('mockserver_nosetup_errors')
if marker:
warnings.warn(
'pytest.mark.mockserver_nosetup_errors is for backward '
'compatibility only, please rewrite your code',
DeprecationWarning,
)
mockserver_nosetup_errors = True

@contextlib.contextmanager
def errors_clear(session: server.Session):
if not mockserver_nosetup_errors:
yield
return
with _mockserver_plugin.register_invalidator(session.clear_errors):
yield

return errors_clear


@pytest.fixture(name='mockserver_nosetup_errors', scope='session')
def fixture_mockserver_nosetup_errors():
return False


@pytest.fixture(name='_mockserver_plugin', scope='session')
def fixture_mockserver_plugin(pytestconfig) -> MockserverPlugin:
return pytestconfig.pluginmanager.get_plugin('mockserver_plugin')


def _mockserver_info_hook(doc: dict, key=None, mockserver_info=None):
if mockserver_info is None:
raise RuntimeError(f'Missing {key} argument')
Expand Down
Loading

0 comments on commit 1375933

Please sign in to comment.