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 java.util.concurrent.* 17 import kotlin.test.* 18 19 @RunWith(RobolectricTestRunner::class) 20 @Config(manifest = Config.NONE, sdk = [28]) 21 @LooperMode(LooperMode.Mode.LEGACY) 22 class HandlerDispatcherAsyncTest : TestBase() { 23 24 /** 25 * Because [Dispatchers.Main] is a singleton, we cannot vary its initialization behavior. As a 26 * result we only test its behavior on the newest API level and assert that it uses async 27 * messages. We rely on the other tests to exercise the variance of the mechanism that the main 28 * dispatcher uses to ensure it has correct behavior on all API levels. 29 */ 30 @Test <lambda>null31 fun mainIsAsync() = runTest { 32 ReflectionHelpers.setStaticField(Build.VERSION::class.java, "SDK_INT", 28) 33 34 val mainLooper = shadowOf(Looper.getMainLooper()) 35 mainLooper.pause() 36 val mainMessageQueue = shadowOf(Looper.getMainLooper().queue) 37 38 val job = launch(Dispatchers.Main) { 39 expect(2) 40 } 41 42 val message = mainMessageQueue.head 43 assertTrue(message.isAsynchronous) 44 job.join(mainLooper) 45 } 46 47 @Test <lambda>null48 fun asyncMessagesApi14() = runTest { 49 ReflectionHelpers.setStaticField(Build.VERSION::class.java, "SDK_INT", 14) 50 51 val main = Looper.getMainLooper().asHandler(async = true).asCoroutineDispatcher() 52 53 val mainLooper = shadowOf(Looper.getMainLooper()) 54 mainLooper.pause() 55 val mainMessageQueue = shadowOf(Looper.getMainLooper().queue) 56 57 val job = launch(main) { 58 expect(2) 59 } 60 61 val message = mainMessageQueue.head 62 assertFalse(message.isAsynchronous) 63 job.join(mainLooper) 64 } 65 66 @Test <lambda>null67 fun asyncMessagesApi16() = runTest { 68 ReflectionHelpers.setStaticField(Build.VERSION::class.java, "SDK_INT", 16) 69 70 val main = Looper.getMainLooper().asHandler(async = true).asCoroutineDispatcher() 71 72 val mainLooper = shadowOf(Looper.getMainLooper()) 73 mainLooper.pause() 74 val mainMessageQueue = shadowOf(Looper.getMainLooper().queue) 75 76 val job = launch(main) { 77 expect(2) 78 } 79 80 val message = mainMessageQueue.head 81 assertTrue(message.isAsynchronous) 82 job.join(mainLooper) 83 } 84 85 @Test <lambda>null86 fun asyncMessagesApi28() = runTest { 87 ReflectionHelpers.setStaticField(Build.VERSION::class.java, "SDK_INT", 28) 88 89 val main = Looper.getMainLooper().asHandler(async = true).asCoroutineDispatcher() 90 91 val mainLooper = shadowOf(Looper.getMainLooper()) 92 mainLooper.pause() 93 val mainMessageQueue = shadowOf(Looper.getMainLooper().queue) 94 95 val job = launch(main) { 96 expect(2) 97 } 98 99 val message = mainMessageQueue.head 100 assertTrue(message.isAsynchronous) 101 job.join(mainLooper) 102 } 103 104 @Test <lambda>null105 fun noAsyncMessagesIfNotRequested() = runTest { 106 ReflectionHelpers.setStaticField(Build.VERSION::class.java, "SDK_INT", 28) 107 108 val main = Looper.getMainLooper().asHandler(async = false).asCoroutineDispatcher() 109 110 val mainLooper = shadowOf(Looper.getMainLooper()) 111 mainLooper.pause() 112 val mainMessageQueue = shadowOf(Looper.getMainLooper().queue) 113 114 val job = launch(main) { 115 expect(2) 116 } 117 118 val message = mainMessageQueue.head 119 assertFalse(message.isAsynchronous) 120 job.join(mainLooper) 121 } 122 123 @Test testToStringnull124 fun testToString() { 125 ReflectionHelpers.setStaticField(Build.VERSION::class.java, "SDK_INT", 28) 126 val main = Looper.getMainLooper().asHandler(async = true).asCoroutineDispatcher("testName") 127 assertEquals("testName", main.toString()) 128 assertEquals("testName.immediate", main.immediate.toString()) 129 assertEquals("testName.immediate", main.immediate.immediate.toString()) 130 } 131 joinnull132 private suspend fun Job.join(mainLooper: ShadowLooper) { 133 expect(1) 134 mainLooper.unPause() 135 join() 136 finish(3) 137 } 138 139 // TODO compile against API 23+ so this can be invoked without reflection. 140 private val Looper.queue: MessageQueue 141 get() = Looper::class.java.getDeclaredMethod("getQueue").invoke(this) as MessageQueue 142 143 // TODO compile against API 22+ so this can be invoked without reflection. 144 private val Message.isAsynchronous: Boolean 145 get() = Message::class.java.getDeclaredMethod("isAsynchronous").invoke(this) as Boolean 146 } 147