trikona / tests /test_codegen.py
disssid's picture
Option A: route live codegen path through pipeline (gate + retry + fallback)
c42f749
Raw
History Blame Contribute Delete
6.12 kB
"""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