From a65d807b3064fb20da2ceb8972bff7503a7c64f8 Mon Sep 17 00:00:00 2001 From: Vytautas Liuolia Date: Wed, 21 Aug 2024 08:07:59 +0200 Subject: [PATCH] refactor(tests): drop `pytest-asyncio` (#2288) --- requirements/cibwtest | 1 - requirements/mintest | 1 - requirements/tests | 2 -- tests/asgi/test_asgi_conductor.py | 4 ---- tests/asgi/test_asgi_helpers.py | 2 -- tests/asgi/test_asgi_servers.py | 14 ------------- tests/asgi/test_boundedstream_asgi.py | 2 -- tests/asgi/test_buffered_reader.py | 19 ++--------------- tests/asgi/test_misc.py | 3 --- tests/asgi/test_request_body_asgi.py | 1 - tests/asgi/test_scheduled_callbacks.py | 2 -- tests/asgi/test_sync.py | 1 - tests/asgi/test_testing_asgi.py | 3 --- tests/asgi/test_ws.py | 29 -------------------------- tests/conftest.py | 11 ++++++++++ 15 files changed, 13 insertions(+), 82 deletions(-) diff --git a/requirements/cibwtest b/requirements/cibwtest index 33dd318b6..5ec7dc629 100644 --- a/requirements/cibwtest +++ b/requirements/cibwtest @@ -1,5 +1,4 @@ msgpack pytest -pytest-asyncio<0.22.0 pyyaml requests diff --git a/requirements/mintest b/requirements/mintest index 65e9332a6..158929194 100644 --- a/requirements/mintest +++ b/requirements/mintest @@ -1,4 +1,3 @@ coverage>=4.1 pytest -pytest-asyncio<0.22.0 requests diff --git a/requirements/tests b/requirements/tests index dd1fa0451..5be00de33 100644 --- a/requirements/tests +++ b/requirements/tests @@ -10,8 +10,6 @@ requests testtools; python_version < '3.10' # ASGI Specific (daphne is installed on a its own tox env) -# TODO(vytas): Some ASGI tests hang with pytest-asyncio-0.23 on 3.8 & 3.9. -pytest-asyncio < 0.22.0 aiofiles httpx uvicorn >= 0.17.0 diff --git a/tests/asgi/test_asgi_conductor.py b/tests/asgi/test_asgi_conductor.py index 2981ec4bc..0a44a692e 100644 --- a/tests/asgi/test_asgi_conductor.py +++ b/tests/asgi/test_asgi_conductor.py @@ -5,7 +5,6 @@ from falcon.asgi import App -@pytest.mark.asyncio async def test_default_headers(): class Resource: async def on_get(self, req, resp): @@ -21,7 +20,6 @@ async def on_get(self, req, resp): @pytest.mark.parametrize('simulate_method', ['request', 'simulate_request']) -@pytest.mark.asyncio async def test_generic_request(simulate_method): class Resource: async def on_lock(self, req, resp): @@ -37,7 +35,6 @@ async def on_lock(self, req, resp): assert result.status_code == 422 -@pytest.mark.asyncio async def test_wsgi_not_supported(): with pytest.raises(falcon.CompatibilityError): async with testing.TestClient(falcon.App()): @@ -52,7 +49,6 @@ async def test_wsgi_not_supported(): 'method', ['get', 'head', 'post', 'put', 'options', 'patch', 'delete'] ) @pytest.mark.parametrize('use_alias', ['alias', 'simulate']) -@pytest.mark.asyncio async def test_responders(method, use_alias): class Resource: async def on_get(self, req, resp): diff --git a/tests/asgi/test_asgi_helpers.py b/tests/asgi/test_asgi_helpers.py index f5f7b1eea..d3b172ad8 100644 --- a/tests/asgi/test_asgi_helpers.py +++ b/tests/asgi/test_asgi_helpers.py @@ -1,6 +1,5 @@ import inspect -import falcon from falcon.asgi import _asgi_helpers @@ -15,7 +14,6 @@ async def _call_factory(self, scope, receive, send): __call__ = _asgi_helpers._wrap_asgi_coroutine_func(unorthodox_call) -@falcon.runs_sync async def test_intricate_app(): async def receive(): pass diff --git a/tests/asgi/test_asgi_servers.py b/tests/asgi/test_asgi_servers.py index 5fdd9acde..c8772db40 100644 --- a/tests/asgi/test_asgi_servers.py +++ b/tests/asgi/test_asgi_servers.py @@ -174,7 +174,6 @@ def test_sse_client_disconnects_early(self, server_base_url): timeout=(_asgi_test_app.SSE_TEST_MAX_DELAY_SEC / 2), ) - @pytest.mark.asyncio @pytest.mark.skipif(httpx is None, reason='httpx is required for this test') async def test_stream_chunked_request(self, server_base_url): """Regression test for https://github.com/falconry/falcon/issues/2024""" @@ -197,7 +196,6 @@ async def emitter(): websockets is None, reason='websockets is required for this test class' ) class TestWebSocket: - @pytest.mark.asyncio @pytest.mark.parametrize('explicit_close', [True, False]) @pytest.mark.parametrize('close_code', [None, 4321]) async def test_hello(self, explicit_close, close_code, server_url_events_ws): @@ -242,7 +240,6 @@ async def test_hello(self, explicit_close, close_code, server_url_events_ws): assert got_message - @pytest.mark.asyncio @pytest.mark.parametrize('explicit_close', [True, False]) @pytest.mark.parametrize('close_code', [None, 4040]) async def test_rejected(self, explicit_close, close_code, server_url_events_ws): @@ -261,7 +258,6 @@ async def test_rejected(self, explicit_close, close_code, server_url_events_ws): assert exc_info.value.status_code == 403 - @pytest.mark.asyncio async def test_missing_responder(self, server_url_events_ws): server_url_events_ws += '/404' @@ -271,7 +267,6 @@ async def test_missing_responder(self, server_url_events_ws): assert exc_info.value.status_code == 403 - @pytest.mark.asyncio @pytest.mark.parametrize( 'subprotocol, expected', [ @@ -290,7 +285,6 @@ async def test_select_subprotocol_known( ) as ws: assert ws.subprotocol == expected - @pytest.mark.asyncio async def test_select_subprotocol_unknown(self, server_url_events_ws): extra_headers = {'X-Subprotocol': 'xmpp'} @@ -322,7 +316,6 @@ async def test_select_subprotocol_unknown(self, server_url_events_ws): # tried to capture this output and check it in the test below, # but the usual ways of capturing stdout/stderr with pytest do # not work. - @pytest.mark.asyncio async def test_disconnecting_client_early(self, server_url_events_ws): ws = await websockets.connect( server_url_events_ws, extra_headers={'X-Close': 'True'} @@ -342,7 +335,6 @@ async def test_disconnecting_client_early(self, server_url_events_ws): # messages after the close. await asyncio.sleep(1) - @pytest.mark.asyncio async def test_send_before_accept(self, server_url_events_ws): extra_headers = {'x-accept': 'skip'} @@ -352,7 +344,6 @@ async def test_send_before_accept(self, server_url_events_ws): message = await ws.recv() assert message == 'OperationNotAllowed' - @pytest.mark.asyncio async def test_recv_before_accept(self, server_url_events_ws): extra_headers = {'x-accept': 'skip', 'x-command': 'recv'} @@ -362,7 +353,6 @@ async def test_recv_before_accept(self, server_url_events_ws): message = await ws.recv() assert message == 'OperationNotAllowed' - @pytest.mark.asyncio async def test_invalid_close_code(self, server_url_events_ws): extra_headers = {'x-close': 'True', 'x-close-code': 42} @@ -379,7 +369,6 @@ async def test_invalid_close_code(self, server_url_events_ws): elapsed = time.time() - start assert elapsed < 2 - @pytest.mark.asyncio async def test_close_code_on_unhandled_error(self, server_url_events_ws): extra_headers = {'x-raise-error': 'generic'} @@ -390,7 +379,6 @@ async def test_close_code_on_unhandled_error(self, server_url_events_ws): assert ws.close_code in {3011, 1011} - @pytest.mark.asyncio async def test_close_code_on_unhandled_http_error(self, server_url_events_ws): extra_headers = {'x-raise-error': 'http'} @@ -401,7 +389,6 @@ async def test_close_code_on_unhandled_http_error(self, server_url_events_ws): assert ws.close_code == 3400 - @pytest.mark.asyncio @pytest.mark.parametrize('mismatch', ['send', 'recv']) @pytest.mark.parametrize('mismatch_type', ['text', 'data']) async def test_type_mismatch(self, mismatch, mismatch_type, server_url_events_ws): @@ -423,7 +410,6 @@ async def test_type_mismatch(self, mismatch, mismatch_type, server_url_events_ws assert ws.close_code in {3011, 1011} - @pytest.mark.asyncio async def test_passing_path_params(self, server_base_url_ws): expected_feed_id = '1ee7' url = f'{server_base_url_ws}feeds/{expected_feed_id}' diff --git a/tests/asgi/test_boundedstream_asgi.py b/tests/asgi/test_boundedstream_asgi.py index acb81215d..54e16c65f 100644 --- a/tests/asgi/test_boundedstream_asgi.py +++ b/tests/asgi/test_boundedstream_asgi.py @@ -166,7 +166,6 @@ async def test_iteration(): falcon.async_to_sync(test_iteration) -@falcon.runs_sync async def test_iterate_streaming_request(): events = iter( ( @@ -274,7 +273,6 @@ async def t(): falcon.async_to_sync(t) -@falcon.runs_sync async def test_exhaust(): emitter = testing.ASGIRequestEventEmitter(b'123456798' * 1024) stream = asgi.BoundedStream(emitter) diff --git a/tests/asgi/test_buffered_reader.py b/tests/asgi/test_buffered_reader.py index 01cab8b1c..27fbb2703 100644 --- a/tests/asgi/test_buffered_reader.py +++ b/tests/asgi/test_buffered_reader.py @@ -108,7 +108,6 @@ def test_basic_aiter(reader1): ] -@falcon.runs_sync async def test_aiter_from_buffer(reader1): assert await reader1.read(4) == b'Hell' @@ -148,27 +147,23 @@ def test_delimit(reader1, delimiter, expected): assert async_take(delimited) == expected -@falcon.runs_sync async def test_exhaust(reader1): await reader1.exhaust() assert await reader1.peek() == b'' @pytest.mark.parametrize('size', [1, 2, 3, 5, 7, 8]) -@falcon.runs_sync async def test_peek(reader1, size): assert await reader1.peek(size) == b'Hello, World'[:size] assert reader1.tell() == 0 -@falcon.runs_sync async def test_peek_at_eof(): source = chop_data(b'Hello!') stream = reader.BufferedReader(source) assert await stream.peek(16) == b'Hello!' -@falcon.runs_sync async def test_pipe(reader1): sink = AsyncSink() await reader1.pipe(sink) @@ -177,7 +172,6 @@ async def test_pipe(reader1): assert reader1.tell() == len(sink.accumulated) -@falcon.runs_sync async def test_pipe_until_delimiter_not_found(reader1): sink = AsyncSink() await reader1.pipe_until(b'404', sink) @@ -202,7 +196,6 @@ async def test_pipe_until_delimiter_not_found(reader1): ((50, 1), [b'Hello, World!\nJust testing some iterator goodness.', b'\n']), ], ) -@falcon.runs_sync async def test_read(reader1, sizes, expected): results = [] for size in sizes: @@ -213,7 +206,6 @@ async def test_read(reader1, sizes, expected): @pytest.mark.parametrize('start_size', [1, 16777216]) @pytest.mark.slow -@falcon.runs_sync async def test_varying_read_size(reader2, start_size): size = start_size result = io.BytesIO() @@ -230,7 +222,6 @@ async def test_varying_read_size(reader2, start_size): @pytest.mark.parametrize('peek', [0, 1, 8]) -@falcon.runs_sync async def test_readall(reader1, peek): if peek: await reader1.peek(peek) @@ -263,7 +254,6 @@ async def test_readall(reader1, peek): (7, b'good', -1, b'World!\nJust testing some iterator '), ], ) -@falcon.runs_sync async def test_read_until(reader1, offset, delimiter, size, expected, fork): if offset: await reader1.read(offset) @@ -274,7 +264,6 @@ async def test_read_until(reader1, offset, delimiter, size, expected, fork): assert await reader1.read_until(delimiter, size) == expected -@falcon.runs_sync async def test_read_until_with_buffer_edge_case(reader1): assert await reader1.read(12) == b'Hello, World' assert await reader1.peek(1) == b'!' @@ -292,7 +281,6 @@ def test_placeholder_methods(reader1): assert not reader1.writable() -@falcon.runs_sync async def test_iteration_started(reader1): async for chunk in reader1: with pytest.raises(OperationNotAllowed): @@ -300,7 +288,6 @@ async def test_iteration_started(reader1): pass -@falcon.runs_sync async def test_invalid_delimiter_length(reader1): with pytest.raises(ValueError): await reader1.read_until(b'') @@ -320,7 +307,6 @@ async def test_invalid_delimiter_length(reader1): ], ) @pytest.mark.slow -@falcon.runs_sync async def test_irregular_large_read_until(reader2, size1, size2): delimiter = b'--boundary1234567890--' @@ -341,7 +327,6 @@ async def test_irregular_large_read_until(reader2, size1, size2): assert chunk1 + chunk2 + remainder == expected[1337:] -@falcon.runs_sync @pytest.mark.parametrize('chunk_size', list(range(46, 63))) async def test_read_until_shared_boundary(chunk_size): source = chop_data( @@ -356,7 +341,8 @@ async def test_read_until_shared_boundary(chunk_size): # NOTE(vytas): This is woefully unoptimized, and this test highlights that. # Work in progress. -@falcon.runs_sync + + async def test_small_reads(reader3): ops = 0 read = 0 @@ -379,7 +365,6 @@ async def test_small_reads(reader3): @pytest.mark.slow -@falcon.runs_sync async def test_small_reads_with_delimiter(reader3): ops = 0 read = 0 diff --git a/tests/asgi/test_misc.py b/tests/asgi/test_misc.py index 1a609db3e..1a66c6955 100644 --- a/tests/asgi/test_misc.py +++ b/tests/asgi/test_misc.py @@ -9,21 +9,18 @@ from falcon.http_status import HTTPStatus -@pytest.mark.asyncio async def test_http_status_not_impl(): app = App() with pytest.raises(NotImplementedError): await app._http_status_handler(MagicMock(), None, HTTPStatus(200), {}, None) -@pytest.mark.asyncio async def test_http_error_not_impl(): app = App() with pytest.raises(NotImplementedError): await app._http_error_handler(MagicMock(), None, HTTPError(400), {}, None) -@pytest.mark.asyncio async def test_python_error_not_impl(): app = App() with pytest.raises(NotImplementedError): diff --git a/tests/asgi/test_request_body_asgi.py b/tests/asgi/test_request_body_asgi.py index fed5267b9..1a3d9b445 100644 --- a/tests/asgi/test_request_body_asgi.py +++ b/tests/asgi/test_request_body_asgi.py @@ -63,7 +63,6 @@ def test_tiny_body_overflow(self, client, resource): (8192, 50), ], ) - @pytest.mark.asyncio async def test_content_length_smaller_than_body(self, body_length, content_length): body_in = os.urandom(body_length) diff --git a/tests/asgi/test_scheduled_callbacks.py b/tests/asgi/test_scheduled_callbacks.py index 36d4f4e5b..c37ff3809 100644 --- a/tests/asgi/test_scheduled_callbacks.py +++ b/tests/asgi/test_scheduled_callbacks.py @@ -4,7 +4,6 @@ import pytest -from falcon import runs_sync from falcon import testing from falcon.asgi import App @@ -138,7 +137,6 @@ def callback_app(simple_resource): ('GET', '/stream', 'One\nTwo\nThree\n'), ], ) -@runs_sync async def test_callback(callback_app, simple_resource, method, uri, expected): async with testing.ASGIConductor(callback_app) as conductor: resp = await conductor.simulate_request(method, uri) diff --git a/tests/asgi/test_sync.py b/tests/asgi/test_sync.py index 6baab8c75..653beaad4 100644 --- a/tests/asgi/test_sync.py +++ b/tests/asgi/test_sync.py @@ -110,7 +110,6 @@ def callme_shirley(a=42, b=None): assert val[1] is None or (0 <= val[1] < 1000) -@pytest.mark.asyncio async def test_sync_asyncio_aliases(): async def dummy_async_func(): pass diff --git a/tests/asgi/test_testing_asgi.py b/tests/asgi/test_testing_asgi.py index 915fec4de..8ec041361 100644 --- a/tests/asgi/test_testing_asgi.py +++ b/tests/asgi/test_testing_asgi.py @@ -8,7 +8,6 @@ from . import _asgi_test_app -@pytest.mark.asyncio @pytest.mark.slow async def test_asgi_request_event_emitter_hang(): # NOTE(kgriffs): This tests the ASGI server behavior that @@ -36,7 +35,6 @@ async def test_asgi_request_event_emitter_hang(): assert (elapsed + 0.1) > expected_elapsed_min -@pytest.mark.asyncio async def test_ignore_extra_asgi_events(): collect = testing.ASGIResponseEventCollector() @@ -49,7 +47,6 @@ async def test_ignore_extra_asgi_events(): assert len(collect.events) == 2 -@pytest.mark.asyncio async def test_invalid_asgi_events(): collect = testing.ASGIResponseEventCollector() diff --git a/tests/asgi/test_ws.py b/tests/asgi/test_ws.py index 6fb7b7667..87ea0c588 100644 --- a/tests/asgi/test_ws.py +++ b/tests/asgi/test_ws.py @@ -55,7 +55,6 @@ def conductor(): return testing.ASGIConductor(app) -@pytest.mark.asyncio @pytest.mark.parametrize('path', ['/ws/yes', '/ws/no']) async def test_ws_not_accepted(path, conductor): class SomeResource: @@ -113,7 +112,6 @@ async def on_websocket(self, req, ws, explicit): assert resource.caught_operation_not_allowed -@pytest.mark.asyncio @pytest.mark.slow async def test_echo(): # noqa: C901 consumer_sleep = 0.01 @@ -232,7 +230,6 @@ async def process_request_ws(self, req, ws): assert resource.caught_operation_not_allowed -@pytest.mark.asyncio async def test_path_not_found(conductor): async with conductor as c: with pytest.raises(falcon.WebSocketDisconnected) as exc_info: @@ -242,7 +239,6 @@ async def test_path_not_found(conductor): assert exc_info.value.code == CloseCode.NOT_FOUND -@pytest.mark.asyncio @pytest.mark.parametrize('clear_error_handlers', [True, False]) async def test_responder_raises_unhandled_error(clear_error_handlers, conductor): class SomeResource: @@ -302,7 +298,6 @@ async def on_websocket(self, req, ws, generic_error): assert exc_info.value.code == 3422 -@pytest.mark.asyncio @pytest.mark.parametrize('direction', ['send', 'receive']) @pytest.mark.parametrize('explicit_close_client', [True, False]) @pytest.mark.parametrize('explicit_close_server', [True, False]) @@ -410,7 +405,6 @@ async def on_websocket(self, req, ws): assert resource.ws_ready is False -@pytest.mark.asyncio @pytest.mark.parametrize('custom_text', [True, False]) @pytest.mark.parametrize('custom_data', [True, False]) @pytest.mark.skipif(msgpack is None, reason='msgpack is required for this test') @@ -525,7 +519,6 @@ def deserialize(self, payload: bytes) -> object: assert doc == doc_expected -@pytest.mark.asyncio @pytest.mark.parametrize('sample_data', [b'123', b'', b'\xe1\x9a\xa0\xe1', b'\0']) async def test_send_receive_data(sample_data, conductor): class Resource: @@ -567,7 +560,6 @@ async def on_websocket(self, req, ws): assert resource.error_count == 4 -@pytest.mark.asyncio @pytest.mark.parametrize( 'subprotocols', [ @@ -605,7 +597,6 @@ async def on_websocket(self, req, ws): resource.test_complete.set() -@pytest.mark.asyncio @pytest.mark.parametrize( 'headers', [ @@ -654,7 +645,6 @@ async def on_websocket(self, req, ws): resource.test_complete.set() -@pytest.mark.asyncio @pytest.mark.parametrize( 'headers', [ @@ -688,7 +678,6 @@ async def on_websocket(self, req, ws): assert isinstance(resource.raised_error, ValueError) -@pytest.mark.asyncio async def test_accept_with_headers_not_supported(conductor): class Resource: def __init__(self): @@ -714,7 +703,6 @@ async def on_websocket(self, req, ws): assert isinstance(resource.raised_error, falcon.OperationNotAllowed) -@pytest.mark.asyncio async def test_missing_ws_handler(conductor): class Resource: async def on_get(self, req, resp): @@ -728,7 +716,6 @@ async def on_get(self, req, resp): pass -@pytest.mark.asyncio async def test_unexpected_param(conductor): class Resource: async def on_websocket(self, req, ws): @@ -742,7 +729,6 @@ async def on_websocket(self, req, ws): pass -@pytest.mark.asyncio @pytest.mark.parametrize( 'subprotocol', [ @@ -775,7 +761,6 @@ async def on_websocket(self, req, ws): pass -@pytest.mark.asyncio async def test_send_receive_wrong_type(conductor): class Resource: def __init__(self): @@ -830,7 +815,6 @@ async def on_websocket(self, req, ws): assert resource.error_count == 4 -@pytest.mark.asyncio @pytest.mark.parametrize( 'options_code', [999, 100, 0, -1, 1004, 1005, 1006, 1015, 1016, 1017, 1050, 1099, 'NaN'], @@ -883,7 +867,6 @@ def process_request_ws(self, req, ws): App(middleware=mw) -@pytest.mark.asyncio @pytest.mark.parametrize('version', ['1.9', '20.5', '3.0', '3.1']) async def test_bad_spec_version(version, conductor): async with conductor as c: @@ -892,7 +875,6 @@ async def test_bad_spec_version(version, conductor): pass -@pytest.mark.asyncio @pytest.mark.parametrize('version', ['1.0', '1']) async def test_bad_http_version(version, conductor): async with conductor as c: @@ -901,7 +883,6 @@ async def test_bad_http_version(version, conductor): pass -@pytest.mark.asyncio async def test_bad_first_event(): app = App() @@ -928,7 +909,6 @@ async def _emit(): assert ws.close_code == CloseCode.SERVER_ERROR -@pytest.mark.asyncio async def test_missing_http_version(): app = App() @@ -942,7 +922,6 @@ async def test_missing_http_version(): await app(scope, ws._emit, ws._collect) -@pytest.mark.asyncio async def test_missing_spec_version(): app = App() @@ -956,7 +935,6 @@ async def test_missing_spec_version(): await app(scope, ws._emit, ws._collect) -@pytest.mark.asyncio async def test_translate_webserver_error(conductor): class Resource: def __init__(self): @@ -1025,7 +1003,6 @@ def test_ws_base_not_implemented(): bh.deserialize(b'') -@pytest.mark.asyncio @pytest.mark.slow async def test_ws_context_timeout(conductor): class Resource: @@ -1040,7 +1017,6 @@ async def on_websocket(self, req, ws): pass -@pytest.mark.asyncio async def test_ws_simulator_client_require_accepted(conductor): class Resource: async def on_websocket(self, req, ws): @@ -1059,7 +1035,6 @@ async def on_websocket(self, req, ws): await ws.receive_text() -@pytest.mark.asyncio async def test_ws_simulator_collect_edge_cases(conductor): class Resource: pass @@ -1098,7 +1073,6 @@ class Resource: event = await ws._emit() -@pytest.mark.asyncio @pytest.mark.slow async def test_ws_responder_never_ready(conductor, monkeypatch): async def noop_close(obj, code=None): @@ -1139,7 +1113,6 @@ def test_msgpack_missing(): handler.deserialize(b'{}') -@pytest.mark.asyncio @pytest.mark.parametrize('status', [200, 500, 422, 400]) @pytest.mark.parametrize('thing', [falcon.HTTPStatus, falcon.HTTPError]) @pytest.mark.parametrize('accept', [True, False]) @@ -1168,7 +1141,6 @@ async def on_websocket(self, req, ws): assert err.value.code == exp_code -@pytest.mark.asyncio @pytest.mark.parametrize('status', [200, 500, 422, 400]) @pytest.mark.parametrize( 'thing', @@ -1211,7 +1183,6 @@ class FooBarError(Exception): pass -@pytest.mark.asyncio @pytest.mark.parametrize('status', [200, 500, 422, 400]) @pytest.mark.parametrize('thing', [falcon.HTTPStatus, falcon.HTTPError]) @pytest.mark.parametrize( diff --git a/tests/conftest.py b/tests/conftest.py index e26f0cefe..6d47362a2 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -1,5 +1,6 @@ import contextlib import importlib.util +import inspect import os import pathlib @@ -139,3 +140,13 @@ def pytest_runtest_protocol(item, nextitem): item.cls._item = item yield + + +@pytest.hookimpl(hookwrapper=True) +def pytest_runtest_call(item): + # NOTE(vytas): We automatically wrap all coroutine functions with + # falcon.runs_sync instead of the fragile pytest-asyncio package. + if isinstance(item, pytest.Function) and inspect.iscoroutinefunction(item.obj): + item.obj = falcon.runs_sync(item.obj) + + yield