11from __future__ import annotations
22
3+ import json
4+ import tomllib
35from dataclasses import replace
46from pathlib import Path
57from typing import TYPE_CHECKING , Any
@@ -88,6 +90,55 @@ def test_py_software_criterion_control_flow_v1_snapshot(
8890 )
8991
9092
93+ def test_py_software_criterion_control_flow_v1_scenario_benchmark_contract () -> None :
94+ scenario = tomllib .loads ((_SCENARIO / "scenario.toml" ).read_text (encoding = "utf-8" ))
95+ benchmark = tomllib .loads (
96+ (_SCENARIO / "benchmark.toml" ).read_text (encoding = "utf-8" )
97+ )
98+ findings = json .loads (
99+ (_SCENARIO / "expect" / "findings.json" ).read_text (encoding = "utf-8" )
100+ )
101+
102+ assert scenario ["policy_ids" ] == [
103+ "PYTHON-AGENT-CONTROL-FLOW-001" ,
104+ "PYTHON-AGENT-NATIVE-IDIOM-001" ,
105+ ]
106+ assert scenario ["inputs" ] == "inputs"
107+ assert scenario ["expected" ] == "expect"
108+ assert _fixture_files (_SCENARIO / scenario ["inputs" ])
109+ assert _fixture_files (_SCENARIO / scenario ["expected" ])
110+
111+ trigger = scenario ["policy_trigger" ]
112+ expected_rule_ids = trigger ["expected_rule_ids" ]
113+ expected_criteria = trigger ["expected_criteria" ]
114+ assert trigger ["kind" ] == "software-criterion"
115+ assert trigger ["evidence" ] == "expect/findings.json"
116+ assert "inputs/criterion.py" in trigger ["trigger" ]
117+ assert "AI agent" in trigger ["agent_failure_mode" ]
118+ assert "Split" in trigger ["expected_resolution" ]
119+ assert expected_rule_ids == [
120+ "PY-AGENT-POLICY-009" ,
121+ "PY-AGENT-POLICY-010" ,
122+ "PY-AGENT-POLICY-011" ,
123+ ]
124+ assert {finding ["rule_id" ] for finding in findings } == set (expected_rule_ids )
125+ assert {finding ["softwareCriteria" ] for finding in findings } == set (
126+ expected_criteria
127+ )
128+
129+ assert benchmark ["harness" ] == "pytest"
130+ assert (
131+ benchmark ["test" ] == "tests/unit/harness/test_software_criterion_snapshots.py"
132+ )
133+ _assert_benchmark_durations (benchmark )
134+ comparison = benchmark ["input_expected_comparison" ]
135+ assert comparison ["input_total" ] == "19ms"
136+ assert comparison ["expected_total" ] == "8ms"
137+ assert comparison ["input_memory_bytes" ] == 8388608
138+ assert comparison ["expected_memory_bytes" ] == 6291456
139+ assert "low-quality input patterns" in comparison ["interpretation" ]
140+
141+
91142def _copy_inputs (source_dir : Path , destination_dir : Path ) -> None :
92143 for source in source_dir .rglob ("*" ):
93144 if source .is_dir ():
@@ -109,3 +160,24 @@ def _filter_software_criterion_findings(
109160 in {"PY-AGENT-POLICY-009" , "PY-AGENT-POLICY-010" , "PY-AGENT-POLICY-011" }
110161 ),
111162 )
163+
164+
165+ def _fixture_files (root : Path ) -> list [Path ]:
166+ return [path for path in root .rglob ("*" ) if path .is_file ()]
167+
168+
169+ def _assert_benchmark_durations (benchmark : dict [str , Any ]) -> None :
170+ for field in [
171+ "target_total" ,
172+ "max_total" ,
173+ "observed_total" ,
174+ "regression_budget" ,
175+ ]:
176+ value = benchmark [field ]
177+ assert isinstance (value , str )
178+ suffix = next (
179+ suffix for suffix in ("ns" , "us" , "ms" , "s" ) if value .endswith (suffix )
180+ )
181+ assert value .removesuffix (suffix ).isdigit ()
182+ assert benchmark ["memory_budget_bytes" ] > 0
183+ assert benchmark ["observed_memory_bytes" ] > 0
0 commit comments