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

1"""The optional testing extras: test.mk, as tasks. 

2 

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. 

8 

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""" 

12 

13from __future__ import annotations 

14 

15from ..config import Config 

16from ..spec import Failed, Guard, task 

17from ..uv import uv_run 

18 

19PYTEST_NO_TESTS_COLLECTED = 5 

20"""pytest's "no tests collected". 

21 

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""" 

25 

26 

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. 

37 

38 The two pins are test.mk's, kept exact: benchmark results are only comparable across 

39 runs of the same tool version. 

40 

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 ) 

54 

55 

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. 

66 

67 Args: 

68 cfg: The resolved config. 

69 

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") 

94 

95 

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. 

106 

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 )