Skip to content

Commit 2e227a7

Browse files
committed
fixup! Introduce Kotlin integration tests
Signed-off-by: Sergey Karpov <[email protected]>
1 parent 41f195a commit 2e227a7

File tree

6 files changed

+39
-42
lines changed

6 files changed

+39
-42
lines changed

kotlin-sdk-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/integration/typescript/KotlinClientTypeScriptServerEdgeCasesTest.kt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -51,7 +51,7 @@ class KotlinClientTypeScriptServerEdgeCasesTest : TypeScriptTestBase() {
5151
if (::tsServerProcess.isInitialized) {
5252
try {
5353
println("Stopping TypeScript server")
54-
stopProcess(tsServerProcess, 3, "TypeScript server")
54+
stopProcess(tsServerProcess)
5555
} catch (e: Exception) {
5656
println("Warning: Error during TypeScript server stop: ${e.message}")
5757
}

kotlin-sdk-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/integration/typescript/KotlinClientTypeScriptServerTest.kt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -56,7 +56,7 @@ class KotlinClientTypeScriptServerTest : TypeScriptTestBase() {
5656
if (::tsServerProcess.isInitialized) {
5757
try {
5858
println("Stopping TypeScript server")
59-
stopProcess(tsServerProcess, 3, "TypeScript server")
59+
stopProcess(tsServerProcess)
6060
} catch (e: Exception) {
6161
println("Warning: Error during TypeScript server stop: ${e.message}")
6262
}

kotlin-sdk-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/integration/typescript/TypeScriptClientKotlinServerTest.kt

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -14,15 +14,14 @@ class TypeScriptClientKotlinServerTest : TypeScriptTestBase() {
1414
private lateinit var serverUrl: String
1515
private var httpServer: KotlinServerForTypeScriptClient? = null
1616

17-
// rerun
1817
@BeforeEach
1918
fun setUp() {
2019
port = findFreePort()
2120
serverUrl = "http://localhost:$port/mcp"
2221
killProcessOnPort(port)
2322
httpServer = KotlinServerForTypeScriptClient()
2423
httpServer?.start(port)
25-
if (!waitForPort("localhost", port, 10)) {
24+
if (!waitForPort(port = port)) {
2625
throw IllegalStateException("Kotlin test server did not become ready on localhost:$port within timeout")
2726
}
2827
println("Kotlin server started on port $port")

kotlin-sdk-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/integration/typescript/TypeScriptEdgeCasesTest.kt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@ class TypeScriptEdgeCasesTest : TypeScriptTestBase() {
2323
killProcessOnPort(port)
2424
httpServer = KotlinServerForTypeScriptClient()
2525
httpServer?.start(port)
26-
if (!waitForPort("localhost", port, 10)) {
26+
if (!waitForPort(port = port)) {
2727
throw IllegalStateException("Kotlin test server did not become ready on localhost:$port within timeout")
2828
}
2929
println("Kotlin server started on port $port")

kotlin-sdk-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/integration/typescript/TypeScriptTestBase.kt

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -107,7 +107,7 @@ abstract class TypeScriptTestBase {
107107
return true
108108
}
109109

110-
protected fun createProcessOutputReader(process: Process, prefix: String): Thread {
110+
protected fun createProcessOutputReader(process: Process, prefix: String = "TS-SERVER"): Thread {
111111
val outputReader = Thread {
112112
try {
113113
process.inputStream.bufferedReader().useLines { lines ->
@@ -123,7 +123,7 @@ abstract class TypeScriptTestBase {
123123
return outputReader
124124
}
125125

126-
protected fun waitForPort(host: String, port: Int, timeoutSeconds: Long = 10): Boolean {
126+
protected fun waitForPort(host: String = "localhost", port: Int, timeoutSeconds: Long = 10): Boolean {
127127
val deadline = System.currentTimeMillis() + timeoutSeconds * 1000
128128
while (System.currentTimeMillis() < deadline) {
129129
try {
@@ -145,14 +145,14 @@ abstract class TypeScriptTestBase {
145145
.directory(sdkDir)
146146
.redirectErrorStream(true)
147147
val process = processBuilder.start()
148-
if (!waitForPort("localhost", port, 10)) {
148+
if (!waitForPort(port = port)) {
149149
throw IllegalStateException("TypeScript server did not become ready on localhost:$port within timeout")
150150
}
151-
createProcessOutputReader(process, "TS-SERVER").start()
151+
createProcessOutputReader(process).start()
152152
return process
153153
}
154154

155-
protected fun stopProcess(process: Process, waitSeconds: Long = 3, name: String = "process") {
155+
protected fun stopProcess(process: Process, waitSeconds: Long = 3, name: String = "TypeScript server") {
156156
process.destroy()
157157
if (waitForProcessTermination(process, waitSeconds)) {
158158
println("$name stopped gracefully")

kotlin-sdk-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/integration/utils/Retry.kt

Lines changed: 30 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -5,18 +5,17 @@ import org.junit.jupiter.api.extension.ExtensionContext
55
import org.junit.jupiter.api.extension.InvocationInterceptor
66
import org.junit.jupiter.api.extension.InvocationInterceptor.Invocation
77
import org.junit.jupiter.api.extension.ReflectiveInvocationContext
8-
import java.lang.annotation.Inherited
8+
import org.opentest4j.TestAbortedException
99
import java.lang.reflect.AnnotatedElement
1010
import java.lang.reflect.Method
1111
import java.util.*
1212

13-
@Target(AnnotationTarget.CLASS, AnnotationTarget.FUNCTION)
13+
@Target(AnnotationTarget.CLASS)
1414
@Retention(AnnotationRetention.RUNTIME)
15-
@Inherited
1615
@ExtendWith(RetryExtension::class)
1716
annotation class Retry(
1817
val times: Int = 3,
19-
val delayMs: Long = 0,
18+
val delayMs: Long = 1000,
2019
)
2120

2221
class RetryExtension : InvocationInterceptor {
@@ -29,8 +28,6 @@ class RetryExtension : InvocationInterceptor {
2928
}
3029

3130
private fun resolveRetryAnnotation(extensionContext: ExtensionContext): Retry? {
32-
val methodAnn = extensionContext.testMethod.flatMap { findRetry(it) }
33-
if (methodAnn.isPresent) return methodAnn.get()
3431
val classAnn = extensionContext.testClass.flatMap { findRetry(it) }
3532
return classAnn.orElse(null)
3633
}
@@ -42,50 +39,51 @@ class RetryExtension : InvocationInterceptor {
4239
private fun executeWithRetry(invocation: Invocation<Void>, extensionContext: ExtensionContext) {
4340
val retry = resolveRetryAnnotation(extensionContext)
4441
if (retry == null || retry.times <= 1) {
45-
// No retry requested or only one attempt
4642
invocation.proceed()
4743
return
4844
}
4945

50-
var attempt = 1
51-
var lastError: Throwable? = null
5246
val maxAttempts = retry.times
5347
val delay = retry.delayMs
48+
var lastError: Throwable? = null
5449

55-
while (attempt <= maxAttempts) {
56-
try {
57-
if (attempt > 1) {
58-
println("[RetryExtension] Attempt $attempt/$maxAttempts for ${describeTest(extensionContext)}")
50+
for (attempt in 1..maxAttempts) {
51+
if (attempt > 1 && delay > 0) {
52+
try {
53+
Thread.sleep(delay)
54+
} catch (_: InterruptedException) {
55+
Thread.currentThread().interrupt()
56+
break
5957
}
60-
invocation.proceed()
61-
if (attempt > 1) {
62-
println("[RetryExtension] Succeeded on attempt $attempt for ${describeTest(extensionContext)}")
58+
}
59+
60+
try {
61+
if (attempt == 1) {
62+
invocation.proceed()
63+
} else {
64+
val instance = extensionContext.requiredTestInstance
65+
val testMethod = extensionContext.requiredTestMethod
66+
testMethod.isAccessible = true
67+
testMethod.invoke(instance)
6368
}
6469
return
6570
} catch (t: Throwable) {
66-
lastError = t
67-
if (attempt >= maxAttempts) {
68-
println("[RetryExtension] Giving up after $attempt attempts for ${describeTest(extensionContext)}: ${t.message}")
69-
throw t
70-
} else {
71-
println("[RetryExtension] Failure on attempt $attempt for ${describeTest(extensionContext)}: ${t.message}")
72-
if (delay > 0) {
73-
try {
74-
Thread.sleep(delay)
75-
} catch (_: InterruptedException) {
76-
}
77-
}
71+
if (t is TestAbortedException) throw t
72+
lastError = if (t is java.lang.reflect.InvocationTargetException) t.targetException ?: t else t
73+
if (attempt == maxAttempts) {
74+
println("[Retry] Giving up after $attempt attempts for ${describeTest(extensionContext)}: ${lastError.message}")
75+
throw lastError
7876
}
77+
println("[Retry] Failure on attempt $attempt/${maxAttempts} for ${describeTest(extensionContext)}: ${lastError.message}")
7978
}
80-
attempt++
8179
}
82-
// Should not reach here; rethrow last error defensively if it happens
83-
lastError?.let { throw it }
80+
81+
throw lastError ?: IllegalStateException("Unexpected state in retry logic")
8482
}
8583

8684
private fun describeTest(ctx: ExtensionContext): String {
8785
val methodName = ctx.testMethod.map(Method::getName).orElse("<unknown>")
8886
val className = ctx.testClass.map { it.name }.orElse("<unknown>")
8987
return "$className#$methodName"
9088
}
91-
}
89+
}

0 commit comments

Comments
 (0)