<lambda>null1 package kotlinx.coroutines.testing
2
3 import kotlinx.coroutines.*
4 import kotlin.test.*
5
6 abstract class MainDispatcherTestBase: TestBase() {
7
8 open fun shouldSkipTesting(): Boolean = false
9
10 open suspend fun spinTest(testBody: Job) {
11 testBody.join()
12 }
13
14 abstract fun isMainThread(): Boolean?
15
16 /** Runs the given block as a test, unless [shouldSkipTesting] indicates that the environment is not suitable. */
17 fun runTestOrSkip(block: suspend CoroutineScope.() -> Unit): TestResult {
18 // written as a block body to make the need to return `TestResult` explicit
19 return runTest {
20 if (shouldSkipTesting()) return@runTest
21 val testBody = launch(Dispatchers.Default) {
22 block()
23 }
24 spinTest(testBody)
25 }
26 }
27
28 /** Tests the [toString] behavior of [Dispatchers.Main] and [MainCoroutineDispatcher.immediate] */
29 @Test
30 fun testMainDispatcherToString() {
31 assertEquals("Dispatchers.Main", Dispatchers.Main.toString())
32 assertEquals("Dispatchers.Main.immediate", Dispatchers.Main.immediate.toString())
33 }
34
35 /** Tests that the tasks scheduled earlier from [MainCoroutineDispatcher.immediate] will be executed earlier,
36 * even if the immediate dispatcher was entered from the main thread. */
37 @Test
38 fun testMainDispatcherOrderingInMainThread() = runTestOrSkip {
39 withContext(Dispatchers.Main) {
40 testMainDispatcherOrdering()
41 }
42 }
43
44 /** Tests that the tasks scheduled earlier from [MainCoroutineDispatcher.immediate] will be executed earlier
45 * if the immediate dispatcher was entered from outside the main thread. */
46 @Test
47 fun testMainDispatcherOrderingOutsideMainThread() = runTestOrSkip {
48 testMainDispatcherOrdering()
49 }
50
51 /** Tests that [Dispatchers.Main] and its [MainCoroutineDispatcher.immediate] are treated as different values. */
52 @Test
53 fun testHandlerDispatcherNotEqualToImmediate() {
54 assertNotEquals(Dispatchers.Main, Dispatchers.Main.immediate)
55 }
56
57 /** Tests that [Dispatchers.Main] shares its queue with [MainCoroutineDispatcher.immediate]. */
58 @Test
59 fun testImmediateDispatcherYield() = runTestOrSkip {
60 withContext(Dispatchers.Main) {
61 expect(1)
62 checkIsMainThread()
63 // launch in the immediate dispatcher
64 launch(Dispatchers.Main.immediate) {
65 expect(2)
66 yield()
67 expect(4)
68 }
69 expect(3) // after yield
70 yield() // yield back
71 expect(5)
72 }
73 finish(6)
74 }
75
76 /** Tests that entering [MainCoroutineDispatcher.immediate] from [Dispatchers.Main] happens immediately. */
77 @Test
78 fun testEnteringImmediateFromMain() = runTestOrSkip {
79 withContext(Dispatchers.Main) {
80 expect(1)
81 val job = launch { expect(3) }
82 withContext(Dispatchers.Main.immediate) {
83 expect(2)
84 }
85 job.join()
86 }
87 finish(4)
88 }
89
90 /** Tests that dispatching to [MainCoroutineDispatcher.immediate] is required from and only from dispatchers
91 * other than the main dispatchers and that it's always required for [Dispatchers.Main] itself. */
92 @Test
93 fun testDispatchRequirements() = runTestOrSkip {
94 checkDispatchRequirements()
95 withContext(Dispatchers.Main) {
96 checkDispatchRequirements()
97 withContext(Dispatchers.Main.immediate) {
98 checkDispatchRequirements()
99 }
100 checkDispatchRequirements()
101 }
102 checkDispatchRequirements()
103 }
104
105 private suspend fun checkDispatchRequirements() {
106 isMainThread()?.let {
107 assertNotEquals(
108 it,
109 Dispatchers.Main.immediate.isDispatchNeeded(currentCoroutineContext())
110 )
111 }
112 assertTrue(Dispatchers.Main.isDispatchNeeded(currentCoroutineContext()))
113 assertTrue(Dispatchers.Default.isDispatchNeeded(currentCoroutineContext()))
114 }
115
116 /** Tests that launching a coroutine in [MainScope] will execute it in the main thread. */
117 @Test
118 fun testLaunchInMainScope() = runTestOrSkip {
119 var executed = false
120 withMainScope {
121 launch {
122 checkIsMainThread()
123 executed = true
124 }.join()
125 if (!executed) throw AssertionError("Should be executed")
126 }
127 }
128
129 /** Tests that a failure in [MainScope] will not propagate upwards. */
130 @Test
131 fun testFailureInMainScope() = runTestOrSkip {
132 var exception: Throwable? = null
133 withMainScope {
134 launch(CoroutineExceptionHandler { ctx, e -> exception = e }) {
135 checkIsMainThread()
136 throw TestException()
137 }.join()
138 }
139 if (exception!! !is TestException) throw AssertionError("Expected TestException, but had $exception")
140 }
141
142 /** Tests cancellation in [MainScope]. */
143 @Test
144 fun testCancellationInMainScope() = runTestOrSkip {
145 withMainScope {
146 cancel()
147 launch(start = CoroutineStart.ATOMIC) {
148 checkIsMainThread()
149 delay(Long.MAX_VALUE)
150 }.join()
151 }
152 }
153
154 private suspend fun <R> withMainScope(block: suspend CoroutineScope.() -> R): R {
155 MainScope().apply {
156 return block().also { coroutineContext[Job]!!.cancelAndJoin() }
157 }
158 }
159
160 private suspend fun testMainDispatcherOrdering() {
161 withContext(Dispatchers.Main.immediate) {
162 expect(1)
163 launch(Dispatchers.Main) {
164 expect(2)
165 }
166 withContext(Dispatchers.Main) {
167 finish(3)
168 }
169 }
170 }
171
172 abstract class WithRealTimeDelay : MainDispatcherTestBase() {
173 abstract fun scheduleOnMainQueue(block: () -> Unit)
174
175 /** Tests that after a delay, the execution gets back to the main thread. */
176 @Test
177 fun testDelay() = runTestOrSkip {
178 expect(1)
179 checkNotMainThread()
180 scheduleOnMainQueue { expect(2) }
181 withContext(Dispatchers.Main) {
182 checkIsMainThread()
183 expect(3)
184 scheduleOnMainQueue { expect(4) }
185 delay(100)
186 checkIsMainThread()
187 expect(5)
188 }
189 checkNotMainThread()
190 finish(6)
191 }
192
193 /** Tests that [Dispatchers.Main] is in agreement with the default time source: it's not much slower. */
194 @Test
195 fun testWithTimeoutContextDelayNoTimeout() = runTestOrSkip {
196 expect(1)
197 withTimeout(1000) {
198 withContext(Dispatchers.Main) {
199 checkIsMainThread()
200 expect(2)
201 delay(100)
202 checkIsMainThread()
203 expect(3)
204 }
205 }
206 checkNotMainThread()
207 finish(4)
208 }
209
210 /** Tests that [Dispatchers.Main] is in agreement with the default time source: it's not much faster. */
211 @Test
212 fun testWithTimeoutContextDelayTimeout() = runTestOrSkip {
213 expect(1)
214 assertFailsWith<TimeoutCancellationException> {
215 withTimeout(300) {
216 // A substitute for withContext(Dispatcher.Main) that is started even if the 300ms
217 // timeout happens fsater then dispatch
218 launch(Dispatchers.Main, start = CoroutineStart.ATOMIC) {
219 checkIsMainThread()
220 expect(2)
221 delay(1000)
222 expectUnreached()
223 }.join()
224 }
225 expectUnreached()
226 }
227 checkNotMainThread()
228 finish(3)
229 }
230
231 /** Tests that the timeout of [Dispatchers.Main] is in agreement with its [delay]: it's not much faster. */
232 @Test
233 fun testWithContextTimeoutDelayNoTimeout() = runTestOrSkip {
234 expect(1)
235 withContext(Dispatchers.Main) {
236 withTimeout(1000) {
237 checkIsMainThread()
238 expect(2)
239 delay(100)
240 checkIsMainThread()
241 expect(3)
242 }
243 }
244 checkNotMainThread()
245 finish(4)
246 }
247
248 /** Tests that the timeout of [Dispatchers.Main] is in agreement with its [delay]: it's not much slower. */
249 @Test
250 fun testWithContextTimeoutDelayTimeout() = runTestOrSkip {
251 expect(1)
252 assertFailsWith<TimeoutCancellationException> {
253 withContext(Dispatchers.Main) {
254 withTimeout(100) {
255 checkIsMainThread()
256 expect(2)
257 delay(1000)
258 expectUnreached()
259 }
260 }
261 expectUnreached()
262 }
263 checkNotMainThread()
264 finish(3)
265 }
266 }
267
268 fun checkIsMainThread() { isMainThread()?.let { check(it) } }
269 fun checkNotMainThread() { isMainThread()?.let { check(!it) } }
270 }
271