"""Phase A2 — sandbox kernel (SPEC_OPTION_A.md §4.1, §10). Offline: a valid generator executes and (via repair) passes the metric gate; disallowed imports, timeouts, runtime errors, and invalid output all raise KernelError. """ import sys from pathlib import Path import numpy as np import pytest ROOT = Path(__file__).resolve().parent.parent if str(ROOT) not in sys.path: sys.path.insert(0, str(ROOT)) from geometry import kernel, metrics, repair # noqa: E402 GEN_OCT = ''' import numpy as np def build(p): v = [[1,0,0],[-1,0,0],[0,1,0],[0,-1,0],[0,0,1],[0,0,-1]] f = [[4,0,2,"f"],[4,2,1,"f"],[4,1,3,"f"],[4,3,0,"f"], [5,2,0,"f"],[5,1,2,"f"],[5,3,1,"f"],[5,0,3,"f"]] return v, f ''' GEN_PARAM_PRISM = ''' import numpy as np, math def build(p): n = int(p.get("sides", 5)) ang = [2*math.pi*i/n for i in range(n)] bot = [[math.cos(a), -1.0, math.sin(a)] for a in ang] top = [[math.cos(a), 1.0, math.sin(a)] for a in ang] v = bot + top f = [] for i in range(n): j = (i+1) % n f.append([i, j, n+j, "side"]); f.append([i, n+j, n+i, "side"]) return v, f ''' def test_valid_generator_runs(): V, F = kernel.run(GEN_OCT) assert V.shape == (6, 3) assert len(F) == 8 and all(len(f) == 4 for f in F) def test_generator_plus_repair_passes_gate(): V, F = kernel.run(GEN_OCT) v, f = repair.repair(V, F, "regular_solid") ok, fails = metrics.passes(v, f, "regular_solid") assert ok, fails def test_params_reach_generator(): V, F = kernel.run(GEN_PARAM_PRISM, params={"sides": 7}) assert V.shape == (14, 3) # 7 bottom + 7 top def test_disallowed_import_rejected(): code = "import os\ndef build(p):\n return [[0,0,0]], [[0,0,0,'x']]\n" with pytest.raises(kernel.KernelError) as e: kernel.run(code) assert "disallowed import" in str(e.value) def test_timeout_rejected(): code = "def build(p):\n x=0\n while True:\n x+=1\n return [],[]\n" with pytest.raises(kernel.KernelError) as e: kernel.run(code, timeout=1) assert "timeout" in str(e.value) def test_runtime_error_rejected(): code = "def build(p):\n return 1/0\n" with pytest.raises(kernel.KernelError): kernel.run(code) def test_non_finite_rejected(): code = ("import numpy as np\n" "def build(p):\n" " return [[float('nan'),0,0],[1,0,0],[0,1,0]], [[0,1,2,'f']]\n") with pytest.raises(kernel.KernelError): kernel.run(code) def test_out_of_range_face_rejected(): code = "def build(p):\n return [[0,0,0],[1,0,0],[0,1,0]], [[0,1,9,'f']]\n" with pytest.raises(kernel.KernelError): kernel.run(code) def test_missing_entry_rejected(): code = "def other(p):\n return [],[]\n" with pytest.raises(kernel.KernelError): kernel.run(code) def test_syntax_error_rejected(): with pytest.raises(kernel.KernelError): kernel.run("def build(p)\n return [],[]\n")