Skip to content

Commit 4c4a67d

Browse files
committed
Document the convenience methods
1 parent f2ab7d0 commit 4c4a67d

File tree

2 files changed

+71
-36
lines changed

2 files changed

+71
-36
lines changed

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

+59-36
Original file line numberDiff line numberDiff line change
@@ -87,6 +87,7 @@ public fun TestCoroutineDispatcher.runBlockingTest(block: suspend TestCoroutineS
8787
* * Don't nest functions returning a [TestResult].
8888
*/
8989
@Suppress("NO_ACTUAL_FOR_EXPECT")
90+
@DelicateCoroutinesApi
9091
public expect class TestResult
9192

9293
/**
@@ -161,44 +162,49 @@ public expect class TestResult
161162
* @throws IllegalArgumentException if the [context] is invalid. See the [TestCoroutineScope] constructor docs for
162163
* details.
163164
*/
165+
@DelicateCoroutinesApi
164166
public fun runTest(
165167
context: CoroutineContext = EmptyCoroutineContext,
166168
dispatchTimeoutMs: Long = DEFAULT_DISPATCH_TIMEOUT_MS,
167169
testBody: suspend TestCoroutineScope.() -> Unit
168-
): TestResult = createTestResult {
169-
val testScope = TestCoroutineScope(context + RunningInRunTest())
170-
val scheduler = testScope.testScheduler
171-
val deferred = testScope.async {
172-
testScope.testBody()
173-
}
174-
var completed = false
175-
while (!completed) {
176-
scheduler.advanceUntilIdle()
177-
if (deferred.isCompleted) {
178-
/* don't even enter `withTimeout`; this allows to use a timeout of zero to check that there are no
179-
non-trivial dispatches. */
180-
completed = true
181-
continue
170+
): TestResult {
171+
if (context[RunningInRunTest] != null)
172+
throw IllegalStateException("Calls to `runTest` can't be nested. Please read the docs on `TestResult` for details.")
173+
return createTestResult {
174+
val testScope = TestCoroutineScope(context + RunningInRunTest())
175+
val scheduler = testScope.testScheduler
176+
val deferred = testScope.async {
177+
testScope.testBody()
182178
}
183-
try {
184-
withTimeout(dispatchTimeoutMs) {
185-
select<Unit> {
186-
deferred.onAwait {
187-
completed = true
188-
}
189-
scheduler.onDispatchEvent {
190-
// we received knowledge that `scheduler` observed a dispatch event, so we reset the timeout
179+
var completed = false
180+
while (!completed) {
181+
scheduler.advanceUntilIdle()
182+
if (deferred.isCompleted) {
183+
/* don't even enter `withTimeout`; this allows to use a timeout of zero to check that there are no
184+
non-trivial dispatches. */
185+
completed = true
186+
continue
187+
}
188+
try {
189+
withTimeout(dispatchTimeoutMs) {
190+
select<Unit> {
191+
deferred.onAwait {
192+
completed = true
193+
}
194+
scheduler.onDispatchEvent {
195+
// we received knowledge that `scheduler` observed a dispatch event, so we reset the timeout
196+
}
191197
}
192198
}
199+
} catch (e: TimeoutCancellationException) {
200+
throw UncompletedCoroutinesError("The test coroutine was not completed after waiting for $dispatchTimeoutMs ms")
193201
}
194-
} catch (e: TimeoutCancellationException) {
195-
throw UncompletedCoroutinesError("The test coroutine was not completed after waiting for $dispatchTimeoutMs ms")
196202
}
203+
deferred.getCompletionExceptionOrNull()?.let {
204+
throw it
205+
}
206+
testScope.cleanupTestCoroutines()
197207
}
198-
deferred.getCompletionExceptionOrNull()?.let {
199-
throw it
200-
}
201-
testScope.cleanupTestCoroutines()
202208
}
203209

204210
/**
@@ -207,18 +213,33 @@ public fun runTest(
207213
@Suppress("NO_ACTUAL_FOR_EXPECT") // actually suppresses `TestResult`
208214
internal expect fun createTestResult(testProcedure: suspend () -> Unit): TestResult
209215

210-
/** TODO: docs */
216+
/**
217+
* Runs a test in a [TestCoroutineScope] based on this one.
218+
*
219+
* Calls [runTest] using a coroutine context from this [TestCoroutineScope]. The [TestCoroutineScope] used to run
220+
* [block] will be different from this one, but will reuse its [Job]; therefore, even if calling
221+
* [TestCoroutineScope.cleanupTestCoroutines] on this scope were to complete its job, [runTest] won't complete it at the
222+
* end of the test.
223+
*
224+
* Since this function returns [TestResult], in order to work correctly on the JS, its result must be returned
225+
* immediately from the test body. See the docs for [TestResult] for details.
226+
*/
227+
@DelicateCoroutinesApi
211228
public fun TestCoroutineScope.runTest(
212229
dispatchTimeoutMs: Long = DEFAULT_DISPATCH_TIMEOUT_MS,
213230
block: suspend TestCoroutineScope.() -> Unit
214-
): TestResult {
215-
val ctx = this.coroutineContext
216-
if (ctx[RunningInRunTest] != null)
217-
throw IllegalStateException("Calls to `runTest` can't be nested. Please read the docs on `TestResult` for details.")
218-
return runTest(ctx, dispatchTimeoutMs, block)
219-
}
231+
): TestResult =
232+
runTest(coroutineContext, dispatchTimeoutMs, block)
220233

221-
/** TODO: docs */
234+
/**
235+
* Run a test using this [TestDispatcher].
236+
*
237+
* A convenience function that calls [runTest] with the given arguments.
238+
*
239+
* Since this function returns [TestResult], in order to work correctly on the JS, its result must be returned
240+
* immediately from the test body. See the docs for [TestResult] for details.
241+
*/
242+
@DelicateCoroutinesApi
222243
public fun TestDispatcher.runTest(
223244
dispatchTimeoutMs: Long = DEFAULT_DISPATCH_TIMEOUT_MS,
224245
block: suspend TestCoroutineScope.() -> Unit
@@ -230,4 +251,6 @@ private class RunningInRunTest: AbstractCoroutineContextElement(RunningInRunTest
230251
companion object Key : CoroutineContext.Key<RunningInRunTest>
231252
}
232253

254+
/** The default timeout to use when waiting for asynchronous completions of the coroutines managed by
255+
* a [TestCoroutineScheduler]. */
233256
private const val DEFAULT_DISPATCH_TIMEOUT_MS = 10_000L

kotlinx-coroutines-test/jvm/test/MultithreadingTest.kt

+12
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,8 @@
44

55
import kotlinx.coroutines.*
66
import kotlinx.coroutines.test.*
7+
import java.util.concurrent.*
8+
import kotlin.concurrent.*
79
import kotlin.coroutines.*
810
import kotlin.test.*
911

@@ -86,4 +88,14 @@ class MultithreadingTest : TestBase() {
8688
assertEquals(3, deferred.await())
8789
}
8890
}
91+
92+
@Test
93+
fun testResumingFromAnotherThread() = runTest {
94+
suspendCancellableCoroutine<Unit> { cont ->
95+
thread {
96+
Thread.sleep(10)
97+
cont.resume(Unit)
98+
}
99+
}
100+
}
89101
}

0 commit comments

Comments
 (0)