• 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 
5 package kotlinx.coroutines.test
6 
7 import kotlinx.coroutines.*
8 import kotlinx.coroutines.channels.*
9 import kotlinx.coroutines.flow.*
10 import kotlinx.coroutines.test.internal.TestMainDispatcher
11 import kotlin.coroutines.*
12 
13 /**
14  * Creates an instance of an unconfined [TestDispatcher].
15  *
16  * This dispatcher is similar to [Dispatchers.Unconfined]: the tasks that it executes are not confined to any particular
17  * thread and form an event loop; it's different in that it skips delays, as all [TestDispatcher]s do.
18  *
19  * Like [Dispatchers.Unconfined], this one does not provide guarantees about the execution order when several coroutines
20  * are queued in this dispatcher. However, we ensure that the [launch] and [async] blocks at the top level of [runTest]
21  * are entered eagerly. This allows launching child coroutines and not calling [runCurrent] for them to start executing.
22  *
23  * ```
24  * @Test
25  * fun testEagerlyEnteringChildCoroutines() = runTest(UnconfinedTestDispatcher()) {
26  *   var entered = false
27  *   val deferred = CompletableDeferred<Unit>()
28  *   var completed = false
29  *   launch {
30  *     entered = true
31  *     deferred.await()
32  *     completed = true
33  *   }
34  *   assertTrue(entered) // `entered = true` already executed.
35  *   assertFalse(completed) // however, the child coroutine then suspended, so it is enqueued.
36  *   deferred.complete(Unit) // resume the coroutine.
37  *   assertTrue(completed) // now the child coroutine is immediately completed.
38  * }
39  * ```
40  *
41  * Using this [TestDispatcher] can greatly simplify writing tests where it's not important which thread is used when and
42  * in which order the queued coroutines are executed.
43  * Another typical use case for this dispatcher is launching child coroutines that are resumed immediately, without
44  * going through a dispatch; this can be helpful for testing [Channel] and [StateFlow] usages.
45  *
46  * ```
47  * @Test
48  * fun testUnconfinedDispatcher() = runTest {
49  *   val values = mutableListOf<Int>()
50  *   val stateFlow = MutableStateFlow(0)
51  *   val job = launch(UnconfinedTestDispatcher(testScheduler)) {
52  *     stateFlow.collect {
53  *       values.add(it)
54  *     }
55  *   }
56  *   stateFlow.value = 1
57  *   stateFlow.value = 2
58  *   stateFlow.value = 3
59  *   job.cancel()
60  *   // each assignment will immediately resume the collecting child coroutine,
61  *   // so no values will be skipped.
62  *   assertEquals(listOf(0, 1, 2, 3), values)
63  * }
64  * ```
65  *
66  * Please be aware that, like [Dispatchers.Unconfined], this is a specific dispatcher with execution order
67  * guarantees that are unusual and not shared by most other dispatchers, so it can only be used reliably for testing
68  * functionality, not the specific order of actions.
69  * See [Dispatchers.Unconfined] for a discussion of the execution order guarantees.
70  *
71  * In order to support delay skipping, this dispatcher is linked to a [TestCoroutineScheduler], which is used to control
72  * the virtual time and can be shared among many test dispatchers.
73  * If no [scheduler] is passed as an argument, [Dispatchers.Main] is checked, and if it was mocked with a
74  * [TestDispatcher] via [Dispatchers.setMain], the [TestDispatcher.scheduler] of the mock dispatcher is used; if
75  * [Dispatchers.Main] is not mocked with a [TestDispatcher], a new [TestCoroutineScheduler] is created.
76  *
77  * Additionally, [name] can be set to distinguish each dispatcher instance when debugging.
78  *
79  * @see StandardTestDispatcher for a more predictable [TestDispatcher].
80  */
81 @ExperimentalCoroutinesApi
82 @Suppress("FunctionName")
UnconfinedTestDispatchernull83 public fun UnconfinedTestDispatcher(
84     scheduler: TestCoroutineScheduler? = null,
85     name: String? = null
86 ): TestDispatcher = UnconfinedTestDispatcherImpl(
87     scheduler ?: TestMainDispatcher.currentTestScheduler ?: TestCoroutineScheduler(), name)
88 
89 private class UnconfinedTestDispatcherImpl(
90     override val scheduler: TestCoroutineScheduler,
91     private val name: String? = null
92 ) : TestDispatcher() {
93 
94     override fun isDispatchNeeded(context: CoroutineContext): Boolean = false
95 
96     @Suppress("INVISIBLE_MEMBER")
97     override fun dispatch(context: CoroutineContext, block: Runnable) {
98         checkSchedulerInContext(scheduler, context)
99         scheduler.sendDispatchEvent(context)
100 
101         /** copy-pasted from [kotlinx.coroutines.Unconfined.dispatch] */
102         /** It can only be called by the [yield] function. See also code of [yield] function. */
103         val yieldContext = context[YieldContext]
104         if (yieldContext !== null) {
105             // report to "yield" that it is an unconfined dispatcher and don't call "block.run()"
106             yieldContext.dispatcherWasUnconfined = true
107             return
108         }
109         throw UnsupportedOperationException(
110             "Function UnconfinedTestCoroutineDispatcher.dispatch can only be used by " +
111                 "the yield function. If you wrap Unconfined dispatcher in your code, make sure you properly delegate " +
112                 "isDispatchNeeded and dispatch calls."
113         )
114     }
115 
116     override fun toString(): String = "${name ?: "UnconfinedTestDispatcher"}[scheduler=$scheduler]"
117 }
118 
119 /**
120  * Creates an instance of a [TestDispatcher] whose tasks are run inside calls to the [scheduler].
121  *
122  * This [TestDispatcher] instance does not itself execute any of the tasks. Instead, it always sends them to its
123  * [scheduler], which can then be accessed via [TestCoroutineScheduler.runCurrent],
124  * [TestCoroutineScheduler.advanceUntilIdle], or [TestCoroutineScheduler.advanceTimeBy], which will then execute these
125  * tasks in a blocking manner.
126  *
127  * In practice, this means that [launch] or [async] blocks will not be entered immediately (unless they are
128  * parameterized with [CoroutineStart.UNDISPATCHED]), and one should either call [TestCoroutineScheduler.runCurrent] to
129  * run these pending tasks, which will block until there are no more tasks scheduled at this point in time, or, when
130  * inside [runTest], call [yield] to yield the (only) thread used by [runTest] to the newly-launched coroutines.
131  *
132  * If no [scheduler] is passed as an argument, [Dispatchers.Main] is checked, and if it was mocked with a
133  * [TestDispatcher] via [Dispatchers.setMain], the [TestDispatcher.scheduler] of the mock dispatcher is used; if
134  * [Dispatchers.Main] is not mocked with a [TestDispatcher], a new [TestCoroutineScheduler] is created.
135  *
136  * One can additionally pass a [name] in order to more easily distinguish this dispatcher during debugging.
137  *
138  * @see UnconfinedTestDispatcher for a dispatcher that is not confined to any particular thread.
139  */
140 @Suppress("FunctionName")
StandardTestDispatchernull141 public fun StandardTestDispatcher(
142     scheduler: TestCoroutineScheduler? = null,
143     name: String? = null
144 ): TestDispatcher = StandardTestDispatcherImpl(
145     scheduler ?: TestMainDispatcher.currentTestScheduler ?: TestCoroutineScheduler(), name)
146 
147 private class StandardTestDispatcherImpl(
148     override val scheduler: TestCoroutineScheduler = TestCoroutineScheduler(),
149     private val name: String? = null
150 ) : TestDispatcher() {
151 
152     override fun dispatch(context: CoroutineContext, block: Runnable) {
153         scheduler.registerEvent(this, 0, block, context) { false }
154     }
155 
156     override fun toString(): String = "${name ?: "StandardTestDispatcher"}[scheduler=$scheduler]"
157 }
158