Files
cleveragents-core/features/steps/coverage_threshold_config_steps.py
T
CoreRasurae cecca72b8e
CI / benchmark-publish (push) Has started running
CI / benchmark-regression (push) Has been skipped
CI / e2e_tests (push) Failing after 14s
CI / helm (push) Failing after 8s
CI / unit_tests (push) Failing after 21s
CI / typecheck (push) Failing after 32s
CI / build (push) Failing after 9s
CI / quality (push) Failing after 24s
CI / integration_tests (push) Failing after 15s
CI / security (push) Failing after 24s
CI / lint (push) Failing after 34s
CI / coverage (push) Has been skipped
CI / docker (push) Has been skipped
CI / push-validation (push) Successful in 20s
CI / status-check (push) Failing after 6s
CI / benchmark-publish (pull_request) Has been skipped
CI / benchmark-regression (pull_request) Failing after 1m11s
CI / coverage (pull_request) Successful in 14m34s
CI / push-validation (pull_request) Successful in 36s
CI / typecheck (pull_request) Successful in 1m14s
CI / integration_tests (pull_request) Successful in 3m42s
CI / e2e_tests (pull_request) Successful in 4m26s
CI / unit_tests (pull_request) Successful in 6m29s
CI / build (pull_request) Successful in 59s
CI / lint (pull_request) Successful in 53s
CI / quality (pull_request) Successful in 56s
CI / security (pull_request) Successful in 1m19s
CI / helm (pull_request) Successful in 53s
CI / docker (pull_request) Successful in 1m46s
CI / status-check (pull_request) Successful in 3s
test(behave): Reduce the coverage level to 96.5%
2026-05-05 02:01:49 +00:00

194 lines
8.0 KiB
Python

"""Step definitions for coverage threshold configuration feature."""
import re
from pathlib import Path
from behave import given, then
from behave.runner import Context
@given("the pyproject toml file is loaded for coverage check")
def step_load_pyproject_for_coverage(context: Context) -> None:
"""Load and parse pyproject.toml for coverage configuration checks."""
toml_path = Path(__file__).resolve().parent.parent.parent / "pyproject.toml"
if not toml_path.exists():
raise FileNotFoundError(f"pyproject.toml not found at {toml_path}")
context.pyproject_text = toml_path.read_text(encoding="utf-8")
@given("the noxfile py is loaded for coverage check")
def step_load_noxfile_for_coverage(context: Context) -> None:
"""Load noxfile.py for coverage session checks."""
nox_path = Path(__file__).resolve().parent.parent.parent / "noxfile.py"
if not nox_path.exists():
raise FileNotFoundError(f"noxfile.py not found at {nox_path}")
context.noxfile_text = nox_path.read_text(encoding="utf-8")
@given("the ci workflow file is loaded for coverage check")
def step_load_ci_workflow_for_coverage(context: Context) -> None:
"""Load CI workflow file for coverage enforcement checks."""
ci_path = (
Path(__file__).resolve().parent.parent.parent
/ ".forgejo"
/ "workflows"
/ "ci.yml"
)
if not ci_path.exists():
raise FileNotFoundError(f"CI workflow not found at {ci_path}")
context.ci_workflow_text = ci_path.read_text(encoding="utf-8")
@given("the nightly quality workflow file is loaded for coverage check")
def step_load_nightly_workflow_for_coverage(context: Context) -> None:
"""Load nightly quality workflow file for coverage threshold checks."""
nightly_path = (
Path(__file__).resolve().parent.parent.parent
/ ".forgejo"
/ "workflows"
/ "nightly-quality.yml"
)
if not nightly_path.exists():
raise FileNotFoundError(f"Nightly workflow not found at {nightly_path}")
context.nightly_workflow_text = nightly_path.read_text(encoding="utf-8")
@then("the coverage run section should exist")
def step_coverage_run_section_exists(context: Context) -> None:
"""Assert [tool.coverage.run] section is present."""
if "[tool.coverage.run]" not in context.pyproject_text:
raise AssertionError("[tool.coverage.run] section missing from pyproject.toml")
@then('the coverage source should include "{source}"')
def step_coverage_source_includes(context: Context, source: str) -> None:
"""Assert coverage source includes the given directory."""
# Parse the source list from pyproject.toml
match = re.search(r"source\s*=\s*\[([^\]]+)\]", context.pyproject_text)
if not match:
raise AssertionError("No source list found in coverage config")
sources = match.group(1)
if f'"{source}"' not in sources:
raise AssertionError(
f'Expected "{source}" in coverage source list, got: {sources}'
)
@then("the coverage branch tracking should be enabled")
def step_coverage_branch_enabled(context: Context) -> None:
"""Assert branch coverage is enabled."""
if "branch = true" not in context.pyproject_text:
raise AssertionError("branch = true not found in coverage config")
@then("the noxfile should contain a fail-under threshold of at least {threshold}")
def step_noxfile_fail_under(context: Context, threshold: str) -> None:
"""Assert noxfile has fail-under >= given threshold.
Supports both literal ``--fail-under=96.5`` and f-string
``f"--fail-under={COVERAGE_THRESHOLD}"`` patterns. When the
f-string form is found, the COVERAGE_THRESHOLD constant value
is resolved from the source.
"""
import ast
threshold_val = float(threshold)
# First try literal --fail-under=N
matches = re.findall(r"--fail-under=([\d.]+)", context.noxfile_text)
if matches:
max_threshold = max(float(m) for m in matches)
if max_threshold < threshold_val:
raise AssertionError(
f"fail-under={max_threshold} is below required {threshold_val}"
)
return
# Fall back: check for f-string referencing COVERAGE_THRESHOLD constant
if (
"fail-under" in context.noxfile_text
and "COVERAGE_THRESHOLD" in context.noxfile_text
):
tree = ast.parse(context.noxfile_text)
for node in ast.walk(tree):
if isinstance(node, ast.Assign):
for target in node.targets:
if (
isinstance(target, ast.Name)
and target.id == "COVERAGE_THRESHOLD"
and isinstance(node.value, ast.Constant)
):
raw = node.value.value
value = float(str(raw))
if value < threshold_val:
raise AssertionError(
f"COVERAGE_THRESHOLD={value} is below required {threshold_val}"
)
return
raise AssertionError("No --fail-under found in noxfile.py")
@then("the noxfile should define a coverage_report session")
def step_noxfile_coverage_session_exists(context: Context) -> None:
"""Assert that a coverage_report session is defined in noxfile."""
if "def coverage_report(" not in context.noxfile_text:
raise AssertionError("coverage_report session not found in noxfile.py")
@then('the coverage html directory should be "{directory}"')
def step_coverage_html_directory(context: Context, directory: str) -> None:
"""Assert HTML coverage output directory."""
if f'directory = "{directory}"' not in context.pyproject_text:
raise AssertionError(
f'Expected directory = "{directory}" in [tool.coverage.html]'
)
@then('the coverage xml output should be "{output}"')
def step_coverage_xml_output(context: Context, output: str) -> None:
"""Assert XML coverage output path."""
if f'output = "{output}"' not in context.pyproject_text:
raise AssertionError(f'Expected output = "{output}" in [tool.coverage.xml]')
@then('the coverage data file should be "{data_file}"')
def step_coverage_data_file(context: Context, data_file: str) -> None:
"""Assert coverage data file path."""
if f'data_file = "{data_file}"' not in context.pyproject_text:
raise AssertionError(
f'Expected data_file = "{data_file}" in [tool.coverage.run]'
)
@then('the coverage omit patterns should include "{pattern}"')
def step_coverage_omit_includes(context: Context, pattern: str) -> None:
"""Assert coverage omit list includes the given pattern."""
if f'"{pattern}"' not in context.pyproject_text:
raise AssertionError(f'Expected "{pattern}" in coverage omit patterns')
@then("the ci workflow should reference nox coverage_report session")
def step_ci_coverage_nox_session(context: Context) -> None:
"""Assert CI workflow runs nox -s coverage_report."""
if "nox -s coverage_report" not in context.ci_workflow_text:
raise AssertionError("CI workflow does not reference 'nox -s coverage_report'")
@then("the nightly workflow should use a fail-under of at least {threshold}")
def step_nightly_fail_under(context: Context, threshold: str) -> None:
"""Assert nightly workflow uses fail-under >= threshold."""
threshold_val = float(threshold)
# Check for --fail-under=N pattern (slipcover/coverage CLI)
matches = re.findall(r"--fail-under=([\d.]+)", context.nightly_workflow_text)
# Also check for --coverage-min N pattern (quality gates script)
matches += re.findall(r"--coverage-min\s+([\d.]+)", context.nightly_workflow_text)
if not matches:
raise AssertionError(
"No --fail-under or --coverage-min found in nightly workflow"
)
max_threshold = max(float(m) for m in matches)
if max_threshold < threshold_val:
raise AssertionError(
f"Nightly fail-under={max_threshold} is below required {threshold_val}"
)