From b499834c0f1d2b57e895e6dac237dafe79ee5dab Mon Sep 17 00:00:00 2001 From: drew Date: Mon, 1 Jun 2026 17:37:24 -0400 Subject: [PATCH] fix(coverage): resolve fail-under from pyproject in threshold-enforcement step (WS5) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The coverage_report noxfile session now delegates COVERAGE_THRESHOLD to the pyproject single source (`COVERAGE_THRESHOLD = _read_coverage_fail_under()`), so the AST value is a Call, not a Constant. The coverage_threshold_enforcement.feature step "I parse the COVERAGE_THRESHOLD constant from noxfile.py" raised ValueError (errored scenario) because it only handled a literal constant — this was the missed third WS5 step file (the config + consolidated steps were already reconciled). Add the same pyproject `[tool.coverage.report].fail_under` fallback used by coverage_threshold_config_steps.py, and fix the phantom-97 in the feature description prose. Verified: the four coverage feature files now pass (136 scenarios, 0 errored); previously unit_tests errored on coverage_threshold_enforcement.feature:7. Co-Authored-By: Claude Opus 4.8 (1M context) --- .../coverage_threshold_enforcement.feature | 2 +- .../coverage_threshold_enforcement_steps.py | 22 ++++++++++++++++++- 2 files changed, 22 insertions(+), 2 deletions(-) diff --git a/features/coverage_threshold_enforcement.feature b/features/coverage_threshold_enforcement.feature index e27b1b061..70acfd0f8 100644 --- a/features/coverage_threshold_enforcement.feature +++ b/features/coverage_threshold_enforcement.feature @@ -1,5 +1,5 @@ Feature: Coverage threshold enforcement - The project enforces a minimum 97% code coverage threshold. + The project enforces a minimum 96.5% code coverage threshold. This feature validates that the coverage configuration and nox session are properly set up to enforce this requirement. diff --git a/features/steps/coverage_threshold_enforcement_steps.py b/features/steps/coverage_threshold_enforcement_steps.py index 7c1a3b40d..1aa314c24 100644 --- a/features/steps/coverage_threshold_enforcement_steps.py +++ b/features/steps/coverage_threshold_enforcement_steps.py @@ -41,6 +41,18 @@ def step_ci_workflow_exists(context: Any) -> None: context.ci_file_content = ci_file.read_text(encoding="utf-8") +def _pyproject_fail_under() -> float | None: + """The canonical coverage floor: pyproject ``[tool.coverage.report]`` + ``fail_under`` (the single source the noxfile delegates to via + ``_read_coverage_fail_under()``). None if absent/unreadable.""" + pyproject = PROJECT_ROOT / "pyproject.toml" + try: + data = tomllib.loads(pyproject.read_text(encoding="utf-8")) + return float(data["tool"]["coverage"]["report"]["fail_under"]) + except (OSError, KeyError, TypeError, ValueError, tomllib.TOMLDecodeError): + return None + + @when("I parse the COVERAGE_THRESHOLD constant from noxfile.py") def step_parse_threshold(context: Any) -> None: content = context.noxfile_content @@ -55,8 +67,16 @@ def step_parse_threshold(context: Any) -> None: and isinstance(node.value, ast.Constant) ): threshold = node.value.value + # WS5: the noxfile now delegates the constant to pyproject's single source + # (``COVERAGE_THRESHOLD = _read_coverage_fail_under()``), so the AST value is + # a Call, not a Constant. Resolve the effective floor from pyproject. if threshold is None: - raise ValueError("COVERAGE_THRESHOLD constant not found in noxfile.py") + threshold = _pyproject_fail_under() + if threshold is None: + raise ValueError( + "COVERAGE_THRESHOLD not found in noxfile.py and pyproject " + "[tool.coverage.report].fail_under is unreadable" + ) context.coverage_threshold = threshold