Skip to content

Commit 9b96ecb

Browse files
devcrocodskarpovdev
authored andcommitted
Mark testMultipleClientParallel as ignored due to flakiness (#267)
Ignore flaky `testMultipleClientParallel`
1 parent d42b17d commit 9b96ecb

File tree

1 file changed

+191
-0
lines changed

1 file changed

+191
-0
lines changed
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,191 @@
1+
package io.modelcontextprotocol.kotlin.sdk.integration.typescript
2+
3+
import kotlinx.coroutines.test.runTest
4+
import org.junit.jupiter.api.AfterEach
5+
import org.junit.jupiter.api.BeforeEach
6+
import org.junit.jupiter.api.Test
7+
import org.junit.jupiter.api.Timeout
8+
import java.util.concurrent.TimeUnit
9+
import kotlin.test.Ignore
10+
import kotlin.test.assertTrue
11+
12+
class TypeScriptClientKotlinServerTest : TypeScriptTestBase() {
13+
14+
private var port: Int = 0
15+
private lateinit var serverUrl: String
16+
private var httpServer: KotlinServerForTypeScriptClient? = null
17+
18+
@BeforeEach
19+
fun setUp() {
20+
port = findFreePort()
21+
serverUrl = "http://localhost:$port/mcp"
22+
killProcessOnPort(port)
23+
httpServer = KotlinServerForTypeScriptClient()
24+
httpServer?.start(port)
25+
if (!waitForPort(port = port)) {
26+
throw IllegalStateException("Kotlin test server did not become ready on localhost:$port within timeout")
27+
}
28+
println("Kotlin server started on port $port")
29+
}
30+
31+
@AfterEach
32+
fun tearDown() {
33+
try {
34+
httpServer?.stop()
35+
println("HTTP server stopped")
36+
} catch (e: Exception) {
37+
println("Error during server shutdown: ${e.message}")
38+
}
39+
}
40+
41+
@Test
42+
@Timeout(30, unit = TimeUnit.SECONDS)
43+
fun testToolCall() = runTest {
44+
val testName = "TestUser"
45+
val command = "npx tsx myClient.ts $serverUrl greet $testName"
46+
val output = executeCommand(command, tsClientDir)
47+
48+
assertTrue(
49+
output.contains("Hello, $testName!"),
50+
"Tool response should contain the greeting with the provided name",
51+
)
52+
assertTrue(output.contains("Tool result:"), "Output should indicate a successful tool call")
53+
assertTrue(output.contains("Text content:"), "Output should contain the text content section")
54+
assertTrue(output.contains("Structured content:"), "Output should contain the structured content section")
55+
assertTrue(
56+
output.contains("\"greeting\": \"Hello, $testName!\""),
57+
"Structured content should contain the greeting",
58+
)
59+
}
60+
61+
@Test
62+
@Timeout(30, unit = TimeUnit.SECONDS)
63+
fun testNotifications() = runTest {
64+
val name = "NotifUser"
65+
val command = "npx tsx myClient.ts $serverUrl multi-greet $name"
66+
val output = executeCommand(command, tsClientDir)
67+
68+
assertTrue(
69+
output.contains("Multiple greetings") || output.contains("greeting"),
70+
"Tool response should contain greeting message",
71+
)
72+
// verify that the server sent 3 notifications
73+
assertTrue(
74+
output.contains("\"notificationCount\": 3") || output.contains("notificationCount: 3"),
75+
"Structured content should indicate that 3 notifications were emitted by the server.\nOutput:\n$output",
76+
)
77+
}
78+
79+
@Test
80+
@Timeout(120, unit = TimeUnit.SECONDS)
81+
fun testMultipleClientSequence() = runTest {
82+
val testName1 = "FirstClient"
83+
val command1 = "npx tsx myClient.ts $serverUrl greet $testName1"
84+
val output1 = executeCommand(command1, tsClientDir)
85+
86+
assertTrue(output1.contains("Connected to server"), "First client should connect to server")
87+
assertTrue(output1.contains("Hello, $testName1!"), "Tool response should contain the greeting for first client")
88+
assertTrue(output1.contains("Disconnected from server"), "First client should disconnect cleanly")
89+
90+
val testName2 = "SecondClient"
91+
val command2 = "npx tsx myClient.ts $serverUrl multi-greet $testName2"
92+
val output2 = executeCommand(command2, tsClientDir)
93+
94+
assertTrue(output2.contains("Connected to server"), "Second client should connect to server")
95+
assertTrue(
96+
output2.contains("Multiple greetings") || output2.contains("greeting"),
97+
"Tool response should contain greeting message",
98+
)
99+
assertTrue(output2.contains("Disconnected from server"), "Second client should disconnect cleanly")
100+
101+
val command3 = "npx tsx myClient.ts $serverUrl"
102+
val output3 = executeCommand(command3, tsClientDir)
103+
104+
assertTrue(output3.contains("Connected to server"), "Third client should connect to server")
105+
assertTrue(output3.contains("Available utils:"), "Third client should list available utils")
106+
assertTrue(output3.contains("greet"), "Greet tool should be available to third client")
107+
assertTrue(output3.contains("multi-greet"), "Multi-greet tool should be available to third client")
108+
assertTrue(output3.contains("Disconnected from server"), "Third client should disconnect cleanly")
109+
}
110+
111+
@Test
112+
@Timeout(30, unit = TimeUnit.SECONDS)
113+
@Ignore // Ignored due to flaky, see issue https://github.com/modelcontextprotocol/kotlin-sdk/issues/262
114+
fun testMultipleClientParallel() = runTest {
115+
val clientCount = 3
116+
val clients = listOf(
117+
"FirstClient" to "greet",
118+
"SecondClient" to "multi-greet",
119+
"ThirdClient" to "",
120+
)
121+
122+
val threads = mutableListOf<Thread>()
123+
val outputs = mutableListOf<Pair<Int, String>>()
124+
val exceptions = mutableListOf<Exception>()
125+
126+
for (i in 0 until clientCount) {
127+
val (clientName, toolName) = clients[i]
128+
val thread = Thread {
129+
try {
130+
val command = if (toolName.isEmpty()) {
131+
"npx tsx myClient.ts $serverUrl"
132+
} else {
133+
"npx tsx myClient.ts $serverUrl $toolName $clientName"
134+
}
135+
136+
val output = executeCommand(command, tsClientDir)
137+
synchronized(outputs) {
138+
outputs.add(i to output)
139+
}
140+
} catch (e: Exception) {
141+
synchronized(exceptions) {
142+
exceptions.add(e)
143+
}
144+
}
145+
}
146+
threads.add(thread)
147+
thread.start()
148+
Thread.sleep(500)
149+
}
150+
151+
threads.forEach { it.join() }
152+
153+
if (exceptions.isNotEmpty()) {
154+
println(
155+
"Exceptions occurred in parallel clients: ${
156+
exceptions.joinToString {
157+
it.message ?: it.toString()
158+
}
159+
}",
160+
)
161+
}
162+
163+
val sortedOutputs = outputs.sortedBy { it.first }.map { it.second }
164+
165+
sortedOutputs.forEachIndexed { index, output ->
166+
val clientName = clients[index].first
167+
val toolName = clients[index].second
168+
169+
when (toolName) {
170+
"greet" -> {
171+
val containsGreeting = output.contains("Hello, $clientName!") ||
172+
output.contains("\"greeting\": \"Hello, $clientName!\"")
173+
assertTrue(
174+
containsGreeting,
175+
"Tool response should contain the greeting for $clientName",
176+
)
177+
}
178+
179+
"multi-greet" -> {
180+
val containsGreeting = output.contains("Multiple greetings") ||
181+
output.contains("greeting") ||
182+
output.contains("greet")
183+
assertTrue(
184+
containsGreeting,
185+
"Tool response should contain greeting message for $clientName",
186+
)
187+
}
188+
}
189+
}
190+
}
191+
}

0 commit comments

Comments
 (0)