Skip to content

Commit

Permalink
Add ability to retry runTest on backend (#4499)
Browse files Browse the repository at this point in the history
* Add retry for server base tests

* Revert "Update tomcat.jakarta to v10.1.33 (#4470)"

* Increase retries

* Remove flaky testGetStreamPerRequestAttributes

* Mute HttpTimeout tests on native
  • Loading branch information
e5l authored Nov 25, 2024
1 parent db760a2 commit d783338
Show file tree
Hide file tree
Showing 9 changed files with 124 additions and 85 deletions.
2 changes: 1 addition & 1 deletion gradle/libs.versions.toml
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ jetty-alpn-boot = "8.1.13.v20181017"
jetty-alpn-openjdk8 = "9.4.56.v20240826"

tomcat = "9.0.97"
tomcat-jakarta = "10.1.33"
tomcat-jakarta = "10.1.31"

apache = "4.1.5"
apache5 = "5.3.1"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import io.ktor.client.request.*
import io.ktor.client.statement.*
import io.ktor.client.tests.utils.*
import io.ktor.http.*
import io.ktor.util.PlatformUtils
import io.ktor.utils.io.*
import io.ktor.utils.io.CancellationException
import kotlinx.coroutines.*
Expand Down Expand Up @@ -169,6 +170,9 @@ class HttpTimeoutTest : ClientLoader() {

@Test
fun testGetWithSeparateReceive() = clientTests {
// https://youtrack.jetbrains.com/issue/KTOR-7847/Investigate-Flaky-timeout-tests-on-linuxX64
if (PlatformUtils.IS_NATIVE) return@clientTests

config {
install(HttpTimeout) { requestTimeoutMillis = 2000 }
}
Expand All @@ -185,7 +189,10 @@ class HttpTimeoutTest : ClientLoader() {
}

@Test
fun testGetWithSeparateReceivePerRequestAttributes() = clientTests(retries = 5) {
fun testGetWithSeparateReceivePerRequestAttributes() = clientTests {
// https://youtrack.jetbrains.com/issue/KTOR-7847/Investigate-Flaky-timeout-tests-on-linuxX64
if (PlatformUtils.IS_NATIVE) return@clientTests

config {
install(HttpTimeout)
}
Expand All @@ -205,6 +212,9 @@ class HttpTimeoutTest : ClientLoader() {

@Test
fun testGetRequestTimeoutWithSeparateReceive() = clientTests(listOf("Js"), retries = 5) {
// https://youtrack.jetbrains.com/issue/KTOR-7847/Investigate-Flaky-timeout-tests-on-linuxX64
if (PlatformUtils.IS_NATIVE) return@clientTests

config {
install(HttpTimeout) { requestTimeoutMillis = 2000 }
}
Expand All @@ -222,24 +232,25 @@ class HttpTimeoutTest : ClientLoader() {
}

@Test
fun testGetRequestTimeoutWithSeparateReceivePerRequestAttributes() =
clientTests(listOf("Js", "Curl", "Darwin", "DarwinLegacy")) {
config {
install(HttpTimeout)
}
fun testGetRequestTimeoutWithSeparateReceivePerRequestAttributes() = clientTests(
listOf("Js", "Curl", "Darwin", "DarwinLegacy")
) {
config {
install(HttpTimeout)
}

test { client ->
val response = client.prepareRequest("$TEST_URL/with-stream") {
method = HttpMethod.Get
parameter("delay", 10000)
test { client ->
val response = client.prepareRequest("$TEST_URL/with-stream") {
method = HttpMethod.Get
parameter("delay", 10000)

timeout { requestTimeoutMillis = 2000 }
}.body<ByteReadChannel>()
assertFailsWith<CancellationException> {
response.readUTF8Line()
}
timeout { requestTimeoutMillis = 2000 }
}.body<ByteReadChannel>()
assertFailsWith<CancellationException> {
response.readUTF8Line()
}
}
}

@Test
fun testGetAfterTimeout() = clientTests(listOf("Curl", "Js", "Darwin", "DarwinLegacy")) {
Expand All @@ -263,31 +274,17 @@ class HttpTimeoutTest : ClientLoader() {
}

@Test
fun testGetStream() = clientTests(retries = 5) {
config {
install(HttpTimeout) { requestTimeoutMillis = 1000 }
}

test { client ->
val responseBody: String = client.get("$TEST_URL/with-stream") {
parameter("delay", 10)
}.body()
fun testGetStream() = clientTests(retries = 10) {
// https://youtrack.jetbrains.com/issue/KTOR-7847/Investigate-Flaky-timeout-tests-on-linuxX64
if (PlatformUtils.IS_NATIVE) return@clientTests

assertEquals("Text", responseBody)
}
}

@Test
fun testGetStreamPerRequestAttributes() = clientTests(retries = 5) {
config {
install(HttpTimeout)
install(HttpTimeout) { requestTimeoutMillis = 1000 }
}

test { client ->
val responseBody: String = client.get("$TEST_URL/with-stream") {
parameter("delay", 10)

timeout { requestTimeoutMillis = 1000 }
}.body()

assertEquals("Text", responseBody)
Expand Down Expand Up @@ -328,7 +325,7 @@ class HttpTimeoutTest : ClientLoader() {

// Js can't configure test timeout in browser
@Test
fun testRedirect() = clientTests(listOf("js")) {
fun testRedirect() = clientTests(listOf("js"), retries = 5) {
config {
install(HttpTimeout) { requestTimeoutMillis = 10000 }
}
Expand Down Expand Up @@ -439,8 +436,7 @@ class HttpTimeoutTest : ClientLoader() {
assertFails {
try {
client.get("http://localhost:11").body<String>()
} catch (_: ConnectTimeoutException) {
/* Ignore. */
} catch (_: ConnectTimeoutException) {/* Ignore. */
}
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ import kotlin.test.*
class LoggingMockedTests {

@Test
fun testLogResponseWithException() = testWithEngine(MockEngine) {
fun testLogResponseWithException() = testWithEngine(MockEngine, retries = 5) {
val testLogger = TestLogger(
"REQUEST: ${URLBuilder.origin}",
"METHOD: HttpMethod(value=GET)",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -376,7 +376,7 @@ class WebSocketTest : ClientLoader() {
}

@Test
fun testAuthenticationWithValidInitialToken() = clientTests(ENGINES_WITHOUT_WS + "Js", retries = 5) {
fun testAuthenticationWithValidInitialToken() = clientTests(ENGINES_WITHOUT_WS + "Js" + "Darwin", retries = 5) {
config {
install(WebSockets)

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,13 +4,33 @@

package io.ktor.server.test.base

import kotlinx.coroutines.*
import kotlinx.coroutines.test.*
import kotlin.time.*
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.test.TestResult
import kotlin.time.Duration
import kotlin.time.Duration.Companion.seconds

expect abstract class BaseTest() {
open val timeout: Duration

open fun beforeTest()
open fun afterTest()

fun collectUnhandledException(error: Throwable) // TODO: better name?
fun runTest(block: suspend CoroutineScope.() -> Unit): TestResult
fun runTest(timeout: Duration, block: suspend CoroutineScope.() -> Unit): TestResult
fun runTest(timeout: Duration = 60.seconds, block: suspend CoroutineScope.() -> Unit): TestResult
}

fun BaseTest.runTest(
retry: Int,
timeout: Duration = this.timeout,
block: suspend CoroutineScope.() -> Unit
): TestResult {
lateinit var lastCause: Throwable
repeat(retry) {
try {
return runTest(timeout, block)
} catch (cause: Throwable) {
lastCause = cause
}
}
throw lastCause
}
Original file line number Diff line number Diff line change
Expand Up @@ -5,13 +5,11 @@
package io.ktor.server.test.base

import io.ktor.test.dispatcher.*
import kotlinx.coroutines.*
import kotlinx.coroutines.test.*
import kotlin.test.*
import kotlin.time.*
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.test.TestResult
import kotlin.time.Duration
import kotlin.time.Duration.Companion.seconds

@Suppress("FunctionName")
actual abstract class BaseTest actual constructor() {
actual open val timeout: Duration = 10.seconds

Expand All @@ -21,8 +19,10 @@ actual abstract class BaseTest actual constructor() {
errors.add(error)
}

@AfterTest
fun _verifyErrors() {
actual open fun beforeTest() {
}

actual open fun afterTest() {
if (errors.isEmpty()) return

val error = UnhandledErrorsException(
Expand All @@ -36,11 +36,17 @@ actual abstract class BaseTest actual constructor() {
throw error // suppressed exceptions print wrong in idea
}

actual fun runTest(block: suspend CoroutineScope.() -> Unit): TestResult =
runTestWithRealTime(timeout = timeout, testBody = block)

actual fun runTest(timeout: Duration, block: suspend CoroutineScope.() -> Unit): TestResult =
runTestWithRealTime(timeout = timeout, testBody = block)
actual fun runTest(
timeout: Duration,
block: suspend CoroutineScope.() -> Unit
): TestResult = runTestWithRealTime(timeout = timeout) {
beforeTest()
try {
block()
} finally {
afterTest()
}
}
}

private class UnhandledErrorsException(override val message: String) : Exception()
Original file line number Diff line number Diff line change
Expand Up @@ -7,13 +7,15 @@ package io.ktor.server.test.base

import io.ktor.junit.*
import io.ktor.test.dispatcher.*
import kotlinx.coroutines.*
import kotlinx.coroutines.debug.junit5.*
import kotlinx.coroutines.test.*
import org.junit.jupiter.api.*
import java.lang.reflect.*
import kotlinx.coroutines.CoroutineName
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.debug.junit5.CoroutinesTimeout
import kotlinx.coroutines.test.TestResult
import org.junit.jupiter.api.BeforeEach
import org.junit.jupiter.api.TestInfo
import java.lang.reflect.Method
import java.util.*
import kotlin.time.*
import kotlin.time.Duration
import kotlin.time.Duration.Companion.seconds

@CoroutinesTimeout(5 * 60 * 1000)
Expand All @@ -33,18 +35,26 @@ actual abstract class BaseTest actual constructor() {
testName = method.map { it.name }.orElse(testInfo.displayName)
}

@AfterEach
fun throwErrors() {
actual open fun afterTest() {
errorCollector.throwErrorIfPresent()
}

actual open fun beforeTest() {
}

actual fun collectUnhandledException(error: Throwable) {
errorCollector += error
}

actual fun runTest(block: suspend CoroutineScope.() -> Unit): TestResult =
runTestWithRealTime(CoroutineName("test-$testName"), timeout, block)

actual fun runTest(timeout: Duration, block: suspend CoroutineScope.() -> Unit): TestResult =
runTestWithRealTime(CoroutineName("test-$testName"), timeout, block)
actual fun runTest(
timeout: Duration,
block: suspend CoroutineScope.() -> Unit
): TestResult = runTestWithRealTime(CoroutineName("test-$testName"), timeout) {
beforeTest()
try {
block()
} finally {
afterTest()
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -81,8 +81,9 @@ actual abstract class EngineTestBase<
System.getProperty("host.test.timeout.seconds")?.toLong()?.seconds ?: 4.minutes
}

@BeforeEach
fun setUpBase() {
override fun beforeTest() {
super.beforeTest()

val method = testMethod.orElseThrow { AssertionError("Method $testName not found") }

if (method.isAnnotationPresent(Http2Only::class.java)) {
Expand All @@ -95,8 +96,7 @@ actual abstract class EngineTestBase<
testLog.trace("Starting server on port $port (SSL $sslPort)")
}

@AfterEach
fun tearDownBase() {
override fun afterTest() {
try {
allConnections.forEach { it.disconnect() }
testLog.trace("Disposing server on port $port (SSL $sslPort)")
Expand All @@ -105,6 +105,7 @@ actual abstract class EngineTestBase<
testJob.cancel()
FreePorts.recycle(port)
FreePorts.recycle(sslPort)
super.afterTest()
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,13 +7,11 @@ package io.ktor.server.test.base
import io.ktor.test.dispatcher.*
import io.ktor.utils.io.*
import io.ktor.utils.io.locks.*
import kotlinx.coroutines.*
import kotlinx.coroutines.test.*
import kotlin.test.*
import kotlin.time.*
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.test.TestResult
import kotlin.time.Duration
import kotlin.time.Duration.Companion.seconds

@Suppress("FunctionName")
actual abstract class BaseTest actual constructor() {
actual open val timeout: Duration = 10.seconds

Expand All @@ -29,8 +27,10 @@ actual abstract class BaseTest actual constructor() {
}
}

@AfterTest
fun _verifyErrors() {
actual open fun beforeTest() {
}

actual open fun afterTest() {
if (errors.isEmpty()) return

val error = UnhandledErrorsException(
Expand All @@ -44,11 +44,17 @@ actual abstract class BaseTest actual constructor() {
throw error // suppressed exceptions print wrong in idea
}

actual fun runTest(block: suspend CoroutineScope.() -> Unit): TestResult =
runTestWithRealTime(timeout = timeout, testBody = block)

actual fun runTest(timeout: Duration, block: suspend CoroutineScope.() -> Unit): TestResult =
runTestWithRealTime(timeout = timeout, testBody = block)
actual fun runTest(
timeout: Duration,
block: suspend CoroutineScope.() -> Unit
): TestResult = runTestWithRealTime(timeout = timeout) {
beforeTest()
try {
block()
} finally {
afterTest()
}
}
}

private class UnhandledErrorsException(override val message: String) : Exception()

0 comments on commit d783338

Please sign in to comment.