mirror of
https://github.com/simonw/datasette.git
synced 2026-06-07 01:27:00 +02:00
Pytest auto-close plugin for Datasette instances
Installs a pytest11 entry point so that every Datasette() constructed inside a pytest_runtest_call phase is auto-closed at the end of the test. Fixture-scoped instances are untouched. Opt out via the datasette_autoclose = false ini option. This gives large test suites a safety net against FD exhaustion and leaked write threads from the now-default temp-disk internal database without requiring every existing test to be rewritten. Refs #2692 Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
d72dd35378
commit
34cc320eab
4 changed files with 190 additions and 0 deletions
93
tests/test_pytest_autoclose_plugin.py
Normal file
93
tests/test_pytest_autoclose_plugin.py
Normal file
|
|
@ -0,0 +1,93 @@
|
|||
"""
|
||||
Tests for datasette._pytest_plugin — the pytest plugin that auto-closes
|
||||
Datasette instances constructed inside test bodies.
|
||||
|
||||
These tests drive a real pytest session in a subprocess so the plugin
|
||||
operates exactly as it would for a downstream consumer.
|
||||
"""
|
||||
|
||||
import subprocess
|
||||
import sys
|
||||
import textwrap
|
||||
from pathlib import Path
|
||||
|
||||
import pytest
|
||||
|
||||
REPO_ROOT = Path(__file__).parent.parent
|
||||
|
||||
|
||||
def _run_pytest(tmp_path: Path) -> subprocess.CompletedProcess:
|
||||
return subprocess.run(
|
||||
[sys.executable, "-m", "pytest", "-v", str(tmp_path)],
|
||||
cwd=str(tmp_path),
|
||||
capture_output=True,
|
||||
text=True,
|
||||
)
|
||||
|
||||
|
||||
def test_auto_close_of_instances_made_in_test_body(tmp_path):
|
||||
# Two ordered tests:
|
||||
# test_a makes a Datasette() and stashes a hard reference
|
||||
# test_b asserts that the hard-reffed instance was closed by the plugin
|
||||
(tmp_path / "test_sample.py").write_text(textwrap.dedent("""
|
||||
from datasette.app import Datasette
|
||||
|
||||
_stash = {}
|
||||
|
||||
def test_a():
|
||||
ds = Datasette(memory=True)
|
||||
_stash["ds"] = ds
|
||||
assert ds._closed is False
|
||||
|
||||
def test_b():
|
||||
assert _stash["ds"]._closed is True
|
||||
"""))
|
||||
result = _run_pytest(tmp_path)
|
||||
assert result.returncode == 0, result.stdout + result.stderr
|
||||
|
||||
|
||||
def test_fixture_scoped_instance_is_not_closed(tmp_path):
|
||||
# A module-scoped fixture instance must survive across tests in the module.
|
||||
(tmp_path / "test_fixture.py").write_text(textwrap.dedent("""
|
||||
import pytest
|
||||
from datasette.app import Datasette
|
||||
|
||||
@pytest.fixture(scope="module")
|
||||
def ds():
|
||||
return Datasette(memory=True)
|
||||
|
||||
def test_first(ds):
|
||||
assert ds._closed is False
|
||||
|
||||
def test_second(ds):
|
||||
# Still alive because the plugin only tracks instances
|
||||
# constructed during pytest_runtest_call, not during fixture
|
||||
# setup.
|
||||
assert ds._closed is False
|
||||
"""))
|
||||
result = _run_pytest(tmp_path)
|
||||
assert result.returncode == 0, result.stdout + result.stderr
|
||||
|
||||
|
||||
def test_opt_out_via_ini(tmp_path):
|
||||
# datasette_autoclose = false should leave instances untouched.
|
||||
(tmp_path / "pytest.ini").write_text(textwrap.dedent("""
|
||||
[pytest]
|
||||
datasette_autoclose = false
|
||||
""").strip())
|
||||
(tmp_path / "test_optout.py").write_text(textwrap.dedent("""
|
||||
from datasette.app import Datasette
|
||||
|
||||
_stash = {}
|
||||
|
||||
def test_a():
|
||||
ds = Datasette(memory=True)
|
||||
_stash["ds"] = ds
|
||||
|
||||
def test_b():
|
||||
# Opt-out: plugin must not have closed it.
|
||||
assert _stash["ds"]._closed is False
|
||||
_stash["ds"].close()
|
||||
"""))
|
||||
result = _run_pytest(tmp_path)
|
||||
assert result.returncode == 0, result.stdout + result.stderr
|
||||
Loading…
Add table
Add a link
Reference in a new issue