Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 12 additions & 0 deletions tests/protocol_fixtures/PROTOCOL_FIXTURES.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
# Protocol Conformance Fixtures (Phase 1)

This directory contains the phase-1 protocol conformance baseline for Python.

- `schema.phase1.json`: fixture document shape and supported case targets.
- `python_phase1_cases.json`: initial baseline cases (report-only mode metadata).

Phase-1 scope:

- No runtime behavior changes.
- Python-only execution through `tests/test_protocol_conformance.py`.
- Fixture format is language-neutral to enable future cross-binding runners.
105 changes: 105 additions & 0 deletions tests/protocol_fixtures/python_phase1_cases.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,105 @@
{
"schema_version": "1.0",
"runtime": "python",
"mode": "report_only",
"cases": [
{
"id": "parse_params/simple_types_and_whitespace",
"target": "parse_params",
"description": "Semicolon-delimited params preserve string values and coerce literals.",
"input": {
"sparams": "delay=5; coeffs=[1,2,3]; label = hello world"
},
"expected": {
"result": {
"delay": 5,
"coeffs": [
1,
2,
3
],
"label": "hello world"
}
}
},
{
"id": "parse_params/embedded_equals_not_split",
"target": "parse_params",
"description": "Only the first '=' is used as key/value separator.",
"input": {
"sparams": "url=https://example.com?a=1&b=2"
},
"expected": {
"result": {
"url": "https://example.com?a=1&b=2"
}
}
},
{
"id": "initval/valid_list_sets_simtime",
"target": "initval",
"description": "initval sets simtime to first numeric entry and returns payload tail.",
"input": {
"initial_simtime": 0,
"simtime_val_str": "[12.5, \"a\", 3]"
},
"expected": {
"result": [
"a",
3
],
"simtime_after": 12.5
}
},
{
"id": "initval/invalid_input_returns_empty_and_preserves_simtime",
"target": "initval",
"description": "Invalid non-list input returns [] and leaves simtime unchanged.",
"input": {
"initial_simtime": 7,
"simtime_val_str": "not_a_list"
},
"expected": {
"result": [],
"simtime_after": 7
}
},
{
"id": "write_zmq/list_payload_prepends_timestamp_without_mutation",
"target": "write_zmq",
"description": "write() prepends simtime+delta for list payloads but does not mutate global simtime.",
"input": {
"initial_simtime": 10,
"delta": 2,
"name": "data",
"value": [
1.5,
2.5
]
},
"expected": {
"sent_payload": [
12,
1.5,
2.5
],
"simtime_after": 10
}
},
{
"id": "write_zmq/non_list_payload_forwarded_as_is",
"target": "write_zmq",
"description": "Non-list payloads are forwarded as-is and simtime remains unchanged.",
"input": {
"initial_simtime": 10,
"delta": 3,
"name": "status",
"value": "ok"
},
"expected": {
"sent_payload": "ok",
"simtime_after": 10
}
}
]
}
62 changes: 62 additions & 0 deletions tests/protocol_fixtures/schema.phase1.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"title": "Concore Protocol Conformance Fixtures (Phase 1)",
"description": "Language-neutral fixture format. Phase 1 executes Python-only baseline checks.",
"type": "object",
"required": [
"schema_version",
"runtime",
"mode",
"cases"
],
"properties": {
"schema_version": {
"type": "string"
},
"runtime": {
"type": "string"
},
"mode": {
"type": "string",
"enum": [
"report_only"
]
},
"cases": {
"type": "array",
"items": {
"type": "object",
"required": [
"id",
"target",
"input",
"expected"
],
"properties": {
"id": {
"type": "string"
},
"target": {
"type": "string",
"enum": [
"parse_params",
"initval",
"write_zmq"
]
},
"description": {
"type": "string"
},
"input": {
"type": "object"
},
"expected": {
"type": "object"
}
},
"additionalProperties": true
}
}
},
"additionalProperties": false
}
115 changes: 115 additions & 0 deletions tests/test_protocol_conformance.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,115 @@
import json
from pathlib import Path

import pytest

import concore


FIXTURE_DIR = Path(__file__).parent / "protocol_fixtures"
SCHEMA_PATH = FIXTURE_DIR / "schema.phase1.json"
CASES_PATH = FIXTURE_DIR / "python_phase1_cases.json"
SUPPORTED_TARGETS = {"parse_params", "initval", "write_zmq"}


def _load_json(path):
with path.open("r", encoding="utf-8") as f:
return json.load(f)


def _validate_fixture_document_shape(doc):
required_top = {"schema_version", "runtime", "mode", "cases"}
missing = required_top - set(doc.keys())
if missing:
raise AssertionError(f"Fixture document missing required top-level keys: {sorted(missing)}")
if doc["runtime"] != "python":
raise AssertionError(f"Phase-1 fixture runtime must be 'python', found: {doc['runtime']}")
if doc["mode"] != "report_only":
raise AssertionError(f"Phase-1 fixture mode must be 'report_only', found: {doc['mode']}")
if not isinstance(doc["cases"], list) or not doc["cases"]:
raise AssertionError("Fixture document must contain a non-empty 'cases' list")

for idx, case in enumerate(doc["cases"]):
for key in ("id", "target", "input", "expected"):
if key not in case:
raise AssertionError(f"Case index {idx} missing required key '{key}'")
if case["target"] not in SUPPORTED_TARGETS:
raise AssertionError(
f"Case '{case['id']}' has unsupported target '{case['target']}'"
)


def _run_parse_params_case(case):
result = concore.parse_params(case["input"]["sparams"])
assert result == case["expected"]["result"]


def _run_initval_case(case):
old_simtime = concore.simtime
try:
concore.simtime = case["input"]["initial_simtime"]
result = concore.initval(case["input"]["simtime_val_str"])
assert result == case["expected"]["result"]
assert concore.simtime == case["expected"]["simtime_after"]
finally:
concore.simtime = old_simtime


def _run_write_zmq_case(case):
class DummyPort:
def __init__(self):
self.sent_payload = None

def send_json_with_retry(self, message):
self.sent_payload = message

old_simtime = concore.simtime
port_name = f"fixture_{case['id'].replace('/', '_')}"
existing_port = concore.zmq_ports.get(port_name)
dummy_port = DummyPort()

try:
concore.simtime = case["input"]["initial_simtime"]
concore.zmq_ports[port_name] = dummy_port
concore.write(
port_name,
case["input"]["name"],
case["input"]["value"],
delta=case["input"]["delta"],
)
assert dummy_port.sent_payload == case["expected"]["sent_payload"]
assert concore.simtime == case["expected"]["simtime_after"]
finally:
concore.simtime = old_simtime
if existing_port is None:
concore.zmq_ports.pop(port_name, None)
else:
concore.zmq_ports[port_name] = existing_port


def _run_case(case):
if case["target"] == "parse_params":
_run_parse_params_case(case)
elif case["target"] == "initval":
_run_initval_case(case)
elif case["target"] == "write_zmq":
_run_write_zmq_case(case)
else:
raise AssertionError(f"Unsupported target: {case['target']}")


def _load_cases():
doc = _load_json(CASES_PATH)
_validate_fixture_document_shape(doc)
return doc["cases"]


def test_phase1_schema_file_present_and_basic_shape():
schema = _load_json(SCHEMA_PATH)
assert schema["title"] == "Concore Protocol Conformance Fixtures (Phase 1)"
assert "cases" in schema["properties"]


@pytest.mark.parametrize("case", _load_cases(), ids=lambda case: case["id"])
def test_phase1_python_protocol_conformance(case):
_run_case(case)