02250473ad
Fix all failing CI quality gates (lint, unit_tests, format) without suppressing any quality enforcement. Root causes and fixes: 1. Format: features/steps/plan_namespaced_name_tdd_steps.py had trailing whitespace; fixed by running ruff format. 2. Unit tests - A2A JSON-RPC 2.0 migration (commit9c6d6915) renamed A2aRequest fields (operation→method, request_id→id, a2a_version→jsonrpc) and A2aResponse fields (status+data→result, request_id→id) but did not update all step files and feature files: - a2a_jsonrpc_wire_format_steps.py: added use_step_matcher('re') and reset to 'parse' at end to prevent parallel test interference - a2a_facade_wiring_steps.py: updated operation= to method=, .status/.data to .result - a2a_facade_steps.py: updated request_id→id, a2a_version→jsonrpc, A2aResponse(request_id=..., status=...) to new API - m6_facade_steps.py: updated all old API usage - devcontainer_cleanup_steps.py: updated A2aRequest(operation=...) - plan_prompt_command_steps.py: updated A2aRequest(operation=...) - wf03_plan_prompt_confidence_steps.py: updated A2aRequest(operation=...) - consolidated_misc.feature: updated old A2aRequest/A2aResponse scenarios 3. Unit tests - Session CLI output changed (commit0d5d9cf0and others): - 'Session Created' → 'Session created' (lowercase) - 'Session Details' → 'Session Summary' - 'Sessions (N total)' → 'Sessions' - session list JSON: top-level 'total' → nested 'summary.total' - Fixed in: session_cli.feature, session_cli_coverage_boost.feature, session_cli_uncovered_branches.feature, session_list_error.feature, tdd_session_create_persist_steps.py 4. Unit tests - Plan list output changed (commit1a07a891): - 'V3 Lifecycle Plans' → 'Plans' - 'Lifecycle Plans' → 'Plans' - Name column removed (restored in source) - Invariants column removed (restored in source) - Project truncation removed (restored in source) - Fixed in: plan_cli_cancel_revert_coverage.feature, plan_lifecycle_cli_coverage.feature, plan_cli_coverage_boost_steps.py, plan.py (source code restored) 5. Unit tests - Plan apply command now requires ULID (commit300a5d6d): - plan_cli_coverage_r3.feature: updated 'PLAN-001' to valid ULID - plan_cli_coverage_r3_steps.py: added --yes flag, added new step for no-eligible-plans path 6. Unit tests - Various source code bugs: - ThoughtBlock: converted from @dataclass to Pydantic BaseModel (architecture test requires all dataclasses to use Pydantic) - session.py: added DatabaseError handling to export, import, tell commands - database.py: fixed rollback_to() to reuse checkpoint connection for writes - database.py: added _get_checkpoint_conn() helper - check-tls-cert.py: fixed SSLCertVerificationError.reason AttributeError 7. Unit tests - Test step bugs: - error_recovery_coverage_boost_steps.py: fixed invalid ULID _PLAN_ID - session_service_coverage_steps.py: fixed 'sha256:' prefix bug in checksum - database_models_new_coverage_steps.py: added 'name' field to session mock - async_audit_recording_steps.py: fixed Settings(audit_async=False) via env var - coverage_threshold_config_steps.py: added --coverage-min pattern support - m5_acms_smoke_steps.py: updated usage hint text - actor_cli_yaml_steps.py: updated 'Removed actor' → 'Actor removed' - aimodelscredentials_steps.py: set context.imported_class in import step - domain_base_model.feature: added missing 'When I examine model_config' step - tui_first_run_steps.py: fixed module reload to restore cleveragents.tui.* modules after test (prevented patch interference in subsequent tests) - tui_first_run_steps.py: added set_search('') step for empty string - resource_handler_base_coverage_r3_steps.py: use _MinimalHandler instead of DatabaseResourceHandler for NotImplementedError tests - resource_handler_crud.feature: updated to test new DatabaseHandler behavior - resource_handler_sandbox.feature: updated to test new DatabaseHandler behavior - tdd_json_decode_crash_persistence.feature: fixed @tdd_bug → @tdd_issue tags 8. Parallel test interference: - All step files using use_step_matcher('re') now reset to 'parse' at end to prevent global matcher state leaking to subsequent step files
191 lines
7.8 KiB
Python
191 lines
7.8 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:d}")
|
|
def step_noxfile_fail_under(context: Context, threshold: int) -> None:
|
|
"""Assert noxfile has fail-under >= given threshold.
|
|
|
|
Supports both literal ``--fail-under=97`` 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
|
|
|
|
# First try literal --fail-under=N
|
|
matches = re.findall(r"--fail-under=(\d+)", context.noxfile_text)
|
|
if matches:
|
|
max_threshold = max(int(m) for m in matches)
|
|
if max_threshold < threshold:
|
|
raise AssertionError(
|
|
f"fail-under={max_threshold} is below required {threshold}"
|
|
)
|
|
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 = int(str(raw))
|
|
if value < threshold:
|
|
raise AssertionError(
|
|
f"COVERAGE_THRESHOLD={value} is below required {threshold}"
|
|
)
|
|
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:d}")
|
|
def step_nightly_fail_under(context: Context, threshold: int) -> None:
|
|
"""Assert nightly workflow uses fail-under >= 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(int(m) for m in matches)
|
|
if max_threshold < threshold:
|
|
raise AssertionError(
|
|
f"Nightly fail-under={max_threshold} is below required {threshold}"
|
|
)
|