Skip to content

Commit f3299c0

Browse files
committed
fix #491: read() returns ReadResult with ReadStatus instead of silent default
1 parent 7adf163 commit f3299c0

2 files changed

Lines changed: 68 additions & 20 deletions

File tree

TestConcoredockerApi.java

Lines changed: 43 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,9 @@ public static void main(String[] args) {
2424
testInitValExtractsSimtime();
2525
testInitValReturnsRemainingValues();
2626
testOutputFileMatchesPythonWireFormat();
27+
testReadFileNotFound();
28+
testReadRetriesExceeded();
29+
testReadParseError();
2730

2831
System.out.println("\n=== Results: " + passed + " passed, " + failed + " failed out of " + (passed + failed) + " tests ===");
2932
if (failed > 0) {
@@ -102,10 +105,11 @@ static void testReadParsesFileAndStripsSimtime() {
102105
concoredocker.setInPath(tmp.toString());
103106
writeFile(tmp, 1, "sensor", "[0.0, 42.0, 99.0]");
104107

105-
List<Object> result = concoredocker.read(1, "sensor", "[0.0, 0.0, 0.0]");
106-
check("read: strips simtime, size=2", 2, result.size());
107-
check("read: val1 correct", 42.0, result.get(0));
108-
check("read: val2 correct", 99.0, result.get(1));
108+
concoredocker.ReadResult result = concoredocker.read(1, "sensor", "[0.0, 0.0, 0.0]");
109+
check("read: status SUCCESS", concoredocker.ReadStatus.SUCCESS, result.status);
110+
check("read: strips simtime, size=2", 2, result.data.size());
111+
check("read: val1 correct", 42.0, result.data.get(0));
112+
check("read: val2 correct", 99.0, result.data.get(1));
109113
}
110114

111115
static void testReadWriteRoundtrip() {
@@ -119,10 +123,11 @@ static void testReadWriteRoundtrip() {
119123
outVals.add(8.0);
120124
concoredocker.write(1, "data", outVals, 1);
121125

122-
List<Object> inVals = concoredocker.read(1, "data", "[0.0, 0.0, 0.0]");
123-
check("roundtrip: size", 2, inVals.size());
124-
check("roundtrip: val1", 7.0, inVals.get(0));
125-
check("roundtrip: val2", 8.0, inVals.get(1));
126+
concoredocker.ReadResult inVals = concoredocker.read(1, "data", "[0.0, 0.0, 0.0]");
127+
check("roundtrip: status", concoredocker.ReadStatus.SUCCESS, inVals.status);
128+
check("roundtrip: size", 2, inVals.data.size());
129+
check("roundtrip: val1", 7.0, inVals.data.get(0));
130+
check("roundtrip: val2", 8.0, inVals.data.get(1));
126131
}
127132

128133
static void testSimtimeAdvancesWithDelta() {
@@ -195,4 +200,34 @@ static void testOutputFileMatchesPythonWireFormat() {
195200
Object reparsed = concoredocker.literalEval(raw);
196201
check("wire format: re-parseable as list", true, reparsed instanceof List);
197202
}
203+
204+
static void testReadFileNotFound() {
205+
Path tmp = makeTempDir();
206+
concoredocker.resetState();
207+
concoredocker.setInPath(tmp.toString());
208+
// no file written, port 1/missing does not exist
209+
concoredocker.ReadResult result = concoredocker.read(1, "missing", "[0.0, 0.0]");
210+
check("read file not found: status", concoredocker.ReadStatus.FILE_NOT_FOUND, result.status);
211+
check("read file not found: data is default", 1, result.data.size());
212+
}
213+
214+
static void testReadRetriesExceeded() {
215+
Path tmp = makeTempDir();
216+
concoredocker.resetState();
217+
concoredocker.setInPath(tmp.toString());
218+
writeFile(tmp, 1, "empty", ""); // always empty, exhausts retries
219+
concoredocker.ReadResult result = concoredocker.read(1, "empty", "[0.0, 0.0]");
220+
check("read retries exceeded: status", concoredocker.ReadStatus.RETRIES_EXCEEDED, result.status);
221+
check("read retries exceeded: data is default", 1, result.data.size());
222+
}
223+
224+
static void testReadParseError() {
225+
Path tmp = makeTempDir();
226+
concoredocker.resetState();
227+
concoredocker.setInPath(tmp.toString());
228+
writeFile(tmp, 1, "bad", "not_a_valid_list");
229+
concoredocker.ReadResult result = concoredocker.read(1, "bad", "[0.0, 0.0]");
230+
check("read parse error: status", concoredocker.ReadStatus.PARSE_ERROR, result.status);
231+
check("read parse error: data is default", 1, result.data.size());
232+
}
198233
}

concoredocker.java

Lines changed: 25 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -160,7 +160,7 @@ public static Object tryParam(String n, Object i) {
160160
* Returns: list of values after simtime
161161
* Includes max retry limit to avoid infinite blocking (matches Python behavior).
162162
*/
163-
public static List<Object> read(int port, String name, String initstr) {
163+
public static ReadResult read(int port, String name, String initstr) {
164164
// Parse default value upfront for consistent return type
165165
List<Object> defaultVal = new ArrayList<>();
166166
try {
@@ -178,7 +178,7 @@ public static List<Object> read(int port, String name, String initstr) {
178178
} catch (InterruptedException e) {
179179
Thread.currentThread().interrupt();
180180
s += initstr;
181-
return defaultVal;
181+
return new ReadResult(ReadStatus.TIMEOUT, defaultVal);
182182
}
183183

184184
String ins;
@@ -187,7 +187,7 @@ public static List<Object> read(int port, String name, String initstr) {
187187
} catch (IOException e) {
188188
System.out.println("File " + filePath + " not found, using default value.");
189189
s += initstr;
190-
return defaultVal;
190+
return new ReadResult(ReadStatus.FILE_NOT_FOUND, defaultVal);
191191
}
192192

193193
int attempts = 0;
@@ -197,7 +197,7 @@ public static List<Object> read(int port, String name, String initstr) {
197197
} catch (InterruptedException e) {
198198
Thread.currentThread().interrupt();
199199
s += initstr;
200-
return defaultVal;
200+
return new ReadResult(ReadStatus.TIMEOUT, defaultVal);
201201
}
202202
try {
203203
ins = new String(Files.readAllBytes(Paths.get(filePath)));
@@ -210,7 +210,7 @@ public static List<Object> read(int port, String name, String initstr) {
210210

211211
if (ins.length() == 0) {
212212
System.out.println("Max retries reached for " + filePath + ", using default value.");
213-
return defaultVal;
213+
return new ReadResult(ReadStatus.RETRIES_EXCEEDED, defaultVal);
214214
}
215215

216216
s += ins;
@@ -219,12 +219,12 @@ public static List<Object> read(int port, String name, String initstr) {
219219
if (!inval.isEmpty()) {
220220
double firstSimtime = ((Number) inval.get(0)).doubleValue();
221221
simtime = Math.max(simtime, firstSimtime);
222-
return new ArrayList<>(inval.subList(1, inval.size()));
222+
return new ReadResult(ReadStatus.SUCCESS, new ArrayList<>(inval.subList(1, inval.size())));
223223
}
224224
} catch (Exception e) {
225225
System.out.println("Error parsing " + ins + ": " + e.getMessage());
226226
}
227-
return defaultVal;
227+
return new ReadResult(ReadStatus.PARSE_ERROR, defaultVal);
228228
}
229229

230230
/**
@@ -392,7 +392,7 @@ private static int zmqSocketTypeFromString(String s) {
392392
* Reads data from a ZMQ port. Same wire format as file-based read:
393393
* expects [simtime, val1, val2, ...], strips simtime, returns the rest.
394394
*/
395-
public static List<Object> read(String portName, String name, String initstr) {
395+
public static ReadResult read(String portName, String name, String initstr) {
396396
List<Object> defaultVal = new ArrayList<>();
397397
try {
398398
List<?> parsed = (List<?>) literalEval(initstr);
@@ -404,24 +404,24 @@ public static List<Object> read(String portName, String name, String initstr) {
404404
ZeroMQPort port = zmqPorts.get(portName);
405405
if (port == null) {
406406
System.err.println("read: ZMQ port '" + portName + "' not initialized");
407-
return defaultVal;
407+
return new ReadResult(ReadStatus.FILE_NOT_FOUND, defaultVal);
408408
}
409409
String msg = port.recvWithRetry();
410410
if (msg == null) {
411411
System.err.println("read: ZMQ recv timeout on port '" + portName + "'");
412-
return defaultVal;
412+
return new ReadResult(ReadStatus.TIMEOUT, defaultVal);
413413
}
414414
s += msg;
415415
try {
416416
List<?> inval = (List<?>) literalEval(msg);
417417
if (!inval.isEmpty()) {
418418
simtime = Math.max(simtime, ((Number) inval.get(0)).doubleValue());
419-
return new ArrayList<>(inval.subList(1, inval.size()));
419+
return new ReadResult(ReadStatus.SUCCESS, new ArrayList<>(inval.subList(1, inval.size())));
420420
}
421421
} catch (Exception e) {
422422
System.out.println("Error parsing ZMQ message '" + msg + "': " + e.getMessage());
423423
}
424-
return defaultVal;
424+
return new ReadResult(ReadStatus.PARSE_ERROR, defaultVal);
425425
}
426426

427427
/**
@@ -472,6 +472,19 @@ static Object literalEval(String s) {
472472
return result;
473473
}
474474

475+
public enum ReadStatus {
476+
SUCCESS, FILE_NOT_FOUND, TIMEOUT, PARSE_ERROR, RETRIES_EXCEEDED
477+
}
478+
479+
public static class ReadResult {
480+
public final ReadStatus status;
481+
public final List<Object> data;
482+
ReadResult(ReadStatus status, List<Object> data) {
483+
this.status = status;
484+
this.data = data;
485+
}
486+
}
487+
475488
/**
476489
* ZMQ socket wrapper with bind/connect, timeouts, and retry.
477490
*/

0 commit comments

Comments
 (0)