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,608 @@
|
|
|
1
|
+
"""Comprehensive Unit Test Suite for Fable-Vector Neuro-Symbolic Vector Engine.
|
|
2
|
+
|
|
3
|
+
Verifies:
|
|
4
|
+
1. OKLCHColor and APCA contrast calibration (|Lc| >= 60, WCAG AAA accessibility, relative luminance, edge cases).
|
|
5
|
+
2. ParametricGeometry (Polar/Cartesian, circular arcs, Catmull-Rom splines with C1/C2 continuity, isometric matrix).
|
|
6
|
+
3. BoundingBox (spatial queries, intersections, enclosure, viewBox formatting).
|
|
7
|
+
4. VNode (AST tree structure, serialization, defensive sanitization).
|
|
8
|
+
5. VLayoutSolver (relational constraints, viewport pinning, thread-safety, defensive guards against NaN/Inf/null-bytes/deep-nesting).
|
|
9
|
+
6. FableVectorCompiler (XML AST well-formedness, viewBox enclosure, procedural noise filters, OKLCH gradients, a11y).
|
|
10
|
+
7. CoderFleetDispatcher vector integration ("compile_vector" and "solve_layout" actions).
|
|
11
|
+
"""
|
|
12
|
+
from __future__ import annotations
|
|
13
|
+
|
|
14
|
+
import concurrent.futures
|
|
15
|
+
import math
|
|
16
|
+
import sys
|
|
17
|
+
import unittest
|
|
18
|
+
import xml.etree.ElementTree as ET
|
|
19
|
+
from pathlib import Path
|
|
20
|
+
|
|
21
|
+
# Ensure workspace root is in sys.path
|
|
22
|
+
sys.path.insert(0, str(Path(__file__).resolve().parent.parent))
|
|
23
|
+
|
|
24
|
+
from fable_v2.coder_fleet import (
|
|
25
|
+
BoundingBox,
|
|
26
|
+
CoderFleetDispatcher,
|
|
27
|
+
FableVectorCompiler,
|
|
28
|
+
OKLCHColor,
|
|
29
|
+
ParametricGeometry,
|
|
30
|
+
VLayoutSolver,
|
|
31
|
+
VNode,
|
|
32
|
+
)
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
class TestOKLCHColorAndAPCA(unittest.TestCase):
|
|
36
|
+
"""Test suite for OKLCH color space math, luminance, and APCA contrast scoring."""
|
|
37
|
+
|
|
38
|
+
def test_oklch_initialization_and_clamping(self) -> None:
|
|
39
|
+
c1 = OKLCHColor(l=0.75, c=0.15, h=250.0, alpha=0.9)
|
|
40
|
+
self.assertAlmostEqual(c1.l, 0.75)
|
|
41
|
+
self.assertAlmostEqual(c1.c, 0.15)
|
|
42
|
+
self.assertAlmostEqual(c1.h, 250.0)
|
|
43
|
+
self.assertAlmostEqual(c1.alpha, 0.9)
|
|
44
|
+
|
|
45
|
+
# Defensive clamping for out-of-bound inputs
|
|
46
|
+
c2 = OKLCHColor(l=-0.5, c=-0.1, h=400.0, alpha=1.5)
|
|
47
|
+
self.assertEqual(c2.l, 0.0)
|
|
48
|
+
self.assertEqual(c2.c, 0.0)
|
|
49
|
+
self.assertAlmostEqual(c2.h, 40.0)
|
|
50
|
+
self.assertEqual(c2.alpha, 1.0)
|
|
51
|
+
|
|
52
|
+
# Defensive coercion for NaN and Inf
|
|
53
|
+
c3 = OKLCHColor(l=float("nan"), c=float("inf"), h=float("-inf"), alpha=float("nan"))
|
|
54
|
+
self.assertEqual(c3.l, 0.0)
|
|
55
|
+
self.assertEqual(c3.c, 0.0)
|
|
56
|
+
self.assertEqual(c3.h, 0.0)
|
|
57
|
+
self.assertEqual(c3.alpha, 1.0)
|
|
58
|
+
|
|
59
|
+
def test_to_svg_formatting(self) -> None:
|
|
60
|
+
c_opaque = OKLCHColor(l=0.96, c=0.01, h=270.0, alpha=1.0)
|
|
61
|
+
self.assertEqual(c_opaque.to_svg(), "oklch(0.9600 0.0100 270.00)")
|
|
62
|
+
|
|
63
|
+
c_translucent = OKLCHColor(l=0.12, c=0.02, h=270.0, alpha=0.85)
|
|
64
|
+
self.assertEqual(c_translucent.to_svg(), "oklch(0.1200 0.0200 270.00 / 0.85)")
|
|
65
|
+
|
|
66
|
+
def test_estimated_y_relative_luminance(self) -> None:
|
|
67
|
+
# Achromatic black
|
|
68
|
+
black = OKLCHColor(l=0.0, c=0.0, h=0.0)
|
|
69
|
+
self.assertAlmostEqual(black.estimated_y(), 0.0, places=4)
|
|
70
|
+
|
|
71
|
+
# Achromatic white
|
|
72
|
+
white = OKLCHColor(l=1.0, c=0.0, h=0.0)
|
|
73
|
+
self.assertAlmostEqual(white.estimated_y(), 1.0, places=4)
|
|
74
|
+
|
|
75
|
+
# Mid-gray achromatic: Y == l^3
|
|
76
|
+
mid_gray = OKLCHColor(l=0.5, c=0.0, h=0.0)
|
|
77
|
+
self.assertAlmostEqual(mid_gray.estimated_y(), 0.125, places=4)
|
|
78
|
+
|
|
79
|
+
# Chromatic color should produce valid Y in [0, 1]
|
|
80
|
+
cyan = OKLCHColor(l=0.75, c=0.18, h=190.0)
|
|
81
|
+
y_cyan = cyan.estimated_y()
|
|
82
|
+
self.assertGreaterEqual(y_cyan, 0.0)
|
|
83
|
+
self.assertLessEqual(y_cyan, 1.0)
|
|
84
|
+
|
|
85
|
+
def test_apca_contrast_high_contrast_pairs(self) -> None:
|
|
86
|
+
"""Verify APCA contrast meets or exceeds |Lc| >= 60 for accessible typography."""
|
|
87
|
+
# Cyber-Obsidian Monolith design tokens:
|
|
88
|
+
# Light text on deep obsidian substrate (WoB - negative score)
|
|
89
|
+
text_primary = OKLCHColor(l=0.96, c=0.01, h=270.0) # High-contrast read
|
|
90
|
+
void_deep = OKLCHColor(l=0.08, c=0.02, h=270.0) # Deep obsidian
|
|
91
|
+
lc_wob = text_primary.apca_contrast(void_deep)
|
|
92
|
+
|
|
93
|
+
self.assertLess(lc_wob, 0.0, "Light text on dark bg should yield negative Lc in APCA")
|
|
94
|
+
self.assertGreaterEqual(abs(lc_wob), 60.0, f"Expected |Lc| >= 60, got {abs(lc_wob)}")
|
|
95
|
+
self.assertGreaterEqual(abs(lc_wob), 75.0, f"Expected WCAG AAA |Lc| >= 75, got {abs(lc_wob)}")
|
|
96
|
+
|
|
97
|
+
# Dark text on light background (BoW - positive score)
|
|
98
|
+
dark_text = OKLCHColor(l=0.08, c=0.02, h=270.0)
|
|
99
|
+
light_bg = OKLCHColor(l=0.98, c=0.01, h=270.0)
|
|
100
|
+
lc_bow = dark_text.apca_contrast(light_bg)
|
|
101
|
+
|
|
102
|
+
self.assertGreater(lc_bow, 0.0, "Dark text on light bg should yield positive Lc in APCA")
|
|
103
|
+
self.assertGreaterEqual(lc_bow, 60.0, f"Expected Lc >= 60, got {lc_bow}")
|
|
104
|
+
self.assertGreaterEqual(lc_bow, 75.0, f"Expected WCAG AAA Lc >= 75, got {lc_bow}")
|
|
105
|
+
|
|
106
|
+
def test_apca_contrast_edge_cases(self) -> None:
|
|
107
|
+
# Identical colors must have zero contrast
|
|
108
|
+
color = OKLCHColor(l=0.5, c=0.1, h=180.0)
|
|
109
|
+
self.assertAlmostEqual(color.apca_contrast(color), 0.0, places=3)
|
|
110
|
+
|
|
111
|
+
# Extremely small delta luminance below noise threshold
|
|
112
|
+
c_a = OKLCHColor(l=0.500, c=0.0, h=0.0)
|
|
113
|
+
c_b = OKLCHColor(l=0.5001, c=0.0, h=0.0)
|
|
114
|
+
self.assertEqual(c_a.apca_contrast(c_b), 0.0)
|
|
115
|
+
|
|
116
|
+
def test_from_hex_conversion(self) -> None:
|
|
117
|
+
c_black = OKLCHColor.from_hex("#000000")
|
|
118
|
+
self.assertAlmostEqual(c_black.l, 0.0, places=3)
|
|
119
|
+
self.assertAlmostEqual(c_black.c, 0.0, places=3)
|
|
120
|
+
|
|
121
|
+
c_white = OKLCHColor.from_hex("#ffffff")
|
|
122
|
+
self.assertAlmostEqual(c_white.l, 1.0, places=3)
|
|
123
|
+
self.assertAlmostEqual(c_white.c, 0.0, places=3)
|
|
124
|
+
|
|
125
|
+
# Contrast between white and black
|
|
126
|
+
lc = c_white.apca_contrast(c_black)
|
|
127
|
+
self.assertGreaterEqual(abs(lc), 100.0)
|
|
128
|
+
|
|
129
|
+
|
|
130
|
+
class TestParametricGeometry(unittest.TestCase):
|
|
131
|
+
"""Test suite for 2D parametric geometry, arcs, splines, and isometric projection."""
|
|
132
|
+
|
|
133
|
+
def test_polar_to_cartesian(self) -> None:
|
|
134
|
+
# Center (100, 100), radius 50
|
|
135
|
+
x, y = ParametricGeometry.polar_to_cartesian(100.0, 100.0, 50.0, 0.0)
|
|
136
|
+
self.assertAlmostEqual(x, 150.0, places=3)
|
|
137
|
+
self.assertAlmostEqual(y, 100.0, places=3)
|
|
138
|
+
|
|
139
|
+
x, y = ParametricGeometry.polar_to_cartesian(100.0, 100.0, 50.0, 90.0)
|
|
140
|
+
self.assertAlmostEqual(x, 100.0, places=3)
|
|
141
|
+
self.assertAlmostEqual(y, 150.0, places=3)
|
|
142
|
+
|
|
143
|
+
x, y = ParametricGeometry.polar_to_cartesian(100.0, 100.0, 50.0, 180.0)
|
|
144
|
+
self.assertAlmostEqual(x, 50.0, places=3)
|
|
145
|
+
self.assertAlmostEqual(y, 100.0, places=3)
|
|
146
|
+
|
|
147
|
+
x, y = ParametricGeometry.polar_to_cartesian(100.0, 100.0, 50.0, 270.0)
|
|
148
|
+
self.assertAlmostEqual(x, 100.0, places=3)
|
|
149
|
+
self.assertAlmostEqual(y, 50.0, places=3)
|
|
150
|
+
|
|
151
|
+
def test_circular_arc(self) -> None:
|
|
152
|
+
# 90 degree arc
|
|
153
|
+
arc_d = ParametricGeometry.circular_arc(50.0, 50.0, 30.0, 0.0, 90.0, clock_wise=True)
|
|
154
|
+
self.assertTrue(arc_d.startswith("M 80.0000 50.0000 A 30.0000 30.0000 0 0 1"))
|
|
155
|
+
|
|
156
|
+
# Full 360 degree circle
|
|
157
|
+
full_circle_d = ParametricGeometry.circular_arc(50.0, 50.0, 30.0, 0.0, 360.0)
|
|
158
|
+
self.assertIn("A 30.0000 30.0000", full_circle_d)
|
|
159
|
+
# Semicircles combined
|
|
160
|
+
self.assertEqual(full_circle_d.count("A"), 2)
|
|
161
|
+
|
|
162
|
+
def test_catmull_rom_spline_c1_continuity(self) -> None:
|
|
163
|
+
"""Verify Catmull-Rom spline generates exact C1 (tangent) continuous cubic Bezier segments."""
|
|
164
|
+
pts = [
|
|
165
|
+
(20.0, 30.0),
|
|
166
|
+
(60.0, 120.0),
|
|
167
|
+
(120.0, 80.0),
|
|
168
|
+
(180.0, 160.0),
|
|
169
|
+
(240.0, 50.0),
|
|
170
|
+
(300.0, 100.0),
|
|
171
|
+
]
|
|
172
|
+
|
|
173
|
+
segments = ParametricGeometry.derive_catmull_rom_bezier_segments(pts, tension=0.5, closed=False)
|
|
174
|
+
self.assertEqual(len(segments), len(pts) - 1)
|
|
175
|
+
|
|
176
|
+
# For every adjacent pair of segments (S_k and S_{k+1}), verify:
|
|
177
|
+
# 1. C0 continuity: end point of S_k == start point of S_{k+1}
|
|
178
|
+
# 2. C1 continuity: tangent leaving S_k == tangent entering S_{k+1}
|
|
179
|
+
for k in range(len(segments) - 1):
|
|
180
|
+
p1_curr, c1_curr, c2_curr, p2_curr = segments[k]
|
|
181
|
+
p1_next, c1_next, c2_next, p2_next = segments[k + 1]
|
|
182
|
+
|
|
183
|
+
# C0 positional continuity
|
|
184
|
+
self.assertAlmostEqual(p2_curr[0], p1_next[0], places=4)
|
|
185
|
+
self.assertAlmostEqual(p2_curr[1], p1_next[1], places=4)
|
|
186
|
+
|
|
187
|
+
# Cubic Bezier first derivative:
|
|
188
|
+
# Incoming tangent at end of segment k: B'_curr(1) = 3 * (P2 - C2)
|
|
189
|
+
tan_in_x = 3.0 * (p2_curr[0] - c2_curr[0])
|
|
190
|
+
tan_in_y = 3.0 * (p2_curr[1] - c2_curr[1])
|
|
191
|
+
|
|
192
|
+
# Outgoing tangent at start of segment k+1: B'_next(0) = 3 * (C1 - P1)
|
|
193
|
+
tan_out_x = 3.0 * (c1_next[0] - p1_next[0])
|
|
194
|
+
tan_out_y = 3.0 * (c1_next[1] - p1_next[1])
|
|
195
|
+
|
|
196
|
+
# Tangents must be identical within floating point threshold
|
|
197
|
+
self.assertAlmostEqual(tan_in_x, tan_out_x, places=4, msg=f"C1 continuity violated in X at junction {k}")
|
|
198
|
+
self.assertAlmostEqual(tan_in_y, tan_out_y, places=4, msg=f"C1 continuity violated in Y at junction {k}")
|
|
199
|
+
|
|
200
|
+
def test_catmull_rom_spline_svg_output(self) -> None:
|
|
201
|
+
pts = [(10.0, 10.0), (50.0, 80.0), (100.0, 30.0)]
|
|
202
|
+
d_str = ParametricGeometry.catmull_rom_spline(pts)
|
|
203
|
+
self.assertTrue(d_str.startswith("M 10.0000 10.0000"))
|
|
204
|
+
self.assertIn("C ", d_str)
|
|
205
|
+
self.assertFalse(d_str.endswith("Z"))
|
|
206
|
+
|
|
207
|
+
# Closed spline
|
|
208
|
+
closed_d = ParametricGeometry.catmull_rom_spline(pts, closed=True)
|
|
209
|
+
self.assertTrue(closed_d.endswith("Z"))
|
|
210
|
+
|
|
211
|
+
def test_isometric_projection_30_degree(self) -> None:
|
|
212
|
+
# Origin
|
|
213
|
+
iso_x, iso_y = ParametricGeometry.isometric_project(0.0, 0.0, 0.0)
|
|
214
|
+
self.assertEqual(iso_x, 0.0)
|
|
215
|
+
self.assertEqual(iso_y, 0.0)
|
|
216
|
+
|
|
217
|
+
# X axis point: (100, 0, 0)
|
|
218
|
+
# x_iso = 100 * cos(30 deg) = 100 * 0.866025 = 86.6025
|
|
219
|
+
# y_iso = 100 * sin(30 deg) = 100 * 0.5 = 50.0
|
|
220
|
+
iso_x, iso_y = ParametricGeometry.isometric_project(100.0, 0.0, 0.0)
|
|
221
|
+
self.assertAlmostEqual(iso_x, 86.6025, places=3)
|
|
222
|
+
self.assertAlmostEqual(iso_y, 50.0, places=3)
|
|
223
|
+
|
|
224
|
+
# Z elevation: (0, 0, 50) -> y_screen shifted upward by 50
|
|
225
|
+
iso_x, iso_y = ParametricGeometry.isometric_project(0.0, 0.0, 50.0)
|
|
226
|
+
self.assertEqual(iso_x, 0.0)
|
|
227
|
+
self.assertAlmostEqual(iso_y, -50.0, places=3)
|
|
228
|
+
|
|
229
|
+
|
|
230
|
+
class TestBoundingBox(unittest.TestCase):
|
|
231
|
+
"""Test suite for spatial queries and viewBox bounding box calculations."""
|
|
232
|
+
|
|
233
|
+
def test_bbox_properties(self) -> None:
|
|
234
|
+
b = BoundingBox(x=10.0, y=20.0, width=100.0, height=50.0)
|
|
235
|
+
self.assertEqual(b.right, 110.0)
|
|
236
|
+
self.assertEqual(b.bottom, 70.0)
|
|
237
|
+
self.assertEqual(b.cx, 60.0)
|
|
238
|
+
self.assertEqual(b.cy, 45.0)
|
|
239
|
+
|
|
240
|
+
def test_contains(self) -> None:
|
|
241
|
+
b = BoundingBox(x=0.0, y=0.0, width=100.0, height=100.0)
|
|
242
|
+
# Point containment
|
|
243
|
+
self.assertTrue(b.contains((50.0, 50.0)))
|
|
244
|
+
self.assertTrue(b.contains((0.0, 0.0)))
|
|
245
|
+
self.assertTrue(b.contains((100.0, 100.0)))
|
|
246
|
+
self.assertFalse(b.contains((101.0, 50.0)))
|
|
247
|
+
self.assertFalse(b.contains((-1.0, 50.0)))
|
|
248
|
+
|
|
249
|
+
# Sub-box containment
|
|
250
|
+
inside = BoundingBox(x=10.0, y=10.0, width=50.0, height=50.0)
|
|
251
|
+
self.assertTrue(b.contains(inside))
|
|
252
|
+
|
|
253
|
+
outside = BoundingBox(x=90.0, y=90.0, width=20.0, height=20.0)
|
|
254
|
+
self.assertFalse(b.contains(outside))
|
|
255
|
+
|
|
256
|
+
def test_intersects(self) -> None:
|
|
257
|
+
b1 = BoundingBox(x=0.0, y=0.0, width=50.0, height=50.0)
|
|
258
|
+
b2 = BoundingBox(x=40.0, y=40.0, width=50.0, height=50.0)
|
|
259
|
+
b3 = BoundingBox(x=60.0, y=60.0, width=50.0, height=50.0)
|
|
260
|
+
|
|
261
|
+
self.assertTrue(b1.intersects(b2))
|
|
262
|
+
self.assertTrue(b2.intersects(b3))
|
|
263
|
+
self.assertFalse(b1.intersects(b3))
|
|
264
|
+
|
|
265
|
+
def test_enclosing_box_and_viewbox(self) -> None:
|
|
266
|
+
boxes = [
|
|
267
|
+
BoundingBox(x=10.0, y=20.0, width=40.0, height=30.0),
|
|
268
|
+
BoundingBox(x=100.0, y=80.0, width=50.0, height=60.0),
|
|
269
|
+
]
|
|
270
|
+
enc = BoundingBox.enclosing(boxes, padding=10.0)
|
|
271
|
+
# min_x = 10, min_y = 20, max_right = 150, max_bottom = 140
|
|
272
|
+
# with pad=10: x=0, y=10, width=140+20=160, height=120+20=140
|
|
273
|
+
self.assertEqual(enc.x, 0.0)
|
|
274
|
+
self.assertEqual(enc.y, 10.0)
|
|
275
|
+
self.assertEqual(enc.width, 160.0)
|
|
276
|
+
self.assertEqual(enc.height, 140.0)
|
|
277
|
+
|
|
278
|
+
viewbox = enc.to_viewbox()
|
|
279
|
+
self.assertEqual(viewbox, "0.00 10.00 160.00 140.00")
|
|
280
|
+
|
|
281
|
+
|
|
282
|
+
class TestVNode(unittest.TestCase):
|
|
283
|
+
"""Test suite for VNode AST representation, serialization, and sanitization."""
|
|
284
|
+
|
|
285
|
+
def test_vnode_creation_and_defaults(self) -> None:
|
|
286
|
+
node = VNode(id="card_main", kind="card", width=300.0, height=200.0)
|
|
287
|
+
self.assertEqual(node.id, "card_main")
|
|
288
|
+
self.assertEqual(node.kind, "card")
|
|
289
|
+
self.assertEqual(node.width, 300.0)
|
|
290
|
+
self.assertEqual(node.height, 200.0)
|
|
291
|
+
self.assertEqual(node.bbox.width, 300.0)
|
|
292
|
+
|
|
293
|
+
def test_vnode_defensive_sanitization(self) -> None:
|
|
294
|
+
# Null bytes stripped
|
|
295
|
+
bad_id = "node\x00_test"
|
|
296
|
+
bad_kind = "CARD\x00"
|
|
297
|
+
node = VNode(id=bad_id, kind=bad_kind, width=-50.0, height=float("nan"))
|
|
298
|
+
self.assertEqual(node.id, "node_test")
|
|
299
|
+
self.assertEqual(node.kind, "card")
|
|
300
|
+
self.assertEqual(node.width, 0.0)
|
|
301
|
+
self.assertEqual(node.height, 0.0)
|
|
302
|
+
|
|
303
|
+
def test_vnode_hierarchy_and_serialization(self) -> None:
|
|
304
|
+
parent = VNode(id="root", kind="card", width=400.0, height=300.0)
|
|
305
|
+
child = VNode(id="badge1", kind="badge", width=80.0, height=24.0)
|
|
306
|
+
parent.add_child(child)
|
|
307
|
+
|
|
308
|
+
self.assertEqual(len(parent.children), 1)
|
|
309
|
+
found = parent.find_by_id("badge1")
|
|
310
|
+
self.assertIsNotNone(found)
|
|
311
|
+
self.assertEqual(found.id, "badge1")
|
|
312
|
+
|
|
313
|
+
# Roundtrip serialization
|
|
314
|
+
data = parent.to_dict()
|
|
315
|
+
restored = VNode.from_dict(data)
|
|
316
|
+
self.assertEqual(restored.id, "root")
|
|
317
|
+
self.assertEqual(len(restored.children), 1)
|
|
318
|
+
self.assertEqual(restored.children[0].id, "badge1")
|
|
319
|
+
|
|
320
|
+
|
|
321
|
+
class TestVLayoutSolver(unittest.TestCase):
|
|
322
|
+
"""Test suite for relational 2D layout solver, constraints, and defensive guards."""
|
|
323
|
+
|
|
324
|
+
def setUp(self) -> None:
|
|
325
|
+
self.solver = VLayoutSolver()
|
|
326
|
+
|
|
327
|
+
def test_pin_to_viewport_anchors(self) -> None:
|
|
328
|
+
node = VNode(id="panel", kind="card", width=200.0, height=100.0)
|
|
329
|
+
|
|
330
|
+
# Top-Left
|
|
331
|
+
self.solver.pin_to_viewport(node, 1920.0, 1080.0, anchor="top-left", margin=20.0)
|
|
332
|
+
self.assertEqual(node.x, 20.0)
|
|
333
|
+
self.assertEqual(node.y, 20.0)
|
|
334
|
+
|
|
335
|
+
# Top-Right
|
|
336
|
+
self.solver.pin_to_viewport(node, 1920.0, 1080.0, anchor="top-right", margin=20.0)
|
|
337
|
+
self.assertEqual(node.x, 1920.0 - 200.0 - 20.0)
|
|
338
|
+
self.assertEqual(node.y, 20.0)
|
|
339
|
+
|
|
340
|
+
# Bottom-Right
|
|
341
|
+
self.solver.pin_to_viewport(node, 1920.0, 1080.0, anchor="bottom-right", margin=10.0)
|
|
342
|
+
self.assertEqual(node.x, 1920.0 - 200.0 - 10.0)
|
|
343
|
+
self.assertEqual(node.y, 1080.0 - 100.0 - 10.0)
|
|
344
|
+
|
|
345
|
+
# Center
|
|
346
|
+
self.solver.pin_to_viewport(node, 1000.0, 600.0, anchor="center")
|
|
347
|
+
self.assertEqual(node.x, 400.0)
|
|
348
|
+
self.assertEqual(node.y, 250.0)
|
|
349
|
+
|
|
350
|
+
def test_align_relative_relations(self) -> None:
|
|
351
|
+
ref = VNode(id="ref", kind="card", x=100.0, y=100.0, width=200.0, height=150.0)
|
|
352
|
+
target = VNode(id="target", kind="badge", width=50.0, height=30.0)
|
|
353
|
+
|
|
354
|
+
# Right of with middle alignment
|
|
355
|
+
self.solver.align_relative(target, ref, relation="right_of", gap=15.0, alignment="center")
|
|
356
|
+
self.assertEqual(target.x, 100.0 + 200.0 + 15.0)
|
|
357
|
+
self.assertEqual(target.y, ref.cy - 15.0)
|
|
358
|
+
|
|
359
|
+
# Below with start alignment
|
|
360
|
+
self.solver.align_relative(target, ref, relation="below", gap=10.0, alignment="start")
|
|
361
|
+
self.assertEqual(target.x, 100.0)
|
|
362
|
+
self.assertEqual(target.y, 100.0 + 150.0 + 10.0)
|
|
363
|
+
|
|
364
|
+
# Left of
|
|
365
|
+
self.solver.align_relative(target, ref, relation="left_of", gap=5.0, alignment="start")
|
|
366
|
+
self.assertEqual(target.x, 100.0 - 50.0 - 5.0)
|
|
367
|
+
self.assertEqual(target.y, 100.0)
|
|
368
|
+
|
|
369
|
+
# Inside / center
|
|
370
|
+
self.solver.align_relative(target, ref, relation="inside")
|
|
371
|
+
self.assertEqual(target.x, 100.0 + (200.0 - 50.0) / 2.0)
|
|
372
|
+
self.assertEqual(target.y, 100.0 + (150.0 - 30.0) / 2.0)
|
|
373
|
+
|
|
374
|
+
def test_solve_with_props_constraints(self) -> None:
|
|
375
|
+
root = VNode(id="container", kind="card", width=800.0, height=600.0)
|
|
376
|
+
header = VNode(id="header", kind="card", width=760.0, height=80.0, props={
|
|
377
|
+
"pin_to_viewport": {"anchor": "top-left", "margin": 20.0},
|
|
378
|
+
})
|
|
379
|
+
content = VNode(id="content", kind="card", width=760.0, height=450.0, props={
|
|
380
|
+
"align_relative": {"reference_id": "header", "relation": "below", "gap": 20.0},
|
|
381
|
+
})
|
|
382
|
+
root.add_child(header)
|
|
383
|
+
root.add_child(content)
|
|
384
|
+
|
|
385
|
+
bbox = self.solver.solve(root, viewport_width=800.0, viewport_height=600.0)
|
|
386
|
+
self.assertEqual(header.x, 20.0)
|
|
387
|
+
self.assertEqual(header.y, 20.0)
|
|
388
|
+
self.assertEqual(content.x, 20.0)
|
|
389
|
+
self.assertEqual(content.y, 120.0)
|
|
390
|
+
self.assertGreater(bbox.width, 0.0)
|
|
391
|
+
self.assertGreater(bbox.height, 0.0)
|
|
392
|
+
|
|
393
|
+
def test_defensive_input_guards(self) -> None:
|
|
394
|
+
# Guard against NaN/Inf in solve
|
|
395
|
+
node = VNode(id="bad_node", kind="card", width=float("nan"), height=float("inf"))
|
|
396
|
+
bbox = self.solver.solve(node, viewport_width=float("nan"), viewport_height=float("-inf"))
|
|
397
|
+
self.assertTrue(math.isfinite(bbox.x))
|
|
398
|
+
self.assertTrue(math.isfinite(bbox.y))
|
|
399
|
+
self.assertTrue(math.isfinite(bbox.width))
|
|
400
|
+
self.assertTrue(math.isfinite(bbox.height))
|
|
401
|
+
|
|
402
|
+
# Guard against deep nesting recursion overflow
|
|
403
|
+
deep_root = VNode(id="lvl_0", kind="card")
|
|
404
|
+
curr = deep_root
|
|
405
|
+
for idx in range(1, 80):
|
|
406
|
+
nxt = VNode(id=f"lvl_{idx}", kind="card")
|
|
407
|
+
curr.add_child(nxt)
|
|
408
|
+
curr = nxt
|
|
409
|
+
|
|
410
|
+
with self.assertRaises(ValueError):
|
|
411
|
+
self.solver.solve(deep_root)
|
|
412
|
+
|
|
413
|
+
def test_thread_safety(self) -> None:
|
|
414
|
+
solver = VLayoutSolver()
|
|
415
|
+
|
|
416
|
+
def worker(idx: int) -> float:
|
|
417
|
+
n1 = VNode(id=f"n1_{idx}", kind="card", width=100.0, height=50.0)
|
|
418
|
+
n2 = VNode(id=f"n2_{idx}", kind="badge", width=40.0, height=20.0)
|
|
419
|
+
solver.pin_to_viewport(n1, 1000.0, 1000.0, anchor="top-left", margin=idx * 2.0)
|
|
420
|
+
solver.align_relative(n2, n1, relation="right_of", gap=5.0)
|
|
421
|
+
return n2.x
|
|
422
|
+
|
|
423
|
+
with concurrent.futures.ThreadPoolExecutor(max_workers=8) as executor:
|
|
424
|
+
futures = [executor.submit(worker, i) for i in range(20)]
|
|
425
|
+
results = [f.result() for f in futures]
|
|
426
|
+
|
|
427
|
+
self.assertEqual(len(results), 20)
|
|
428
|
+
for i, res in enumerate(results):
|
|
429
|
+
expected = (i * 2.0) + 100.0 + 5.0
|
|
430
|
+
self.assertEqual(res, expected)
|
|
431
|
+
|
|
432
|
+
|
|
433
|
+
class TestFableVectorCompiler(unittest.TestCase):
|
|
434
|
+
"""Test suite for SVG compilation, XML AST well-formedness, filters, and gradients."""
|
|
435
|
+
|
|
436
|
+
def setUp(self) -> None:
|
|
437
|
+
self.compiler = FableVectorCompiler()
|
|
438
|
+
|
|
439
|
+
def test_compile_xml_ast_well_formedness(self) -> None:
|
|
440
|
+
# Build comprehensive scene with all 7 VNode kinds
|
|
441
|
+
root = VNode(id="dashboard", kind="card", x=0.0, y=0.0, width=1200.0, height=800.0, props={
|
|
442
|
+
"title": "Quantum Telemetry Dashboard",
|
|
443
|
+
"desc": "Real-time Fable Neuro-Symbolic Vector Telemetry",
|
|
444
|
+
})
|
|
445
|
+
|
|
446
|
+
# Dial node
|
|
447
|
+
dial = VNode(id="cpu_gauge", kind="dial", x=50.0, y=50.0, width=160.0, height=160.0, props={
|
|
448
|
+
"value": 0.82,
|
|
449
|
+
"label": "82% CPU",
|
|
450
|
+
})
|
|
451
|
+
root.add_child(dial)
|
|
452
|
+
|
|
453
|
+
# Spline node
|
|
454
|
+
spline = VNode(id="waveform", kind="spline", x=250.0, y=50.0, width=400.0, height=160.0, props={
|
|
455
|
+
"points": [(0, 80), (100, 20), (200, 140), (300, 40), (400, 80)],
|
|
456
|
+
"tension": 0.5,
|
|
457
|
+
})
|
|
458
|
+
root.add_child(spline)
|
|
459
|
+
|
|
460
|
+
# Text node
|
|
461
|
+
text_node = VNode(id="header_text", kind="text", x=700.0, y=80.0, width=300.0, height=30.0, props={
|
|
462
|
+
"text": "NEURO-SYMBOLIC RUNTIME: NOMINAL",
|
|
463
|
+
"font_size": 16,
|
|
464
|
+
"font_weight": "700",
|
|
465
|
+
})
|
|
466
|
+
root.add_child(text_node)
|
|
467
|
+
|
|
468
|
+
# Badge node
|
|
469
|
+
badge = VNode(id="status_badge", kind="badge", x=700.0, y=120.0, width=100.0, height=28.0, props={
|
|
470
|
+
"label": "ONLINE",
|
|
471
|
+
})
|
|
472
|
+
root.add_child(badge)
|
|
473
|
+
|
|
474
|
+
# Grid node
|
|
475
|
+
grid = VNode(id="bg_grid", kind="grid", x=50.0, y=260.0, width=500.0, height=400.0, props={
|
|
476
|
+
"spacing": 32.0,
|
|
477
|
+
"isometric": False,
|
|
478
|
+
})
|
|
479
|
+
root.add_child(grid)
|
|
480
|
+
|
|
481
|
+
# Isometric grid
|
|
482
|
+
iso_grid = VNode(id="iso_grid", kind="grid", x=600.0, y=260.0, width=500.0, height=400.0, props={
|
|
483
|
+
"spacing": 40.0,
|
|
484
|
+
"isometric": True,
|
|
485
|
+
})
|
|
486
|
+
root.add_child(iso_grid)
|
|
487
|
+
|
|
488
|
+
# Conduit node
|
|
489
|
+
conduit = VNode(id="wire_1", kind="conduit", x=210.0, y=130.0, width=40.0, height=0.0, props={
|
|
490
|
+
"x1": 210.0, "y1": 130.0, "x2": 250.0, "y2": 130.0,
|
|
491
|
+
})
|
|
492
|
+
root.add_child(conduit)
|
|
493
|
+
|
|
494
|
+
# Compile to SVG
|
|
495
|
+
svg_code = self.compiler.compile(root)
|
|
496
|
+
self.assertIsInstance(svg_code, str)
|
|
497
|
+
self.assertGreater(len(svg_code), 500)
|
|
498
|
+
|
|
499
|
+
# Validate that output is strictly well-formed XML AST
|
|
500
|
+
xml_root = ET.fromstring(svg_code)
|
|
501
|
+
self.assertEqual(xml_root.tag.split("}")[-1], "svg")
|
|
502
|
+
|
|
503
|
+
# Accessibility checks
|
|
504
|
+
self.assertEqual(xml_root.attrib.get("role"), "img")
|
|
505
|
+
self.assertIn("Quantum Telemetry", xml_root.attrib.get("aria-label", ""))
|
|
506
|
+
|
|
507
|
+
# Verify <title> and <desc>
|
|
508
|
+
titles = [e.text for e in xml_root.findall("{http://www.w3.org/2000/svg}title") or xml_root.findall("title")]
|
|
509
|
+
self.assertTrue(any("Quantum Telemetry" in str(t) for t in titles))
|
|
510
|
+
|
|
511
|
+
# Procedural noise filter check
|
|
512
|
+
filters = xml_root.findall(".//{http://www.w3.org/2000/svg}filter") or xml_root.findall(".//filter")
|
|
513
|
+
filter_ids = [f.attrib.get("id") for f in filters]
|
|
514
|
+
self.assertIn("fable-micro-grain", filter_ids)
|
|
515
|
+
|
|
516
|
+
turbulences = xml_root.findall(".//{http://www.w3.org/2000/svg}feTurbulence") or xml_root.findall(".//feTurbulence")
|
|
517
|
+
self.assertGreaterEqual(len(turbulences), 1)
|
|
518
|
+
|
|
519
|
+
# OKLCH Gradients check
|
|
520
|
+
linear_grads = xml_root.findall(".//{http://www.w3.org/2000/svg}linearGradient") or xml_root.findall(".//linearGradient")
|
|
521
|
+
grad_ids = [g.attrib.get("id") for g in linear_grads]
|
|
522
|
+
self.assertIn("fable-grad-obsidian", grad_ids)
|
|
523
|
+
self.assertIn("fable-grad-specular", grad_ids)
|
|
524
|
+
self.assertIn("fable-grad-accent", grad_ids)
|
|
525
|
+
|
|
526
|
+
# ViewBox enclosure check
|
|
527
|
+
viewbox = xml_root.attrib.get("viewBox", "")
|
|
528
|
+
self.assertTrue(viewbox, "viewBox attribute must be present")
|
|
529
|
+
vb_parts = [float(p) for p in viewbox.split()]
|
|
530
|
+
self.assertEqual(len(vb_parts), 4)
|
|
531
|
+
self.assertGreaterEqual(vb_parts[2], 1200.0)
|
|
532
|
+
self.assertGreaterEqual(vb_parts[3], 800.0)
|
|
533
|
+
|
|
534
|
+
|
|
535
|
+
class TestFleetDispatcherVectorIntegration(unittest.TestCase):
|
|
536
|
+
"""Test suite for CoderFleetDispatcher vector action dispatching."""
|
|
537
|
+
|
|
538
|
+
def setUp(self) -> None:
|
|
539
|
+
self.dispatcher = CoderFleetDispatcher()
|
|
540
|
+
|
|
541
|
+
def test_list_actions_contains_vector_actions(self) -> None:
|
|
542
|
+
actions = self.dispatcher.list_actions()
|
|
543
|
+
self.assertIn("compile_vector", actions)
|
|
544
|
+
self.assertIn("solve_layout", actions)
|
|
545
|
+
|
|
546
|
+
def test_dispatch_compile_vector(self) -> None:
|
|
547
|
+
node_def = {
|
|
548
|
+
"id": "telemetry_card",
|
|
549
|
+
"kind": "card",
|
|
550
|
+
"width": 320.0,
|
|
551
|
+
"height": 180.0,
|
|
552
|
+
"props": {
|
|
553
|
+
"title": "Core Status",
|
|
554
|
+
},
|
|
555
|
+
"children": [
|
|
556
|
+
{
|
|
557
|
+
"id": "badge_ok",
|
|
558
|
+
"kind": "badge",
|
|
559
|
+
"x": 20.0,
|
|
560
|
+
"y": 20.0,
|
|
561
|
+
"width": 60.0,
|
|
562
|
+
"height": 24.0,
|
|
563
|
+
"props": {"label": "OK"},
|
|
564
|
+
}
|
|
565
|
+
],
|
|
566
|
+
}
|
|
567
|
+
res = self.dispatcher.dispatch("compile_vector", {"root": node_def})
|
|
568
|
+
self.assertTrue(res["success"])
|
|
569
|
+
self.assertIn("svg", res["result"])
|
|
570
|
+
self.assertTrue(res["result"]["valid"])
|
|
571
|
+
|
|
572
|
+
# Check SVG string validity
|
|
573
|
+
svg_code = res["result"]["svg"]
|
|
574
|
+
parsed = ET.fromstring(svg_code)
|
|
575
|
+
self.assertEqual(parsed.tag.split("}")[-1], "svg")
|
|
576
|
+
|
|
577
|
+
def test_dispatch_solve_layout(self) -> None:
|
|
578
|
+
node_def = {
|
|
579
|
+
"id": "root_card",
|
|
580
|
+
"kind": "card",
|
|
581
|
+
"width": 500.0,
|
|
582
|
+
"height": 300.0,
|
|
583
|
+
"children": [
|
|
584
|
+
{
|
|
585
|
+
"id": "sidebar",
|
|
586
|
+
"kind": "card",
|
|
587
|
+
"width": 120.0,
|
|
588
|
+
"height": 260.0,
|
|
589
|
+
"props": {"pin_to_viewport": {"anchor": "top-left", "margin": 20.0}},
|
|
590
|
+
},
|
|
591
|
+
{
|
|
592
|
+
"id": "main_view",
|
|
593
|
+
"kind": "card",
|
|
594
|
+
"width": 320.0,
|
|
595
|
+
"height": 260.0,
|
|
596
|
+
"props": {"align_relative": {"reference_id": "sidebar", "relation": "right_of", "gap": 20.0}},
|
|
597
|
+
},
|
|
598
|
+
],
|
|
599
|
+
}
|
|
600
|
+
res = self.dispatcher.dispatch("solve_layout", {"root": node_def, "viewport_width": 500.0, "viewport_height": 300.0})
|
|
601
|
+
self.assertTrue(res["success"])
|
|
602
|
+
self.assertIn("bbox", res["result"])
|
|
603
|
+
self.assertIn("viewBox", res["result"])
|
|
604
|
+
self.assertEqual(res["result"]["root"]["children"][1]["x"], 160.0)
|
|
605
|
+
|
|
606
|
+
|
|
607
|
+
if __name__ == "__main__":
|
|
608
|
+
unittest.main()
|