Skip to content

Commit 847eba7

Browse files
committed
Improve HandlerDispatcherTest, check that jobs are actually executed
1 parent 8adbb70 commit 847eba7

File tree

1 file changed

+122
-111
lines changed

1 file changed

+122
-111
lines changed

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

Lines changed: 122 additions & 111 deletions
Original file line numberDiff line numberDiff line change
@@ -4,121 +4,132 @@
44

55
package kotlinx.coroutines.experimental.android
66

7-
import android.os.Build
8-
import android.os.Looper
9-
import android.os.Message
10-
import android.os.MessageQueue
11-
import kotlinx.coroutines.experimental.Dispatchers
12-
import kotlinx.coroutines.experimental.GlobalScope
13-
import kotlinx.coroutines.experimental.launch
7+
import android.os.*
8+
import kotlinx.coroutines.experimental.*
149
import org.junit.Test
15-
import org.junit.runner.RunWith
16-
import org.robolectric.RobolectricTestRunner
17-
import org.robolectric.Shadows.shadowOf
18-
import org.robolectric.annotation.Config
19-
import org.robolectric.shadows.ShadowLooper
20-
import org.robolectric.util.ReflectionHelpers
21-
import kotlin.test.assertFalse
22-
import kotlin.test.assertTrue
10+
import org.junit.runner.*
11+
import org.robolectric.*
12+
import org.robolectric.Shadows.*
13+
import org.robolectric.annotation.*
14+
import org.robolectric.shadows.*
15+
import org.robolectric.util.*
16+
import kotlin.test.*
2317

2418
@RunWith(RobolectricTestRunner::class)
2519
@Config(manifest = Config.NONE, sdk = [28])
26-
class HandlerDispatcherTest {
20+
class HandlerDispatcherTest : TestBase() {
2721

28-
/**
29-
* Because [Dispatchers.Main] is a singleton, we cannot vary its initialization behavior. As a
30-
* result we only test its behavior on the newest API level and assert that it uses async
31-
* messages. We rely on the other tests to exercise the variance of the mechanism that the main
32-
* dispatcher uses to ensure it has correct behavior on all API levels.
33-
*/
34-
@Test fun mainIsAsync() {
35-
ReflectionHelpers.setStaticField(Build.VERSION::class.java, "SDK_INT", 28)
22+
/**
23+
* Because [Dispatchers.Main] is a singleton, we cannot vary its initialization behavior. As a
24+
* result we only test its behavior on the newest API level and assert that it uses async
25+
* messages. We rely on the other tests to exercise the variance of the mechanism that the main
26+
* dispatcher uses to ensure it has correct behavior on all API levels.
27+
*/
28+
@Test
29+
fun mainIsAsync() = runTest {
30+
ReflectionHelpers.setStaticField(Build.VERSION::class.java, "SDK_INT", 28)
31+
32+
val mainLooper = ShadowLooper.getShadowMainLooper()
33+
mainLooper.pause()
34+
val mainMessageQueue = shadowOf(Looper.getMainLooper().queue)
35+
36+
val job = launch(Dispatchers.Main) {
37+
expect(2)
38+
}
39+
40+
val message = mainMessageQueue.head
41+
assertTrue(message.isAsynchronous)
42+
job.join(mainLooper)
43+
}
44+
45+
@Test
46+
fun asyncMessagesApi14() = runTest {
47+
ReflectionHelpers.setStaticField(Build.VERSION::class.java, "SDK_INT", 14)
48+
49+
val main = Looper.getMainLooper().asHandler(async = true).asCoroutineDispatcher()
50+
51+
val mainLooper = ShadowLooper.getShadowMainLooper()
52+
mainLooper.pause()
53+
val mainMessageQueue = shadowOf(Looper.getMainLooper().queue)
54+
55+
val job = launch(main) {
56+
expect(2)
57+
}
58+
59+
val message = mainMessageQueue.head
60+
assertFalse(message.isAsynchronous)
61+
job.join(mainLooper)
62+
}
63+
64+
@Test
65+
fun asyncMessagesApi16() = runTest {
66+
ReflectionHelpers.setStaticField(Build.VERSION::class.java, "SDK_INT", 16)
67+
68+
val main = Looper.getMainLooper().asHandler(async = true).asCoroutineDispatcher()
69+
70+
val mainLooper = ShadowLooper.getShadowMainLooper()
71+
mainLooper.pause()
72+
val mainMessageQueue = shadowOf(Looper.getMainLooper().queue)
73+
74+
val job = launch(main) {
75+
expect(2)
76+
}
77+
78+
val message = mainMessageQueue.head
79+
assertTrue(message.isAsynchronous)
80+
job.join(mainLooper)
81+
}
82+
83+
@Test
84+
fun asyncMessagesApi28() = runTest {
85+
ReflectionHelpers.setStaticField(Build.VERSION::class.java, "SDK_INT", 28)
86+
87+
val main = Looper.getMainLooper().asHandler(async = true).asCoroutineDispatcher()
88+
89+
val mainLooper = ShadowLooper.getShadowMainLooper()
90+
mainLooper.pause()
91+
val mainMessageQueue = shadowOf(Looper.getMainLooper().queue)
92+
93+
val job = launch(main) {
94+
expect(2)
95+
}
96+
97+
val message = mainMessageQueue.head
98+
assertTrue(message.isAsynchronous)
99+
job.join(mainLooper)
100+
}
101+
102+
@Test
103+
fun noAsyncMessagesIfNotRequested() = runTest {
104+
ReflectionHelpers.setStaticField(Build.VERSION::class.java, "SDK_INT", 28)
105+
106+
val main = Looper.getMainLooper().asHandler(async = false).asCoroutineDispatcher()
107+
108+
val mainLooper = ShadowLooper.getShadowMainLooper()
109+
mainLooper.pause()
110+
val mainMessageQueue = shadowOf(Looper.getMainLooper().queue)
111+
112+
val job = launch(main) {
113+
expect(2)
114+
}
115+
116+
val message = mainMessageQueue.head
117+
assertFalse(message.isAsynchronous)
118+
job.join(mainLooper)
119+
}
120+
121+
private suspend fun Job.join(mainLooper: ShadowLooper) {
122+
expect(1)
123+
mainLooper.unPause()
124+
join()
125+
finish(3)
126+
}
127+
128+
// TODO compile against API 23+ so this can be invoked without reflection.
129+
private val Looper.queue: MessageQueue
130+
get() = Looper::class.java.getDeclaredMethod("getQueue").invoke(this) as MessageQueue
36131

37-
val mainLooper = ShadowLooper.getShadowMainLooper()
38-
mainLooper.pause()
39-
val mainMessageQueue = shadowOf(Looper.getMainLooper().queue)
40-
41-
val job = GlobalScope.launch(Dispatchers.Main) {}
42-
43-
val message = mainMessageQueue.head
44-
assertTrue(message.isAsynchronous)
45-
46-
job.cancel()
47-
}
48-
49-
@Test fun asyncMessagesApi14() {
50-
ReflectionHelpers.setStaticField(Build.VERSION::class.java, "SDK_INT", 14)
51-
52-
val main = Looper.getMainLooper().asHandler(async = true).asCoroutineDispatcher()
53-
54-
val mainLooper = ShadowLooper.getShadowMainLooper()
55-
mainLooper.pause()
56-
val mainMessageQueue = shadowOf(Looper.getMainLooper().queue)
57-
58-
val job = GlobalScope.launch(main) {}
59-
60-
val message = mainMessageQueue.head
61-
assertFalse(message.isAsynchronous)
62-
63-
job.cancel()
64-
}
65-
66-
@Test fun asyncMessagesApi16() {
67-
ReflectionHelpers.setStaticField(Build.VERSION::class.java, "SDK_INT", 16)
68-
69-
val main = Looper.getMainLooper().asHandler(async = true).asCoroutineDispatcher()
70-
71-
val mainLooper = ShadowLooper.getShadowMainLooper()
72-
mainLooper.pause()
73-
val mainMessageQueue = shadowOf(Looper.getMainLooper().queue)
74-
75-
val job = GlobalScope.launch(main) {}
76-
77-
val message = mainMessageQueue.head
78-
assertTrue(message.isAsynchronous)
79-
80-
job.cancel()
81-
}
82-
83-
@Test fun asyncMessagesApi28() {
84-
ReflectionHelpers.setStaticField(Build.VERSION::class.java, "SDK_INT", 28)
85-
86-
val main = Looper.getMainLooper().asHandler(async = true).asCoroutineDispatcher()
87-
88-
val mainLooper = ShadowLooper.getShadowMainLooper()
89-
mainLooper.pause()
90-
val mainMessageQueue = shadowOf(Looper.getMainLooper().queue)
91-
92-
val job = GlobalScope.launch(main) {}
93-
94-
val message = mainMessageQueue.head
95-
assertTrue(message.isAsynchronous)
96-
97-
job.cancel()
98-
}
99-
100-
@Test fun noAsyncMessagesIfNotRequested() {
101-
ReflectionHelpers.setStaticField(Build.VERSION::class.java, "SDK_INT", 28)
102-
103-
val main = Looper.getMainLooper().asHandler(async = false).asCoroutineDispatcher()
104-
105-
val mainLooper = ShadowLooper.getShadowMainLooper()
106-
mainLooper.pause()
107-
val mainMessageQueue = shadowOf(Looper.getMainLooper().queue)
108-
109-
val job = GlobalScope.launch(main) {}
110-
111-
val message = mainMessageQueue.head
112-
assertFalse(message.isAsynchronous)
113-
114-
job.cancel()
115-
}
116-
117-
// TODO compile against API 23+ so this can be invoked without reflection.
118-
private val Looper.queue: MessageQueue
119-
get() = Looper::class.java.getDeclaredMethod("getQueue").invoke(this) as MessageQueue
120-
121-
// TODO compile against API 22+ so this can be invoked without reflection.
122-
private val Message.isAsynchronous: Boolean
123-
get() = Message::class.java.getDeclaredMethod("isAsynchronous").invoke(this) as Boolean
132+
// TODO compile against API 22+ so this can be invoked without reflection.
133+
private val Message.isAsynchronous: Boolean
134+
get() = Message::class.java.getDeclaredMethod("isAsynchronous").invoke(this) as Boolean
124135
}

0 commit comments

Comments
 (0)