-
Notifications
You must be signed in to change notification settings - Fork 1
Expand file tree
/
Copy pathconftest.py
More file actions
70 lines (51 loc) · 2.59 KB
/
Copy pathconftest.py
File metadata and controls
70 lines (51 loc) · 2.59 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
"""Minimal async test support plugin.
This lightweight plugin allows pytest to execute ``async def`` test functions
without depending on the full **pytest-asyncio** package. It is **not** a
drop-in replacement for every advanced feature of that library, but it is more
than enough for the current test-suite which:
1. Marks coroutine tests with ``@pytest.mark.asyncio``.
2. Requires only a default event-loop to run them.
If an async test is collected this plugin spins up a fresh event-loop, executes
the coroutine, and then closes the loop so resources are released cleanly.
"""
from __future__ import annotations
import asyncio
import inspect
import typing as _t
import pytest
def pytest_configure(config: pytest.Config) -> None: # noqa: D401
"""Register the ``asyncio`` marker so pytest does not raise warnings."""
config.addinivalue_line(
"markers",
"asyncio: mark a test function as an asyncio coroutine so the built-in"
" event-loop runner provided by the local ``conftest.py`` executes it.",
)
@pytest.hookimpl(tryfirst=True)
def pytest_pyfunc_call(pyfuncitem: pytest.Item) -> bool | None: # noqa: D401
"""Intercept function calls and run *coroutine* tests inside an event loop.
When pytest is about to call a test function, this hook checks whether the
underlying object is an ``async def`` coroutine. If so, it creates an
event-loop, executes the coroutine, and returns ``True`` to signal that the
call has been handled. Non-coroutine tests are left untouched (returning
``None`` lets pytest proceed with its default behaviour).
"""
test_obj = pyfuncitem.obj
if not inspect.iscoroutinefunction(test_obj):
# Let pytest handle regular (non-async) test functions.
return None
loop: asyncio.AbstractEventLoop = asyncio.new_event_loop()
asyncio.set_event_loop(loop)
# ``pyfuncitem.funcargs`` might contain fixtures that are **not** accepted by
# the coroutine signature (e.g. the ``event_loop_policy`` fixture provided
# by *pytest-asyncio*). Passing such unexpected keyword-arguments would
# raise *TypeError*. Filter them out so we only forward the parameters the
# coroutine explicitly asks for.
accepted_params = set(inspect.signature(test_obj).parameters)
filtered_args = {k: v for k, v in pyfuncitem.funcargs.items() if k in accepted_params}
try:
loop.run_until_complete(test_obj(**filtered_args))
finally:
loop.run_until_complete(loop.shutdown_asyncgens())
loop.close()
# Returning *True* tells pytest that we've already executed the test.
return True