7e6f6fae37
CI / benchmark-publish (pull_request) Has been skipped
CI / lint (pull_request) Successful in 12s
CI / build (pull_request) Successful in 15s
CI / quality (pull_request) Successful in 17s
CI / typecheck (pull_request) Successful in 31s
CI / security (pull_request) Successful in 31s
CI / unit_tests (pull_request) Successful in 1m46s
CI / docker (pull_request) Successful in 38s
CI / integration_tests (pull_request) Successful in 2m52s
CI / coverage (pull_request) Successful in 3m41s
CI / benchmark-regression (pull_request) Successful in 23m24s
CI / lint (push) Successful in 14s
CI / build (push) Successful in 15s
CI / quality (push) Successful in 17s
CI / typecheck (push) Successful in 33s
CI / benchmark-regression (push) Has been skipped
CI / security (push) Successful in 35s
CI / unit_tests (push) Successful in 2m13s
CI / docker (push) Successful in 38s
CI / integration_tests (push) Successful in 2m58s
CI / coverage (push) Successful in 3m44s
CI / benchmark-publish (push) Successful in 13m19s
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
174 lines
5.8 KiB
Python
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']}'"
|
|
)
|