gabriel / muse public
test_schema_supercharge.py python
458 lines 20.5 KB
Raw
sha256:b636f72dcba9e190afb980bece906fa5b717fbde014b76ef023df8cb96e01eb9 docs: expand cache plan with all seven testing tiers and do… Sonnet 4.6 134 days ago
1 """Seven-tier tests for ``muse/core/schema.py``.
2
3 All types are TypedDicts — no runtime logic, so the tiers focus on:
4
5 Unit — field presence/types on every TypedDict, Literal constraints.
6 Integration — ElementSchema union membership, MapSchema recursive nesting,
7 DomainSchema round-trips through json.dumps / json.loads.
8 End-to-end — schema instances accepted by functions that consume DomainSchema.
9 Stress — 10 000 construction cycles; deeply-nested MapSchema.
10 Data integrity — field values survive JSON round-trip unchanged.
11 Security — hostile strings in str fields do not cause crashes.
12 Performance — 10 000 constructions under 1 s.
13 """
14
15 from __future__ import annotations
16
17 import json
18 import time
19 import typing
20 from collections.abc import Mapping
21
22 import pytest
23
24
25 # ──────────────────────────────────────────────────────────────────────────────
26 # Helpers
27 # ──────────────────────────────────────────────────────────────────────────────
28
29
30 def _seq(**kw) -> Mapping[str, object]:
31 base = dict(
32 kind="sequence",
33 element_type="note",
34 identity="by_position",
35 diff_algorithm="lcs",
36 alphabet=None,
37 )
38 base.update(kw)
39 return base
40
41
42 def _tree(**kw) -> Mapping[str, object]:
43 base = dict(kind="tree", node_type="ast_node", diff_algorithm="zhang_shasha")
44 base.update(kw)
45 return base
46
47
48 def _tensor(**kw) -> Mapping[str, object]:
49 base = dict(
50 kind="tensor", dtype="float32", rank=2, epsilon=1e-6, diff_mode="sparse"
51 )
52 base.update(kw)
53 return base
54
55
56 def _set(**kw) -> Mapping[str, object]:
57 base = dict(kind="set", element_type="file_id", identity="by_id")
58 base.update(kw)
59 return base
60
61
62 def _map(value_schema: Mapping[str, object] | None = None, **kw) -> Mapping[str, object]:
63 base = dict(
64 kind="map",
65 key_type="str",
66 value_schema=value_schema or _seq(),
67 identity="by_key",
68 )
69 base.update(kw)
70 return base
71
72
73 def _dim(**kw) -> Mapping[str, object]:
74 base = dict(
75 name="notes",
76 description="MIDI note events",
77 schema=_seq(),
78 independent_merge=True,
79 )
80 base.update(kw)
81 return base
82
83
84 def _crdt_dim(**kw) -> Mapping[str, object]:
85 base = dict(
86 name="tempo",
87 description="BPM value",
88 crdt_type="lww_register",
89 independent_merge=True,
90 )
91 base.update(kw)
92 return base
93
94
95 def _domain(**kw) -> Mapping[str, object]:
96 base = dict(
97 domain="midi",
98 description="MIDI music domain",
99 dimensions=[_dim()],
100 top_level=_set(),
101 merge_mode="three_way",
102 schema_version="0.1.0",
103 )
104 base.update(kw)
105 return base
106
107
108 # ──────────────────────────────────────────────────────────────────────────────
109 # Unit — SequenceSchema
110 # ──────────────────────────────────────────────────────────────────────────────
111
112
113 class TestSequenceSchema:
114 def test_required_keys(self) -> None:
115 from muse.core.schema import SequenceSchema
116 hints = typing.get_type_hints(SequenceSchema)
117 assert {"kind", "element_type", "identity", "diff_algorithm", "alphabet"} <= set(hints)
118
119 def test_kind_literal_is_sequence(self) -> None:
120 from muse.core.schema import SequenceSchema
121 hints = typing.get_type_hints(SequenceSchema)
122 args = typing.get_args(hints["kind"])
123 assert "sequence" in args
124
125 def test_valid_diff_algorithms(self) -> None:
126 from muse.core.schema import SequenceSchema
127 hints = typing.get_type_hints(SequenceSchema)
128 algos = set(typing.get_args(hints["diff_algorithm"]))
129 assert algos == {"lcs", "myers", "patience"}
130
131 def test_valid_identity_values(self) -> None:
132 from muse.core.schema import SequenceSchema
133 hints = typing.get_type_hints(SequenceSchema)
134 ids = set(typing.get_args(hints["identity"]))
135 assert ids == {"by_id", "by_position", "by_content"}
136
137 def test_alphabet_is_optional_list(self) -> None:
138 from muse.core.schema import SequenceSchema
139 hints = typing.get_type_hints(SequenceSchema)
140 # Should be list[str] | None
141 args = typing.get_args(hints["alphabet"])
142 assert type(None) in args
143
144
145 # ──────────────────────────────────────────────────────────────────────────────
146 # Unit — TreeSchema
147 # ──────────────────────────────────────────────────────────────────────────────
148
149
150 class TestTreeSchema:
151 def test_required_keys(self) -> None:
152 from muse.core.schema import TreeSchema
153 hints = typing.get_type_hints(TreeSchema)
154 assert {"kind", "node_type", "diff_algorithm"} <= set(hints)
155
156 def test_valid_diff_algorithms(self) -> None:
157 from muse.core.schema import TreeSchema
158 hints = typing.get_type_hints(TreeSchema)
159 algos = set(typing.get_args(hints["diff_algorithm"]))
160 assert algos == {"zhang_shasha", "gumtree"}
161
162
163 # ──────────────────────────────────────────────────────────────────────────────
164 # Unit — TensorSchema
165 # ──────────────────────────────────────────────────────────────────────────────
166
167
168 class TestTensorSchema:
169 def test_required_keys(self) -> None:
170 from muse.core.schema import TensorSchema
171 hints = typing.get_type_hints(TensorSchema)
172 assert {"kind", "dtype", "rank", "epsilon", "diff_mode"} <= set(hints)
173
174 def test_valid_dtypes(self) -> None:
175 from muse.core.schema import TensorSchema
176 hints = typing.get_type_hints(TensorSchema)
177 dtypes = set(typing.get_args(hints["dtype"]))
178 assert dtypes == {"float32", "float64", "int8", "int16", "int32", "int64"}
179
180 def test_valid_diff_modes(self) -> None:
181 from muse.core.schema import TensorSchema
182 hints = typing.get_type_hints(TensorSchema)
183 modes = set(typing.get_args(hints["diff_mode"]))
184 assert modes == {"sparse", "block", "full"}
185
186
187 # ──────────────────────────────────────────────────────────────────────────────
188 # Unit — SetSchema
189 # ──────────────────────────────────────────────────────────────────────────────
190
191
192 class TestSetSchema:
193 def test_required_keys(self) -> None:
194 from muse.core.schema import SetSchema
195 hints = typing.get_type_hints(SetSchema)
196 assert {"kind", "element_type", "identity"} <= set(hints)
197
198 def test_valid_identity_values(self) -> None:
199 from muse.core.schema import SetSchema
200 hints = typing.get_type_hints(SetSchema)
201 ids = set(typing.get_args(hints["identity"]))
202 assert ids == {"by_content", "by_id"}
203
204
205 # ──────────────────────────────────────────────────────────────────────────────
206 # Unit — MapSchema
207 # ──────────────────────────────────────────────────────────────────────────────
208
209
210 class TestMapSchema:
211 def test_required_keys(self) -> None:
212 from muse.core.schema import MapSchema
213 hints = typing.get_type_hints(MapSchema)
214 assert {"kind", "key_type", "value_schema", "identity"} <= set(hints)
215
216 def test_identity_is_by_key(self) -> None:
217 from muse.core.schema import MapSchema
218 hints = typing.get_type_hints(MapSchema)
219 args = typing.get_args(hints["identity"])
220 assert "by_key" in args
221
222
223 # ──────────────────────────────────────────────────────────────────────────────
224 # Unit — DimensionSpec
225 # ──────────────────────────────────────────────────────────────────────────────
226
227
228 class TestDimensionSpec:
229 def test_required_keys(self) -> None:
230 from muse.core.schema import DimensionSpec
231 hints = typing.get_type_hints(DimensionSpec)
232 assert {"name", "description", "schema", "independent_merge"} <= set(hints)
233
234 def test_independent_merge_is_bool(self) -> None:
235 from muse.core.schema import DimensionSpec
236 hints = typing.get_type_hints(DimensionSpec)
237 assert hints["independent_merge"] is bool
238
239
240 # ──────────────────────────────────────────────────────────────────────────────
241 # Unit — CRDTDimensionSpec
242 # ──────────────────────────────────────────────────────────────────────────────
243
244
245 class TestCRDTDimensionSpec:
246 def test_required_keys(self) -> None:
247 from muse.core.schema import CRDTDimensionSpec
248 hints = typing.get_type_hints(CRDTDimensionSpec)
249 assert {"name", "description", "crdt_type", "independent_merge"} <= set(hints)
250
251 def test_valid_crdt_types(self) -> None:
252 from muse.core.schema import CRDTPrimitive
253 args = set(typing.get_args(CRDTPrimitive))
254 assert args == {"lww_register", "or_set", "rga", "aw_map", "g_counter"}
255
256
257 # ──────────────────────────────────────────────────────────────────────────────
258 # Unit — DomainSchema
259 # ──────────────────────────────────────────────────────────────────────────────
260
261
262 class TestDomainSchema:
263 def test_required_keys(self) -> None:
264 from muse.core.schema import DomainSchema
265 hints = typing.get_type_hints(DomainSchema)
266 assert {"domain", "description", "dimensions", "top_level", "merge_mode", "schema_version"} <= set(hints)
267
268 def test_valid_merge_modes(self) -> None:
269 from muse.core.schema import DomainSchema
270 hints = typing.get_type_hints(DomainSchema)
271 modes = set(typing.get_args(hints["merge_mode"]))
272 assert modes == {"three_way", "crdt"}
273
274
275 # ──────────────────────────────────────────────────────────────────────────────
276 # Integration — ElementSchema union, recursive nesting, JSON round-trip
277 # ──────────────────────────────────────────────────────────────────────────────
278
279
280 class TestIntegration:
281 def test_element_schema_includes_all_five_types(self) -> None:
282 from muse.core.schema import (
283 ElementSchema, MapSchema, SequenceSchema,
284 SetSchema, TensorSchema, TreeSchema,
285 )
286 members = typing.get_args(ElementSchema)
287 assert SequenceSchema in members
288 assert TreeSchema in members
289 assert TensorSchema in members
290 assert SetSchema in members
291 assert MapSchema in members
292
293 def test_map_schema_recursive_nesting(self) -> None:
294 """MapSchema.value_schema can itself be a MapSchema — recursive."""
295 inner = _map(value_schema=_seq())
296 outer = _map(value_schema=inner)
297 # Should be JSON-serialisable without error.
298 json.dumps(outer)
299
300 def test_domain_schema_json_round_trip(self) -> None:
301 schema = _domain()
302 raw = json.dumps(schema)
303 back = json.loads(raw)
304 assert back == schema
305
306 def test_dimension_spec_json_round_trip(self) -> None:
307 dim = _dim()
308 assert json.loads(json.dumps(dim)) == dim
309
310 def test_crdt_dimension_spec_json_round_trip(self) -> None:
311 cdim = _crdt_dim()
312 assert json.loads(json.dumps(cdim)) == cdim
313
314 def test_all_element_schema_types_json_serialisable(self) -> None:
315 for schema in [_seq(), _tree(), _tensor(), _set(), _map()]:
316 json.dumps(schema) # must not raise
317
318 def test_domain_with_crdt_merge_mode(self) -> None:
319 schema = _domain(merge_mode="crdt")
320 assert json.loads(json.dumps(schema))["merge_mode"] == "crdt"
321
322 def test_multiple_dimensions_in_domain(self) -> None:
323 schema = _domain(dimensions=[_dim(name="notes"), _dim(name="tempo")])
324 raw = json.dumps(schema)
325 back = json.loads(raw)
326 assert len(back["dimensions"]) == 2
327
328
329 # ──────────────────────────────────────────────────────────────────────────────
330 # End-to-end — schema used as plugin contract
331 # ──────────────────────────────────────────────────────────────────────────────
332
333
334 class TestEndToEnd:
335 def test_schema_importable_from_public_path(self) -> None:
336 from muse.core.schema import DomainSchema # noqa: F401
337
338 def test_element_schema_importable(self) -> None:
339 from muse.core.schema import ElementSchema # noqa: F401
340
341 def test_crdt_primitive_importable(self) -> None:
342 from muse.core.schema import CRDTPrimitive # noqa: F401
343
344 def test_domain_schema_dict_passable_to_json_dumps(self) -> None:
345 schema = _domain()
346 result = json.dumps(schema, sort_keys=True)
347 assert '"domain": "midi"' in result
348
349 def test_sequence_schema_with_alphabet(self) -> None:
350 seq = _seq(alphabet=["C", "D", "E", "F", "G", "A", "B"])
351 assert json.loads(json.dumps(seq))["alphabet"] == ["C", "D", "E", "F", "G", "A", "B"]
352
353
354 # ──────────────────────────────────────────────────────────────────────────────
355 # Stress
356 # ──────────────────────────────────────────────────────────────────────────────
357
358
359 class TestStress:
360 def test_10000_domain_schema_constructions(self) -> None:
361 for i in range(10_000):
362 schema = _domain(domain=f"domain_{i}", schema_version=f"0.{i}.0")
363 assert schema["domain"] == f"domain_{i}"
364
365 def test_deeply_nested_map_schema(self) -> None:
366 """MapSchema.value_schema is recursive — 50 levels deep must not crash."""
367 schema = _seq()
368 for _ in range(50):
369 schema = _map(value_schema=schema)
370 # Must be JSON-serialisable regardless of depth.
371 json.dumps(schema)
372
373 def test_domain_with_100_dimensions(self) -> None:
374 dims = [_dim(name=f"dim_{i}") for i in range(100)]
375 schema = _domain(dimensions=dims)
376 raw = json.loads(json.dumps(schema))
377 assert len(raw["dimensions"]) == 100
378
379
380 # ──────────────────────────────────────────────────────────────────────────────
381 # Data integrity
382 # ──────────────────────────────────────────────────────────────────────────────
383
384
385 class TestDataIntegrity:
386 def test_tensor_epsilon_survives_json_round_trip(self) -> None:
387 t = _tensor(epsilon=1e-9)
388 back = json.loads(json.dumps(t))
389 assert abs(back["epsilon"] - 1e-9) < 1e-20
390
391 def test_tensor_rank_survives_json_round_trip(self) -> None:
392 t = _tensor(rank=4)
393 assert json.loads(json.dumps(t))["rank"] == 4
394
395 def test_independent_merge_bool_survives_round_trip(self) -> None:
396 dim = _dim(independent_merge=False)
397 back = json.loads(json.dumps(dim))
398 assert back["independent_merge"] is False
399
400 def test_domain_schema_version_string_preserved(self) -> None:
401 schema = _domain(schema_version="1.2.3")
402 back = json.loads(json.dumps(schema))
403 assert back["schema_version"] == "1.2.3"
404
405 def test_set_element_type_preserved(self) -> None:
406 s = _set(element_type="track_id")
407 assert json.loads(json.dumps(s))["element_type"] == "track_id"
408
409
410 # ──────────────────────────────────────────────────────────────────────────────
411 # Security
412 # ──────────────────────────────────────────────────────────────────────────────
413
414
415 class TestSecurity:
416 def test_hostile_string_in_domain_name_survives_json(self) -> None:
417 evil = '"; DROP TABLE domains; --'
418 schema = _domain(domain=evil)
419 back = json.loads(json.dumps(schema))
420 assert back["domain"] == evil
421
422 def test_ansi_in_description_survives_json(self) -> None:
423 desc = "\x1b[31mevil\x1b[0m"
424 schema = _domain(description=desc)
425 back = json.loads(json.dumps(schema))
426 assert back["description"] == desc
427
428 def test_null_byte_in_element_type_survives_json(self) -> None:
429 s = _seq(element_type="note\x00evil")
430 back = json.loads(json.dumps(s))
431 assert back["element_type"] == "note\x00evil"
432
433 def test_unicode_in_dimension_name_survives_json(self) -> None:
434 dim = _dim(name="音符")
435 back = json.loads(json.dumps(dim))
436 assert back["name"] == "音符"
437
438
439 # ──────────────────────────────────────────────────────────────────────────────
440 # Performance
441 # ──────────────────────────────────────────────────────────────────────────────
442
443
444 class TestPerformance:
445 def test_10000_constructions_under_1s(self) -> None:
446 start = time.perf_counter()
447 for i in range(10_000):
448 _domain(schema_version=f"0.{i}.0")
449 elapsed = time.perf_counter() - start
450 assert elapsed < 1.0
451
452 def test_json_round_trip_10000_times_under_2s(self) -> None:
453 schema = _domain()
454 start = time.perf_counter()
455 for _ in range(10_000):
456 json.loads(json.dumps(schema))
457 elapsed = time.perf_counter() - start
458 assert elapsed < 2.0
File History 2 commits
sha256:b636f72dcba9e190afb980bece906fa5b717fbde014b76ef023df8cb96e01eb9 docs: expand cache plan with all seven testing tiers and do… Sonnet 4.6 134 days ago
sha256:7f9e2ef5286aedad9c1e6011b4c46ca27f39dbdad6e3409357e36b26e46b3b7c docs: docstring sprint for-each-ref→hotspots — idiomatic ru… Sonnet 4.6 patch 140 days ago