Spaces:
Sleeping
Sleeping
File size: 6,121 Bytes
36b02e9 c42f749 36b02e9 | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 | """Phase A4 — codegen integration, offline (SPEC_OPTION_A.md §10).
Exercises app.codegen_assemble (the backend post-processing layer) with CANNED
model build_spec output — no live Modal. The full server-side path
(build_spec text -> kernel -> repair -> assembler -> contract) must yield JSON
that passes the existing frontend contract and the metric gate. Also covers
passthrough, primitive fallback, and model-free regeneration. The LIVE model
call is FLAGGED for the user (not tested here).
"""
import importlib.util
import json
import sys
from pathlib import Path
import pytest
ROOT = Path(__file__).resolve().parent.parent
if str(ROOT) not in sys.path:
sys.path.insert(0, str(ROOT))
import app # noqa: E402
from geometry import metrics # noqa: E402
_spec = importlib.util.spec_from_file_location("schema_mod", ROOT / "tests" / "test_schema.py")
_schema = importlib.util.module_from_spec(_spec)
_spec.loader.exec_module(_schema)
validate_shape = _schema.validate_shape
FIX = ROOT / "tests" / "fixtures" / "codegen"
SPECS = {"octahedron": "spec_octahedron.json",
"cube": "spec_cube.json",
"cylindrical_vent": "spec_cylindrical_vent.json"}
def raw_spec(name):
return (FIX / SPECS[name]).read_text()
def regularity(name):
return json.loads(raw_spec(name))["regularity"]
# --------------------------------------------------------------------------- #
# full codegen path on canned build_spec output #
# --------------------------------------------------------------------------- #
@pytest.mark.parametrize("name", list(SPECS))
def test_codegen_assemble_produces_valid_contract(name):
out = app.codegen_assemble(raw_spec(name))
contract = json.loads(out)
assert validate_shape(contract) == []
verts = contract["geometry"]["vertices"]
faces = [f["indices"] for f in contract["geometry"]["faces"]]
ok, fails = metrics.passes(verts, faces, regularity(name))
assert ok, fails
# --------------------------------------------------------------------------- #
# passthrough of non-build_spec responses #
# --------------------------------------------------------------------------- #
def test_guided_response_passthrough():
raw = json.dumps({"type": "guided_response", "message": "hi", "suggestions": ["cube"]})
assert json.loads(app.codegen_assemble(raw)) == json.loads(raw)
def test_error_passthrough():
raw = json.dumps({"error": "endpoint_unreachable"})
assert json.loads(app.codegen_assemble(raw)) == json.loads(raw)
def test_malformed_becomes_error():
assert json.loads(app.codegen_assemble("not json at all"))["error"] == "model_invalid_json"
def test_non_buildspec_dict_passthrough():
raw = json.dumps({"shape_id": "x", "geometry": {"vertices": [], "faces": []}})
assert json.loads(app.codegen_assemble(raw)) == json.loads(raw)
# --------------------------------------------------------------------------- #
# generator failure -> primitive fallback #
# --------------------------------------------------------------------------- #
def test_broken_generator_falls_back_to_primitive():
spec = json.loads(raw_spec("octahedron"))
spec["generator"]["code"] = "def build(p):\n raise ValueError('boom')\n"
out = app.codegen_assemble(json.dumps(spec))
contract = json.loads(out)
assert validate_shape(contract) == [] # recovered via primitives.REGISTRY['octahedron']
assert contract["shape_id"] == "octahedron"
def test_broken_generator_unknown_shape_errors():
spec = json.loads(raw_spec("octahedron"))
spec["shape_id"] = "no_such_primitive"
spec["generator"]["code"] = "def build(p):\n raise ValueError('boom')\n"
out = app.codegen_assemble(json.dumps(spec))
assert json.loads(out)["error"] == "model_invalid_json"
# --------------------------------------------------------------------------- #
# caching for model-free regeneration #
# --------------------------------------------------------------------------- #
def test_assemble_caches_spec_for_regen():
app._SPEC_CACHE.pop("k1", None)
app.codegen_assemble(raw_spec("cylindrical_vent"), cache_key="k1")
assert "k1" in app._SPEC_CACHE
# Model-free regen: re-run the cached generator with more vanes.
from geometry import assembler
contract, _ = assembler.build(app._SPEC_CACHE["k1"], {"vanes": 12, "height": 2.0})
assert validate_shape(contract) == []
assert len([c for c in contract["components"] if c["id"].startswith("vane_")]) == 12
# --------------------------------------------------------------------------- #
# model-free regeneration helper (_regen_modelfree) #
# --------------------------------------------------------------------------- #
def test_regen_modelfree_good_spec():
spec = json.loads(raw_spec("cylindrical_vent"))
out = app._regen_modelfree(spec, {"vanes": 10, "height": 2.0})
contract = json.loads(out)
assert validate_shape(contract) == []
assert len([c for c in contract["components"] if c["id"].startswith("vane_")]) == 10
def test_regen_modelfree_broken_generator_falls_back_to_primitive():
spec = json.loads(raw_spec("octahedron"))
spec["generator"]["code"] = "def build(p):\n raise ValueError('x')\n"
out = app._regen_modelfree(spec, None) # shape_id 'octahedron' is a primitive
assert out is not None and validate_shape(json.loads(out)) == []
def test_regen_modelfree_broken_unknown_returns_none():
spec = json.loads(raw_spec("octahedron"))
spec["shape_id"] = "no_such_primitive"
spec["generator"]["code"] = "def build(p):\n raise ValueError('x')\n"
assert app._regen_modelfree(spec, None) is None
# --------------------------------------------------------------------------- #
# default path is unchanged when the flag is off #
# --------------------------------------------------------------------------- #
def test_codegen_flag_defaults_off():
assert app.CODEGEN is False
|