1 /*
2  * Copyright 2016-2020 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.debug.*
8 import org.junit.rules.*
9 import org.junit.runner.*
10 import org.junit.runners.model.*
11 import java.util.concurrent.*
12 
13 /**
14  * Coroutines timeout rule for JUnit4 that is applied to all methods in the class.
15  * This rule is very similar to [Timeout] rule: it runs tests in a separate thread,
16  * fails tests after the given timeout and interrupts test thread.
17  *
18  * Additionally, this rule installs [DebugProbes] and dumps all coroutines at the moment of the timeout.
19  * It may cancel coroutines on timeout if [cancelOnTimeout] set to `true`.
20  * [enableCoroutineCreationStackTraces] controls the corresponding [DebugProbes.enableCreationStackTraces] property
21  * and can be optionally disabled to speed-up tests if creation stack traces are not needed.
22  *
23  * Example of usage:
24  * ```
25  * class HangingTest {
26  *     @get:Rule
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  *     }
35  * }
36  * ```
37  */
38 public class CoroutinesTimeout(
39     private val testTimeoutMs: Long,
40     private val cancelOnTimeout: Boolean = false,
41     private val enableCoroutineCreationStackTraces: Boolean = true
42 ) : TestRule {
43 
44     @Suppress("UNUSED") // Binary compatibility
45     public constructor(testTimeoutMs: Long, cancelOnTimeout: Boolean = false) : this(
46         testTimeoutMs,
47         cancelOnTimeout,
48         true
49     )
50 
51     init {
<lambda>null52         require(testTimeoutMs > 0) { "Expected positive test timeout, but had $testTimeoutMs" }
53         /*
54          * Install probes in the constructor, so all the coroutines launched from within
55          * target test constructor will be captured
56          */
57         // Do not preserve previous state for unit-test environment
58         DebugProbes.enableCreationStackTraces = enableCoroutineCreationStackTraces
59         DebugProbes.install()
60     }
61 
62     public companion object {
63         /**
64          * Creates [CoroutinesTimeout] rule with the given timeout in seconds.
65          */
66         @JvmOverloads
secondsnull67         public fun seconds(
68             seconds: Int,
69             cancelOnTimeout: Boolean = false,
70             enableCoroutineCreationStackTraces: Boolean = true
71         ): CoroutinesTimeout =
72             seconds(seconds.toLong(), cancelOnTimeout, enableCoroutineCreationStackTraces)
73 
74         /**
75          * Creates [CoroutinesTimeout] rule with the given timeout in seconds.
76          */
77         @JvmOverloads
78         public fun seconds(
79             seconds: Long,
80             cancelOnTimeout: Boolean = false,
81             enableCoroutineCreationStackTraces: Boolean = true
82         ): CoroutinesTimeout =
83             CoroutinesTimeout(
84                 TimeUnit.SECONDS.toMillis(seconds),  // Overflow is properly handled by TimeUnit
85                 cancelOnTimeout,
86                 enableCoroutineCreationStackTraces
87             )
88     }
89 
90     /**
91      * @suppress suppress from Dokka
92      */
93     override fun apply(base: Statement, description: Description): Statement =
94         CoroutinesTimeoutStatement(base, description, testTimeoutMs, cancelOnTimeout)
95 }
96