• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright 2016-2021 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
3  */
4 @file:Suppress("DEPRECATION")
5 @file:JvmName("TestBuildersKt")
6 @file:JvmMultifileClass
7 
8 package kotlinx.coroutines.test
9 
10 import kotlinx.coroutines.*
11 import kotlin.coroutines.*
12 import kotlin.jvm.*
13 
14 /**
15  * Executes a [testBody] inside an immediate execution dispatcher.
16  *
17  * This method is deprecated in favor of [runTest]. Please see the
18  * [migration guide](https://github.com/Kotlin/kotlinx.coroutines/blob/master/kotlinx-coroutines-test/MIGRATION.md)
19  * for an instruction on how to update the code for the new API.
20  *
21  * This is similar to [runBlocking] but it will immediately progress past delays and into [launch] and [async] blocks.
22  * You can use this to write tests that execute in the presence of calls to [delay] without causing your test to take
23  * extra time.
24  *
25  * ```
26  * @Test
27  * fun exampleTest() = runBlockingTest {
28  *     val deferred = async {
29  *         delay(1_000)
30  *         async {
31  *             delay(1_000)
32  *         }.await()
33  *     }
34  *
35  *     deferred.await() // result available immediately
36  * }
37  *
38  * ```
39  *
40  * This method requires that all coroutines launched inside [testBody] complete, or are cancelled, as part of the test
41  * conditions.
42  *
43  * Unhandled exceptions thrown by coroutines in the test will be re-thrown at the end of the test.
44  *
45  * @throws AssertionError If the [testBody] does not complete (or cancel) all coroutines that it launches
46  * (including coroutines suspended on join/await).
47  *
48  * @param context additional context elements. If [context] contains [CoroutineDispatcher] or [CoroutineExceptionHandler],
49  *        then they must implement [DelayController] and [TestCoroutineExceptionHandler] respectively.
50  * @param testBody The code of the unit-test.
51  */
52 @Deprecated("Use `runTest` instead to support completing from other dispatchers. " +
53     "Please see the migration guide for details: " +
54     "https://github.com/Kotlin/kotlinx.coroutines/blob/master/kotlinx-coroutines-test/MIGRATION.md",
55     level = DeprecationLevel.WARNING)
56 // Since 1.6.0, ERROR in 1.7.0 and removed as experimental in 1.8.0
runBlockingTestnull57 public fun runBlockingTest(
58     context: CoroutineContext = EmptyCoroutineContext,
59     testBody: suspend TestCoroutineScope.() -> Unit
60 ) {
61     val scope = createTestCoroutineScope(TestCoroutineDispatcher() + SupervisorJob() + context)
62     val scheduler = scope.testScheduler
63     val deferred = scope.async {
64         scope.testBody()
65     }
66     scheduler.advanceUntilIdle()
67     deferred.getCompletionExceptionOrNull()?.let {
68         throw it
69     }
70     scope.cleanupTestCoroutines()
71 }
72 
73 /**
74  * A version of [runBlockingTest] that works with [TestScope].
75  */
76 @Deprecated("Use `runTest` instead to support completing from other dispatchers.", level = DeprecationLevel.WARNING)
77 // Since 1.6.0, ERROR in 1.7.0 and removed as experimental in 1.8.0
runBlockingTestOnTestScopenull78 public fun runBlockingTestOnTestScope(
79     context: CoroutineContext = EmptyCoroutineContext,
80     testBody: suspend TestScope.() -> Unit
81 ) {
82     val completeContext = TestCoroutineDispatcher() + SupervisorJob() + context
83     val startJobs = completeContext.activeJobs()
84     val scope = TestScope(completeContext).asSpecificImplementation()
85     scope.enter()
86     scope.start(CoroutineStart.UNDISPATCHED, scope) {
87         scope.testBody()
88     }
89     scope.testScheduler.advanceUntilIdle()
90     val throwable = try {
91         scope.getCompletionExceptionOrNull()
92     } catch (e: IllegalStateException) {
93         null // the deferred was not completed yet; `scope.leave()` should complain then about unfinished jobs
94     }
95     scope.backgroundScope.cancel()
96     scope.testScheduler.advanceUntilIdleOr { false }
97     throwable?.let {
98         val exceptions = try {
99             scope.leave()
100         } catch (e: UncompletedCoroutinesError) {
101             listOf()
102         }
103         (listOf(it) + exceptions).throwAll()
104         return
105     }
106     scope.leave().throwAll()
107     val jobs = completeContext.activeJobs() - startJobs
108     if (jobs.isNotEmpty())
109         throw UncompletedCoroutinesError("Some jobs were not completed at the end of the test: $jobs")
110 }
111 
112 /**
113  * Convenience method for calling [runBlockingTest] on an existing [TestCoroutineScope].
114  *
115  * This method is deprecated in favor of [runTest], whereas [TestCoroutineScope] is deprecated in favor of [TestScope].
116  * Please see the
117  * [migration guide](https://github.com/Kotlin/kotlinx.coroutines/blob/master/kotlinx-coroutines-test/MIGRATION.md)
118  * for an instruction on how to update the code for the new API.
119  */
120 @Deprecated("Use `runTest` instead to support completing from other dispatchers. " +
121     "Please see the migration guide for details: " +
122     "https://github.com/Kotlin/kotlinx.coroutines/blob/master/kotlinx-coroutines-test/MIGRATION.md",
123     level = DeprecationLevel.WARNING)
124 // Since 1.6.0, ERROR in 1.7.0 and removed as experimental in 1.8.0
runBlockingTestnull125 public fun TestCoroutineScope.runBlockingTest(block: suspend TestCoroutineScope.() -> Unit): Unit =
126     runBlockingTest(coroutineContext, block)
127 
128 /**
129  * Convenience method for calling [runBlockingTestOnTestScope] on an existing [TestScope].
130  */
131 @Deprecated("Use `runTest` instead to support completing from other dispatchers.", level = DeprecationLevel.WARNING)
132 // Since 1.6.0, ERROR in 1.7.0 and removed as experimental in 1.8.0
133 public fun TestScope.runBlockingTest(block: suspend TestScope.() -> Unit): Unit =
134     runBlockingTestOnTestScope(coroutineContext, block)
135 
136 /**
137  * Convenience method for calling [runBlockingTest] on an existing [TestCoroutineDispatcher].
138  *
139  * This method is deprecated in favor of [runTest], whereas [TestCoroutineScope] is deprecated in favor of [TestScope].
140  * Please see the
141  * [migration guide](https://github.com/Kotlin/kotlinx.coroutines/blob/master/kotlinx-coroutines-test/MIGRATION.md)
142  * for an instruction on how to update the code for the new API.
143  */
144 @Deprecated("Use `runTest` instead to support completing from other dispatchers. " +
145     "Please see the migration guide for details: " +
146     "https://github.com/Kotlin/kotlinx.coroutines/blob/master/kotlinx-coroutines-test/MIGRATION.md",
147     level = DeprecationLevel.WARNING)
148 // Since 1.6.0, ERROR in 1.7.0 and removed as experimental in 1.8.0
149 public fun TestCoroutineDispatcher.runBlockingTest(block: suspend TestCoroutineScope.() -> Unit): Unit =
150     runBlockingTest(this, block)
151 
152 /**
153  * This is an overload of [runTest] that works with [TestCoroutineScope].
154  */
155 @ExperimentalCoroutinesApi
156 @Deprecated("Use `runTest` instead.", level = DeprecationLevel.WARNING)
157 // Since 1.6.0, ERROR in 1.7.0 and removed as experimental in 1.8.0
158 public fun runTestWithLegacyScope(
159     context: CoroutineContext = EmptyCoroutineContext,
160     dispatchTimeoutMs: Long = DEFAULT_DISPATCH_TIMEOUT_MS,
161     testBody: suspend TestCoroutineScope.() -> Unit
162 ): TestResult {
163     if (context[RunningInRunTest] != null)
164         throw IllegalStateException("Calls to `runTest` can't be nested. Please read the docs on `TestResult` for details.")
165     val testScope = TestBodyCoroutine(createTestCoroutineScope(context + RunningInRunTest))
166     return createTestResult {
167         runTestCoroutine(testScope, dispatchTimeoutMs, TestBodyCoroutine::tryGetCompletionCause, testBody) {
168             try {
169                 testScope.cleanup()
170                 emptyList()
171             } catch (e: UncompletedCoroutinesError) {
172                 throw e
173             } catch (e: Throwable) {
174                 listOf(e)
175             }
176         }
177     }
178 }
179 
180 /**
181  * Runs a test in a [TestCoroutineScope] based on this one.
182  *
183  * Calls [runTest] using a coroutine context from this [TestCoroutineScope]. The [TestCoroutineScope] used to run the
184  * [block] will be different from this one, but will use its [Job] as a parent.
185  *
186  * Since this function returns [TestResult], in order to work correctly on the JS, its result must be returned
187  * immediately from the test body. See the docs for [TestResult] for details.
188  */
189 @ExperimentalCoroutinesApi
190 @Deprecated("Use `TestScope.runTest` instead.", level = DeprecationLevel.WARNING)
191 // Since 1.6.0, ERROR in 1.7.0 and removed as experimental in 1.8.0
runTestnull192 public fun TestCoroutineScope.runTest(
193     dispatchTimeoutMs: Long = DEFAULT_DISPATCH_TIMEOUT_MS,
194     block: suspend TestCoroutineScope.() -> Unit
195 ): TestResult = runTestWithLegacyScope(coroutineContext, dispatchTimeoutMs, block)
196 
197 private class TestBodyCoroutine(
198     private val testScope: TestCoroutineScope,
199 ) : AbstractCoroutine<Unit>(testScope.coroutineContext, initParentJob = true, active = true), TestCoroutineScope {
200 
201     override val testScheduler get() = testScope.testScheduler
202 
203     @Deprecated(
204         "This deprecation is to prevent accidentally calling `cleanupTestCoroutines` in our own code.",
205         ReplaceWith("this.cleanup()"),
206         DeprecationLevel.ERROR
207     )
208     override fun cleanupTestCoroutines() =
209         throw UnsupportedOperationException(
210             "Calling `cleanupTestCoroutines` inside `runTest` is prohibited: " +
211                 "it will be called at the end of the test in any case."
212         )
213 
214     fun cleanup() = testScope.cleanupTestCoroutines()
215 
216     /** Throws an exception if the coroutine is not completing. */
217     fun tryGetCompletionCause(): Throwable? = completionCause
218 }
219