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