Skip to content

Commit 9a98eab

Browse files
authored
Rollback runTest timeout to 60 seconds, configure it with getenv (#3945)
This commit attempts to fix #3800. The first part of the fix, reverting the timeout to 60 seconds, is successful. The second part, allowing global configuration, is only provided for the JVM so far. On JVM, Native, and Node JS, it's possible to use environment variables to communicate data to the process, and it could in theory be the solution, but it doesn't seem to interoperate well with Gradle. The best attempt so far was to use this: ```kotlin tasks.withType(AbstractTestTask::class).all { if (this is ProcessForkOptions) { environment("kotlinx.coroutines.test.default_timeout", "1ms") } } ``` Unfortunately, only `jvmTest` implements `ProcessForkOptions`. Without a clear way to configure the `runTest` timeout in Gradle builds for all targets, we only support this via system properties on the JVM until a better mechanism appears.
1 parent d5c0eff commit 9a98eab

File tree

4 files changed

+40
-7
lines changed

4 files changed

+40
-7
lines changed

kotlinx-coroutines-test/common/src/TestBuilders.kt

Lines changed: 29 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,6 @@ import kotlin.jvm.*
1414
import kotlin.time.*
1515
import kotlin.time.Duration.Companion.milliseconds
1616
import kotlin.time.Duration.Companion.seconds
17-
import kotlinx.coroutines.internal.*
1817

1918
/**
2019
* A test result.
@@ -122,8 +121,14 @@ public expect class TestResult
122121
*
123122
* #### Timing out
124123
*
125-
* There's a built-in timeout of 10 seconds for the test body. If the test body doesn't complete within this time,
126-
* then the test fails with an [AssertionError]. The timeout can be changed by setting the [timeout] parameter.
124+
* There's a built-in timeout of 60 seconds for the test body. If the test body doesn't complete within this time,
125+
* then the test fails with an [AssertionError]. The timeout can be changed for each test separately by setting the
126+
* [timeout] parameter.
127+
*
128+
* Additionally, setting the `kotlinx.coroutines.test.default_timeout` system property on the
129+
* JVM to any string that can be parsed using [Duration.parse] (like `1m`, `30s` or `1500ms`) will change the default
130+
* timeout to that value for all tests whose [timeout] is not set explicitly; setting it to anything else will throw an
131+
* exception every time [runTest] is invoked.
127132
*
128133
* On timeout, the test body is cancelled so that the test finishes. If the code inside the test body does not
129134
* respond to cancellation, the timeout will not be able to make the test execution stop.
@@ -157,7 +162,7 @@ public expect class TestResult
157162
*/
158163
public fun runTest(
159164
context: CoroutineContext = EmptyCoroutineContext,
160-
timeout: Duration = DEFAULT_TIMEOUT,
165+
timeout: Duration = DEFAULT_TIMEOUT.getOrThrow(),
161166
testBody: suspend TestScope.() -> Unit
162167
): TestResult {
163168
check(context[RunningInRunTest] == null) {
@@ -301,7 +306,7 @@ public fun runTest(
301306
* Performs [runTest] on an existing [TestScope]. See the documentation for [runTest] for details.
302307
*/
303308
public fun TestScope.runTest(
304-
timeout: Duration = DEFAULT_TIMEOUT,
309+
timeout: Duration = DEFAULT_TIMEOUT.getOrThrow(),
305310
testBody: suspend TestScope.() -> Unit
306311
): TestResult = asSpecificImplementation().let { scope ->
307312
scope.enter()
@@ -421,8 +426,15 @@ internal const val DEFAULT_DISPATCH_TIMEOUT_MS = 60_000L
421426

422427
/**
423428
* The default timeout to use when running a test.
429+
*
430+
* It's not just a [Duration] but a [Result] so that every access to [runTest]
431+
* throws the same clear exception if parsing the environment variable failed.
432+
* Otherwise, the parsing error would only be thrown in one tests, while the
433+
* other ones would get an incomprehensible `NoClassDefFoundError`.
424434
*/
425-
internal val DEFAULT_TIMEOUT = 10.seconds
435+
private val DEFAULT_TIMEOUT: Result<Duration> = runCatching {
436+
systemProperty("kotlinx.coroutines.test.default_timeout", Duration::parse, 60.seconds)
437+
}
426438

427439
/**
428440
* Run the [body][testBody] of the [test coroutine][coroutine], waiting for asynchronous completions for at most
@@ -571,6 +583,17 @@ internal fun throwAll(head: Throwable?, other: List<Throwable>) {
571583

572584
internal expect fun dumpCoroutines()
573585

586+
private fun <T: Any> systemProperty(
587+
name: String,
588+
parse: (String) -> T,
589+
default: T,
590+
): T {
591+
val value = systemPropertyImpl(name) ?: return default
592+
return parse(value)
593+
}
594+
595+
internal expect fun systemPropertyImpl(name: String): String?
596+
574597
@Deprecated(
575598
"This is for binary compatibility with the `runTest` overload that existed at some point",
576599
level = DeprecationLevel.HIDDEN

kotlinx-coroutines-test/js/src/TestBuilders.kt

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,8 @@ import kotlin.js.*
99
@Suppress("ACTUAL_WITHOUT_EXPECT", "ACTUAL_TYPE_ALIAS_TO_CLASS_WITH_DECLARATION_SITE_VARIANCE")
1010
public actual typealias TestResult = Promise<Unit>
1111

12+
internal actual fun systemPropertyImpl(name: String): String? = null
13+
1214
internal actual fun createTestResult(testProcedure: suspend CoroutineScope.() -> Unit): TestResult =
1315
GlobalScope.promise {
1416
testProcedure()

kotlinx-coroutines-test/jvm/src/TestBuildersJvm.kt

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,13 @@ internal actual fun createTestResult(testProcedure: suspend CoroutineScope.() ->
1515
}
1616
}
1717

18+
internal actual fun systemPropertyImpl(name: String): String? =
19+
try {
20+
System.getProperty(name)
21+
} catch (e: SecurityException) {
22+
null
23+
}
24+
1825
internal actual fun dumpCoroutines() {
1926
@Suppress("INVISIBLE_REFERENCE", "INVISIBLE_MEMBER")
2027
if (DebugProbesImpl.isInstalled) {

kotlinx-coroutines-test/native/src/TestBuilders.kt

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,6 @@
44

55
package kotlinx.coroutines.test
66
import kotlinx.coroutines.*
7-
import kotlin.native.concurrent.*
87

98
@Suppress("ACTUAL_WITHOUT_EXPECT")
109
public actual typealias TestResult = Unit
@@ -15,4 +14,6 @@ internal actual fun createTestResult(testProcedure: suspend CoroutineScope.() ->
1514
}
1615
}
1716

17+
internal actual fun systemPropertyImpl(name: String): String? = null
18+
1819
internal actual fun dumpCoroutines() { }

0 commit comments

Comments
 (0)