The validator is a small function from call text to verdict. It returns ok or one named fault, and execution follows only ok.
Input class | Example | Verdict |
|---|---|---|
Valid read |
|
|
Valid write with extra key |
|
|
Unknown name |
|
|
Missing argument |
|
|
Wrong type |
|
|
Out of range |
|
|
The suite below fixes eleven calls: four accepted and seven rejected. The split keeps the census exact.
11 fixed cases in the suite
accept: 4 of 11
reject: 7 of 11
check: 4 + 7 = 11The program below is the full validator plus its suite, with scripted calls standing in for a model. Thirteen checks accept four calls and reject seven.
In Python, bool is a subclass of int, so the type check compares exact types and rejects boolean values.
TOOLS = {
"read_register": {
"required": ["addr"],
"types": {"addr": int},
"bounds": {"addr": (0, 15)},
},
"write_register": {
"required": ["addr", "data"],
"types": {"addr": int, "data": int},
"bounds": {"addr": (0, 15), "data": (0, 255)},
},
}
def validate(call):
if not isinstance(call, dict):
return "not-an-object"
if "tool" not in call:
return "missing-tool"
if call["tool"] not in TOOLS:
return "unknown-tool"
args = call.get("arguments")
if not isinstance(args, dict):
return "bad-arguments"
spec = TOOLS[call["tool"]]
for key in spec["required"]:
if key not in args:
return "missing-" + key
for key in spec["required"]:
if type(args[key]) is not int:
return "type-" + key
for key in spec["required"]:
lo, hi = spec["bounds"][key]
if args[key] < lo or args[key] > hi:
return "range-" + key
return "ok"
CASES = [
({"tool": "read_register", "arguments": {"addr": 5}}, "ok"),
({"tool": "write_register", "arguments": {"addr": 2, "data": 255}}, "ok"),
({"tool": "write_register", "arguments": {"addr": 2, "data": 1, "tag": "x"}}, "ok"),
({"tool": "read_register", "arguments": {"addr": 15}}, "ok"),
({"tool": "zap_register", "arguments": {"addr": 1}}, "unknown-tool"),
({"tool": "read_register", "arguments": {}}, "missing-addr"),
({"tool": "read_register", "arguments": {"addr": True}}, "type-addr"),
({"tool": "write_register", "arguments": {"addr": 2, "data": "12"}}, "type-data"),
({"tool": "read_register", "arguments": {"addr": 16}}, "range-addr"),
({"tool": "write_register", "arguments": {"addr": 2, "data": 256}}, "range-data"),
({"tool": "read_register", "arguments": [5]}, "bad-arguments"),
]
bad = 0
accepted = 0
rejected = 0
for call, want in CASES:
got = validate(call)
if got != want:
bad += 1
if got == "ok":
accepted += 1
else:
rejected += 1
if accepted != 4:
bad += 1
if rejected != 7:
bad += 1
if bad == 0:
print("PASS: 13 validator checks accept 4 calls and reject 7 across 11 cases")
else:
print("FAIL: " + str(bad) + " of 13 validator checks went wrong")
raise SystemExit(1)tag appears in calls. Must the validator change?No. Unknown extra keys are allowed, so old checks keep passing while the schema grows.
It barely exercises rejection. Flip the balance toward malformed cases, one per fault shape.