Files
cleveragents-core/features/fixtures/validation/suite_helpers.py
CoreRasurae 7e6f6fae37 test(validation): add semantic validation suites
Add comprehensive semantic validation test suites covering five
new fixture categories: language porting mismatches, dependency
graph violations, API surface changes, cross-file symbols, and
circular import detection.

New BDD scenarios (38) exercise all six built-in rules against
fixture-driven inputs.  Robot Framework integration tests (11)
validate end-to-end rule execution via the SemanticValidationService.
ASV benchmarks (6 suites) establish performance baselines for
batch validation throughput and per-rule latency.

Files added:
- features/fixtures/validation/{language_porting_mismatches,
  dependency_graph_violations, api_surface_changes,
  cross_file_symbols, circular_import_detection}.json
- features/semantic_validation_suite.feature
- features/steps/semantic_validation_suite_steps.py
- robot/semantic_validation_suite.robot
- robot/helper_semantic_validation_suite.py
- benchmarks/semantic_validation_suite_bench.py
- docs/reference/semantic_validation_coverage.md

All 11 nox sessions pass (lint, format, typecheck, security_scan,
dead_code, unit_tests, integration_tests, docs, build, benchmark,
coverage_report).  Coverage remains at 97%.

ISSUES CLOSED: #316
2026-03-02 22:05:53 +00:00

174 lines
5.8 KiB
Python

"""Shared helpers for semantic validation fixture suites.
Provides fixture loading, lookup, and rule-map construction used by
Behave steps, Robot Framework helpers, and ASV benchmarks. Centralises
the boilerplate so that adding a new rule or fixture file requires a
single update instead of three.
"""
from __future__ import annotations
import json
from pathlib import Path
from typing import Any
from cleveragents.application.services.semantic_validation_rules import (
APIMisuseRule,
BrokenReferenceRule,
DuplicateImportRule,
MissingImportRule,
MissingSymbolRule,
SemanticCheckResult,
SyntaxCheckRule,
)
from cleveragents.application.services.semantic_validation_service import (
SemanticValidationRule,
)
# ---------------------------------------------------------------------------
# Fixture directory
# ---------------------------------------------------------------------------
FIXTURE_DIR: Path = Path(__file__).resolve().parent
# ---------------------------------------------------------------------------
# Canonical fixture filenames
# ---------------------------------------------------------------------------
PORTING_FILENAME = "language_porting_mismatches.json"
DEPENDENCY_FILENAME = "dependency_graph_violations.json"
API_FILENAME = "api_surface_changes.json"
CROSS_FILE_FILENAME = "cross_file_symbols.json"
CIRCULAR_FILENAME = "circular_import_detection.json"
ALL_FIXTURE_FILENAMES: list[str] = [
PORTING_FILENAME,
DEPENDENCY_FILENAME,
API_FILENAME,
CROSS_FILE_FILENAME,
CIRCULAR_FILENAME,
]
# ---------------------------------------------------------------------------
# Rule map (Protocol-typed)
# ---------------------------------------------------------------------------
RULE_MAP: dict[str, SemanticValidationRule] = {
"syntax_error": SyntaxCheckRule(),
"missing_import": MissingImportRule(),
"broken_reference": BrokenReferenceRule(),
"duplicate_import": DuplicateImportRule(),
"api_misuse": APIMisuseRule(),
"missing_symbol": MissingSymbolRule(),
}
# ---------------------------------------------------------------------------
# Fixture loading & lookup
# ---------------------------------------------------------------------------
def load_fixtures(filename: str) -> list[dict[str, Any]]:
"""Load fixtures from a JSON file in the validation fixtures directory."""
path = FIXTURE_DIR / filename
with open(path, encoding="utf-8") as fh:
data: dict[str, Any] = json.load(fh)
result: list[dict[str, Any]] = data["fixtures"]
return result
def find_fixture(fixtures: list[dict[str, Any]], name: str) -> dict[str, Any]:
"""Find a fixture by name in a fixture list.
Raises ``ValueError`` if not found.
"""
for f in fixtures:
if f["name"] == name:
return f
msg = f"Fixture '{name}' not found"
raise ValueError(msg)
# ---------------------------------------------------------------------------
# Fixture runner
# ---------------------------------------------------------------------------
def run_fixture(fixture: dict[str, Any]) -> SemanticCheckResult:
"""Run a single fixture through its expected rule and return the result."""
rule_name: str = fixture["expected_rule"]
rule = RULE_MAP[rule_name]
source: str = fixture["source"]
filename: str = fixture["filename"]
return rule.check(source, filename)
def run_all_fixtures(fixtures: list[dict[str, Any]]) -> list[SemanticCheckResult]:
"""Run all fixtures through their expected rules."""
return [run_fixture(f) for f in fixtures]
def verify_fixture_result(
fixture: dict[str, Any],
result: SemanticCheckResult,
) -> None:
"""Verify a fixture result against expectations.
Checks ``passed``, optional ``expected_message_contains``, and
optional ``expected_data_contains``. Raises ``AssertionError``
on mismatch.
"""
expected_passed: bool = fixture["expected_passed"]
assert result.passed is expected_passed, (
f"Fixture '{fixture['name']}': expected passed={expected_passed}, "
f"got passed={result.passed}, message='{result.message}'"
)
expected_msg: str | None = fixture.get("expected_message_contains")
if expected_msg is not None:
assert expected_msg in result.message, (
f"Fixture '{fixture['name']}': expected message containing "
f"'{expected_msg}', got '{result.message}'"
)
expected_data: dict[str, object] | None = fixture.get("expected_data_contains")
if expected_data is not None:
assert result.data is not None, (
f"Fixture '{fixture['name']}': expected data containing "
f"{expected_data}, but result.data is None"
)
for key, value in expected_data.items():
assert key in result.data, (
f"Fixture '{fixture['name']}': expected key '{key}' in "
f"result.data, got keys {list(result.data.keys())}"
)
if isinstance(value, str):
assert value in str(result.data[key]), (
f"Fixture '{fixture['name']}': expected data['{key}'] "
f"to contain '{value}', got '{result.data[key]}'"
)
def validate_fixture_schema(fixture: dict[str, Any]) -> None:
"""Validate that a fixture dict has the required keys.
Raises ``AssertionError`` if any required key is missing.
"""
required_keys = {
"name",
"description",
"source",
"filename",
"expected_rule",
"expected_passed",
}
actual_keys = set(fixture.keys())
missing = required_keys - actual_keys
assert not missing, (
f"Fixture '{fixture.get('name', '<unknown>')}' is missing "
f"required keys: {missing}"
)
assert fixture["expected_rule"] in RULE_MAP, (
f"Fixture '{fixture['name']}' references unknown rule "
f"'{fixture['expected_rule']}'"
)