tests: switch to using pytester to test the test suite
- ID
6c1cb05- date
2026-02-28 09:14:03+00:00- author
Alex Chan <alex@alexwlchan.net>- parent
eecb946- message
tests: switch to using pytester to test the test suite This doesn't change the behaviour of anything, but it's a more idiomatic way to test my test suite, and lays the groundwork for some other changes I'd like to make.- changed files
2 files, 117 additions, 86 deletions
Changed files
tests/conftest.py (144) → tests/conftest.py (173)
diff --git a/tests/conftest.py b/tests/conftest.py
index 302fe1c..9a07b84 100644
--- a/tests/conftest.py
+++ b/tests/conftest.py
@@ -2,4 +2,6 @@
from nitrate.cassettes import cassette_name, vcr_cassette
+pytest_plugins = "pytester"
+
__all__ = ["cassette_name", "vcr_cassette"]
tests/test_static_site_tests.py (7599) → tests/test_static_site_tests.py (8753)
diff --git a/tests/test_static_site_tests.py b/tests/test_static_site_tests.py
index 157ea9a..3a9c8f2 100644
--- a/tests/test_static_site_tests.py
+++ b/tests/test_static_site_tests.py
@@ -2,20 +2,23 @@
Tests for `chives.static_site_tests`.
"""
-from collections.abc import Iterator
from pathlib import Path
import shutil
import subprocess
from typing import Any, TypeVar
import pytest
+from pytest import Pytester
from chives import dates
-from chives.static_site_tests import StaticSiteTestSuite
M = TypeVar("M")
+GIT_ROOT = Path(
+ subprocess.check_output(["git", "rev-parse", "--show-toplevel"], text=True).strip()
+)
+
@pytest.fixture
def site_root(tmp_path: Path) -> Path:
@@ -25,35 +28,61 @@ def site_root(tmp_path: Path) -> Path:
return tmp_path
-def create_test_suite[M](
- site_root: Path,
- metadata: M,
+def create_pyfile(
+ pytester: Pytester,
+ site_root: Path | None = None,
+ metadata: Any = None,
*,
paths_in_metadata: set[Path] | None = None,
tags_in_metadata: set[str] | None = None,
-) -> StaticSiteTestSuite[M]:
+ date_formats: list[str] | None = None,
+ known_similar_tags: set[tuple[str, str]] | None = None,
+) -> None:
"""
- Create a new instance of StaticSiteTestSuite with the hard-coded data
- provided.
+ Create a new instance of `pytest.Pytester` which is ready to run
+ a test suite based on StaticSiteTestSuite.
"""
-
- class TestSuite(StaticSiteTestSuite[M]):
- def site_root(self) -> Path: # pragma: no cover
- return site_root
-
- def metadata(self, site_root: Path) -> M: # pragma: no cover
- return metadata
-
- def list_paths_in_metadata(self, metadata: M) -> set[Path]:
- return paths_in_metadata or set()
-
- def list_tags_in_metadata(self, metadata: M) -> Iterator[str]:
- yield from (tags_in_metadata or set())
-
- return TestSuite()
+ default_date_formats = [
+ "%Y-%m-%dT%H:%M:%SZ",
+ "%Y-%m-%d",
+ ]
+
+ pytester.makepyfile(
+ f"""
+ from collections.abc import Iterator
+ from pathlib import Path, PosixPath
+ from typing import Any
+
+ import pytest
+
+ from chives.static_site_tests import StaticSiteTestSuite
+
+
+ class TestSuite(StaticSiteTestSuite[Any]):
+ @pytest.fixture
+ def site_root(self) -> Path:
+ return Path({str(site_root or pytester.path)!r})
+
+ @pytest.fixture
+ def metadata(self, site_root: Path) -> Any:
+ return {repr(metadata)}
+
+ def list_paths_in_metadata(self, metadata: Any) -> set[Path]:
+ return {repr(paths_in_metadata or set())}
+
+ def list_tags_in_metadata(self, metadata: Any) -> Iterator[str]:
+ yield from {repr(tags_in_metadata or set())}
+
+ date_formats = {repr(date_formats or default_date_formats)}
+
+ known_similar_tags = {repr(known_similar_tags or set())}
+ """
+ )
-def test_paths_saved_locally_match_metadata(site_root: Path) -> None:
+def test_paths_saved_locally_match_metadata(
+ pytester: Pytester, site_root: Path
+) -> None:
"""
The tests check that the set of paths saved locally match the metadata.
"""
@@ -73,35 +102,35 @@ def test_paths_saved_locally_match_metadata(site_root: Path) -> None:
metadata = [Path("media/cat.jpg"), Path("media/dog.png"), Path("media/emu.gif")]
- t = create_test_suite(site_root, metadata, paths_in_metadata=set(metadata))
- t.test_every_file_in_metadata_is_saved_locally(metadata, site_root)
- t.test_every_local_file_is_in_metadata(metadata, site_root)
+ create_pyfile(pytester, site_root, metadata, paths_in_metadata=set(metadata))
+
+ keyword = (
+ "test_every_file_in_metadata_is_saved_locally or "
+ "test_every_local_file_is_in_metadata"
+ )
+ pytester.runpytest("-k", keyword).assert_outcomes(passed=2)
# Add a new file locally, and check the test starts failing.
(site_root / "media/fish.tiff").write_text("test")
-
- with pytest.raises(AssertionError):
- t.test_every_local_file_is_in_metadata(metadata, site_root)
-
+ pytester.runpytest("-k", keyword).assert_outcomes(passed=1, failed=1)
(site_root / "media/fish.tiff").unlink()
# Delete one of the local files, and check the test starts failing.
(site_root / "media/cat.jpg").unlink()
-
- with pytest.raises(AssertionError):
- t.test_every_file_in_metadata_is_saved_locally(metadata, site_root)
+ pytester.runpytest("-k", keyword).assert_outcomes(passed=1, failed=1)
-def test_checks_for_git_changes(site_root: Path) -> None:
+def test_checks_for_git_changes(pytester: Pytester, site_root: Path) -> None:
"""
The tests check that there are no uncommitted Git changes.
"""
- t = create_test_suite(site_root, metadata=[1, 2, 3])
+ create_pyfile(pytester, site_root)
+
+ keyword = "test_no_uncommitted_git_changes"
# Initially this should fail, because there isn't a Git repo in
# the folder.
- with pytest.raises(AssertionError):
- t.test_no_uncommitted_git_changes(site_root)
+ pytester.runpytest("-k", keyword).assert_outcomes(failed=1)
# Create a Git repo, add a file, and commit it.
(site_root / "README.md").write_text("hello world")
@@ -110,23 +139,23 @@ def test_checks_for_git_changes(site_root: Path) -> None:
subprocess.check_call(["git", "commit", "-m", "initial commit"], cwd=site_root)
# Check there are no uncommitted Git changes
- t.test_no_uncommitted_git_changes(site_root)
+ pytester.runpytest("-k", keyword).assert_outcomes(passed=1)
# Make a new change, and check it's spotted
(site_root / "README.md").write_text("a different hello world")
-
- with pytest.raises(AssertionError):
- t.test_no_uncommitted_git_changes(site_root)
+ pytester.runpytest("-k", keyword).assert_outcomes(failed=1)
-def test_checks_for_url_safe_paths(site_root: Path) -> None:
+def test_checks_for_url_safe_paths(pytester: Pytester, site_root: Path) -> None:
"""
The tests check for URL-safe paths.
"""
- t = create_test_suite(site_root, metadata=[1, 2, 3])
+ create_pyfile(pytester, site_root)
+
+ keyword = "test_every_path_is_url_safe"
# This should pass trivially when the site is empty.
- t.test_every_path_is_url_safe(site_root)
+ pytester.runpytest("-k", keyword).assert_outcomes(passed=1)
# Now write some files with URL-safe names, and check it's still okay.
for filename in [
@@ -136,39 +165,38 @@ def test_checks_for_url_safe_paths(site_root: Path) -> None:
]:
(site_root / filename).write_text("test")
- t.test_every_path_is_url_safe(site_root)
+ pytester.runpytest("-k", keyword).assert_outcomes(passed=1)
# Write another file with a URL-unsafe name, and check it's caught
# by the test.
(site_root / "a#b#c").write_text("test")
+ pytester.runpytest("-k", keyword).assert_outcomes(failed=1)
- with pytest.raises(AssertionError):
- t.test_every_path_is_url_safe(site_root)
-
-def test_checks_for_av1_videos(site_root: Path) -> None:
+def test_checks_for_av1_videos(pytester: Pytester, site_root: Path) -> None:
"""
The tests check for AV1-encoded videos.
"""
- t = create_test_suite(site_root, metadata=[1, 2, 3])
+ create_pyfile(pytester, site_root)
+
+ keyword = "test_no_videos_are_av1"
# This should pass trivially when the site is empty.
- t.test_no_videos_are_av1(site_root)
+ pytester.runpytest("-k", keyword).assert_outcomes(passed=1)
# Copy in an H.264-encoded video, and check it's not flagged.
shutil.copyfile(
- "tests/fixtures/media/Sintel_360_10s_1MB_H264.mp4",
+ GIT_ROOT / "tests/fixtures/media/Sintel_360_10s_1MB_H264.mp4",
site_root / "Sintel_360_10s_1MB_H264.mp4",
)
- t.test_no_videos_are_av1(site_root)
+ pytester.runpytest("-k", keyword).assert_outcomes(passed=1)
# Copy in an AV1-encoded video, and check it's caught by the test
shutil.copyfile(
- "tests/fixtures/media/Sintel_360_10s_1MB_AV1.mp4",
+ GIT_ROOT / "tests/fixtures/media/Sintel_360_10s_1MB_AV1.mp4",
site_root / "Sintel_360_10s_1MB_AV1.mp4",
)
- with pytest.raises(AssertionError):
- t.test_no_videos_are_av1(site_root)
+ pytester.runpytest("-k", keyword).assert_outcomes(failed=1)
class TestAllTimestampsAreConsistent:
@@ -183,65 +211,66 @@ class TestAllTimestampsAreConsistent:
{"date_saved": dates.now()},
],
)
- def test_allows_correct_date_formats(self, site_root: Path, metadata: Any) -> None:
+ def test_allows_correct_date_formats(
+ self, pytester: Pytester, metadata: Any
+ ) -> None:
"""
The tests pass if all the dates are in the correct format.
"""
- t = create_test_suite(site_root, metadata)
- t.test_all_timestamps_are_consistent(metadata)
+ create_pyfile(pytester, metadata=metadata)
+
+ keyword = "test_all_timestamps_are_consistent"
+
+ pytester.runpytest("-k", keyword).assert_outcomes(passed=1)
@pytest.mark.parametrize("metadata", [{"date_saved": "AAAA-BB-CC"}])
def test_rejects_incorrect_date_formats(
- self, site_root: Path, metadata: Any
+ self, pytester: Pytester, site_root: Path, metadata: Any
) -> None:
"""
The tests fail if the metadata has inconsistent date formats.
"""
- t = create_test_suite(site_root, metadata)
- with pytest.raises(AssertionError):
- t.test_all_timestamps_are_consistent(metadata)
+ create_pyfile(pytester, metadata=metadata)
+
+ keyword = "test_all_timestamps_are_consistent"
- def test_can_override_date_formats(self, site_root: Path) -> None:
+ pytester.runpytest("-k", keyword).assert_outcomes(failed=1)
+
+ def test_can_override_date_formats(self, pytester: Pytester) -> None:
"""
A previously-blocked date format is allowed if you add it to
the `date_formats` list.
"""
metadata = {"date_saved": "2025"}
- t = create_test_suite(site_root, metadata)
+ keyword = "test_all_timestamps_are_consistent"
# It fails with the default settings
- with pytest.raises(AssertionError):
- t.test_all_timestamps_are_consistent(metadata)
+ create_pyfile(pytester, metadata=metadata)
+ pytester.runpytest("-k", keyword).assert_outcomes(failed=1)
# It passes if we add the format to `date_formats`
- t.date_formats.append("%Y")
- t.test_all_timestamps_are_consistent(metadata)
+ create_pyfile(pytester, metadata=metadata, date_formats=["%Y"])
+ pytester.runpytest("-k", keyword).assert_outcomes(passed=1)
-def test_checks_for_similar_tags(site_root: Path) -> None:
+def test_checks_for_similar_tags(pytester: Pytester) -> None:
"""
The tests check for similar and misspelt tags.
"""
- metadata = [1, 2, 3]
+ keyword = "test_no_similar_tags"
# Check a site with distinct tags.
- t1 = create_test_suite(
- site_root, metadata, tags_in_metadata={"red", "green", "blue"}
- )
- t1.test_no_similar_tags(metadata)
+ create_pyfile(pytester, tags_in_metadata={"red", "green", "blue"})
+ pytester.runpytest("-k", keyword).assert_outcomes(passed=1)
# Check a site with similar tags.
- t2 = create_test_suite(
- site_root, metadata, tags_in_metadata={"red robot", "rod robot", "rid robot"}
- )
- with pytest.raises(AssertionError):
- t2.test_no_similar_tags(metadata)
+ create_pyfile(pytester, tags_in_metadata={"red robot", "rod robot", "rid robot"})
+ pytester.runpytest("-k", keyword).assert_outcomes(failed=1)
# Check a site with similar tags, but marked as known-similar.
- t3 = create_test_suite(
- site_root,
- metadata,
+ create_pyfile(
+ pytester,
tags_in_metadata={"red robot", "rod robot", "green", "blue"},
+ known_similar_tags={("red robot", "rod robot")},
)
- t3.known_similar_tags = {("red robot", "rod robot")}
- t3.test_no_similar_tags(metadata)
+ pytester.runpytest("-k", keyword).assert_outcomes(passed=1)