1 /*
2  * Copyright 2016-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
3  */
4 
5 package kotlinx.coroutines.android
6 
7 import android.os.*
8 import kotlinx.coroutines.*
9 import org.junit.Test
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.*
17 
18 @RunWith(RobolectricTestRunner::class)
19 @Config(manifest = Config.NONE, sdk = [28])
20 class HandlerDispatcherTest : TestBase() {
21 
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
<lambda>null29     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
<lambda>null46     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
<lambda>null65     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
<lambda>null84     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
<lambda>null103     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     @Test
testToStringnull122     fun testToString() {
123         ReflectionHelpers.setStaticField(Build.VERSION::class.java, "SDK_INT", 28)
124         val main = Looper.getMainLooper().asHandler(async = true).asCoroutineDispatcher("testName")
125         assertEquals("testName", main.toString())
126         assertEquals("testName.immediate", main.immediate.toString())
127         assertEquals("testName.immediate", main.immediate.immediate.toString())
128     }
129 
joinnull130     private suspend fun Job.join(mainLooper: ShadowLooper) {
131         expect(1)
132         mainLooper.unPause()
133         join()
134         finish(3)
135     }
136 
137     // TODO compile against API 23+ so this can be invoked without reflection.
138     private val Looper.queue: MessageQueue
139         get() = Looper::class.java.getDeclaredMethod("getQueue").invoke(this) as MessageQueue
140 
141     // TODO compile against API 22+ so this can be invoked without reflection.
142     private val Message.isAsynchronous: Boolean
143         get() = Message::class.java.getDeclaredMethod("isAsynchronous").invoke(this) as Boolean
144 
145     @Test
<lambda>null146     fun testImmediateDispatcherYield() = runBlocking(Dispatchers.Main) {
147         expect(1)
148         // launch in the immediate dispatcher
149         launch(Dispatchers.Main.immediate) {
150             expect(2)
151             yield()
152             expect(4)
153         }
154         expect(3) // after yield
155         yield() // yield back
156         finish(5)
157     }
158 
159     @Test
testMainDispatcherToStringnull160     fun testMainDispatcherToString() {
161         assertEquals("Dispatchers.Main", Dispatchers.Main.toString())
162         assertEquals("Dispatchers.Main.immediate", Dispatchers.Main.immediate.toString())
163     }
164 }
165