返回 CodeWhale
test_check_runtime_contract_budget.py
根目录 / scripts / test_check_runtime_contract_budget.py
1 #!/usr/bin/env python3
2 """Hermetic tests for scripts/check-runtime-contract-budget.py."""
3
4 from __future__ import annotations
5
6 import importlib.util
7 import io
8 import json
9 import os
10 import stat
11 import sys
12 import tempfile
13 import unittest
14 from contextlib import redirect_stderr, redirect_stdout
15 from pathlib import Path
16 from unittest import mock
17
18 ROOT = Path(__file__).resolve().parents[1]
19 SCRIPT = ROOT / "scripts" / "check-runtime-contract-budget.py"
20
21 SPEC = importlib.util.spec_from_file_location("check_runtime_contract_budget", SCRIPT)
22 assert SPEC and SPEC.loader
23 mod = importlib.util.module_from_spec(SPEC)
24 sys.modules[SPEC.name] = mod
25 SPEC.loader.exec_module(mod)
26
27
28 def receipt_fixture() -> dict:
29 def stage(stage_id: str, byte_count: int, delta: int | None = None) -> dict:
30 value = {
31 "bytes": byte_count,
32 "identity_sha256": mod.hashlib.sha256(stage_id.encode()).hexdigest(),
33 }
34 if delta is not None:
35 value["delta_bytes"] = delta
36 return value
37
38 def tool_surface(names: list[str], byte_count: int) -> dict:
39 names = sorted(names)
40 return {
41 "bytes": byte_count,
42 "identity_sha256": mod.tool_identity_digest(names),
43 "tokens_est": (byte_count + 3) // 4,
44 "tool_names": names,
45 "tools": len(names),
46 }
47
48 return {
49 "document_kind": mod.RECEIPT_KIND,
50 "schema_version": mod.SCHEMA_VERSION,
51 "representative_context": {
52 "fixture_id": mod.REPRESENTATIVE_FIXTURE_ID,
53 "prompts_byte_identical": True,
54 "stages": {
55 "base": stage("base", 24000),
56 "project": stage("project", 24100, 100),
57 "instructions": stage("instructions", 24220, 120),
58 "skill": stage("skill", 24360, 140),
59 "memory": stage("memory", 24450, 90),
60 "goal": stage("goal", 24540, 90),
61 "handoff": stage("handoff", 24780, 240),
62 },
63 "system_prompt_blocks": 6,
64 "total_bytes": 24780,
65 "total_tokens_est": 6195,
66 },
67 "skill_discovery": {
68 "first_delta": {
69 "directories_visited": 1,
70 "root_discovery_calls": 1,
71 "skill_md_read_attempts": 1,
72 },
73 "prompts_byte_identical": True,
74 "second_delta": {
75 "directories_visited": 1,
76 "root_discovery_calls": 1,
77 "skill_md_read_attempts": 1,
78 },
79 },
80 "system_prompt": {
81 "modes": {
82 "plan": {
83 "system_prompt_bytes": 24000,
84 "system_prompt_tokens_est": 6000,
85 "system_prompt_blocks": 4,
86 "mode_instructions_bytes": 700,
87 "mode_instructions_tokens_est": 175,
88 },
89 "act": {
90 "system_prompt_bytes": 25040,
91 "system_prompt_tokens_est": 6260,
92 "system_prompt_blocks": 4,
93 "mode_instructions_bytes": 805,
94 "mode_instructions_tokens_est": 202,
95 },
96 "operate": {
97 "system_prompt_bytes": 24500,
98 "system_prompt_tokens_est": 6125,
99 "system_prompt_blocks": 4,
100 "mode_instructions_bytes": 750,
101 "mode_instructions_tokens_est": 188,
102 },
103 }
104 },
105 "tool_catalog": {
106 "surface_profile": mod.TOOL_SURFACE_PROFILE,
107 "modes": {
108 "plan": {
109 "full": tool_surface(["File", "Git", "create_goal"], 18000),
110 "active": tool_surface(["File", "Git"], 12000),
111 },
112 "act": {
113 "full": tool_surface(["Bash", "File", "verify"], 20000),
114 "active": tool_surface(["Bash", "File"], 13000),
115 },
116 "operate": {
117 "full": tool_surface(["File", "Run", "verify"], 21000),
118 "active": tool_surface(["File", "Run"], 14000),
119 },
120 },
121 },
122 }
123
124
125 def set_path(document: dict, path: tuple[str, ...], value: int) -> None:
126 target = document
127 for part in path[:-1]:
128 target = target[part]
129 target[path[-1]] = value
130
131
132 def write_documents(tmp: str, receipt: dict, budget: dict) -> tuple[Path, Path]:
133 receipt_path = Path(tmp) / "receipt.json"
134 budget_path = Path(tmp) / "budget.json"
135 receipt_path.write_text(json.dumps(receipt), encoding="utf-8")
136 budget_path.write_text(json.dumps(budget), encoding="utf-8")
137 return receipt_path, budget_path
138
139
140 class RuntimeContractBudgetTests(unittest.TestCase):
141 def test_equal_fixture_passes_all_ceiling_checks(self) -> None:
142 receipt = receipt_fixture()
143 budget = mod.budget_from_receipt(receipt)
144 self.assertEqual(len(mod.METRICS), 55)
145 self.assertEqual(mod.compare(receipt, budget), ([], []))
146
147 def test_every_owned_metric_rejects_an_increase(self) -> None:
148 budget = mod.budget_from_receipt(receipt_fixture())
149 for path, _label in mod.METRICS:
150 with self.subTest(metric=".".join(path)):
151 receipt = receipt_fixture()
152 current = mod.metric_value(receipt, path, "receipt")
153 set_path(receipt, path, current + 1)
154 if path[-1] == "tools":
155 with self.assertRaisesRegex(
156 mod.RuntimeContractError, "tool_names length"
157 ):
158 mod.compare(receipt, budget)
159 continue
160 increases, decreases = mod.compare(receipt, budget)
161 self.assertEqual([item[0] for item in increases], [".".join(path)])
162 self.assertEqual(decreases, [])
163
164 def test_decrease_passes_and_is_reported_for_tightening(self) -> None:
165 receipt = receipt_fixture()
166 budget = mod.budget_from_receipt(receipt)
167 path = ("system_prompt", "modes", "operate", "system_prompt_bytes")
168 set_path(receipt, path, 24400)
169 increases, decreases = mod.compare(receipt, budget)
170 self.assertEqual(increases, [])
171 self.assertEqual([item[0] for item in decreases], [".".join(path)])
172
173 def test_missing_and_non_integer_metrics_are_rejected(self) -> None:
174 receipt = receipt_fixture()
175 budget = mod.budget_from_receipt(receipt)
176 del receipt["tool_catalog"]["modes"]["act"]["active"]["bytes"]
177 with self.assertRaisesRegex(mod.RuntimeContractError, "active.bytes"):
178 mod.compare(receipt, budget)
179
180 receipt = receipt_fixture()
181 receipt["tool_catalog"]["modes"]["act"]["active"]["bytes"] = True
182 with self.assertRaisesRegex(mod.RuntimeContractError, "non-negative integer"):
183 mod.compare(receipt, budget)
184
185 def test_document_kinds_and_schema_versions_are_distinct_and_required(self) -> None:
186 receipt = receipt_fixture()
187 budget = mod.budget_from_receipt(receipt)
188 with self.assertRaisesRegex(mod.RuntimeContractError, "document_kind"):
189 mod.compare(budget, budget)
190
191 receipt.pop("schema_version")
192 with self.assertRaisesRegex(mod.RuntimeContractError, "schema_version"):
193 mod.compare(receipt, budget)
194
195 receipt = receipt_fixture()
196 budget["schema_version"] = mod.SCHEMA_VERSION + 1
197 with self.assertRaisesRegex(mod.RuntimeContractError, "schema_version"):
198 mod.compare(receipt, budget)
199
200 def test_saved_receipt_requires_byte_identical_prompts(self) -> None:
201 receipt = receipt_fixture()
202 budget = mod.budget_from_receipt(receipt)
203 receipt["skill_discovery"]["prompts_byte_identical"] = False
204 with self.assertRaisesRegex(mod.RuntimeContractError, "must be true"):
205 mod.compare(receipt, budget)
206
207 def test_representative_fixture_identity_and_stability_are_required(self) -> None:
208 receipt = receipt_fixture()
209 budget = mod.budget_from_receipt(receipt)
210 receipt["representative_context"]["fixture_id"] = "ambient-developer-state"
211 with self.assertRaisesRegex(mod.RuntimeContractError, "fixture_id"):
212 mod.compare(receipt, budget)
213
214 receipt = receipt_fixture()
215 receipt["representative_context"]["prompts_byte_identical"] = False
216 with self.assertRaisesRegex(mod.RuntimeContractError, "must be true"):
217 mod.compare(receipt, budget)
218
219 def test_tool_identity_rejects_equal_size_substitution_and_removal(self) -> None:
220 budget = mod.budget_from_receipt(receipt_fixture())
221
222 receipt = receipt_fixture()
223 active = receipt["tool_catalog"]["modes"]["act"]["active"]
224 active["tool_names"] = sorted(["File", "Hash"])
225 active["identity_sha256"] = mod.tool_identity_digest(active["tool_names"])
226 with self.assertRaisesRegex(mod.RuntimeContractError, "identity changed"):
227 mod.compare(receipt, budget)
228
229 receipt = receipt_fixture()
230 full = receipt["tool_catalog"]["modes"]["plan"]["full"]
231 full["tool_names"].remove("Git")
232 full["tools"] -= 1
233 full["identity_sha256"] = mod.tool_identity_digest(full["tool_names"])
234 with self.assertRaisesRegex(mod.RuntimeContractError, "identity changed"):
235 mod.compare(receipt, budget)
236
237 def test_tool_identity_rejects_missing_structure_and_wrong_mode_swap(self) -> None:
238 budget = mod.budget_from_receipt(receipt_fixture())
239
240 receipt = receipt_fixture()
241 del receipt["tool_catalog"]["modes"]["operate"]["full"]["tool_names"]
242 with self.assertRaisesRegex(mod.RuntimeContractError, "missing required field"):
243 mod.compare(receipt, budget)
244
245 receipt = receipt_fixture()
246 plan = receipt["tool_catalog"]["modes"]["plan"]["active"]
247 act = receipt["tool_catalog"]["modes"]["act"]["active"]
248 for field in ["tool_names", "identity_sha256"]:
249 plan[field], act[field] = act[field], plan[field]
250 with self.assertRaisesRegex(mod.RuntimeContractError, "identity changed"):
251 mod.compare(receipt, budget)
252
253 def test_representative_stage_identity_rejects_same_length_substitution(self) -> None:
254 receipt = receipt_fixture()
255 budget = mod.budget_from_receipt(receipt)
256 digest = receipt["representative_context"]["stages"]["project"][
257 "identity_sha256"
258 ]
259 receipt["representative_context"]["stages"]["project"][
260 "identity_sha256"
261 ] = ("0" if digest[0] != "0" else "1") + digest[1:]
262 with self.assertRaisesRegex(mod.RuntimeContractError, "identity changed"):
263 mod.compare(receipt, budget)
264
265 def test_same_resolved_receipt_and_budget_path_is_rejected(self) -> None:
266 budget = mod.budget_from_receipt(receipt_fixture())
267 with tempfile.TemporaryDirectory() as tmp:
268 budget_path = Path(tmp) / "budget.json"
269 nested = Path(tmp) / "nested"
270 nested.mkdir()
271 budget_path.write_text(json.dumps(budget), encoding="utf-8")
272 alias = nested / ".." / "budget.json"
273 errors = io.StringIO()
274 with redirect_stderr(errors):
275 result = mod.main(
276 ["--receipt", str(alias), "--budget", str(budget_path)]
277 )
278 self.assertEqual(result, 2)
279 self.assertIn("distinct filesystem paths", errors.getvalue())
280
281 def test_receipt_path_avoids_running_measurement(self) -> None:
282 receipt = receipt_fixture()
283 budget = mod.budget_from_receipt(receipt)
284 with tempfile.TemporaryDirectory() as tmp:
285 receipt_path, budget_path = write_documents(tmp, receipt, budget)
286 output = io.StringIO()
287 with (
288 mock.patch.object(
289 mod, "run_measurement", side_effect=AssertionError("must stay hermetic")
290 ),
291 redirect_stdout(output),
292 ):
293 result = mod.main(
294 ["--receipt", str(receipt_path), "--budget", str(budget_path)]
295 )
296 self.assertEqual(result, 0)
297 self.assertIn("all 55 metrics are exactly at budget", output.getvalue())
298
299 def test_default_measurement_forces_cargo_offline(self) -> None:
300 receipt = receipt_fixture()
301 completed = mock.Mock(returncode=0, stdout=json.dumps(receipt), stderr="")
302 with mock.patch.object(mod.subprocess, "run", return_value=completed) as run:
303 measured = mod.run_measurement()
304 self.assertEqual(measured, receipt)
305 command = run.call_args.args[0]
306 environment = run.call_args.kwargs["env"]
307 self.assertEqual(command, [sys.executable, str(mod.MEASURE_SCRIPT)])
308 self.assertEqual(environment["CARGO_NET_OFFLINE"], "true")
309
310 def test_cli_decrease_prints_a_tightening_command(self) -> None:
311 receipt = receipt_fixture()
312 budget = mod.budget_from_receipt(receipt)
313 set_path(receipt, ("system_prompt", "modes", "plan", "system_prompt_bytes"), 23000)
314 with tempfile.TemporaryDirectory() as tmp:
315 receipt_path, budget_path = write_documents(tmp, receipt, budget)
316 output = io.StringIO()
317 with redirect_stdout(output):
318 result = mod.main(
319 ["--receipt", str(receipt_path), "--budget", str(budget_path)]
320 )
321 self.assertEqual(result, 0)
322 self.assertIn("1 can be tightened", output.getvalue())
323 self.assertIn("--update", output.getvalue())
324
325 def test_update_tightens_decreases_and_preserves_permissions(self) -> None:
326 receipt = receipt_fixture()
327 budget = mod.budget_from_receipt(receipt)
328 path = ("tool_catalog", "modes", "plan", "active", "bytes")
329 set_path(receipt, path, 11000)
330 with tempfile.TemporaryDirectory() as tmp:
331 receipt_path, budget_path = write_documents(tmp, receipt, budget)
332 os.chmod(budget_path, 0o640)
333 with redirect_stdout(io.StringIO()):
334 result = mod.main(
335 [
336 "--receipt",
337 str(receipt_path),
338 "--budget",
339 str(budget_path),
340 "--update",
341 ]
342 )
343 updated = json.loads(budget_path.read_text(encoding="utf-8"))
344 updated_mode = stat.S_IMODE(budget_path.stat().st_mode)
345 self.assertEqual(result, 0)
346 self.assertEqual(mod.metric_value(updated, path, "budget"), 11000)
347 self.assertEqual(updated_mode, 0o640)
348
349 def test_atomic_update_failure_leaves_original_and_removes_temporary_file(self) -> None:
350 budget = mod.budget_from_receipt(receipt_fixture())
351 replacement = mod.budget_from_receipt(receipt_fixture())
352 replacement["_comment"] = "replacement"
353 with tempfile.TemporaryDirectory() as tmp:
354 budget_path = Path(tmp) / "budget.json"
355 original = json.dumps(budget)
356 budget_path.write_text(original, encoding="utf-8")
357 with (
358 mock.patch.object(mod.os, "replace", side_effect=OSError("stop")),
359 self.assertRaisesRegex(OSError, "stop"),
360 ):
361 mod.write_budget_atomic(budget_path, replacement)
362 after = budget_path.read_text(encoding="utf-8")
363 temporary_files = list(Path(tmp).glob(".budget.json.*.tmp"))
364 self.assertEqual(after, original)
365 self.assertEqual(temporary_files, [])
366
367 def test_update_refuses_an_increase_without_rewriting_budget(self) -> None:
368 receipt = receipt_fixture()
369 budget = mod.budget_from_receipt(receipt)
370 path = ("skill_discovery", "second_delta", "directories_visited")
371 set_path(receipt, path, 2)
372 with tempfile.TemporaryDirectory() as tmp:
373 receipt_path, budget_path = write_documents(tmp, receipt, budget)
374 original = budget_path.read_text(encoding="utf-8")
375 with redirect_stderr(io.StringIO()):
376 result = mod.main(
377 [
378 "--receipt",
379 str(receipt_path),
380 "--budget",
381 str(budget_path),
382 "--update",
383 ]
384 )
385 after = budget_path.read_text(encoding="utf-8")
386 self.assertEqual(result, 1)
387 self.assertEqual(after, original)
388
389
390 if __name__ == "__main__":
391 raise SystemExit(unittest.main())
392
392 lines PYTHON