Skip to content

Commit 88b7beb

Browse files
committed
Enhance StdioClientTransportTest with clean shutdown validation, error handling, and added assertions. Extend timeout for stability.
1 parent 12d8461 commit 88b7beb

File tree

1 file changed

+30
-4
lines changed

1 file changed

+30
-4
lines changed

kotlin-sdk-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/client/StdioClientTransportTest.kt

Lines changed: 30 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ package io.modelcontextprotocol.kotlin.sdk.client
33
import io.modelcontextprotocol.kotlin.sdk.shared.BaseTransportTest
44
import io.modelcontextprotocol.kotlin.sdk.types.Implementation
55
import io.modelcontextprotocol.kotlin.sdk.types.McpException
6+
import kotlinx.coroutines.delay
67
import kotlinx.coroutines.runBlocking
78
import kotlinx.coroutines.test.runTest
89
import kotlinx.io.asSink
@@ -12,12 +13,18 @@ import org.junit.jupiter.api.Test
1213
import org.junit.jupiter.api.Timeout
1314
import org.junit.jupiter.api.assertThrows
1415
import java.util.concurrent.TimeUnit
15-
16-
@Timeout(20, unit = TimeUnit.SECONDS)
16+
import kotlin.concurrent.atomics.AtomicBoolean
17+
import kotlin.concurrent.atomics.ExperimentalAtomicApi
18+
import kotlin.test.assertFalse
19+
import kotlin.test.assertTrue
20+
import kotlin.test.fail
21+
import kotlin.time.Duration.Companion.milliseconds
22+
import kotlin.time.Duration.Companion.seconds
23+
24+
@Timeout(30, unit = TimeUnit.SECONDS)
1725
class StdioClientTransportTest : BaseTransportTest() {
1826

1927
@Test
20-
@Timeout(30, unit = TimeUnit.SECONDS)
2128
fun `handle stdio error`(): Unit = runBlocking {
2229
val processBuilder = if (System.getProperty("os.name").lowercase().contains("win")) {
2330
ProcessBuilder("cmd", "/c", "pause 1 && echo simulated error 1>&2 && exit 1")
@@ -55,6 +62,7 @@ class StdioClientTransportTest : BaseTransportTest() {
5562
process.destroyForcibly()
5663
}
5764

65+
@OptIn(ExperimentalAtomicApi::class)
5866
@Test
5967
fun `should start then close cleanly`() = runTest {
6068
// Run process "/usr/bin/tee"
@@ -63,15 +71,33 @@ class StdioClientTransportTest : BaseTransportTest() {
6371

6472
val input = process.inputStream.asSource().buffered()
6573
val output = process.outputStream.asSink().buffered()
74+
val error = process.errorStream.asSource().buffered()
6675

6776
val transport = StdioClientTransport(
6877
input = input,
6978
output = output,
79+
error = error,
7080
)
7181

72-
testTransportOpenClose(transport)
82+
transport.onError { error ->
83+
fail("Unexpected error: $error")
84+
}
85+
86+
val didClose = AtomicBoolean(false)
87+
transport.onClose { didClose.store(true) }
88+
89+
transport.start()
90+
delay(1.seconds)
7391

92+
assertFalse(didClose.load(), "Transport should not be closed immediately after start")
93+
94+
// Destroy process BEFORE close() to unblock stdin reader
7495
process.destroyForcibly()
96+
delay(100.milliseconds) // Give time for EOF to propagate
97+
98+
transport.close()
99+
100+
assertTrue(didClose.load(), "Transport should be closed after close() call")
75101
}
76102

77103
@Test

0 commit comments

Comments
 (0)