Files
cleveragents-core/features/steps/semantic_validation_suite_steps.py

174 lines
5.7 KiB
Python

"""Step definitions for semantic validation suite scenarios.
Loads JSON fixture files covering language-porting mismatches, dependency
graph violations, API surface changes, cross-file symbol resolution,
and circular import detection. Runs the appropriate semantic validation
rule against each fixture and asserts the expected outcome.
Uses :mod:`features.fixtures.validation.suite_helpers` for shared
loading, lookup, and verification logic.
"""
from __future__ import annotations
import sys
from pathlib import Path
from typing import TYPE_CHECKING, Any
from behave import given, then, when
if TYPE_CHECKING:
from behave.runner import Context
# Ensure the fixture helpers package is importable
_FIXTURES_PARENT = str(Path(__file__).resolve().parents[1] / "fixtures" / "validation")
if _FIXTURES_PARENT not in sys.path:
sys.path.insert(0, _FIXTURES_PARENT)
from suite_helpers import ( # noqa: E402
API_FILENAME,
CIRCULAR_FILENAME,
CROSS_FILE_FILENAME,
DEPENDENCY_FILENAME,
PORTING_FILENAME,
find_fixture,
load_fixtures,
validate_fixture_schema,
verify_fixture_result,
)
# ---------------------------------------------------------------------------
# Lazy fixture cache (FLAW-3 fix: avoid module-level loading)
# ---------------------------------------------------------------------------
_fixture_cache: dict[str, list[dict[str, Any]]] = {}
def _get_fixtures(filename: str) -> list[dict[str, Any]]:
"""Lazily load and cache fixtures by filename."""
if filename not in _fixture_cache:
_fixture_cache[filename] = load_fixtures(filename)
return _fixture_cache[filename]
# ---------------------------------------------------------------------------
# Background
# ---------------------------------------------------------------------------
@given("a semantic validation suite test environment")
def step_suite_env(context: Context) -> None:
context.suite_fixture = None
context.suite_result = None
# ---------------------------------------------------------------------------
# Given steps - load individual fixtures
# ---------------------------------------------------------------------------
@given('the porting fixture "{name}"')
def step_porting_fixture(context: Context, name: str) -> None:
context.suite_fixture = find_fixture(_get_fixtures(PORTING_FILENAME), name)
@given('the dependency fixture "{name}"')
def step_dependency_fixture(context: Context, name: str) -> None:
context.suite_fixture = find_fixture(_get_fixtures(DEPENDENCY_FILENAME), name)
@given('the API surface fixture "{name}"')
def step_api_fixture(context: Context, name: str) -> None:
context.suite_fixture = find_fixture(_get_fixtures(API_FILENAME), name)
@given('the cross-file fixture "{name}"')
def step_cross_file_fixture(context: Context, name: str) -> None:
context.suite_fixture = find_fixture(_get_fixtures(CROSS_FILE_FILENAME), name)
@given('the circular import fixture "{name}"')
def step_circular_fixture(context: Context, name: str) -> None:
context.suite_fixture = find_fixture(_get_fixtures(CIRCULAR_FILENAME), name)
# ---------------------------------------------------------------------------
# When step - run the expected rule
# ---------------------------------------------------------------------------
@when("the suite runs the expected rule")
def step_run_expected_rule(context: Context) -> None:
from suite_helpers import RULE_MAP
fixture = context.suite_fixture
if fixture is None:
msg = "No fixture loaded"
raise RuntimeError(msg)
rule_name: str = fixture["expected_rule"]
rule = RULE_MAP[rule_name]
source: str = fixture["source"]
filename: str = fixture["filename"]
context.suite_result = rule.check(source, filename)
# ---------------------------------------------------------------------------
# Then steps - verify outcomes (with data field verification: FLAW-2 fix)
# ---------------------------------------------------------------------------
@then("the suite result matches the fixture expectation")
def step_verify_result(context: Context) -> None:
fixture = context.suite_fixture
result = context.suite_result
if fixture is None or result is None:
msg = "Fixture or result not set"
raise RuntimeError(msg)
verify_fixture_result(fixture, result)
# ---------------------------------------------------------------------------
# Fixture-loading checks (FLAW-4 fix: validate schema, not just len > 0)
# ---------------------------------------------------------------------------
@then("all porting fixtures load without error")
def step_all_porting(context: Context) -> None:
fixtures = _get_fixtures(PORTING_FILENAME)
assert len(fixtures) > 0
for f in fixtures:
validate_fixture_schema(f)
@then("all dependency fixtures load without error")
def step_all_dependency(context: Context) -> None:
fixtures = _get_fixtures(DEPENDENCY_FILENAME)
assert len(fixtures) > 0
for f in fixtures:
validate_fixture_schema(f)
@then("all API surface fixtures load without error")
def step_all_api(context: Context) -> None:
fixtures = _get_fixtures(API_FILENAME)
assert len(fixtures) > 0
for f in fixtures:
validate_fixture_schema(f)
@then("all cross-file fixtures load without error")
def step_all_cross_file(context: Context) -> None:
fixtures = _get_fixtures(CROSS_FILE_FILENAME)
assert len(fixtures) > 0
for f in fixtures:
validate_fixture_schema(f)
@then("all circular import fixtures load without error")
def step_all_circular(context: Context) -> None:
fixtures = _get_fixtures(CIRCULAR_FILENAME)
assert len(fixtures) > 0
for f in fixtures:
validate_fixture_schema(f)