Coverage for src/rhiza_task/tasks/extras.py: 100%
23 statements
« prev ^ index » next coverage.py v7.15.4, created at 2026-08-25 04:13 +0000
« prev ^ index » next coverage.py v7.15.4, created at 2026-08-25 04:13 +0000
1"""The optional testing extras: test.mk, as tasks.
3Three gates no ``all`` depends on -- ``book`` is the one aggregate that names them, for
4their reports -- each needing its own tool and folder convention. They stay separate from
5the language layer for the reason test.mk gives: a project should be able to take the Python
6gate set without also declaring an opinion on benchmarks, stress runs or property-based
7testing.
9Each body is one vector plus, for ``hypothesis-test``, a single exit code: pytest's "no
10tests collected", which is a skip rather than a failure for a project that has none.
11"""
13from __future__ import annotations
15from ..config import Config
16from ..spec import Failed, Guard, task
17from ..uv import uv_run
19PYTEST_NO_TESTS_COLLECTED = 5
20"""pytest's "no tests collected".
22For ``hypothesis-test`` this is a skip, not a failure: a project with no property-based
23tests is a valid project, and the marker expression legitimately matches nothing.
24"""
27@task(
28 "benchmark",
29 "run the performance benchmarks",
30 section="Testing extras",
31 layer="python",
32 needs=("install",),
33 guards=(Guard("tests_folder", glob="benchmarks/*.py", reason="no benchmarks folder"),),
34)
35def benchmark(cfg: Config) -> None:
36 """Run pytest-benchmark over ``tests/benchmarks``, writing a histogram and JSON.
38 The two pins are test.mk's, kept exact: benchmark results are only comparable across
39 runs of the same tool version.
41 Args:
42 cfg: The resolved config.
43 """
44 (cfg.root / "_tests" / "benchmarks").mkdir(parents=True, exist_ok=True)
45 uv_run(
46 "pytest",
47 f"{cfg.tests_folder}/benchmarks/",
48 "--benchmark-only",
49 "--benchmark-histogram=_tests/benchmarks/histogram",
50 "--benchmark-json=_tests/benchmarks/results.json",
51 cwd=cfg.root,
52 withs=("pytest", "pytest-benchmark==5.2.3", "pygal==3.1.0"),
53 )
56@task(
57 "hypothesis-test",
58 "run the property-based tests",
59 section="Testing extras",
60 layer="python",
61 needs=("install",),
62 guards=(Guard("tests_folder", glob="test_*.py", reason="no test files found"),),
63)
64def hypothesis_test(cfg: Config) -> None:
65 """Run the Hypothesis-marked tests with statistics and a fixed seed.
67 Args:
68 cfg: The resolved config.
70 Raises:
71 Failed: When a property test fails.
72 """
73 (cfg.root / "_tests" / "hypothesis").mkdir(parents=True, exist_ok=True)
74 code = uv_run(
75 "pytest",
76 f"--ignore={cfg.tests_folder}/benchmarks",
77 "-v",
78 "--hypothesis-show-statistics",
79 "--hypothesis-seed=0",
80 "-m",
81 "hypothesis or property",
82 "--tb=short",
83 "--html=_tests/hypothesis/report.html",
84 cwd=cfg.root,
85 withs=("pytest", "hypothesis", "pytest-html"),
86 env={"PYTEST_HTML_TITLE": "Hypothesis tests"},
87 check=False,
88 )
89 if code == PYTEST_NO_TESTS_COLLECTED:
90 print("[INFO] no hypothesis/property tests collected")
91 return
92 if code:
93 raise Failed(code, "property tests failed")
96@task(
97 "stress",
98 "run the stress and load tests",
99 section="Testing extras",
100 layer="python",
101 needs=("install",),
102 guards=(Guard("tests_folder", glob="stress/*.py", reason="no stress folder"),),
103)
104def stress(cfg: Config) -> None:
105 """Run the stress-marked tests.
107 Args:
108 cfg: The resolved config.
109 """
110 (cfg.root / "_tests" / "stress").mkdir(parents=True, exist_ok=True)
111 uv_run(
112 "pytest",
113 "-v",
114 "-m",
115 "stress",
116 "--tb=short",
117 "--html=_tests/stress/report.html",
118 cwd=cfg.root,
119 withs=("pytest", "pytest-html"),
120 )