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