mirror of
https://github.com/simonw/datasette.git
synced 2026-05-27 20:36:17 +02:00
Auto-close Datasette instances from function-scoped fixtures too
The plugin now tracks instances across the full test protocol (setup, call, teardown) and closes all of them at the end — including ones created inside function-scoped pytest fixtures. Session-, module-, class- and package-scoped fixtures are still exempted by subtracting any instances their setup adds from the tracking list. This makes downstream projects like datasette-alerts work at low FD limits without every fixture needing an explicit ds.close() call. Refs #2692 See https://github.com/simonw/datasette/issues/2692#issuecomment-4265072230 Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
d23b32c3e5
commit
df96e12737
1 changed files with 33 additions and 3 deletions
|
|
@ -1,6 +1,9 @@
|
|||
"""
|
||||
Pytest plugin that automatically closes any Datasette instances constructed
|
||||
inside a test body. Fixture-scoped instances survive.
|
||||
during a pytest test — both in the test body and in function-scoped
|
||||
fixtures. Instances constructed by session-, module-, class- or package-
|
||||
scoped fixtures are left alone, because other tests in the session will
|
||||
still want to use them.
|
||||
|
||||
Registered as a pytest11 entry point in pyproject.toml so that downstream
|
||||
projects using Datasette get the same FD-safety net for their own tests.
|
||||
|
|
@ -40,7 +43,7 @@ def pytest_addoption(parser):
|
|||
"datasette_autoclose",
|
||||
help=(
|
||||
"Automatically close Datasette instances created inside test "
|
||||
"bodies (default: true)."
|
||||
"bodies and function-scoped fixtures (default: true)."
|
||||
),
|
||||
default="true",
|
||||
)
|
||||
|
|
@ -54,7 +57,8 @@ def _enabled(config) -> bool:
|
|||
|
||||
|
||||
@pytest.hookimpl(hookwrapper=True)
|
||||
def pytest_runtest_call(item):
|
||||
def pytest_runtest_protocol(item, nextitem):
|
||||
"""Track Datasette instances across setup, call and teardown; close at end."""
|
||||
if not _enabled(item.config):
|
||||
yield
|
||||
return
|
||||
|
|
@ -76,3 +80,29 @@ def pytest_runtest_call(item):
|
|||
f"Error closing Datasette instance: {e!r}"
|
||||
)
|
||||
)
|
||||
|
||||
|
||||
@pytest.hookimpl(hookwrapper=True)
|
||||
def pytest_fixture_setup(fixturedef, request):
|
||||
"""Exempt instances created by non-function-scoped fixtures.
|
||||
|
||||
Session-, module-, class- and package-scoped fixtures produce Datasette
|
||||
instances that must survive beyond the current test — other tests in
|
||||
the session will still use them. When such a fixture creates one or
|
||||
more Datasette instances during its setup, we snapshot the tracking
|
||||
list before the fixture runs and subtract off any instances that were
|
||||
added during its setup, so they don't get closed at test teardown.
|
||||
"""
|
||||
refs = _active_instances.get()
|
||||
if refs is None:
|
||||
yield
|
||||
return
|
||||
before_ids = {id(ref) for ref in refs}
|
||||
yield
|
||||
if fixturedef.scope != "function":
|
||||
new_refs = [ref for ref in refs if id(ref) not in before_ids]
|
||||
for new_ref in new_refs:
|
||||
try:
|
||||
refs.remove(new_ref)
|
||||
except ValueError:
|
||||
pass
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue