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