diff --git a/TestConcoredockerApi.java b/TestConcoredockerApi.java index 2193444..9047243 100644 --- a/TestConcoredockerApi.java +++ b/TestConcoredockerApi.java @@ -24,6 +24,9 @@ public static void main(String[] args) { testInitValExtractsSimtime(); testInitValReturnsRemainingValues(); testOutputFileMatchesPythonWireFormat(); + testReadFileNotFound(); + testReadRetriesExceeded(); + testReadParseError(); System.out.println("\n=== Results: " + passed + " passed, " + failed + " failed out of " + (passed + failed) + " tests ==="); if (failed > 0) { @@ -102,10 +105,11 @@ static void testReadParsesFileAndStripsSimtime() { concoredocker.setInPath(tmp.toString()); writeFile(tmp, 1, "sensor", "[0.0, 42.0, 99.0]"); - List result = concoredocker.read(1, "sensor", "[0.0, 0.0, 0.0]"); - check("read: strips simtime, size=2", 2, result.size()); - check("read: val1 correct", 42.0, result.get(0)); - check("read: val2 correct", 99.0, result.get(1)); + concoredocker.ReadResult result = concoredocker.read(1, "sensor", "[0.0, 0.0, 0.0]"); + check("read: status SUCCESS", concoredocker.ReadStatus.SUCCESS, result.status); + check("read: strips simtime, size=2", 2, result.data.size()); + check("read: val1 correct", 42.0, result.data.get(0)); + check("read: val2 correct", 99.0, result.data.get(1)); } static void testReadWriteRoundtrip() { @@ -119,10 +123,11 @@ static void testReadWriteRoundtrip() { outVals.add(8.0); concoredocker.write(1, "data", outVals, 1); - List inVals = concoredocker.read(1, "data", "[0.0, 0.0, 0.0]"); - check("roundtrip: size", 2, inVals.size()); - check("roundtrip: val1", 7.0, inVals.get(0)); - check("roundtrip: val2", 8.0, inVals.get(1)); + concoredocker.ReadResult inVals = concoredocker.read(1, "data", "[0.0, 0.0, 0.0]"); + check("roundtrip: status", concoredocker.ReadStatus.SUCCESS, inVals.status); + check("roundtrip: size", 2, inVals.data.size()); + check("roundtrip: val1", 7.0, inVals.data.get(0)); + check("roundtrip: val2", 8.0, inVals.data.get(1)); } static void testSimtimeAdvancesWithDelta() { @@ -195,4 +200,34 @@ static void testOutputFileMatchesPythonWireFormat() { Object reparsed = concoredocker.literalEval(raw); check("wire format: re-parseable as list", true, reparsed instanceof List); } + + static void testReadFileNotFound() { + Path tmp = makeTempDir(); + concoredocker.resetState(); + concoredocker.setInPath(tmp.toString()); + // no file written, port 1/missing does not exist + concoredocker.ReadResult result = concoredocker.read(1, "missing", "[0.0, 0.0]"); + check("read file not found: status", concoredocker.ReadStatus.FILE_NOT_FOUND, result.status); + check("read file not found: data is default", 1, result.data.size()); + } + + static void testReadRetriesExceeded() { + Path tmp = makeTempDir(); + concoredocker.resetState(); + concoredocker.setInPath(tmp.toString()); + writeFile(tmp, 1, "empty", ""); // always empty, exhausts retries + concoredocker.ReadResult result = concoredocker.read(1, "empty", "[0.0, 0.0]"); + check("read retries exceeded: status", concoredocker.ReadStatus.RETRIES_EXCEEDED, result.status); + check("read retries exceeded: data is default", 1, result.data.size()); + } + + static void testReadParseError() { + Path tmp = makeTempDir(); + concoredocker.resetState(); + concoredocker.setInPath(tmp.toString()); + writeFile(tmp, 1, "bad", "not_a_valid_list"); + concoredocker.ReadResult result = concoredocker.read(1, "bad", "[0.0, 0.0]"); + check("read parse error: status", concoredocker.ReadStatus.PARSE_ERROR, result.status); + check("read parse error: data is default", 1, result.data.size()); + } } diff --git a/concoredocker.java b/concoredocker.java index 1e66bca..8dbbaed 100644 --- a/concoredocker.java +++ b/concoredocker.java @@ -160,7 +160,7 @@ public static Object tryParam(String n, Object i) { * Returns: list of values after simtime * Includes max retry limit to avoid infinite blocking (matches Python behavior). */ - public static List read(int port, String name, String initstr) { + public static ReadResult read(int port, String name, String initstr) { // Parse default value upfront for consistent return type List defaultVal = new ArrayList<>(); try { @@ -178,7 +178,7 @@ public static List read(int port, String name, String initstr) { } catch (InterruptedException e) { Thread.currentThread().interrupt(); s += initstr; - return defaultVal; + return new ReadResult(ReadStatus.TIMEOUT, defaultVal); } String ins; @@ -187,7 +187,7 @@ public static List read(int port, String name, String initstr) { } catch (IOException e) { System.out.println("File " + filePath + " not found, using default value."); s += initstr; - return defaultVal; + return new ReadResult(ReadStatus.FILE_NOT_FOUND, defaultVal); } int attempts = 0; @@ -197,7 +197,7 @@ public static List read(int port, String name, String initstr) { } catch (InterruptedException e) { Thread.currentThread().interrupt(); s += initstr; - return defaultVal; + return new ReadResult(ReadStatus.TIMEOUT, defaultVal); } try { ins = new String(Files.readAllBytes(Paths.get(filePath))); @@ -210,7 +210,7 @@ public static List read(int port, String name, String initstr) { if (ins.length() == 0) { System.out.println("Max retries reached for " + filePath + ", using default value."); - return defaultVal; + return new ReadResult(ReadStatus.RETRIES_EXCEEDED, defaultVal); } s += ins; @@ -219,12 +219,12 @@ public static List read(int port, String name, String initstr) { if (!inval.isEmpty()) { double firstSimtime = ((Number) inval.get(0)).doubleValue(); simtime = Math.max(simtime, firstSimtime); - return new ArrayList<>(inval.subList(1, inval.size())); + return new ReadResult(ReadStatus.SUCCESS, new ArrayList<>(inval.subList(1, inval.size()))); } } catch (Exception e) { System.out.println("Error parsing " + ins + ": " + e.getMessage()); } - return defaultVal; + return new ReadResult(ReadStatus.PARSE_ERROR, defaultVal); } /** @@ -392,7 +392,7 @@ private static int zmqSocketTypeFromString(String s) { * Reads data from a ZMQ port. Same wire format as file-based read: * expects [simtime, val1, val2, ...], strips simtime, returns the rest. */ - public static List read(String portName, String name, String initstr) { + public static ReadResult read(String portName, String name, String initstr) { List defaultVal = new ArrayList<>(); try { List parsed = (List) literalEval(initstr); @@ -404,24 +404,24 @@ public static List read(String portName, String name, String initstr) { ZeroMQPort port = zmqPorts.get(portName); if (port == null) { System.err.println("read: ZMQ port '" + portName + "' not initialized"); - return defaultVal; + return new ReadResult(ReadStatus.FILE_NOT_FOUND, defaultVal); } String msg = port.recvWithRetry(); if (msg == null) { System.err.println("read: ZMQ recv timeout on port '" + portName + "'"); - return defaultVal; + return new ReadResult(ReadStatus.TIMEOUT, defaultVal); } s += msg; try { List inval = (List) literalEval(msg); if (!inval.isEmpty()) { simtime = Math.max(simtime, ((Number) inval.get(0)).doubleValue()); - return new ArrayList<>(inval.subList(1, inval.size())); + return new ReadResult(ReadStatus.SUCCESS, new ArrayList<>(inval.subList(1, inval.size()))); } } catch (Exception e) { System.out.println("Error parsing ZMQ message '" + msg + "': " + e.getMessage()); } - return defaultVal; + return new ReadResult(ReadStatus.PARSE_ERROR, defaultVal); } /** @@ -472,6 +472,19 @@ static Object literalEval(String s) { return result; } + public enum ReadStatus { + SUCCESS, FILE_NOT_FOUND, TIMEOUT, PARSE_ERROR, RETRIES_EXCEEDED + } + + public static class ReadResult { + public final ReadStatus status; + public final List data; + ReadResult(ReadStatus status, List data) { + this.status = status; + this.data = data; + } + } + /** * ZMQ socket wrapper with bind/connect, timeouts, and retry. */