diff --git a/tests/protocol_fixtures/PROTOCOL_FIXTURES.md b/tests/protocol_fixtures/PROTOCOL_FIXTURES.md index 79b06ae..dd42bc6 100644 --- a/tests/protocol_fixtures/PROTOCOL_FIXTURES.md +++ b/tests/protocol_fixtures/PROTOCOL_FIXTURES.md @@ -11,6 +11,7 @@ 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. +- Baseline now includes `read_file` runtime-behavior checks in addition to parser/API targets. Phase-2 scope (mapping only): @@ -18,6 +19,7 @@ Phase-2 scope (mapping only): - Adds a cross-runtime matrix to track per-case audit status and classification. - Java runtime entries are tracked with observed status from the Java regression suite (`TestLiteralEval.java`, `TestConcoredockerApi.java`). - Current baseline records Java as `observed_pass` for the listed phase-2 cases. +- Phase-2 matrix includes `read_file` status rows for cross-runtime tracking. - Keeps CI non-blocking for non-Python runtimes that are not yet audited by marking them as `not_audited`. Java conformance execution in CI: diff --git a/tests/protocol_fixtures/cross_runtime_matrix.phase2.json b/tests/protocol_fixtures/cross_runtime_matrix.phase2.json index ed642cd..712ba96 100644 --- a/tests/protocol_fixtures/cross_runtime_matrix.phase2.json +++ b/tests/protocol_fixtures/cross_runtime_matrix.phase2.json @@ -237,6 +237,78 @@ "note": "May require binding-specific interpretation." } } + }, + { + "id": "read_file/missing_file_returns_default_and_false", + "target": "read_file", + "runtime_results": { + "python": { + "status": "observed_pass", + "classification": "required", + "note": "Phase-1 baseline execution." + }, + "cpp": { + "status": "not_audited", + "classification": "required", + "note": "Audit planned in phase 2." + }, + "java": { + "status": "observed_pass", + "classification": "required", + "note": "Validated by TestConcoredockerApi.java." + }, + "matlab": { + "status": "not_audited", + "classification": "required", + "note": "Audit planned in phase 2." + }, + "octave": { + "status": "not_audited", + "classification": "required", + "note": "Audit planned in phase 2." + }, + "verilog": { + "status": "not_audited", + "classification": "implementation_defined", + "note": "May require binding-specific interpretation." + } + } + }, + { + "id": "read_file/older_timestamp_does_not_decrease_simtime", + "target": "read_file", + "runtime_results": { + "python": { + "status": "observed_pass", + "classification": "required", + "note": "Phase-1 baseline execution." + }, + "cpp": { + "status": "not_audited", + "classification": "required", + "note": "Audit planned in phase 2." + }, + "java": { + "status": "observed_pass", + "classification": "required", + "note": "Validated by TestConcoredockerApi.java simtime progression checks." + }, + "matlab": { + "status": "not_audited", + "classification": "required", + "note": "Audit planned in phase 2." + }, + "octave": { + "status": "not_audited", + "classification": "required", + "note": "Audit planned in phase 2." + }, + "verilog": { + "status": "not_audited", + "classification": "implementation_defined", + "note": "May require binding-specific interpretation." + } + } } ] } diff --git a/tests/protocol_fixtures/python_phase1_cases.json b/tests/protocol_fixtures/python_phase1_cases.json index f8a7218..ccdeb02 100644 --- a/tests/protocol_fixtures/python_phase1_cases.json +++ b/tests/protocol_fixtures/python_phase1_cases.json @@ -100,6 +100,43 @@ "sent_payload": "ok", "simtime_after": 10 } + }, + { + "id": "read_file/missing_file_returns_default_and_false", + "target": "read_file", + "description": "read() returns init default with ok=False when file is missing.", + "input": { + "initial_simtime": 4, + "port": 1, + "name": "missing", + "initstr_val": "[0.0, 5.0]" + }, + "expected": { + "result": [ + 5.0 + ], + "ok": false, + "simtime_after": 4 + } + }, + { + "id": "read_file/older_timestamp_does_not_decrease_simtime", + "target": "read_file", + "description": "read() keeps simtime monotonic when incoming file timestamp is older.", + "input": { + "initial_simtime": 10, + "port": 1, + "name": "ym", + "file_content": "[7.0, 3.14]", + "initstr_val": "[0.0, 0.0]" + }, + "expected": { + "result": [ + 3.14 + ], + "ok": true, + "simtime_after": 10 + } } ] } diff --git a/tests/protocol_fixtures/schema.phase1.json b/tests/protocol_fixtures/schema.phase1.json index 3b54e13..33495b8 100644 --- a/tests/protocol_fixtures/schema.phase1.json +++ b/tests/protocol_fixtures/schema.phase1.json @@ -41,7 +41,8 @@ "enum": [ "parse_params", "initval", - "write_zmq" + "write_zmq", + "read_file" ] }, "description": { diff --git a/tests/test_protocol_conformance.py b/tests/test_protocol_conformance.py index 9bb5ab3..e12ad2b 100644 --- a/tests/test_protocol_conformance.py +++ b/tests/test_protocol_conformance.py @@ -1,5 +1,7 @@ import json +import os from pathlib import Path +import tempfile import pytest @@ -9,7 +11,7 @@ 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"} +SUPPORTED_TARGETS = {"parse_params", "initval", "write_zmq", "read_file"} def _load_json(path): @@ -93,6 +95,42 @@ def send_json_with_retry(self, message): concore.zmq_ports[port_name] = existing_port +def _run_read_file_case(case): + old_simtime = concore.simtime + old_inpath = concore.inpath + old_delay = concore.delay + try: + with tempfile.TemporaryDirectory() as temp_dir: + concore.simtime = case["input"]["initial_simtime"] + concore.inpath = os.path.join(temp_dir, "in") + concore.delay = 0 + + port_dir = os.path.join(temp_dir, f"in{case['input']['port']}") + os.makedirs(port_dir, exist_ok=True) + + if "file_content" in case["input"]: + with open( + os.path.join(port_dir, case["input"]["name"]), + "w", + encoding="utf-8", + ) as f: + f.write(case["input"]["file_content"]) + + result, ok = concore.read( + case["input"]["port"], + case["input"]["name"], + case["input"]["initstr_val"], + ) + + assert result == case["expected"]["result"] + assert ok == case["expected"]["ok"] + assert concore.simtime == case["expected"]["simtime_after"] + finally: + concore.simtime = old_simtime + concore.inpath = old_inpath + concore.delay = old_delay + + def _run_case(case): if case["target"] == "parse_params": _run_parse_params_case(case) @@ -100,6 +138,8 @@ def _run_case(case): _run_initval_case(case) elif case["target"] == "write_zmq": _run_write_zmq_case(case) + elif case["target"] == "read_file": + _run_read_file_case(case) else: raise AssertionError(f"Unsupported target: {case['target']}")