diff --git a/tests/conformance/README.md b/tests/conformance/README.md index eca5c22..753f45d 100644 --- a/tests/conformance/README.md +++ b/tests/conformance/README.md @@ -4,11 +4,13 @@ A language-neutral set of golden-frame tests. Any DCP implementation should be able to load `golden_frames.yaml` and verify that: 1. **Encode**: building a frame with the given fields produces the listed - `wire_hex`. -2. **Decode**: parsing the `wire_hex` reproduces the listed fields. + header plus `cbor_hex`. +2. **Decode**: parsing the frame bytes derived from the header and `cbor_hex` + reproduces the listed fields. 3. **Framing**: when `uart_wire_hex` is present, COBS+CRC encoding of the frame produces those bytes; decoding them recovers the frame. -4. **CRC**: `crc16` is the CRC-16/CCITT of the named bytes. +4. **CRC**: when `crc16` is present, it is the CRC-16/CCITT of the frame + bytes before UART wrapping. The Python reference test runner is in `test_conformance.py` and can serve as a template for ports. @@ -22,9 +24,9 @@ as a template for ports. intent: "set_brightness" # required: source string for the intent_id payload: # required: CBOR map contents level: 50.0 - wire_hex: "01 01 ..." # required: full DCP frame (header + cbor) - uart_wire_hex: "0a 01 ..." # optional: COBS-wrapped on-the-wire bytes (no trailing 0x00) - crc16: 0x29b1 # optional: CRC-16 of wire_hex bytes + cbor_hex: "a1 65 ..." # required: CBOR body bytes only + uart_wire_hex: "0a 01 ..." # optional: COBS-wrapped frame+CRC, no trailing 0x00 + crc16: 0x29b1 # optional: CRC-16 of full frame bytes ``` Whitespace in hex is allowed and ignored. Hex is lowercase. @@ -32,8 +34,10 @@ Whitespace in hex is allowed and ignored. Hex is lowercase. ## Adding a new test 1. Pick a single concrete frame shape (call, reply, event, dry-run, error). -2. Compute `wire_hex` from a known-good implementation. -3. Where the spec is silent, prefer the simplest possible CBOR encoding (no +2. Compute `cbor_hex` from a known-good implementation. +3. Include `uart_wire_hex` and `crc16` when you want to pin framing and CRC + behavior for that case. +4. Where the spec is silent, prefer the simplest possible CBOR encoding (no indefinite-length, no tags). If your implementation disagrees with a golden frame, **the golden is the diff --git a/tests/conformance/golden_frames.yaml b/tests/conformance/golden_frames.yaml index 3539bee..f5d6294 100644 --- a/tests/conformance/golden_frames.yaml +++ b/tests/conformance/golden_frames.yaml @@ -32,6 +32,13 @@ value: 0.0 cbor_hex: "a1 65 76 61 6c 75 65 fb 00 00 00 00 00 00 00 00" +- name: reply with empty-map body + kind: 0x02 + seq: 8 + intent: "ping" + payload: {} + cbor_hex: "a0" + - name: dry-run call with one float kind: 0x81 seq: 99 @@ -48,6 +55,16 @@ status: 4 cbor_hex: "a1 66 73 74 61 74 75 73 04" +- name: error with multi-field payload + kind: 0x04 + seq: 13 + intent: "unknown" + payload: + status: 4 + message: unknown + code: 404 + cbor_hex: "a3 66 73 74 61 74 75 73 04 67 6d 65 73 73 61 67 65 67 75 6e 6b 6e 6f 77 6e 64 63 6f 64 65 19 01 94" + - name: event with float payload kind: 0x03 seq: 0 @@ -56,6 +73,15 @@ confidence: 1.0 cbor_hex: "a1 6a 63 6f 6e 66 69 64 65 6e 63 65 fb 3f f0 00 00 00 00 00 00" +- name: event with integer payload at boundaries + kind: 0x03 + seq: 0 + intent: "motion_detected" + payload: + min: 0 + max: 255 + cbor_hex: "a2 63 6d 69 6e 00 63 6d 61 78 18 ff" + - name: call with three small ints kind: 0x01 seq: 0x1234 @@ -65,6 +91,27 @@ g: 100 b: 0 cbor_hex: "a3 61 72 18 ff 61 67 18 64 61 62 00" + uart_wire_hex: "12 01 01 12 34 7a 22 a3 61 72 18 ff 61 67 18 64 61 62 03 94 ef" + crc16: 0x94ef + +- name: call with mixed-width sequence number + kind: 0x01 + seq: 0xff01 + intent: "set_color" + payload: + r: 255 + g: 100 + b: 0 + cbor_hex: "a3 61 72 18 ff 61 67 18 64 61 62 00" + +- name: call with explicit bool + string payload + kind: 0x01 + seq: 6 + intent: "set_enabled" + payload: + enabled: true + label: ok + cbor_hex: "a2 67 65 6e 61 62 6c 65 64 f5 65 6c 61 62 65 6c 62 6f 6b" - name: reply with bool kind: 0x02 diff --git a/tests/conformance/test_conformance.py b/tests/conformance/test_conformance.py index da8ad7e..1b6a164 100644 --- a/tests/conformance/test_conformance.py +++ b/tests/conformance/test_conformance.py @@ -36,6 +36,10 @@ def _build_frame(case: dict) -> bytes: return header + _hex(case.get("cbor_hex", "")) +def _crc16_bytes(case: dict) -> bytes: + return _hex(f"{int(case['crc16']):04x}") + + def load_cases() -> list[dict]: return yaml.safe_load(GOLDEN.read_text(encoding="utf-8")) @@ -90,6 +94,10 @@ def test_uart_wrap_roundtrip(case: dict): assert wire.endswith(b"\x00") assert b"\x00" not in wire[:-1] assert unwrap(wire[:-1]) == frame_bytes + if "uart_wire_hex" in case: + assert wire[:-1] == _hex(case["uart_wire_hex"]) + if "crc16" in case: + assert wire[-3:-1] == _crc16_bytes(case) def test_intent_id_table():