STAR
Cal STAR — firmware, DAQ server, and engine design
Loading...
Searching...
No Matches
test_cea_cache_presets.py
Go to the documentation of this file.
1"""Every committed propellant preset must ship a matching CEA cache table.
2
3This is the guard that would have caught kerolox shipping with no cache: the UI
4exposes every configs/propellants/*.yaml preset, and switching to one whose
5cache_file is missing falls through to a live rocketcea build -- minutes of
6synchronous work that hangs the UI. Contributors regenerate caches by hand (see
7.github/workflows/regenerate-cea-cache.yml), so nothing but this test stops a new
8preset, a renamed cache_file, or a schema bump from silently losing its table.
9
10Reads only (np.load + metadata compare) -- no rocketcea, so it runs in the CI
11subset exactly like the rest of the gate.
12"""
13
14import json
15import unittest
16from pathlib import Path
17
18import numpy as np
19import yaml
20
21from engine.pipeline.cea_cache import CEA_TABLE_SCHEMA_VERSION
22
23ROOT = Path(__file__).resolve().parents[1]
24PRESET_DIR = ROOT / "configs" / "propellants"
25
26
28 return sorted(PRESET_DIR.glob("*.yaml"))
29
30
31class TestCeaCachePresets(unittest.TestCase):
33 # A glob that silently matches nothing would make every check below
34 # vacuously pass, so assert we actually found presets to check.
35 self.assertTrue(_presets(), f"no propellant presets under {PRESET_DIR}")
36
38 for preset_path in _presets():
39 with self.subTest(preset=preset_path.name):
40 preset = yaml.safe_load(preset_path.read_text()) or {}
41 cea = (preset.get("combustion") or {}).get("cea") or {}
42 # A preset with no CEA identity (e.g. a hypothetical "custom")
43 # owns no cache -- nothing to enforce.
44 if not cea.get("cache_file"):
45 self.skipTest("preset declares no cea.cache_file")
46
47 cache_path = ROOT / cea["cache_file"]
48 self.assertTrue(
49 cache_path.exists(),
50 f"{preset_path.name} references {cea['cache_file']} which is "
51 f"not committed -- regenerate it (see regenerate-cea-cache.yml) "
52 f"and commit it, or a live rocketcea build will hang the UI.",
53 )
54
55 data = np.load(cache_path, allow_pickle=True)
56 self.assertIn("meta", data.files, f"{cache_path.name} has no meta block")
57 meta = json.loads(data["meta"].tolist())
58
59 # Schema is the load-bearing one: a stale-schema table is served
60 # under the wrong meaning until it is rebuilt.
61 self.assertEqual(
62 meta.get("table_schema"), CEA_TABLE_SCHEMA_VERSION,
63 f"{cache_path.name} table_schema={meta.get('table_schema')} "
64 f"but code expects v{CEA_TABLE_SCHEMA_VERSION}",
65 )
66 # Identity: the cache must be for the propellant the preset names.
67 self.assertEqual(meta.get("ox_name"), cea.get("ox_name"), cache_path.name)
68 self.assertEqual(meta.get("fuel_name"), cea.get("fuel_name"), cache_path.name)
69 self.assertEqual(meta.get("dimensions"), 3, cache_path.name)
70 self.assertEqual(meta.get("n_points"), cea.get("n_points"), cache_path.name)
71 # Grid extents: an off-grid cache silently extrapolates.
72 for key in ("Pc_range", "MR_range", "eps_range"):
73 self.assertEqual(
74 [float(x) for x in (meta.get(key) or [])],
75 [float(x) for x in (cea.get(key) or [])],
76 f"{cache_path.name}: {key} mismatch",
77 )
78
79
80if __name__ == "__main__":
81 unittest.main()