config: generalize config_validate into a shared schema engine
require_schema(config, schema) is a generic recursive declarative
checker (nested-dict schema, leaves name required type(s), each
intermediate object node gets its own dict-type check so a wrong-typed
section is named directly instead of surfacing as a confusing "missing
child" error). validate_temp_ctrl() is now just require_schema(config,
TEMPCTRL_SCHEMA) against a declarative schema dict, replacing the
hand-rolled nested loops.
utils/sud_validate.py's static structural check ('steps' must be a
list) now goes through the same require_schema()/SUD_SCHEMA - one
shared mechanism for both server config.json and sud.json documents.
The genuinely dynamic per-step check (ramp.rate required only when a
step sets its own temperature) stays hand-written, since it depends on
the document's content, not just its shape.
Added TestRequireSchema, exercising the generic engine directly
against a schema unrelated to TempCtrl.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01F4oEE99rkKVU8L8tkzXWdG
This commit is contained in:
@@ -1,6 +1,6 @@
|
||||
import unittest
|
||||
|
||||
from utils.config_validate import ConfigError, require, validate_temp_ctrl
|
||||
from utils.config_validate import ConfigError, require, require_schema, validate_temp_ctrl
|
||||
|
||||
|
||||
def _valid_temp_ctrl_config():
|
||||
@@ -42,6 +42,36 @@ class TestRequire(unittest.TestCase):
|
||||
self.assertEqual(require({"a": 1.5}, "a", type=(int, float)), 1.5)
|
||||
|
||||
|
||||
class TestRequireSchema(unittest.TestCase):
|
||||
"""require_schema() is the generic engine both validate_temp_ctrl()
|
||||
(config.json's TempCtrl section) and utils/sud_validate.py's
|
||||
validate_sud() (a sud.json's fixed 'steps' shape) are built on - tested
|
||||
here directly, against a schema unrelated to either, to confirm it's
|
||||
not accidentally coupled to TempCtrl's specific shape."""
|
||||
|
||||
SCHEMA = {'a': {'b': int, 'c': (int, float)}, 'd': str}
|
||||
|
||||
def test_valid_document_passes(self):
|
||||
require_schema({'a': {'b': 1, 'c': 2.5}, 'd': 'x'}, self.SCHEMA)
|
||||
|
||||
def test_missing_nested_leaf_raises(self):
|
||||
with self.assertRaisesRegex(ConfigError, r'a\.b'):
|
||||
require_schema({'a': {'c': 2.5}, 'd': 'x'}, self.SCHEMA)
|
||||
|
||||
def test_missing_top_level_leaf_raises(self):
|
||||
with self.assertRaisesRegex(ConfigError, r'missing required config key: d$'):
|
||||
require_schema({'a': {'b': 1, 'c': 2.5}}, self.SCHEMA)
|
||||
|
||||
def test_wrong_type_on_nested_object_itself_is_named_directly(self):
|
||||
# 'a' present but not an object - must be reported as 'a' itself,
|
||||
# not as a confusing "missing a.b"/"missing a.c".
|
||||
with self.assertRaisesRegex(ConfigError, r'^config key a must be dict'):
|
||||
require_schema({'a': 'not an object', 'd': 'x'}, self.SCHEMA)
|
||||
|
||||
def test_extra_keys_not_in_schema_are_ignored(self):
|
||||
require_schema({'a': {'b': 1, 'c': 2.5}, 'd': 'x', 'extra': 'stray'}, self.SCHEMA)
|
||||
|
||||
|
||||
class TestValidateTempCtrl(unittest.TestCase):
|
||||
def test_valid_config_passes(self):
|
||||
validate_temp_ctrl(_valid_temp_ctrl_config())
|
||||
|
||||
Reference in New Issue
Block a user