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