Files
cleveragents-core/robot/helper_semantic_validation.py
CoreRasurae a2be3e67b0
CI / benchmark-publish (pull_request) Has been skipped
CI / lint (pull_request) Successful in 13s
CI / build (pull_request) Successful in 17s
CI / quality (pull_request) Successful in 18s
CI / security (pull_request) Successful in 30s
CI / typecheck (pull_request) Successful in 59s
CI / integration_tests (pull_request) Successful in 4m40s
CI / unit_tests (pull_request) Successful in 11m32s
CI / docker (pull_request) Successful in 1m1s
CI / benchmark-regression (pull_request) Successful in 28m54s
CI / coverage (pull_request) Successful in 44m11s
CI / lint (push) Successful in 12s
CI / build (push) Successful in 14s
CI / quality (push) Successful in 16s
CI / typecheck (push) Successful in 31s
CI / benchmark-regression (push) Has been skipped
CI / security (push) Successful in 42s
CI / integration_tests (push) Successful in 2m53s
CI / unit_tests (push) Successful in 11m20s
CI / docker (push) Successful in 9s
CI / benchmark-publish (push) Successful in 12m18s
CI / coverage (push) Successful in 43m33s
feat(validation): add semantic validation service
Added SemanticValidationService with built-in Python checks for syntax
errors, missing imports, broken references, dependency cycles, API misuse,
and missing symbols. Implemented SemanticRuleRegistry for extensible
validator registration. Integrated with ValidationPipeline as informational
by default. Added per-project/per-plan config keys, severity mapping
(info/warn/error), output schema normalization, and file-hash-based caching.

Includes Behave BDD scenarios, Robot integration tests, ASV benchmarks,
and reference documentation.

ISSUES CLOSED: #207
2026-02-28 17:46:54 +00:00

287 lines
8.4 KiB
Python

"""Helper script for Robot Framework semantic validation tests.
Self-contained Python helper that exercises the SemanticValidationService
and related components, printing sentinel strings on success and exiting
with code 1 on failure.
"""
from __future__ import annotations
import sys
from collections.abc import Callable
from pathlib import Path
# Ensure src is importable when run from workspace root
sys.path.insert(0, str(Path(__file__).resolve().parents[1] / "src"))
from cleveragents.application.services.semantic_validation_rules import (
APIMisuseRule,
BrokenReferenceRule,
DuplicateImportRule,
MissingSymbolRule,
SemanticCheckResult,
SemanticValidationSeverity,
SyntaxCheckRule,
)
from cleveragents.application.services.semantic_validation_service import (
CONFIG_KEY_ENABLED,
SemanticValidationCache,
SemanticValidationService,
create_default_registry,
map_severity_to_mode,
)
from cleveragents.domain.models.core.tool import ValidationMode
# ---------------------------------------------------------------------------
# Source code fixtures
# ---------------------------------------------------------------------------
VALID_PYTHON = """\
import os
x = 10
def hello():
return x
"""
SYNTAX_ERROR_PYTHON = """\
def broken(
return 42
"""
EVAL_PYTHON = """\
result = eval("1 + 2")
"""
FUNC_LOCAL_PYTHON = """\
def process(items):
result = []
for item in items:
result.append(item)
return result
"""
CLASS_METHOD_PYTHON = """\
class MyClass:
def method(self):
return undefined_xyz
"""
# ---------------------------------------------------------------------------
# Sub-commands
# ---------------------------------------------------------------------------
def test_models() -> None:
"""Verify SemanticCheckResult and SemanticValidationSeverity."""
result = SemanticCheckResult(
passed=True,
message="All good",
severity=SemanticValidationSeverity.INFO,
)
assert result.passed is True
assert result.message == "All good"
assert result.severity == SemanticValidationSeverity.INFO
assert result.data is None
assert SemanticValidationSeverity.INFO == "info"
assert SemanticValidationSeverity.WARN == "warn"
assert SemanticValidationSeverity.ERROR == "error"
print("models-ok")
def test_syntax_pass() -> None:
"""SyntaxCheckRule passes for valid Python."""
rule = SyntaxCheckRule()
result = rule.check(VALID_PYTHON, "test.py")
assert result.passed is True
print("syntax-pass-ok")
def test_syntax_fail() -> None:
"""SyntaxCheckRule fails for invalid Python."""
rule = SyntaxCheckRule()
result = rule.check(SYNTAX_ERROR_PYTHON, "test.py")
assert result.passed is False
assert "Syntax error" in result.message
print("syntax-fail-ok")
def test_registry() -> None:
"""Default registry has all six built-in rules."""
registry = create_default_registry()
assert len(registry) == 6
names = registry.list_rules()
assert "syntax_error" in names
assert "missing_import" in names
assert "broken_reference" in names
assert "duplicate_import" in names
assert "api_misuse" in names
assert "missing_symbol" in names
print("registry-ok")
def test_cache() -> None:
"""Cache stores and retrieves correctly."""
cache = SemanticValidationCache()
assert cache.get("rule", "hash1") is None
result = SemanticCheckResult(passed=True, message="cached")
cache.put("rule", "hash1", result)
cached = cache.get("rule", "hash1")
assert cached is not None
assert cached.message == "cached"
cache.invalidate("rule", "hash1")
assert cache.get("rule", "hash1") is None
h1 = SemanticValidationCache.compute_hash("hello")
h2 = SemanticValidationCache.compute_hash("hello")
assert h1 == h2
assert len(h1) == 64
print("cache-ok")
def test_service_check() -> None:
"""Service runs all rules."""
service = SemanticValidationService()
results = service.check_file(VALID_PYTHON, "test.py")
assert len(results) > 0
assert all(r.passed for r in results)
print("service-check-ok")
def test_severity_mapping() -> None:
"""Severity maps to correct validation mode."""
assert (
map_severity_to_mode(SemanticValidationSeverity.ERROR)
== ValidationMode.REQUIRED
)
assert (
map_severity_to_mode(SemanticValidationSeverity.WARN)
== ValidationMode.INFORMATIONAL
)
assert (
map_severity_to_mode(SemanticValidationSeverity.INFO)
== ValidationMode.INFORMATIONAL
)
print("severity-mapping-ok")
def test_pipeline_integration() -> None:
"""as_pipeline_results returns dicts with expected keys."""
service = SemanticValidationService()
results = service.as_pipeline_results(VALID_PYTHON, "test.py")
assert len(results) > 0
for r in results:
assert "passed" in r
assert "message" in r
assert "data" in r
assert "severity" in r
assert "mode" in r
print("pipeline-integration-ok")
def test_config_disabled() -> None:
"""Service returns empty when disabled."""
service = SemanticValidationService(config={CONFIG_KEY_ENABLED: False})
results = service.check_file(VALID_PYTHON, "test.py")
assert len(results) == 0
print("config-disabled-ok")
def test_api_misuse() -> None:
"""APIMisuseRule detects eval usage via AST analysis."""
rule = APIMisuseRule()
result = rule.check(EVAL_PYTHON, "test.py")
assert result.passed is False
assert "API misuse" in result.message
print("api-misuse-ok")
def test_broken_ref_scope() -> None:
"""BrokenReferenceRule does not false-positive on function locals."""
rule = BrokenReferenceRule()
result = rule.check(FUNC_LOCAL_PYTHON, "test.py")
assert result.passed is True, f"Expected pass, got: {result.message}"
print("broken-ref-scope-ok")
def test_missing_symbol_class() -> None:
"""MissingSymbolRule detects undefined symbols in class methods."""
rule = MissingSymbolRule()
result = rule.check(CLASS_METHOD_PYTHON, "test.py")
assert result.passed is False, f"Expected fail, got: {result.message}"
assert "missing symbol" in result.message
print("missing-symbol-class-ok")
def test_cache_lru() -> None:
"""Cache evicts LRU entries when max_size is exceeded."""
cache = SemanticValidationCache(max_size=2)
r = SemanticCheckResult(passed=True, message="ok")
cache.put("a", "h1", r)
cache.put("b", "h2", r)
cache.put("c", "h3", r)
assert len(cache) == 2
assert cache.get("a", "h1") is None # evicted
assert cache.get("b", "h2") is not None
assert cache.get("c", "h3") is not None
print("cache-lru-ok")
def test_non_python_skip() -> None:
"""Service returns empty for non-Python files."""
service = SemanticValidationService()
results = service.check_file(VALID_PYTHON, "config.yaml")
assert len(results) == 0
print("non-python-skip-ok")
def test_duplicate_import_alias() -> None:
"""DuplicateImportRule is accessible via DependencyCycleRule alias."""
rule = DuplicateImportRule()
assert rule.name == "duplicate_import"
print("duplicate-import-alias-ok")
# ---------------------------------------------------------------------------
# Main
# ---------------------------------------------------------------------------
_COMMANDS: dict[str, Callable[[], None]] = {
"models": test_models,
"syntax_pass": test_syntax_pass,
"syntax_fail": test_syntax_fail,
"registry": test_registry,
"cache": test_cache,
"service_check": test_service_check,
"severity_mapping": test_severity_mapping,
"pipeline_integration": test_pipeline_integration,
"config_disabled": test_config_disabled,
"api_misuse": test_api_misuse,
"broken_ref_scope": test_broken_ref_scope,
"missing_symbol_class": test_missing_symbol_class,
"cache_lru": test_cache_lru,
"non_python_skip": test_non_python_skip,
"duplicate_import_alias": test_duplicate_import_alias,
}
if __name__ == "__main__":
if len(sys.argv) < 2:
print("Usage: helper_semantic_validation.py <command>", file=sys.stderr)
sys.exit(1)
cmd = sys.argv[1]
if cmd not in _COMMANDS:
print(f"Unknown command: {cmd}", file=sys.stderr)
sys.exit(1)
try:
_COMMANDS[cmd]()
except Exception as exc:
print(f"FAILED: {exc}", file=sys.stderr)
sys.exit(1)