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
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
468 lines
21 KiB
Gherkin
468 lines
21 KiB
Gherkin
Feature: Semantic validation service
|
|
As a plan executor
|
|
I want semantic validation checks for Python source code
|
|
So that syntax errors, missing imports, and broken references are caught early
|
|
|
|
Background:
|
|
Given a semantic validation test environment
|
|
|
|
# ── Severity enum ──────────────────────────────────────────────
|
|
|
|
Scenario: SemanticValidationSeverity has three levels
|
|
Then the semantic severity enum has values "info", "warn", "error"
|
|
|
|
# ── SemanticCheckResult model ──────────────────────────────────
|
|
|
|
Scenario: SemanticCheckResult normalised output has passed message data
|
|
Given a semantic check result with passed true and message "All good"
|
|
Then the semantic check result passed is true
|
|
And the semantic check result message is "All good"
|
|
And the semantic check result data is none
|
|
|
|
Scenario: SemanticCheckResult with failure and data
|
|
Given a semantic check result with passed false and message "Error found" and data
|
|
Then the semantic check result passed is false
|
|
And the semantic check result has data key "detail"
|
|
|
|
# ── SyntaxCheckRule ────────────────────────────────────────────
|
|
|
|
Scenario: SyntaxCheckRule passes for valid Python
|
|
Given valid Python source code
|
|
When the syntax check rule runs
|
|
Then the semantic check result passed is true
|
|
|
|
Scenario: SyntaxCheckRule fails for invalid Python
|
|
Given Python source code with a syntax error
|
|
When the syntax check rule runs
|
|
Then the semantic check result passed is false
|
|
And the semantic check result message contains "Syntax error"
|
|
And the semantic check result severity is "error"
|
|
|
|
# ── MissingImportRule ──────────────────────────────────────────
|
|
|
|
Scenario: MissingImportRule passes for valid imports
|
|
Given Python source with standard imports
|
|
When the missing import rule runs
|
|
Then the semantic check result passed is true
|
|
|
|
Scenario: MissingImportRule flags suspicious private imports
|
|
Given Python source with suspicious private imports
|
|
When the missing import rule runs
|
|
Then the semantic check result passed is false
|
|
And the semantic check result message contains "suspicious import"
|
|
|
|
Scenario: MissingImportRule skips on syntax error
|
|
Given Python source code with a syntax error
|
|
When the missing import rule runs
|
|
Then the semantic check result passed is true
|
|
And the semantic check result message contains "Skipped"
|
|
|
|
# ── BrokenReferenceRule ────────────────────────────────────────
|
|
|
|
Scenario: BrokenReferenceRule passes for well-defined code
|
|
Given Python source with all names defined
|
|
When the broken reference rule runs
|
|
Then the semantic check result passed is true
|
|
|
|
Scenario: BrokenReferenceRule flags undefined names
|
|
Given Python source with undefined references
|
|
When the broken reference rule runs
|
|
Then the semantic check result passed is false
|
|
And the semantic check result message contains "broken reference"
|
|
|
|
# ── DependencyCycleRule ────────────────────────────────────────
|
|
|
|
Scenario: DependencyCycleRule passes for no duplicate relative imports
|
|
Given Python source with unique relative imports
|
|
When the dependency cycle rule runs
|
|
Then the semantic check result passed is true
|
|
|
|
Scenario: DependencyCycleRule flags duplicate relative imports
|
|
Given Python source with duplicate relative imports
|
|
When the dependency cycle rule runs
|
|
Then the semantic check result passed is false
|
|
And the semantic check result message contains "Duplicate relative imports"
|
|
|
|
# ── APIMisuseRule ──────────────────────────────────────────────
|
|
|
|
Scenario: APIMisuseRule passes for clean code
|
|
Given Python source without API misuse
|
|
When the API misuse rule runs
|
|
Then the semantic check result passed is true
|
|
|
|
Scenario: APIMisuseRule flags eval usage
|
|
Given Python source using eval
|
|
When the API misuse rule runs
|
|
Then the semantic check result passed is false
|
|
And the semantic check result message contains "API misuse"
|
|
|
|
# ── MissingSymbolRule ──────────────────────────────────────────
|
|
|
|
Scenario: MissingSymbolRule passes for well-scoped functions
|
|
Given Python source with well-scoped functions
|
|
When the missing symbol rule runs
|
|
Then the semantic check result passed is true
|
|
|
|
Scenario: MissingSymbolRule flags undefined symbols in functions
|
|
Given Python source with undefined symbols in functions
|
|
When the missing symbol rule runs
|
|
Then the semantic check result passed is false
|
|
And the semantic check result message contains "missing symbol"
|
|
|
|
# ── SemanticRuleRegistry ───────────────────────────────────────
|
|
|
|
Scenario: Default registry has all six built-in rules
|
|
Given a default semantic rule registry
|
|
Then the registry has 6 rules
|
|
And the registry contains rule "syntax_error"
|
|
And the registry contains rule "missing_import"
|
|
And the registry contains rule "broken_reference"
|
|
And the registry contains rule "duplicate_import"
|
|
And the registry contains rule "api_misuse"
|
|
And the registry contains rule "missing_symbol"
|
|
|
|
Scenario: Registry lookup returns None for unknown rule
|
|
Given a default semantic rule registry
|
|
Then the registry returns None for rule "nonexistent"
|
|
|
|
Scenario: Registry remove returns true for existing rule
|
|
Given a default semantic rule registry
|
|
When the rule "syntax_error" is removed from the registry
|
|
Then the registry has 5 rules
|
|
And the registry returns None for rule "syntax_error"
|
|
|
|
Scenario: Registry remove returns false for unknown rule
|
|
Given a default semantic rule registry
|
|
Then removing rule "nonexistent" returns false
|
|
|
|
# ── SemanticValidationCache ────────────────────────────────────
|
|
|
|
Scenario: Cache returns None for uncached entries
|
|
Given a semantic validation cache
|
|
Then the cache returns None for rule "check" and hash "abc123"
|
|
|
|
Scenario: Cache stores and retrieves results by rule and hash
|
|
Given a semantic validation cache
|
|
And a cached semantic result for rule "check" and hash "abc123"
|
|
Then the cache returns the result for rule "check" and hash "abc123"
|
|
|
|
Scenario: Cache invalidation removes entries
|
|
Given a semantic validation cache
|
|
And a cached semantic result for rule "check" and hash "abc123"
|
|
When the cache entry for rule "check" and hash "abc123" is invalidated
|
|
Then the cache returns None for rule "check" and hash "abc123"
|
|
|
|
Scenario: Cache clear removes all entries
|
|
Given a semantic validation cache
|
|
And a cached semantic result for rule "a" and hash "h1"
|
|
And a cached semantic result for rule "b" and hash "h2"
|
|
When the semantic cache is cleared
|
|
Then the semantic cache has 0 entries
|
|
|
|
Scenario: Cache compute_hash returns consistent SHA-256
|
|
Then the semantic cache hash for "hello" is consistent
|
|
|
|
# ── Severity mapping ───────────────────────────────────────────
|
|
|
|
Scenario: ERROR severity maps to REQUIRED mode
|
|
Then semantic severity "error" maps to validation mode "required"
|
|
|
|
Scenario: WARN severity maps to INFORMATIONAL mode
|
|
Then semantic severity "warn" maps to validation mode "informational"
|
|
|
|
Scenario: INFO severity maps to INFORMATIONAL mode
|
|
Then semantic severity "info" maps to validation mode "informational"
|
|
|
|
Scenario: resolve_severity uses default mapping
|
|
Then resolve_severity for "syntax_error" returns "error"
|
|
And resolve_severity for "missing_import" returns "warn"
|
|
And resolve_severity for "duplicate_import" returns "error"
|
|
And resolve_severity for "missing_symbol" returns "info"
|
|
|
|
Scenario: resolve_severity uses custom mapping
|
|
Given a custom severity mapping with "syntax_error" as "warn"
|
|
Then resolve_severity with custom mapping for "syntax_error" returns "warn"
|
|
|
|
Scenario: resolve_severity falls back to info for unknown rule
|
|
Then resolve_severity for "unknown_rule" returns "info"
|
|
|
|
# ── SemanticValidationService ──────────────────────────────────
|
|
|
|
Scenario: Service check_file runs all rules on valid Python
|
|
Given a semantic validation service
|
|
And valid Python source code
|
|
When the service checks the file
|
|
Then all semantic check results passed
|
|
|
|
Scenario: Service check_file detects syntax error
|
|
Given a semantic validation service
|
|
And Python source code with a syntax error
|
|
When the service checks the file
|
|
Then at least one semantic check result failed
|
|
And a failing result has message containing "Syntax error"
|
|
|
|
Scenario: Service check_file with specific rules
|
|
Given a semantic validation service
|
|
And valid Python source code
|
|
When the service checks the file with rules "syntax_error"
|
|
Then only 1 semantic check result is returned
|
|
|
|
Scenario: Service returns empty when disabled
|
|
Given a semantic validation service with semantic disabled
|
|
And valid Python source code
|
|
When the service checks the file
|
|
Then 0 semantic check results are returned
|
|
|
|
Scenario: Service returns empty for Python when python disabled
|
|
Given a semantic validation service with python disabled
|
|
And valid Python source code
|
|
When the service checks the python file
|
|
Then 0 semantic check results are returned
|
|
|
|
Scenario: Service uses cache for repeated checks
|
|
Given a semantic validation service
|
|
And valid Python source code
|
|
When the service checks the file twice
|
|
Then the cache has entries
|
|
|
|
# ── Pipeline integration ───────────────────────────────────────
|
|
|
|
Scenario: as_pipeline_results returns normalised dicts
|
|
Given a semantic validation service
|
|
And valid Python source code
|
|
When the service returns pipeline results
|
|
Then each pipeline result has keys "passed" and "message" and "data"
|
|
And each pipeline result has key "severity"
|
|
And each pipeline result has key "mode"
|
|
|
|
Scenario: normalise_output returns standard schema
|
|
Given a semantic check result with passed true and message "OK"
|
|
When the result is normalised
|
|
Then the normalised output has keys "passed" and "message" and "data"
|
|
|
|
# ── Config keys ────────────────────────────────────────────────
|
|
|
|
Scenario: Config key constants are defined
|
|
Then config key "validation.semantic.enabled" exists
|
|
And config key "validation.semantic.python.enabled" exists
|
|
And config key "validation.semantic.severity_mapping" exists
|
|
|
|
# ── Required vs informational attachment modes ─────────────────
|
|
|
|
Scenario: Error severity findings integrate as required in pipeline
|
|
Given a semantic validation service
|
|
And Python source code with a syntax error
|
|
When the service returns pipeline results
|
|
Then a pipeline result with severity "error" has mode "required"
|
|
|
|
Scenario: Warn severity findings integrate as informational in pipeline
|
|
Given a semantic validation service
|
|
And Python source using eval
|
|
When the service returns pipeline results
|
|
Then a pipeline result with severity "warn" has mode "informational"
|
|
|
|
# ── BrokenReferenceRule scope-awareness ─────────────────────────
|
|
|
|
Scenario: BrokenReferenceRule passes for function-local variables
|
|
Given Python source with function-local variables
|
|
When the broken reference rule runs
|
|
Then the semantic check result passed is true
|
|
|
|
Scenario: BrokenReferenceRule passes for annotated assignments
|
|
Given Python source with annotated assignment
|
|
When the broken reference rule runs
|
|
Then the semantic check result passed is true
|
|
|
|
Scenario: BrokenReferenceRule passes for with-statement variables
|
|
Given Python source with with-statement variable
|
|
When the broken reference rule runs
|
|
Then the semantic check result passed is true
|
|
|
|
# ── MissingSymbolRule class/nested function support ────────────
|
|
|
|
Scenario: MissingSymbolRule detects undefined symbols in class methods
|
|
Given Python source with a class method using undefined symbol
|
|
When the missing symbol rule runs
|
|
Then the semantic check result passed is false
|
|
And the semantic check result message contains "missing symbol"
|
|
|
|
Scenario: MissingSymbolRule passes for nested function calls
|
|
Given Python source with nested functions
|
|
When the missing symbol rule runs
|
|
Then the semantic check result passed is true
|
|
|
|
# ── MissingSymbolRule comprehension scope ───────────────────────
|
|
|
|
Scenario: MissingSymbolRule does not flag comprehension variables
|
|
Given Python source with comprehension variables
|
|
When the missing symbol rule runs
|
|
Then the semantic check result passed is true
|
|
|
|
# ── DuplicateImportRule alias ──────────────────────────────────
|
|
|
|
Scenario: DuplicateImportRule passes for no duplicate relative imports
|
|
Given Python source with unique relative imports
|
|
When the duplicate import rule runs
|
|
Then the semantic check result passed is true
|
|
|
|
Scenario: DuplicateImportRule flags duplicate relative imports
|
|
Given Python source with duplicate relative imports
|
|
When the duplicate import rule runs
|
|
Then the semantic check result passed is false
|
|
And the semantic check result message contains "Duplicate relative imports"
|
|
|
|
# ── APIMisuseRule AST-based detection ──────────────────────────
|
|
|
|
Scenario: APIMisuseRule flags exec usage
|
|
Given Python source using exec
|
|
When the API misuse rule runs
|
|
Then the semantic check result passed is false
|
|
And the semantic check result message contains "API misuse"
|
|
|
|
Scenario: APIMisuseRule flags os.popen usage
|
|
Given Python source using os.popen
|
|
When the API misuse rule runs
|
|
Then the semantic check result passed is false
|
|
And the semantic check result message contains "API misuse"
|
|
|
|
Scenario: APIMisuseRule flags subprocess.run usage
|
|
Given Python source using subprocess.run
|
|
When the API misuse rule runs
|
|
Then the semantic check result passed is false
|
|
And the semantic check result message contains "API misuse"
|
|
|
|
Scenario: APIMisuseRule ignores eval in string literals
|
|
Given Python source with eval in a string literal
|
|
When the API misuse rule runs
|
|
Then the semantic check result passed is true
|
|
|
|
# ── Non-Python file handling ───────────────────────────────────
|
|
|
|
Scenario: Service returns empty for non-Python files
|
|
Given a semantic validation service
|
|
And valid Python source code
|
|
And a non-Python filename
|
|
When the service checks the non-python file
|
|
Then 0 semantic check results are returned
|
|
|
|
# ── Empty source ───────────────────────────────────────────────
|
|
|
|
Scenario: Service handles empty source code
|
|
Given a semantic validation service
|
|
And empty Python source code
|
|
When the service checks the file
|
|
Then all semantic check results passed
|
|
|
|
# ── Cache with max size ────────────────────────────────────────
|
|
|
|
Scenario: Cache respects max size with LRU eviction
|
|
Given a semantic validation cache with max size 2
|
|
And a cached semantic result for rule "a" and hash "h1"
|
|
And a cached semantic result for rule "b" and hash "h2"
|
|
And a cached semantic result for rule "c" and hash "h3"
|
|
Then the semantic cache has 2 entries
|
|
|
|
# ── Safe initialisation and cleanup ────────────────────────────
|
|
|
|
Scenario: Cache rejects invalid max size on construction
|
|
Then creating a semantic validation cache with max size 0 raises ValueError
|
|
|
|
Scenario: Cache put updates existing entry without growing
|
|
Given a semantic validation cache
|
|
And a cached semantic result for rule "r1" and hash "h1"
|
|
When the cache entry for rule "r1" and hash "h1" is overwritten with message "updated"
|
|
Then the cache returns the updated result for rule "r1" and hash "h1" with message "updated"
|
|
And the semantic cache has 1 entries
|
|
|
|
Scenario: Registry list_rules returns sorted names
|
|
Given a default semantic rule registry
|
|
Then the registry list_rules returns sorted rule names
|
|
|
|
Scenario: Service exposes registry property for inspection
|
|
Given a semantic validation service
|
|
Then the service registry property returns a registry with 6 rules
|
|
|
|
Scenario: resolve_severity falls back to info for invalid severity value
|
|
Given a custom severity mapping with "syntax_error" as "bogus_value"
|
|
Then resolve_severity with custom mapping for "syntax_error" returns "info"
|
|
|
|
Scenario: Service functions with non-dict severity mapping config
|
|
Given a semantic validation service with non-dict severity mapping
|
|
And valid Python source code
|
|
When the service checks the file
|
|
Then all semantic check results passed
|
|
|
|
# ── MissingImportRule from-import form ─────────────────────────
|
|
|
|
Scenario: MissingImportRule flags suspicious from-import with private module
|
|
Given Python source with from-import of private module
|
|
When the missing import rule runs
|
|
Then the semantic check result passed is false
|
|
And the semantic check result message contains "suspicious import"
|
|
|
|
# ── BrokenReferenceRule advanced scope patterns ────────────────
|
|
|
|
Scenario: BrokenReferenceRule passes for code with varargs and kwargs
|
|
Given Python source with varargs and kwargs function
|
|
When the broken reference rule runs
|
|
Then the semantic check result passed is true
|
|
|
|
Scenario: BrokenReferenceRule passes for from-import bindings
|
|
Given Python source with from-import bindings
|
|
When the broken reference rule runs
|
|
Then the semantic check result passed is true
|
|
|
|
Scenario: BrokenReferenceRule passes for except handler variable
|
|
Given Python source with except handler variable
|
|
When the broken reference rule runs
|
|
Then the semantic check result passed is true
|
|
|
|
Scenario: BrokenReferenceRule passes for tuple and starred unpacking
|
|
Given Python source with tuple and starred unpacking
|
|
When the broken reference rule runs
|
|
Then the semantic check result passed is true
|
|
|
|
Scenario: BrokenReferenceRule passes for with-statement without as clause
|
|
Given Python source with with-statement without as clause
|
|
When the broken reference rule runs
|
|
Then the semantic check result passed is true
|
|
|
|
# ── APIMisuseRule safe attribute on flagged module ──────────────
|
|
|
|
Scenario: APIMisuseRule passes for safe method call on flagged module
|
|
Given Python source calling a safe method on a flagged module
|
|
When the API misuse rule runs
|
|
Then the semantic check result passed is true
|
|
|
|
# ── MissingSymbolRule function-local binding patterns ──────────
|
|
|
|
Scenario: MissingSymbolRule passes for function with all parameter kinds
|
|
Given Python source with function using varargs kwonly and kwargs
|
|
When the missing symbol rule runs
|
|
Then the semantic check result passed is true
|
|
|
|
Scenario: MissingSymbolRule passes for function with for-loop variable
|
|
Given Python source with function containing for-loop variable
|
|
When the missing symbol rule runs
|
|
Then the semantic check result passed is true
|
|
|
|
Scenario: MissingSymbolRule passes for function with with-statement variable
|
|
Given Python source with function containing with-statement variable
|
|
When the missing symbol rule runs
|
|
Then the semantic check result passed is true
|
|
|
|
Scenario: MissingSymbolRule passes for function with except handler variable
|
|
Given Python source with function containing except handler variable
|
|
When the missing symbol rule runs
|
|
Then the semantic check result passed is true
|
|
|
|
Scenario: MissingSymbolRule passes for function with local import
|
|
Given Python source with function containing local import
|
|
When the missing symbol rule runs
|
|
Then the semantic check result passed is true
|
|
|
|
Scenario: MissingSymbolRule passes for function with local from-import
|
|
Given Python source with function containing local from-import
|
|
When the missing symbol rule runs
|
|
Then the semantic check result passed is true
|