sud: fail fast on malformed sud.json documents, one test per fixture
Sud.load() now calls utils/sud_validate.py's validate_sud() before parsing: requires a non-empty 'steps' list, and for every resolved step that sets its own 'temperature', a numeric ramp.rate - the one genuinely unguarded crash site (ramp['rate'], no .get() fallback) hit in tasks/sud.py, components/sud_forecast.py and demo_sud.py the moment that step is reached, which for SudForecastEstimator can be as early as the Load itself. A refused load is now logged with the specific reason (previously silent). validate_sud() reuses Sud._parse_data() directly rather than reimplementing default-merging/step-building. tests/utils/test_sud_validate_fixtures.py covers 8 fixture docs plus all 6 real sude/*.json files; tests/components/sud/test_sud_load.py exercises the Sud.load() wiring itself (rejects, keeps prior schedule). Also reworked both this and test_config_fixtures.py to generate one dedicated test method per fixture file (dynamic setattr) instead of looping with subTest inside a single method - subTest iterations don't show up individually in `unittest -v`/IDE test runners, which made it look like far fewer fixtures were covered than actually are. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01F4oEE99rkKVU8L8tkzXWdG
This commit is contained in:
+20
-2
@@ -140,12 +140,30 @@ class Sud(AttributeChange):
|
||||
a schedule does so explicitly itself, e.g. by writing save()'s
|
||||
result to one of the sude/*.json files), resetting to IDLE as if
|
||||
freshly loaded. Refused while a brew is in progress, or if data is
|
||||
malformed - in either case the current schedule is left untouched."""
|
||||
malformed - in either case the current schedule is left untouched.
|
||||
|
||||
validate_sud() catches the same malformed-document cases _parse_data()
|
||||
itself would raise on, plus content _parse_data() happily resolves
|
||||
but that would crash much later - a step setting its own 'temperature'
|
||||
with no ramp.rate anywhere to inherit only blows up when tasks/sud.py
|
||||
or SudForecastEstimator actually reaches that step (ramp['rate'] has
|
||||
no .get() fallback), which for the forecast estimator can be as soon
|
||||
as this very Load. Refusing it here instead, with the reason logged
|
||||
(nothing else here logs *why* a load was refused), is strictly better
|
||||
than a bare KeyError from inside a run or a worker-thread forecast."""
|
||||
# Imported here, not at module level: utils/sud_validate.py imports
|
||||
# Sud from this very module to reuse _parse_data(), so a top-level
|
||||
# import here would be circular.
|
||||
from utils.sud_validate import validate_sud
|
||||
from utils.config_validate import ConfigError
|
||||
|
||||
if self.state not in (SudState.IDLE, SudState.DONE):
|
||||
return False
|
||||
try:
|
||||
validate_sud(data)
|
||||
parsed = self._parse_data(data, self._pot_config)
|
||||
except (KeyError, TypeError):
|
||||
except (KeyError, TypeError, ConfigError) as e:
|
||||
self.log.warning("Refusing to load malformed sud document: {}".format(e))
|
||||
return False
|
||||
|
||||
(self.name, self.description, self.schedule, self.pot_mass,
|
||||
|
||||
Reference in New Issue
Block a user