Skip to content

Commit b08a516

Browse files
committed
WIP: unify the tests for all Main dispatchers
1 parent c5d1e5e commit b08a516

File tree

6 files changed

+207
-179
lines changed

6 files changed

+207
-179
lines changed
Lines changed: 148 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,148 @@
1+
/*
2+
* Copyright 2016-2023 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
3+
*/
4+
5+
package kotlinx.coroutines
6+
7+
import kotlin.test.*
8+
9+
abstract class MainDispatcherTestBase: TestBase() {
10+
11+
open fun shouldSkipTesting(): Boolean = false
12+
13+
open fun checkIsMainThread() {}
14+
15+
open fun checkNotMainThread() {}
16+
17+
/** Runs the given block as a test, unless [shouldSkipTesting] indicates that the environment is not suitable. */
18+
fun runTestOrSkip(block: suspend CoroutineScope.() -> Unit) {
19+
if (shouldSkipTesting()) return
20+
runTest(block = block)
21+
}
22+
23+
/** Tests the [toString] behavior of [Dispatchers.Main] and [MainCoroutineDispatcher.immediate] */
24+
@Test
25+
fun testMainDispatcherToString() {
26+
// TODO: workaround for not having module-info.java for the `test` source set
27+
if (Dispatchers.Main.toString() != "Dispatchers.Main") {
28+
throw AssertionError("Expected Dispatchers.Main, but had ${Dispatchers.Main}")
29+
}
30+
if (Dispatchers.Main.immediate.toString() != "Dispatchers.Main.immediate") {
31+
throw AssertionError("Expected Dispatchers.Main.immediate, but had ${Dispatchers.Main.immediate}")
32+
}
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+
// TODO: workaround for not having module-info.java for the `test` source set
55+
if (Dispatchers.Main == Dispatchers.Main.immediate) {
56+
throw AssertionError("Expected Dispatchers.Main and Dispatchers.Main.immediate not to be equal")
57+
}
58+
}
59+
60+
/** Tests that after a delay, the execution gets back to the main thread. */
61+
@Test
62+
@Ignore // TODO: hangs on Android
63+
fun testDelay() = runTestOrSkip {
64+
expect(1)
65+
withContext(Dispatchers.Main) {
66+
checkIsMainThread()
67+
expect(2)
68+
delay(100)
69+
checkIsMainThread()
70+
expect(3)
71+
}
72+
finish(4)
73+
}
74+
75+
/** Tests that [Dispatchers.Main] shares its queue with [MainCoroutineDispatcher.immediate]. */
76+
@Test
77+
fun testImmediateDispatcherYield() = runTestOrSkip {
78+
withContext(Dispatchers.Main) {
79+
expect(1)
80+
checkIsMainThread()
81+
// launch in the immediate dispatcher
82+
launch(Dispatchers.Main.immediate) {
83+
expect(2)
84+
yield()
85+
expect(4)
86+
}
87+
expect(3) // after yield
88+
yield() // yield back
89+
finish(5)
90+
}
91+
}
92+
93+
/** Tests that launching a coroutine in [MainScope] will execute it in the main thread. */
94+
@Test
95+
fun testLaunchInMainScope() = runTestOrSkip {
96+
var executed = false
97+
withMainScope {
98+
launch {
99+
checkIsMainThread()
100+
executed = true
101+
}.join()
102+
if (!executed) throw AssertionError("Should be executed")
103+
}
104+
}
105+
106+
/** Tests that a failure in [MainScope] will not propagate upwards. */
107+
@Test
108+
fun testFailureInMainScope() = runTestOrSkip {
109+
var exception: Throwable? = null
110+
withMainScope {
111+
launch(CoroutineExceptionHandler { ctx, e -> exception = e }) {
112+
checkIsMainThread()
113+
throw TestException()
114+
}.join()
115+
}
116+
if (exception!! !is TestException) throw AssertionError("Expected TestException, but had $exception")
117+
}
118+
119+
/** Tests cancellation in [MainScope]. */
120+
@Test
121+
fun testCancellationInMainScope() = runTestOrSkip {
122+
withMainScope {
123+
cancel()
124+
launch(start = CoroutineStart.ATOMIC) {
125+
checkIsMainThread()
126+
delay(Long.MAX_VALUE)
127+
}.join()
128+
}
129+
}
130+
131+
private suspend fun <R> withMainScope(block: suspend CoroutineScope.() -> R): R {
132+
MainScope().apply {
133+
return block().also { coroutineContext[Job]!!.cancelAndJoin() }
134+
}
135+
}
136+
137+
private suspend fun testMainDispatcherOrdering() {
138+
withContext(Dispatchers.Main.immediate) {
139+
expect(1)
140+
launch(Dispatchers.Main) {
141+
expect(2)
142+
}
143+
withContext(Dispatchers.Main) {
144+
finish(3)
145+
}
146+
}
147+
}
148+
}

kotlinx-coroutines-core/js/test/ImmediateDispatcherTest.kt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ package kotlinx.coroutines
66

77
import kotlin.test.*
88

9-
class ImmediateDispatcherTest : TestBase() {
9+
class ImmediateDispatcherTest : MainDispatcherTestBase(Dispatchers.Main) {
1010

1111
@Test
1212
fun testImmediate() = runTest {

kotlinx-coroutines-core/nativeDarwin/test/MainDispatcherTest.kt

Lines changed: 31 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -9,19 +9,19 @@ import platform.darwin.*
99
import kotlin.coroutines.*
1010
import kotlin.test.*
1111

12-
class MainDispatcherTest : TestBase() {
12+
class MainDispatcherTest : MainDispatcherTestBase() {
1313

1414
private fun isMainThread(): Boolean = CFRunLoopGetCurrent() == CFRunLoopGetMain()
15-
private fun canTestMainDispatcher() = !isMainThread()
1615

17-
private fun runTestNotOnMainDispatcher(block: suspend CoroutineScope.() -> Unit) {
18-
// skip if already on the main thread, run blocking doesn't really work well with that
19-
if (!canTestMainDispatcher()) return
20-
runTest(block = block)
21-
}
16+
override fun checkIsMainThread() = assertTrue(isMainThread())
17+
18+
override fun checkNotMainThread() = assertFalse(isMainThread())
19+
20+
// skip if already on the main thread, run blocking doesn't really work well with that
21+
override fun shouldSkipTesting(): Boolean = isMainThread()
2222

2323
@Test
24-
fun testDispatchNecessityCheckWithMainImmediateDispatcher() = runTestNotOnMainDispatcher {
24+
fun testDispatchNecessityCheckWithMainImmediateDispatcher() = runTestOrSkip {
2525
val main = Dispatchers.Main.immediate
2626
assertTrue(main.isDispatchNeeded(EmptyCoroutineContext))
2727
withContext(Dispatchers.Default) {
@@ -34,96 +34,96 @@ class MainDispatcherTest : TestBase() {
3434
}
3535

3636
@Test
37-
fun testWithContext() = runTestNotOnMainDispatcher {
37+
fun testWithContext() = runTestOrSkip {
3838
expect(1)
39-
assertFalse(isMainThread())
39+
checkNotMainThread()
4040
withContext(Dispatchers.Main) {
41-
assertTrue(isMainThread())
41+
checkIsMainThread()
4242
expect(2)
4343
}
44-
assertFalse(isMainThread())
44+
checkNotMainThread()
4545
finish(3)
4646
}
4747

4848
@Test
49-
fun testWithContextDelay() = runTestNotOnMainDispatcher {
49+
fun testWithContextDelay() = runTestOrSkip {
5050
expect(1)
5151
withContext(Dispatchers.Main) {
52-
assertTrue(isMainThread())
52+
checkIsMainThread()
5353
expect(2)
5454
delay(100)
55-
assertTrue(isMainThread())
55+
checkIsMainThread()
5656
expect(3)
5757
}
58-
assertFalse(isMainThread())
58+
checkNotMainThread()
5959
finish(4)
6060
}
6161

6262
@Test
63-
fun testWithTimeoutContextDelayNoTimeout() = runTestNotOnMainDispatcher {
63+
fun testWithTimeoutContextDelayNoTimeout() = runTestOrSkip {
6464
expect(1)
6565
withTimeout(1000) {
6666
withContext(Dispatchers.Main) {
67-
assertTrue(isMainThread())
67+
checkIsMainThread()
6868
expect(2)
6969
delay(100)
70-
assertTrue(isMainThread())
70+
checkIsMainThread()
7171
expect(3)
7272
}
7373
}
74-
assertFalse(isMainThread())
74+
checkNotMainThread()
7575
finish(4)
7676
}
7777

7878
@Test
79-
fun testWithTimeoutContextDelayTimeout() = runTestNotOnMainDispatcher {
79+
fun testWithTimeoutContextDelayTimeout() = runTestOrSkip {
8080
expect(1)
81-
assertFailsWith<TimeoutCancellationException> {
81+
assertFailsWith<TimeoutCancellationException> {
8282
withTimeout(100) {
8383
withContext(Dispatchers.Main) {
84-
assertTrue(isMainThread())
84+
checkIsMainThread()
8585
expect(2)
8686
delay(1000)
8787
expectUnreached()
8888
}
8989
}
9090
expectUnreached()
9191
}
92-
assertFalse(isMainThread())
92+
checkNotMainThread()
9393
finish(3)
9494
}
9595

9696
@Test
97-
fun testWithContextTimeoutDelayNoTimeout() = runTestNotOnMainDispatcher {
97+
fun testWithContextTimeoutDelayNoTimeout() = runTestOrSkip {
9898
expect(1)
9999
withContext(Dispatchers.Main) {
100100
withTimeout(1000) {
101-
assertTrue(isMainThread())
101+
checkIsMainThread()
102102
expect(2)
103103
delay(100)
104-
assertTrue(isMainThread())
104+
checkIsMainThread()
105105
expect(3)
106106
}
107107
}
108-
assertFalse(isMainThread())
108+
checkNotMainThread()
109109
finish(4)
110110
}
111111

112112
@Test
113-
fun testWithContextTimeoutDelayTimeout() = runTestNotOnMainDispatcher {
113+
fun testWithContextTimeoutDelayTimeout() = runTestOrSkip {
114114
expect(1)
115115
assertFailsWith<TimeoutCancellationException> {
116116
withContext(Dispatchers.Main) {
117117
withTimeout(100) {
118-
assertTrue(isMainThread())
118+
checkIsMainThread()
119119
expect(2)
120120
delay(1000)
121121
expectUnreached()
122122
}
123123
}
124124
expectUnreached()
125125
}
126-
assertFalse(isMainThread())
126+
checkNotMainThread()
127127
finish(3)
128128
}
129129
}

ui/kotlinx-coroutines-android/test/HandlerDispatcherTest.kt

Lines changed: 1 addition & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -17,27 +17,7 @@ import kotlin.test.*
1717
@RunWith(RobolectricTestRunner::class)
1818
@Config(manifest = Config.NONE, sdk = [28])
1919
@LooperMode(LooperMode.Mode.LEGACY)
20-
class HandlerDispatcherTest : TestBase() {
21-
@Test
22-
fun testImmediateDispatcherYield() = runBlocking(Dispatchers.Main) {
23-
expect(1)
24-
// launch in the immediate dispatcher
25-
launch(Dispatchers.Main.immediate) {
26-
expect(2)
27-
yield()
28-
expect(4)
29-
}
30-
expect(3) // after yield
31-
yield() // yield back
32-
finish(5)
33-
}
34-
35-
@Test
36-
fun testMainDispatcherToString() {
37-
assertEquals("Dispatchers.Main", Dispatchers.Main.toString())
38-
assertEquals("Dispatchers.Main.immediate", Dispatchers.Main.immediate.toString())
39-
}
40-
20+
class HandlerDispatcherTest : MainDispatcherTestBase() {
4121
@Test
4222
fun testDefaultDelayIsNotDelegatedToMain() = runTest {
4323
val mainLooper = Shadows.shadowOf(Looper.getMainLooper())
@@ -132,11 +112,4 @@ class HandlerDispatcherTest : TestBase() {
132112
mainLooper.scheduler.advanceBy(51, TimeUnit.MILLISECONDS)
133113
finish(5)
134114
}
135-
136-
@Test
137-
fun testHandlerDispatcherNotEqualToImmediate() {
138-
val main = AndroidDispatcherFactory().createDispatcher(listOf())
139-
val immediate = main.immediate
140-
assertNotEquals(main, immediate)
141-
}
142115
}

0 commit comments

Comments
 (0)