Skip to content

Commit 5b32ff9

Browse files
committed
test(runtime): lock incremental CLI streaming and cancellation
1 parent 8b8a27f commit 5b32ff9

1 file changed

Lines changed: 99 additions & 0 deletions

File tree

Lines changed: 99 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,99 @@
1+
package io.github.easy4j.opencode.cli;
2+
3+
import io.github.easy4j.opencode.OpenCodeCliConfig;
4+
import org.junit.jupiter.api.Test;
5+
6+
import java.lang.reflect.Method;
7+
import java.util.List;
8+
import java.util.concurrent.CompletableFuture;
9+
import java.util.concurrent.CopyOnWriteArrayList;
10+
import java.util.concurrent.CountDownLatch;
11+
import java.util.concurrent.TimeUnit;
12+
import java.util.function.Consumer;
13+
14+
import static org.junit.jupiter.api.Assertions.*;
15+
16+
class OpenCodeCliStreamingContractTest {
17+
18+
@Test
19+
void streamingExecutionMustDeliverStdoutBeforeProcessExit() throws Exception {
20+
OpenCodeCliConfig config = new OpenCodeCliConfig();
21+
config.setExecutable("sh");
22+
config.setTimeout(5);
23+
OpenCodeCliExecutor executor = new OpenCodeCliExecutor(config);
24+
25+
Class<?> handleType = assertDoesNotThrow(
26+
() -> Class.forName("io.github.easy4j.opencode.cli.OpenCodeCliStreamHandle"));
27+
28+
Method stream = assertDoesNotThrow(
29+
() -> OpenCodeCliExecutor.class.getMethod(
30+
"stream",
31+
OpenCodeCliExecutionContext.class,
32+
Consumer.class,
33+
Consumer.class,
34+
String[].class));
35+
36+
List<String> stdout = new CopyOnWriteArrayList<>();
37+
CountDownLatch firstLine = new CountDownLatch(1);
38+
Consumer<String> stdoutConsumer = line -> {
39+
stdout.add(line);
40+
firstLine.countDown();
41+
};
42+
43+
Object handle = stream.invoke(
44+
executor,
45+
new OpenCodeCliExecutionContext(),
46+
stdoutConsumer,
47+
(Consumer<String>) line -> { },
48+
new String[]{"-c", "printf 'first\\n'; sleep 1; printf 'second\\n'"});
49+
50+
Method completionMethod = handleType.getMethod("getCompletion");
51+
@SuppressWarnings("unchecked")
52+
CompletableFuture<OpenCodeCliResult> completion =
53+
(CompletableFuture<OpenCodeCliResult>) completionMethod.invoke(handle);
54+
55+
assertTrue(firstLine.await(700, TimeUnit.MILLISECONDS),
56+
"first stdout line must be observable while the process is still running");
57+
assertFalse(completion.isDone(),
58+
"process should still be running after first line");
59+
60+
OpenCodeCliResult result = completion.get(4, TimeUnit.SECONDS);
61+
assertTrue(result.isSuccess());
62+
assertEquals(List.of("first", "second"), stdout);
63+
}
64+
65+
@Test
66+
void streamingExecutionMustBeCancellable() throws Exception {
67+
OpenCodeCliConfig config = new OpenCodeCliConfig();
68+
config.setExecutable("sh");
69+
config.setTimeout(30);
70+
OpenCodeCliExecutor executor = new OpenCodeCliExecutor(config);
71+
72+
Class<?> handleType = assertDoesNotThrow(
73+
() -> Class.forName("io.github.easy4j.opencode.cli.OpenCodeCliStreamHandle"));
74+
Method stream = OpenCodeCliExecutor.class.getMethod(
75+
"stream",
76+
OpenCodeCliExecutionContext.class,
77+
Consumer.class,
78+
Consumer.class,
79+
String[].class);
80+
81+
Object handle = stream.invoke(
82+
executor,
83+
new OpenCodeCliExecutionContext(),
84+
(Consumer<String>) line -> { },
85+
(Consumer<String>) line -> { },
86+
new String[]{"-c", "sleep 20"});
87+
88+
Method cancel = handleType.getMethod("cancel");
89+
Method completionMethod = handleType.getMethod("getCompletion");
90+
91+
assertEquals(Boolean.TRUE, cancel.invoke(handle));
92+
93+
@SuppressWarnings("unchecked")
94+
CompletableFuture<OpenCodeCliResult> completion =
95+
(CompletableFuture<OpenCodeCliResult>) completionMethod.invoke(handle);
96+
OpenCodeCliResult result = completion.get(4, TimeUnit.SECONDS);
97+
assertFalse(result.isSuccess());
98+
}
99+
}

0 commit comments

Comments
 (0)