Skip to content

Commit fcc004c

Browse files
committed
Install debug probes in CoroutinesTimeout constructor to capture coroutines launched from within a test constructor
Fixes #1542
1 parent 858094e commit fcc004c

File tree

4 files changed

+63
-16
lines changed

4 files changed

+63
-16
lines changed

kotlinx-coroutines-debug/README.md

+1-2
Original file line numberDiff line numberDiff line change
@@ -31,8 +31,7 @@ Its usage is better demonstrated by the example (runnable code is [here](test/Te
3131

3232
```kotlin
3333
class TestRuleExample {
34-
@Rule
35-
@JvmField
34+
@get:Rule
3635
public val timeout = CoroutinesTimeout.seconds(1)
3736

3837
private suspend fun someFunctionDeepInTheStack() {

kotlinx-coroutines-debug/src/junit4/CoroutinesTimeout.kt

+13-10
Original file line numberDiff line numberDiff line change
@@ -21,17 +21,15 @@ import java.util.concurrent.*
2121
* Example of usage:
2222
* ```
2323
* class HangingTest {
24+
* @get:Rule
25+
* val timeout = CoroutinesTimeout.seconds(5)
2426
*
25-
* @Rule
26-
* @JvmField
27-
* val timeout = CoroutinesTimeout.seconds(5)
28-
*
29-
* @Test
30-
* fun testThatHangs() = runBlocking {
31-
* ...
32-
* delay(Long.MAX_VALUE) // somewhere deep in the stack
33-
* ...
34-
* }
27+
* @Test
28+
* fun testThatHangs() = runBlocking {
29+
* ...
30+
* delay(Long.MAX_VALUE) // somewhere deep in the stack
31+
* ...
32+
* }
3533
* }
3634
* ```
3735
*/
@@ -42,6 +40,11 @@ public class CoroutinesTimeout(
4240

4341
init {
4442
require(testTimeoutMs > 0) { "Expected positive test timeout, but had $testTimeoutMs" }
43+
/*
44+
* Install probes in the constructor, so all the coroutines launched from within
45+
* target test constructor will be captured
46+
*/
47+
DebugProbes.install()
4548
}
4649

4750
companion object {

kotlinx-coroutines-debug/src/junit4/CoroutinesTimeoutStatement.kt

+3-4
Original file line numberDiff line numberDiff line change
@@ -30,11 +30,10 @@ internal class CoroutinesTimeoutStatement(
3030
private val testThread = Thread(testResult, "Timeout test thread").apply { isDaemon = true }
3131

3232
override fun evaluate() {
33-
DebugProbes.install()
34-
testThread.start()
35-
// Await until test is started to take only test execution time into account
36-
testStartedLatch.await()
3733
try {
34+
testThread.start()
35+
// Await until test is started to take only test execution time into account
36+
testStartedLatch.await()
3837
testResult.get(testTimeoutMs, TimeUnit.MILLISECONDS)
3938
return
4039
} catch (e: TimeoutException) {
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
/*
2+
* Copyright 2016-2019 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
3+
*/
4+
5+
package kotlinx.coroutines.debug.junit4
6+
7+
import kotlinx.coroutines.*
8+
import org.junit.*
9+
import org.junit.runners.model.*
10+
11+
class CoroutinesTimeoutEagerTest : TestBase() {
12+
13+
@Rule
14+
@JvmField
15+
public val validation = TestFailureValidation(
16+
500, true,
17+
TestResultSpec(
18+
"hangingTest", expectedOutParts = listOf(
19+
"Coroutines dump",
20+
"Test hangingTest timed out after 500 milliseconds",
21+
"BlockingCoroutine{Active}",
22+
"runBlocking",
23+
"at kotlinx.coroutines.debug.junit4.CoroutinesTimeoutEagerTest.hangForever",
24+
"at kotlinx.coroutines.debug.junit4.CoroutinesTimeoutEagerTest.waitForHangJob"),
25+
error = TestTimedOutException::class.java)
26+
)
27+
28+
private val job = GlobalScope.launch(Dispatchers.Unconfined) { hangForever() }
29+
30+
private suspend fun hangForever() {
31+
suspendCancellableCoroutine<Unit> { }
32+
expectUnreached()
33+
}
34+
35+
@Test
36+
fun hangingTest() = runBlocking<Unit> {
37+
waitForHangJob()
38+
expectUnreached()
39+
}
40+
41+
private suspend fun waitForHangJob() {
42+
job.join()
43+
expectUnreached()
44+
}
45+
46+
}

0 commit comments

Comments
 (0)