Skip to content

Commit 513d4fa

Browse files
authored
Merge pull request #19 from nolar/drop-noise
Drop unused code & clarify comments
2 parents 0d4951d + f292a60 commit 513d4fa

4 files changed

Lines changed: 14 additions & 54 deletions

File tree

.github/workflows/ci.yaml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ jobs:
1616
- uses: actions/setup-python@v6
1717
with:
1818
python-version: "3.14"
19-
- run: pip install --group dev --group lint -e .
19+
- run: pip install --group lint -e .
2020
- run: pre-commit run --all-files
2121
- run: mypy looptime --strict
2222

README.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -180,12 +180,12 @@ async def test_me():
180180
The markers can also be artificially injected by plugins/hooks if needed:
181181

182182
```python
183-
import asyncio
183+
import inspect
184184
import pytest
185185

186186
@pytest.hookimpl(hookwrapper=True)
187187
def pytest_pycollect_makeitem(collector, name, obj):
188-
if collector.funcnamefilter(name) and asyncio.iscoroutinefunction(obj):
188+
if collector.funcnamefilter(name) and inspect.iscoroutinefunction(obj):
189189
pytest.mark.asyncio(obj)
190190
pytest.mark.looptime(end=60)(obj)
191191
yield

looptime/plugin.py

Lines changed: 10 additions & 51 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@
1919
- A single event loop can be shared by multiple (but not all) tests.
2020
- A single test can be spread over multiple (but not all) event loops.
2121
22-
An classic example:
22+
A classic example:
2323
2424
- A session-scoped fixture ``server`` starts a port listener & an HTTP server.
2525
- A module-scoped fixture ``data`` populates the server via POST requests.
@@ -52,7 +52,7 @@
5252
Previously, the higher-scoped fixtures did not exist, so nothing breaks.
5353
5454
2. If the start time is explicitly defined and is in the future, move the time
55-
forwards as specified — indistinguishable from the previous behaviour
55+
forward as specified — indistinguishable from the previous behaviour
5656
(except there could be artifacts from the previous tests in the loop).
5757
5858
3. If the start time is explicitly defined and is in the past, issue a warning
@@ -157,10 +157,6 @@ def pytest_addoption(parser: Any) -> None:
157157
help="Run unmarked tests with the fake loop time by default.")
158158

159159

160-
EventLoopScopes = dict[str, list[str]] # {fixture_name -> [outer_scopes, …, innermost_scope]}
161-
EVENT_LOOP_SCOPES = pytest.StashKey[EventLoopScopes]()
162-
163-
164160
@pytest.hookimpl(wrapper=True)
165161
def pytest_fixture_setup(fixturedef: pytest.FixtureDef[Any], request: pytest.FixtureRequest) -> Any:
166162
# Setup as usual. We do the magic only afterwards, when we have the event loop created.
@@ -182,19 +178,12 @@ def pytest_fixture_setup(fixturedef: pytest.FixtureDef[Any], request: pytest.Fix
182178
else:
183179
is_bp_runner = isinstance(result, bp_Runner)
184180

181+
# Patch the event loop at creation — even if unused and not enabled. We cannot patch later
182+
# in the middle of the run: e.g. for a session-scoped loop used in a few tests out of many.
183+
# NB: For the lowest "function" scope, we still cannot decide which options to use, since
184+
# we do not know yet if it will be the running loop or not — so we cannot optimize here
185+
# in order to patch-and-configure only once; we must patch here & configure+activate later.
185186
if should_patch and (is_loop or is_runner or is_bp_runner):
186-
187-
# Populate the helper mapper of names-to-scopes, as used in the test hook below.
188-
if EVENT_LOOP_SCOPES not in request.session.stash:
189-
request.session.stash[EVENT_LOOP_SCOPES] = {}
190-
event_loop_scopes: EventLoopScopes = request.session.stash[EVENT_LOOP_SCOPES]
191-
event_loop_scopes.setdefault(fixturedef.argname, []).append(fixturedef.scope)
192-
193-
# Patch the event loop at creation — even if unused and not enabled. We cannot patch later
194-
# in the middle of the run: e.g. for a session-scoped loop used in a few tests out of many.
195-
# NB: For the lowest "function" scope, we still cannot decide which options to use, since
196-
# we do not know yet if it will be the running loop or not — so we cannot optimize here
197-
# in order to patch-and-configure only once; we must patch here & configure+activate later.
198187
if isinstance(result, asyncio.BaseEventLoop):
199188
patchers.patch_event_loop(result, _enabled=False)
200189
elif sys.version_info >= (3, 11) and isinstance(result, asyncio.Runner):
@@ -212,39 +201,6 @@ def pytest_fixture_setup(fixturedef: pytest.FixtureDef[Any], request: pytest.Fix
212201
return result
213202

214203

215-
@pytest.hookimpl(wrapper=True)
216-
def pytest_fixture_post_finalizer(fixturedef: pytest.FixtureDef[Any], request: pytest.FixtureRequest) -> Any:
217-
# Cleanup the helper mapper of the fixture's names-to-scopes, as used in the test-running hook.
218-
# Internal consistency check: some cases should not happen, but we do not fail if they do.
219-
should_patch = _should_patch(fixturedef, request)
220-
if should_patch and EVENT_LOOP_SCOPES in request.session.stash:
221-
event_loop_scopes: EventLoopScopes = request.session.stash[EVENT_LOOP_SCOPES]
222-
if fixturedef.argname not in event_loop_scopes:
223-
warnings.warn(
224-
f"Fixture {fixturedef.argname!r} not found in the cache of scopes."
225-
f" Report as a bug, please add a reproducible snippet.",
226-
RuntimeWarning,
227-
)
228-
elif not event_loop_scopes[fixturedef.argname]:
229-
warnings.warn(
230-
f"Fixture {fixturedef.argname!r} has the empty cache of scopes."
231-
f" Report as a bug, please add a reproducible snippet.",
232-
RuntimeWarning,
233-
)
234-
elif event_loop_scopes[fixturedef.argname][-1] != fixturedef.scope:
235-
warnings.warn(
236-
f"Fixture {fixturedef.argname!r} has the broken cache of scopes:"
237-
f" {event_loop_scopes[fixturedef.argname]!r}, expecting {fixturedef.scope!r}"
238-
f" Report as a bug, please add a reproducible snippet.",
239-
RuntimeWarning,
240-
)
241-
else:
242-
event_loop_scopes[fixturedef.argname][-1:] = []
243-
244-
# Go as usual.
245-
return (yield)
246-
247-
248204
# This hook is the latest (deepest) possible entrypoint before diving into the test function itself,
249205
# with all the fixtures executed earlier, so that their setup time is not taken into account.
250206
# Here, we know the actual running loop (out of many) chosen by pytest-asyncio & its marks/configs.
@@ -307,6 +263,9 @@ def _should_patch(fixturedef: pytest.FixtureDef[Any], request: pytest.FixtureReq
307263
return True
308264

309265
# pytest-asyncio>=1.0.0 exposes several event loops, one per scope, all hidden in the module.
266+
# We patch BOTH the default implementation, AND all those dirty hacks that users might make.
267+
# NB: We also report True on unrelated fixtures, such as `unused_tcp_port_factory`, etc.
268+
# This has no effect: they will not pass the extra test on being patchable loops & runners.
310269
asyncio_plugin = request.config.pluginmanager.getplugin("asyncio") # a module object
311270
asyncio_names: set[str] = {
312271
name for name in dir(asyncio_plugin) if _is_fixture(getattr(asyncio_plugin, name))

pyproject.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,7 @@ dev = [
5757
"pytest-mock",
5858
]
5959
lint = [
60+
{include-group = "dev"},
6061
"mypy==1.19.0",
6162
"pre-commit",
6263
"isort",

0 commit comments

Comments
 (0)