fable-engine 1.3.1__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- fable_compressor.py +356 -0
- fable_engine/__init__.py +1 -0
- fable_engine/actions/__init__.py +291 -0
- fable_engine/actions/cas.py +182 -0
- fable_engine/actions/deliberation.py +523 -0
- fable_engine/actions/fleet.py +807 -0
- fable_engine/actions/lifecycle.py +298 -0
- fable_engine/actions/scrapers.py +116 -0
- fable_engine/actions/system3.py +815 -0
- fable_engine/browser.py +824 -0
- fable_engine/cas.py +974 -0
- fable_engine/fable_session.json +510 -0
- fable_engine/guards.py +283 -0
- fable_engine/schema.py +714 -0
- fable_engine/scrapers/__init__.py +32 -0
- fable_engine/scrapers/arxiv.py +115 -0
- fable_engine/scrapers/base.py +386 -0
- fable_engine/scrapers/github.py +129 -0
- fable_engine/scrapers/reddit.py +154 -0
- fable_engine/scrapers/web.py +120 -0
- fable_engine/scrapers/x.py +125 -0
- fable_engine/scrapers/youtube.py +132 -0
- fable_engine/server.py +414 -0
- fable_engine/session.py +1819 -0
- fable_engine/test_server.py +1362 -0
- fable_engine/updater.py +541 -0
- fable_engine-1.3.1.dist-info/LICENSE +22 -0
- fable_engine-1.3.1.dist-info/METADATA +173 -0
- fable_engine-1.3.1.dist-info/RECORD +104 -0
- fable_engine-1.3.1.dist-info/WHEEL +5 -0
- fable_engine-1.3.1.dist-info/entry_points.txt +5 -0
- fable_engine-1.3.1.dist-info/top_level.txt +6 -0
- fable_mode/__init__.py +3 -0
- fable_mode/__main__.py +4 -0
- fable_mode/adapters.py +1014 -0
- fable_mode/installer.py +553 -0
- fable_mode/launcher.py +437 -0
- fable_mode/manifest.py +142 -0
- fable_mode/resources.json +114 -0
- fable_mode/safety.py +103 -0
- fable_mode_entry.py +10 -0
- fable_v2/__init__.py +146 -0
- fable_v2/adapters.py +151 -0
- fable_v2/coder_fleet/__init__.py +100 -0
- fable_v2/coder_fleet/ast_tools.py +158 -0
- fable_v2/coder_fleet/compute.py +199 -0
- fable_v2/coder_fleet/design_engine.py +1316 -0
- fable_v2/coder_fleet/diagnostics.py +293 -0
- fable_v2/coder_fleet/fleet_dispatcher.py +214 -0
- fable_v2/coder_fleet/mock_auditor.py +306 -0
- fable_v2/coder_fleet/mutation.py +216 -0
- fable_v2/coder_fleet/property_oracle.py +260 -0
- fable_v2/coder_fleet/receipt_attestor.py +122 -0
- fable_v2/coder_fleet/red_team_swarm.py +908 -0
- fable_v2/coder_fleet/test_harness.py +198 -0
- fable_v2/coder_fleet/vector_engine.py +1287 -0
- fable_v2/coder_fleet/visual.py +357 -0
- fable_v2/coder_fleet/workspace.py +153 -0
- fable_v2/cortical/__init__.py +20 -0
- fable_v2/cortical/plasticity_engine.py +992 -0
- fable_v2/execution_broker.py +811 -0
- fable_v2/proof_engine.py +1141 -0
- fable_v2/protocol.py +485 -0
- fable_v2/runtime.py +1010 -0
- fable_v2/system3/__init__.py +204 -0
- fable_v2/system3/causal.py +558 -0
- fable_v2/system3/dialectical.py +577 -0
- fable_v2/system3/evolution.py +503 -0
- fable_v2/system3/executive.py +338 -0
- fable_v2/system3/free_energy.py +479 -0
- fable_v2/system3/hyperbolic.py +555 -0
- fable_v2/system3/induction.py +336 -0
- fable_v2/system3/kripke.py +548 -0
- fable_v2/system3/oracle.py +745 -0
- fable_v2/verifiers.py +72 -0
- tests/__init__.py +1 -0
- tests/test_anti_loop_circuit_breaker.py +64 -0
- tests/test_auto_updater.py +407 -0
- tests/test_coder_fleet.py +535 -0
- tests/test_delegation_compiler.py +54 -0
- tests/test_descriptor_boundaries.py +126 -0
- tests/test_design_engine.py +603 -0
- tests/test_epistemic_evidence_validator.py +66 -0
- tests/test_execution_broker.py +233 -0
- tests/test_fable_v2.py +406 -0
- tests/test_fleet_transitions.py +116 -0
- tests/test_fsm_redteam_evolution.py +406 -0
- tests/test_goal_rubric_and_pipeline.py +367 -0
- tests/test_hebbian_plasticity.py +585 -0
- tests/test_packaging_runtime.py +194 -0
- tests/test_proof_engine.py +259 -0
- tests/test_red_team_swarm.py +645 -0
- tests/test_redteam_remediation.py +169 -0
- tests/test_registration_transaction.py +375 -0
- tests/test_requested_regressions.py +467 -0
- tests/test_scrapers.py +370 -0
- tests/test_server_actions.py +93 -0
- tests/test_server_frontier_actions.py +269 -0
- tests/test_server_protocol.py +88 -0
- tests/test_stealth_browser.py +970 -0
- tests/test_system3.py +381 -0
- tests/test_system3_deep_integration.py +385 -0
- tests/test_system3_frontier.py +436 -0
- tests/test_vector_engine.py +608 -0
|
@@ -0,0 +1,535 @@
|
|
|
1
|
+
"""Comprehensive Unit Test Suite for the 10-Tool Coder Subagent MCP Fleet.
|
|
2
|
+
|
|
3
|
+
Tests all engines:
|
|
4
|
+
- VisualGroundingEngine
|
|
5
|
+
- DiagnosticsEngine
|
|
6
|
+
- TreeSitterCodemodEngine
|
|
7
|
+
- AtomicWorkspaceEngine
|
|
8
|
+
- TestHarnessEngine
|
|
9
|
+
- MutationVerifierEngine
|
|
10
|
+
- MockAuditorEngine
|
|
11
|
+
- PropertyOracleEngine
|
|
12
|
+
- ReceiptAttestorEngine
|
|
13
|
+
- ComputeOrchestratorEngine
|
|
14
|
+
- CoderFleetDispatcher
|
|
15
|
+
"""
|
|
16
|
+
from __future__ import annotations
|
|
17
|
+
|
|
18
|
+
import sys
|
|
19
|
+
import unittest
|
|
20
|
+
from pathlib import Path
|
|
21
|
+
|
|
22
|
+
# Ensure workspace root is in sys.path
|
|
23
|
+
sys.path.insert(0, str(Path(__file__).resolve().parent.parent))
|
|
24
|
+
|
|
25
|
+
from fable_v2.coder_fleet import (
|
|
26
|
+
AtomicWorkspaceEngine,
|
|
27
|
+
CoderFleetDispatcher,
|
|
28
|
+
ComputeOrchestratorEngine,
|
|
29
|
+
DiagnosticsEngine,
|
|
30
|
+
MockAuditorEngine,
|
|
31
|
+
MutationVerifierEngine,
|
|
32
|
+
PropertyOracleEngine,
|
|
33
|
+
ReceiptAttestorEngine,
|
|
34
|
+
TestHarnessEngine,
|
|
35
|
+
TreeSitterCodemodEngine,
|
|
36
|
+
VisualGroundingEngine,
|
|
37
|
+
)
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
class TestVisualGroundingEngine(unittest.TestCase):
|
|
41
|
+
def setUp(self) -> None:
|
|
42
|
+
self.engine = VisualGroundingEngine()
|
|
43
|
+
|
|
44
|
+
def test_render_vector_valid(self) -> None:
|
|
45
|
+
svg_code = (
|
|
46
|
+
'<svg viewBox="0 0 100 100" width="100" height="100">'
|
|
47
|
+
' <rect x="10" y="10" width="50" height="50" fill="#ff0000"/>'
|
|
48
|
+
' <circle cx="50" cy="50" r="25" stroke="#00ff00"/>'
|
|
49
|
+
' <path d="M 10 10 L 90 90 Z" />'
|
|
50
|
+
"</svg>"
|
|
51
|
+
)
|
|
52
|
+
res = self.engine.render_vector(svg_code)
|
|
53
|
+
self.assertTrue(res["valid"])
|
|
54
|
+
self.assertEqual(res["viewBox"], [0.0, 0.0, 100.0, 100.0])
|
|
55
|
+
self.assertEqual(res["width"], "100")
|
|
56
|
+
self.assertEqual(res["height"], "100")
|
|
57
|
+
self.assertIn("rect", res["element_types"])
|
|
58
|
+
self.assertIn("circle", res["element_types"])
|
|
59
|
+
self.assertIn("path", res["element_types"])
|
|
60
|
+
self.assertEqual(len(res["paths"]), 1)
|
|
61
|
+
self.assertGreaterEqual(res["metadata"]["total_path_commands"], 3)
|
|
62
|
+
|
|
63
|
+
def test_render_vector_invalid(self) -> None:
|
|
64
|
+
res = self.engine.render_vector("<svg><unclosed></svg>")
|
|
65
|
+
self.assertFalse(res["valid"])
|
|
66
|
+
self.assertIn("error", res)
|
|
67
|
+
|
|
68
|
+
empty_res = self.engine.render_vector("")
|
|
69
|
+
self.assertFalse(empty_res["valid"])
|
|
70
|
+
|
|
71
|
+
def test_perceptual_diff(self) -> None:
|
|
72
|
+
svg_code = (
|
|
73
|
+
'<svg viewBox="0 0 200 200">'
|
|
74
|
+
' <rect x="0" y="0" width="100" height="100" fill="#123456"/>'
|
|
75
|
+
' <path d="M 0 0 L 100 100"/>'
|
|
76
|
+
"</svg>"
|
|
77
|
+
)
|
|
78
|
+
target_spec = {
|
|
79
|
+
"expected_types": ["rect", "path"],
|
|
80
|
+
"palette": ["#123456"],
|
|
81
|
+
"min_elements": 2,
|
|
82
|
+
"min_paths": 1,
|
|
83
|
+
"viewBox": [0.0, 0.0, 200.0, 200.0],
|
|
84
|
+
}
|
|
85
|
+
res = self.engine.perceptual_diff(svg_code, target_spec)
|
|
86
|
+
self.assertGreaterEqual(res["similarity_score"], 0.9)
|
|
87
|
+
self.assertEqual(res["coverage"], 1.0)
|
|
88
|
+
self.assertEqual(len(res["diff_details"]), 0)
|
|
89
|
+
|
|
90
|
+
def test_extract_palette_and_boxes(self) -> None:
|
|
91
|
+
svg_code = (
|
|
92
|
+
'<svg viewBox="0 0 100 100">'
|
|
93
|
+
' <rect x="10" y="20" width="30" height="40" fill="#abcdef" stroke="rgb(0, 128, 255)"/>'
|
|
94
|
+
' <circle cx="60" cy="70" r="15" style="fill: oklch(0.7 0.15 150); stroke: #ffffff"/>'
|
|
95
|
+
"</svg>"
|
|
96
|
+
)
|
|
97
|
+
res = self.engine.extract_palette_and_boxes(svg_code)
|
|
98
|
+
palette = res["palette"]
|
|
99
|
+
self.assertIn("#abcdef", palette["fills"])
|
|
100
|
+
self.assertIn("#ffffff", palette["strokes"])
|
|
101
|
+
self.assertTrue(any("oklch" in c.lower() for c in palette["all_colors"]))
|
|
102
|
+
|
|
103
|
+
boxes = res["bounding_boxes"]
|
|
104
|
+
self.assertEqual(len(boxes), 2)
|
|
105
|
+
rect_box = [b for b in boxes if b["tag"] == "rect"][0]
|
|
106
|
+
self.assertEqual(rect_box["x"], 10.0)
|
|
107
|
+
self.assertEqual(rect_box["y"], 20.0)
|
|
108
|
+
self.assertEqual(rect_box["width"], 30.0)
|
|
109
|
+
self.assertEqual(rect_box["height"], 40.0)
|
|
110
|
+
|
|
111
|
+
|
|
112
|
+
class TestDiagnosticsEngine(unittest.TestCase):
|
|
113
|
+
def setUp(self) -> None:
|
|
114
|
+
self.engine = DiagnosticsEngine()
|
|
115
|
+
|
|
116
|
+
def test_run_diagnostics_syntax_error(self) -> None:
|
|
117
|
+
bad_code = "def broken(\n return 1"
|
|
118
|
+
diags = self.engine.run_diagnostics(bad_code)
|
|
119
|
+
self.assertEqual(len(diags), 1)
|
|
120
|
+
self.assertEqual(diags[0]["code"], "E999")
|
|
121
|
+
self.assertEqual(diags[0]["severity"], "error")
|
|
122
|
+
|
|
123
|
+
def test_run_diagnostics_unused_import(self) -> None:
|
|
124
|
+
code = "import os\n\ndef run():\n return 42\n"
|
|
125
|
+
diags = self.engine.run_diagnostics(code)
|
|
126
|
+
unused = [d for d in diags if d["code"] == "W0611"]
|
|
127
|
+
self.assertEqual(len(unused), 1)
|
|
128
|
+
self.assertEqual(unused[0]["symbol"], "os")
|
|
129
|
+
|
|
130
|
+
def test_run_diagnostics_undefined_name(self) -> None:
|
|
131
|
+
code = "def compute():\n return unknown_symbol * 2\n"
|
|
132
|
+
diags = self.engine.run_diagnostics(code)
|
|
133
|
+
undef = [d for d in diags if d["code"] == "E0602"]
|
|
134
|
+
self.assertEqual(len(undef), 1)
|
|
135
|
+
self.assertEqual(undef[0]["symbol"], "unknown_symbol")
|
|
136
|
+
|
|
137
|
+
def test_run_diagnostics_bare_except(self) -> None:
|
|
138
|
+
code = "try:\n x = 1\nexcept:\n x = 0\n"
|
|
139
|
+
diags = self.engine.run_diagnostics(code)
|
|
140
|
+
bare = [d for d in diags if d["code"] == "W0702"]
|
|
141
|
+
self.assertEqual(len(bare), 1)
|
|
142
|
+
|
|
143
|
+
def test_apply_quick_fix_unused_import(self) -> None:
|
|
144
|
+
code = "import os\n\ndef run():\n return 42\n"
|
|
145
|
+
diags = self.engine.run_diagnostics(code)
|
|
146
|
+
unused_diag = [d for d in diags if d["code"] == "W0611"][0]
|
|
147
|
+
fixed = self.engine.apply_quick_fix(unused_diag, code)
|
|
148
|
+
self.assertNotIn("import os", fixed)
|
|
149
|
+
|
|
150
|
+
def test_apply_quick_fix_undefined_name(self) -> None:
|
|
151
|
+
code = "def calc():\n return math.sqrt(16)\n"
|
|
152
|
+
diags = self.engine.run_diagnostics(code)
|
|
153
|
+
undef_diag = [d for d in diags if d["code"] == "E0602" and d["symbol"] == "math"][0]
|
|
154
|
+
fixed = self.engine.apply_quick_fix(undef_diag, code)
|
|
155
|
+
self.assertIn("import math", fixed)
|
|
156
|
+
|
|
157
|
+
def test_apply_quick_fix_bare_except(self) -> None:
|
|
158
|
+
code = "try:\n x = 1\nexcept:\n x = 0\n"
|
|
159
|
+
diags = self.engine.run_diagnostics(code)
|
|
160
|
+
bare_diag = [d for d in diags if d["code"] == "W0702"][0]
|
|
161
|
+
fixed = self.engine.apply_quick_fix(bare_diag, code)
|
|
162
|
+
self.assertIn("except Exception:", fixed)
|
|
163
|
+
|
|
164
|
+
|
|
165
|
+
class TestTreeSitterCodemodEngine(unittest.TestCase):
|
|
166
|
+
def setUp(self) -> None:
|
|
167
|
+
self.engine = TreeSitterCodemodEngine()
|
|
168
|
+
|
|
169
|
+
def test_query_ast_functions(self) -> None:
|
|
170
|
+
code = (
|
|
171
|
+
"def add(a: int, b: int = 0) -> int:\n"
|
|
172
|
+
' """Add two numbers."""\n'
|
|
173
|
+
" return a + b\n"
|
|
174
|
+
)
|
|
175
|
+
nodes = self.engine.query_ast(code, node_type="FunctionDef")
|
|
176
|
+
self.assertEqual(len(nodes), 1)
|
|
177
|
+
self.assertEqual(nodes[0]["name"], "add")
|
|
178
|
+
self.assertEqual(nodes[0]["args"], ["a", "b"])
|
|
179
|
+
self.assertEqual(nodes[0]["docstring"], "Add two numbers.")
|
|
180
|
+
|
|
181
|
+
def test_rename_symbol_safe(self) -> None:
|
|
182
|
+
source = (
|
|
183
|
+
"def calculate(value):\n"
|
|
184
|
+
" # Note: calculate is fast\n"
|
|
185
|
+
' msg = "do not calculate this calculate"\n'
|
|
186
|
+
" return calculate(value - 1) if value > 0 else 0\n"
|
|
187
|
+
)
|
|
188
|
+
new_code, count = self.engine.rename_symbol(source, "calculate", "compute")
|
|
189
|
+
self.assertEqual(count, 2) # Function definition and recursive call
|
|
190
|
+
self.assertIn("def compute(value):", new_code)
|
|
191
|
+
self.assertIn("return compute(value - 1)", new_code)
|
|
192
|
+
# Verify comment and string were NOT touched
|
|
193
|
+
self.assertIn("# Note: calculate is fast", new_code)
|
|
194
|
+
self.assertIn('"do not calculate this calculate"', new_code)
|
|
195
|
+
|
|
196
|
+
def test_verify_syntax(self) -> None:
|
|
197
|
+
valid_py = "x = 42\n"
|
|
198
|
+
invalid_py = "x = (\n"
|
|
199
|
+
self.assertTrue(self.engine.verify_syntax(valid_py, "python")["valid"])
|
|
200
|
+
self.assertFalse(self.engine.verify_syntax(invalid_py, "python")["valid"])
|
|
201
|
+
|
|
202
|
+
valid_json = '{"a": 1, "b": [2, 3]}'
|
|
203
|
+
invalid_json = '{"a": 1,}'
|
|
204
|
+
self.assertTrue(self.engine.verify_syntax(valid_json, "json")["valid"])
|
|
205
|
+
self.assertFalse(self.engine.verify_syntax(invalid_json, "json")["valid"])
|
|
206
|
+
|
|
207
|
+
|
|
208
|
+
class TestAtomicWorkspaceEngine(unittest.TestCase):
|
|
209
|
+
def setUp(self) -> None:
|
|
210
|
+
self.engine = AtomicWorkspaceEngine()
|
|
211
|
+
|
|
212
|
+
def test_checkpoint_inspect_and_rollback(self) -> None:
|
|
213
|
+
initial_files = {
|
|
214
|
+
"main.py": "def main():\n print('hello')\n",
|
|
215
|
+
"utils.py": "def util():\n return 1\n",
|
|
216
|
+
}
|
|
217
|
+
chk_id = self.engine.create_checkpoint("task-101", initial_files)
|
|
218
|
+
self.assertTrue(chk_id.startswith("chk_task-101_"))
|
|
219
|
+
|
|
220
|
+
# Modify files
|
|
221
|
+
current_files = {
|
|
222
|
+
"main.py": "def main():\n print('world')\n", # modified
|
|
223
|
+
"new.py": "NEW = True\n", # added
|
|
224
|
+
# utils.py deleted
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
patch = self.engine.inspect_patch(chk_id, current_files)
|
|
228
|
+
self.assertEqual(patch["files_changed"], 3)
|
|
229
|
+
self.assertEqual(patch["details"]["main.py"]["status"], "modified")
|
|
230
|
+
self.assertEqual(patch["details"]["new.py"]["status"], "added")
|
|
231
|
+
self.assertEqual(patch["details"]["utils.py"]["status"], "deleted")
|
|
232
|
+
|
|
233
|
+
# Rollback
|
|
234
|
+
restored = self.engine.rollback(chk_id)
|
|
235
|
+
self.assertEqual(restored, initial_files)
|
|
236
|
+
|
|
237
|
+
def test_commit_milestone(self) -> None:
|
|
238
|
+
files = {"config.json": '{"v": 1}'}
|
|
239
|
+
chk_id = self.engine.create_checkpoint("milestone-task", files)
|
|
240
|
+
milestone = self.engine.commit_milestone(chk_id, "Initial baseline commit")
|
|
241
|
+
self.assertEqual(milestone["checkpoint_id"], chk_id)
|
|
242
|
+
self.assertEqual(milestone["message"], "Initial baseline commit")
|
|
243
|
+
self.assertEqual(milestone["status"], "committed")
|
|
244
|
+
self.assertEqual(len(milestone["digest"]), 64)
|
|
245
|
+
|
|
246
|
+
|
|
247
|
+
class TestTestHarnessEngine(unittest.TestCase):
|
|
248
|
+
def setUp(self) -> None:
|
|
249
|
+
self.engine = TestHarnessEngine()
|
|
250
|
+
|
|
251
|
+
def test_run_scratch_test_success(self) -> None:
|
|
252
|
+
code = "print('harness_success')\n"
|
|
253
|
+
res = self.engine.run_scratch_test(code, timeout_sec=3.0)
|
|
254
|
+
self.assertTrue(res["success"])
|
|
255
|
+
self.assertEqual(res["returncode"], 0)
|
|
256
|
+
self.assertIn("harness_success", res["stdout"])
|
|
257
|
+
self.assertFalse(res["timed_out"])
|
|
258
|
+
|
|
259
|
+
def test_run_scratch_test_timeout(self) -> None:
|
|
260
|
+
code = "import time\ntime.sleep(2.0)\n"
|
|
261
|
+
res = self.engine.run_scratch_test(code, timeout_sec=0.2)
|
|
262
|
+
self.assertFalse(res["success"])
|
|
263
|
+
self.assertTrue(res["timed_out"])
|
|
264
|
+
self.assertEqual(res["returncode"], -1)
|
|
265
|
+
|
|
266
|
+
def test_concurrency_fuzz(self) -> None:
|
|
267
|
+
code = (
|
|
268
|
+
"counter = 0\n"
|
|
269
|
+
"def target_fn():\n"
|
|
270
|
+
" global counter\n"
|
|
271
|
+
" counter += 1\n"
|
|
272
|
+
)
|
|
273
|
+
res = self.engine.concurrency_fuzz(code, threads=2, iterations=20)
|
|
274
|
+
self.assertTrue(res["success"])
|
|
275
|
+
self.assertFalse(res["race_conditions_detected"])
|
|
276
|
+
|
|
277
|
+
def test_profile_memory_and_cpu(self) -> None:
|
|
278
|
+
code = "data = [i ** 2 for i in range(10000)]\n"
|
|
279
|
+
res = self.engine.profile_memory_and_cpu(code)
|
|
280
|
+
self.assertTrue(res["success"])
|
|
281
|
+
self.assertGreater(res["peak_memory_bytes"], 0)
|
|
282
|
+
self.assertGreaterEqual(res["duration_ms"], 0.0)
|
|
283
|
+
|
|
284
|
+
|
|
285
|
+
class TestMutationVerifierEngine(unittest.TestCase):
|
|
286
|
+
def setUp(self) -> None:
|
|
287
|
+
self.engine = MutationVerifierEngine()
|
|
288
|
+
|
|
289
|
+
def test_inject_mutants(self) -> None:
|
|
290
|
+
code = (
|
|
291
|
+
"def check_value(x: int) -> bool:\n"
|
|
292
|
+
" if x > 10:\n"
|
|
293
|
+
" return True\n"
|
|
294
|
+
" return False\n"
|
|
295
|
+
)
|
|
296
|
+
mutants = self.engine.inject_mutants(code)
|
|
297
|
+
self.assertGreaterEqual(len(mutants), 3)
|
|
298
|
+
self.assertLessEqual(len(mutants), 5)
|
|
299
|
+
for m in mutants:
|
|
300
|
+
self.assertIn("mutant_id", m)
|
|
301
|
+
self.assertIn("mutated_code", m)
|
|
302
|
+
self.assertIn("mutation_type", m)
|
|
303
|
+
|
|
304
|
+
def test_audit_test_strength_kills_mutants(self) -> None:
|
|
305
|
+
source_code = (
|
|
306
|
+
"def is_positive(x: int) -> bool:\n"
|
|
307
|
+
" if x > 0:\n"
|
|
308
|
+
" return True\n"
|
|
309
|
+
" return False\n"
|
|
310
|
+
)
|
|
311
|
+
# Strong test suite that tests > 0 thoroughly
|
|
312
|
+
test_code = (
|
|
313
|
+
"assert is_positive(5) is True\n"
|
|
314
|
+
"assert is_positive(0) is False\n"
|
|
315
|
+
"assert is_positive(-5) is False\n"
|
|
316
|
+
)
|
|
317
|
+
res = self.engine.audit_test_strength(source_code, test_code)
|
|
318
|
+
self.assertGreater(res["mutants_total"], 0)
|
|
319
|
+
self.assertGreater(res["mutants_killed"], 0)
|
|
320
|
+
self.assertGreaterEqual(res["mutation_score"], 0.5)
|
|
321
|
+
|
|
322
|
+
def test_audit_test_strength_detects_fake_tests(self) -> None:
|
|
323
|
+
source_code = (
|
|
324
|
+
"def is_positive(x: int) -> bool:\n"
|
|
325
|
+
" if x > 0:\n"
|
|
326
|
+
" return True\n"
|
|
327
|
+
" return False\n"
|
|
328
|
+
)
|
|
329
|
+
# Fake test that does not actually check function output
|
|
330
|
+
fake_test = "assert True\nassert 1 == 1\n"
|
|
331
|
+
res = self.engine.audit_test_strength(source_code, fake_test)
|
|
332
|
+
self.assertEqual(res["mutants_killed"], 0)
|
|
333
|
+
self.assertEqual(res["mutation_score"], 0.0)
|
|
334
|
+
self.assertFalse(res["is_thorough"])
|
|
335
|
+
|
|
336
|
+
|
|
337
|
+
class TestMockAuditorEngine(unittest.TestCase):
|
|
338
|
+
def setUp(self) -> None:
|
|
339
|
+
self.engine = MockAuditorEngine()
|
|
340
|
+
|
|
341
|
+
def test_audit_assertions_flags_tautologies(self) -> None:
|
|
342
|
+
test_code = (
|
|
343
|
+
"def test_fake():\n"
|
|
344
|
+
" assert True\n"
|
|
345
|
+
" assert 1 == 1\n"
|
|
346
|
+
" self.assertTrue(True)\n"
|
|
347
|
+
)
|
|
348
|
+
res = self.engine.audit_assertions(test_code)
|
|
349
|
+
self.assertFalse(res["passed_audit"])
|
|
350
|
+
self.assertGreaterEqual(res["trivial_assertions_count"], 3)
|
|
351
|
+
self.assertEqual(len(res["tautologies"]), res["trivial_assertions_count"])
|
|
352
|
+
|
|
353
|
+
def test_audit_assertions_passes_clean_test(self) -> None:
|
|
354
|
+
clean_code = (
|
|
355
|
+
"def test_real():\n"
|
|
356
|
+
" result = 2 + 2\n"
|
|
357
|
+
" assert result == 4\n"
|
|
358
|
+
)
|
|
359
|
+
res = self.engine.audit_assertions(clean_code)
|
|
360
|
+
self.assertTrue(res["passed_audit"])
|
|
361
|
+
self.assertEqual(res["trivial_assertions_count"], 0)
|
|
362
|
+
|
|
363
|
+
def test_detect_mock_leakage(self) -> None:
|
|
364
|
+
excessive_mock_code = (
|
|
365
|
+
"from unittest.mock import Mock, patch\n"
|
|
366
|
+
"@patch('service.api')\n"
|
|
367
|
+
"def test_stubbed(mock_api):\n"
|
|
368
|
+
" m1 = Mock()\n"
|
|
369
|
+
" m2 = Mock()\n"
|
|
370
|
+
" m3 = Mock()\n"
|
|
371
|
+
)
|
|
372
|
+
res = self.engine.detect_mock_leakage(excessive_mock_code)
|
|
373
|
+
self.assertGreaterEqual(res["mock_count"], 3)
|
|
374
|
+
self.assertTrue(res["has_excessive_mocking"])
|
|
375
|
+
|
|
376
|
+
def test_enforce_negative_paths(self) -> None:
|
|
377
|
+
code_with_negatives = (
|
|
378
|
+
"import unittest\n"
|
|
379
|
+
"class TestErrors(unittest.TestCase):\n"
|
|
380
|
+
" def test_invalid_input_error(self):\n"
|
|
381
|
+
" with self.assertRaises(ValueError):\n"
|
|
382
|
+
" int('abc')\n"
|
|
383
|
+
)
|
|
384
|
+
res = self.engine.enforce_negative_paths(code_with_negatives)
|
|
385
|
+
self.assertTrue(res["has_negative_tests"])
|
|
386
|
+
self.assertGreaterEqual(res["negative_test_count"], 1)
|
|
387
|
+
|
|
388
|
+
|
|
389
|
+
class TestPropertyOracleEngine(unittest.TestCase):
|
|
390
|
+
def setUp(self) -> None:
|
|
391
|
+
self.engine = PropertyOracleEngine()
|
|
392
|
+
|
|
393
|
+
def test_generate_property_matrix(self) -> None:
|
|
394
|
+
matrix = self.engine.generate_property_matrix(["str", "int", "float", "list"], count=30)
|
|
395
|
+
self.assertEqual(len(matrix), 30)
|
|
396
|
+
# Check boundary elements exist
|
|
397
|
+
self.assertTrue(any(isinstance(x, str) and x == "" for x in matrix))
|
|
398
|
+
self.assertTrue(any(isinstance(x, int) and x == 0 for x in matrix))
|
|
399
|
+
self.assertTrue(any(isinstance(x, float) for x in matrix))
|
|
400
|
+
self.assertTrue(any(isinstance(x, list) and len(x) == 0 for x in matrix))
|
|
401
|
+
|
|
402
|
+
def test_verify_algebraic_invariants_holds(self) -> None:
|
|
403
|
+
module_code = (
|
|
404
|
+
"import json\n"
|
|
405
|
+
"def encode(x):\n"
|
|
406
|
+
" return json.dumps(x)\n"
|
|
407
|
+
"def decode(s):\n"
|
|
408
|
+
" return json.loads(s)\n"
|
|
409
|
+
)
|
|
410
|
+
sample_inputs = [{"a": 1}, [1, 2, 3], "test string", 42, True]
|
|
411
|
+
res = self.engine.verify_algebraic_invariants(module_code, "encode", "decode", sample_inputs)
|
|
412
|
+
self.assertTrue(res["roundtrip_invariant_holds"])
|
|
413
|
+
self.assertEqual(res["passed"], 5)
|
|
414
|
+
self.assertEqual(res["failed"], 0)
|
|
415
|
+
|
|
416
|
+
def test_verify_algebraic_invariants_fails(self) -> None:
|
|
417
|
+
module_code = (
|
|
418
|
+
"def encode(x):\n"
|
|
419
|
+
" return x + 1\n"
|
|
420
|
+
"def decode(x):\n"
|
|
421
|
+
" return x # buggy decode, missing - 1\n"
|
|
422
|
+
)
|
|
423
|
+
sample_inputs = [10, 20, 30]
|
|
424
|
+
res = self.engine.verify_algebraic_invariants(module_code, "encode", "decode", sample_inputs)
|
|
425
|
+
self.assertFalse(res["roundtrip_invariant_holds"])
|
|
426
|
+
self.assertEqual(res["failed"], 3)
|
|
427
|
+
self.assertGreaterEqual(len(res["failure_examples"]), 1)
|
|
428
|
+
|
|
429
|
+
|
|
430
|
+
class TestReceiptAttestorEngine(unittest.TestCase):
|
|
431
|
+
def setUp(self) -> None:
|
|
432
|
+
self.engine = ReceiptAttestorEngine()
|
|
433
|
+
|
|
434
|
+
def test_attest_execution_and_verify(self) -> None:
|
|
435
|
+
cmd = [sys.executable, "-c", "print('attest_test_output')"]
|
|
436
|
+
receipt = self.engine.attest_execution(cmd)
|
|
437
|
+
self.assertEqual(receipt["exit_code"], 0)
|
|
438
|
+
self.assertIn("attest_test_output", receipt["stdout"])
|
|
439
|
+
self.assertTrue(receipt["tamper_evident"])
|
|
440
|
+
self.assertGreater(receipt["pid"], 0)
|
|
441
|
+
|
|
442
|
+
# Verification must pass
|
|
443
|
+
self.assertTrue(self.engine.verify_receipt(receipt))
|
|
444
|
+
|
|
445
|
+
def test_verify_receipt_tampered(self) -> None:
|
|
446
|
+
cmd = [sys.executable, "-c", "print('ok')"]
|
|
447
|
+
receipt = self.engine.attest_execution(cmd)
|
|
448
|
+
self.assertTrue(self.engine.verify_receipt(receipt))
|
|
449
|
+
|
|
450
|
+
# Tamper with stdout
|
|
451
|
+
tampered_receipt = dict(receipt)
|
|
452
|
+
tampered_receipt["stdout"] = "tampered output\n"
|
|
453
|
+
self.assertFalse(self.engine.verify_receipt(tampered_receipt))
|
|
454
|
+
|
|
455
|
+
# Tamper with exit code
|
|
456
|
+
tampered_exit = dict(receipt)
|
|
457
|
+
tampered_exit["exit_code"] = 1
|
|
458
|
+
self.assertFalse(self.engine.verify_receipt(tampered_exit))
|
|
459
|
+
|
|
460
|
+
|
|
461
|
+
class TestComputeOrchestratorEngine(unittest.TestCase):
|
|
462
|
+
def setUp(self) -> None:
|
|
463
|
+
self.engine = ComputeOrchestratorEngine()
|
|
464
|
+
|
|
465
|
+
def test_calculate_thinking_budget(self) -> None:
|
|
466
|
+
# Low complexity
|
|
467
|
+
low_res = self.engine.calculate_thinking_budget(complexity_score=2.0, failure_count=0)
|
|
468
|
+
self.assertEqual(low_res["model_tier"], "flash")
|
|
469
|
+
self.assertLessEqual(low_res["recommended_tokens"], 16384)
|
|
470
|
+
|
|
471
|
+
# High complexity with failures
|
|
472
|
+
high_res = self.engine.calculate_thinking_budget(complexity_score=9.0, failure_count=2)
|
|
473
|
+
self.assertEqual(high_res["model_tier"], "deepthink")
|
|
474
|
+
self.assertGreaterEqual(high_res["recommended_tokens"], 32768)
|
|
475
|
+
|
|
476
|
+
def test_mcts_explore(self) -> None:
|
|
477
|
+
spec = {"problem": "find best search algorithm", "strategies": ["binary_search", "hash_index", "b_tree"]}
|
|
478
|
+
res = self.engine.mcts_explore(spec, branches=3, depth=2)
|
|
479
|
+
self.assertGreater(res["root_visits"], 0)
|
|
480
|
+
self.assertIn("best_branch", res)
|
|
481
|
+
self.assertGreater(len(res["recommended_path"]), 0)
|
|
482
|
+
self.assertEqual(len(res["tree_summary"]), 3)
|
|
483
|
+
|
|
484
|
+
def test_best_of_n_consensus(self) -> None:
|
|
485
|
+
candidates = [
|
|
486
|
+
{"id": "cand_A", "score": 0.95, "test_pass_rate": 1.0, "complexity": 3.0},
|
|
487
|
+
{"id": "cand_B", "score": 0.80, "test_pass_rate": 0.9, "complexity": 7.0},
|
|
488
|
+
{"id": "cand_C", "score": 0.50, "test_pass_rate": 0.6, "complexity": 9.0},
|
|
489
|
+
]
|
|
490
|
+
res = self.engine.best_of_n_consensus(candidates)
|
|
491
|
+
self.assertEqual(res["selected_candidate"]["id"], "cand_A")
|
|
492
|
+
self.assertGreater(res["consensus_score"], 0.0)
|
|
493
|
+
self.assertEqual(len(res["rankings"]), 3)
|
|
494
|
+
|
|
495
|
+
|
|
496
|
+
class TestCoderFleetDispatcher(unittest.TestCase):
|
|
497
|
+
def setUp(self) -> None:
|
|
498
|
+
self.dispatcher = CoderFleetDispatcher()
|
|
499
|
+
|
|
500
|
+
def test_list_actions(self) -> None:
|
|
501
|
+
actions = self.dispatcher.list_actions()
|
|
502
|
+
expected = [
|
|
503
|
+
"render_vector", "perceptual_diff", "extract_palette_and_boxes",
|
|
504
|
+
"run_diagnostics", "apply_quick_fix",
|
|
505
|
+
"query_ast", "rename_symbol", "verify_syntax",
|
|
506
|
+
"create_checkpoint", "inspect_patch", "rollback", "commit_milestone",
|
|
507
|
+
"run_scratch_test", "concurrency_fuzz", "profile_memory_and_cpu",
|
|
508
|
+
"inject_mutants", "audit_test_strength",
|
|
509
|
+
"audit_assertions", "detect_mock_leakage", "enforce_negative_paths",
|
|
510
|
+
"generate_property_matrix", "verify_algebraic_invariants",
|
|
511
|
+
"attest_execution", "verify_receipt",
|
|
512
|
+
"calculate_thinking_budget", "mcts_explore", "best_of_n_consensus",
|
|
513
|
+
]
|
|
514
|
+
for a in expected:
|
|
515
|
+
self.assertIn(a, actions)
|
|
516
|
+
|
|
517
|
+
def test_dispatch_render_vector(self) -> None:
|
|
518
|
+
svg_code = '<svg viewBox="0 0 50 50"><circle cx="25" cy="25" r="20"/></svg>'
|
|
519
|
+
res = self.dispatcher.dispatch("render_vector", {"code": svg_code})
|
|
520
|
+
self.assertTrue(res["success"])
|
|
521
|
+
self.assertTrue(res["result"]["valid"])
|
|
522
|
+
|
|
523
|
+
def test_dispatch_run_diagnostics(self) -> None:
|
|
524
|
+
res = self.dispatcher.dispatch("run_diagnostics", {"source_code": "x = 1\n"})
|
|
525
|
+
self.assertTrue(res["success"])
|
|
526
|
+
self.assertEqual(len(res["result"]), 0)
|
|
527
|
+
|
|
528
|
+
def test_dispatch_unknown_action(self) -> None:
|
|
529
|
+
res = self.dispatcher.dispatch("unknown_fleet_action", {})
|
|
530
|
+
self.assertFalse(res["success"])
|
|
531
|
+
self.assertIn("Unknown fleet action", res["error"])
|
|
532
|
+
|
|
533
|
+
|
|
534
|
+
if __name__ == "__main__":
|
|
535
|
+
unittest.main()
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
import unittest
|
|
2
|
+
from pathlib import Path
|
|
3
|
+
import sys
|
|
4
|
+
|
|
5
|
+
BASE_DIR = Path(__file__).resolve().parent.parent
|
|
6
|
+
for p in [str(BASE_DIR), str(BASE_DIR / "fable_engine"), str(Path(__file__).resolve().parent)]:
|
|
7
|
+
if p not in sys.path:
|
|
8
|
+
sys.path.insert(0, p)
|
|
9
|
+
|
|
10
|
+
from server import DelegationContractCompiler
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
class TestDelegationContractCompiler(unittest.TestCase):
|
|
14
|
+
def setUp(self):
|
|
15
|
+
self.compiler = DelegationContractCompiler()
|
|
16
|
+
|
|
17
|
+
def test_vague_prompt_fails_all_checks(self):
|
|
18
|
+
prompt = "Hey please implement the payment gateway and test it."
|
|
19
|
+
valid, errors, parsed = self.compiler.compile_and_validate(prompt)
|
|
20
|
+
self.assertFalse(valid)
|
|
21
|
+
self.assertEqual(len(errors), 4)
|
|
22
|
+
self.assertTrue(any("TargetFile" in e for e in errors))
|
|
23
|
+
self.assertTrue(any("VerificationCommand" in e for e in errors))
|
|
24
|
+
self.assertTrue(any("InterfaceContract" in e for e in errors))
|
|
25
|
+
self.assertTrue(any("StrictConstraints" in e for e in errors))
|
|
26
|
+
|
|
27
|
+
def test_valid_delegation_contract_passes(self):
|
|
28
|
+
prompt = """
|
|
29
|
+
### SUBAGENT DELEGATION CONTRACT
|
|
30
|
+
- TargetFile: src/payment/gateway.py
|
|
31
|
+
- InterfaceContract: def process_charge(amount_cents: int, token: str) -> PaymentResult
|
|
32
|
+
- StrictConstraints: Zero-alloc hot path, no network blocking, idempotent retry key
|
|
33
|
+
- VerificationCommand: pytest tests/test_payment.py -v
|
|
34
|
+
"""
|
|
35
|
+
valid, errors, parsed = self.compiler.compile_and_validate(prompt)
|
|
36
|
+
self.assertTrue(valid, f"Unexpected errors: {errors}")
|
|
37
|
+
self.assertEqual(len(errors), 0)
|
|
38
|
+
self.assertEqual(parsed.get("TargetFile"), "src/payment/gateway.py")
|
|
39
|
+
self.assertEqual(parsed.get("VerificationCommand"), "pytest tests/test_payment.py -v")
|
|
40
|
+
|
|
41
|
+
def test_missing_verification_command_fails(self):
|
|
42
|
+
prompt = """
|
|
43
|
+
### SUBAGENT DELEGATION CONTRACT
|
|
44
|
+
- TargetFile: src/utils/math.py
|
|
45
|
+
- InterfaceContract: def add(a: int, b: int) -> int
|
|
46
|
+
- StrictConstraints: Pure function, zero side-effects
|
|
47
|
+
"""
|
|
48
|
+
valid, errors, parsed = self.compiler.compile_and_validate(prompt)
|
|
49
|
+
self.assertFalse(valid)
|
|
50
|
+
self.assertTrue(any("VerificationCommand" in e for e in errors))
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
if __name__ == "__main__":
|
|
54
|
+
unittest.main()
|