1 /* 2 * Copyright 2019 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 package androidx.lifecycle 18 19 import androidx.lifecycle.testing.TestLifecycleOwner 20 import com.google.common.truth.Truth.assertThat 21 import java.util.concurrent.CancellationException 22 import kotlinx.coroutines.Dispatchers 23 import kotlinx.coroutines.ExperimentalCoroutinesApi 24 import kotlinx.coroutines.async 25 import kotlinx.coroutines.delay 26 import kotlinx.coroutines.launch 27 import kotlinx.coroutines.runBlocking 28 import kotlinx.coroutines.sync.Mutex 29 import kotlinx.coroutines.test.UnconfinedTestDispatcher 30 import kotlinx.coroutines.withContext 31 import org.junit.Test 32 33 @OptIn(ExperimentalCoroutinesApi::class) 34 abstract class LifecycleCoroutineScopeTestBase { 35 @Test initializationnull36 fun initialization() { 37 val owner = TestLifecycleOwner(Lifecycle.State.INITIALIZED, UnconfinedTestDispatcher()) 38 val scope = owner.lifecycleScope 39 assertThat(owner.lifecycle.internalScopeRef.get()).isSameInstanceAs(scope) 40 val scope2 = owner.lifecycleScope 41 assertThat(scope).isSameInstanceAs(scope2) 42 runBlocking(Dispatchers.Main) { assertThat(owner.observerCount).isEqualTo(1) } 43 } 44 45 @Test simpleLaunchnull46 fun simpleLaunch() { 47 val owner = TestLifecycleOwner(Lifecycle.State.INITIALIZED, UnconfinedTestDispatcher()) 48 assertThat( 49 runBlocking { 50 owner.lifecycleScope 51 .async { 52 // do nothing 53 true 54 } 55 .await() 56 } 57 ) 58 .isTrue() 59 } 60 61 @Test launchAfterDestroynull62 fun launchAfterDestroy() { 63 val owner = TestLifecycleOwner(Lifecycle.State.CREATED, UnconfinedTestDispatcher()) 64 owner.lifecycle.currentState = Lifecycle.State.DESTROYED 65 runBlocking { 66 owner.lifecycleScope 67 .launch { 68 // do nothing 69 throw AssertionError("should not run") 70 } 71 .join() 72 } 73 } 74 75 @Test launchOnMainnull76 fun launchOnMain() { 77 val owner = TestLifecycleOwner(Lifecycle.State.STARTED, UnconfinedTestDispatcher()) 78 assertThat(runBlocking(Dispatchers.Main) { owner.lifecycleScope.async { true }.await() }) 79 .isTrue() 80 } 81 82 @Test launchOnIOnull83 fun launchOnIO() { 84 val owner = TestLifecycleOwner(Lifecycle.State.STARTED, UnconfinedTestDispatcher()) 85 assertThat(runBlocking(Dispatchers.IO) { owner.lifecycleScope.async { true }.await() }) 86 .isTrue() 87 } 88 89 @Test destroyWhileRunningnull90 fun destroyWhileRunning() { 91 val startMutex = Mutex(locked = true) 92 val alwaysLocked = Mutex(locked = true) 93 val owner = TestLifecycleOwner(Lifecycle.State.STARTED, UnconfinedTestDispatcher()) 94 val actionWasActive = 95 owner.lifecycleScope.async(Dispatchers.IO) { 96 startMutex.unlock() 97 alwaysLocked.lock() // wait 4ever 98 } 99 runBlocking(Dispatchers.Main) { 100 startMutex.lock() // wait until it starts 101 owner.currentState = Lifecycle.State.DESTROYED 102 actionWasActive.join() 103 assertThat(actionWasActive.isCancelled).isTrue() 104 } 105 } 106 107 @Test throwExceptionnull108 fun throwException() { 109 val owner = TestLifecycleOwner(Lifecycle.State.STARTED, UnconfinedTestDispatcher()) 110 runBlocking { 111 val action = owner.lifecycleScope.async { throw RuntimeException("foo") } 112 action.join() 113 assertThat(action.getCompletionExceptionOrNull()) 114 .hasMessageThat() 115 .isSameInstanceAs("foo") 116 } 117 } 118 119 @Test throwException_onStartnull120 fun throwException_onStart() { 121 val owner = TestLifecycleOwner(Lifecycle.State.CREATED, UnconfinedTestDispatcher()) 122 runBlocking { 123 // TODO guarantee later execution 124 val action = owner.lifecycleScope.async { throw RuntimeException("foo") } 125 withContext(Dispatchers.Main) { owner.currentState = Lifecycle.State.STARTED } 126 action.join() 127 assertThat(action.getCompletionExceptionOrNull()) 128 .hasMessageThat() 129 .isSameInstanceAs("foo") 130 } 131 } 132 133 @Test runAnotherAfterCancellation_cancelOutsidenull134 fun runAnotherAfterCancellation_cancelOutside() { 135 val owner = TestLifecycleOwner(Lifecycle.State.STARTED, UnconfinedTestDispatcher()) 136 runBlocking { 137 val action = owner.lifecycleScope.async { delay(20000) } 138 action.cancel() 139 action.join() 140 } 141 assertThat(runBlocking { owner.lifecycleScope.async { true }.await() }).isTrue() 142 } 143 144 @Test runAnotherAfterCancellation_cancelInsidenull145 fun runAnotherAfterCancellation_cancelInside() { 146 val owner = TestLifecycleOwner(Lifecycle.State.STARTED, UnconfinedTestDispatcher()) 147 runBlocking { 148 val action = owner.lifecycleScope.async { throw CancellationException("") } 149 action.join() 150 } 151 assertThat(runBlocking { owner.lifecycleScope.async { true }.await() }).isTrue() 152 } 153 154 @Test runAnotherAfterFailurenull155 fun runAnotherAfterFailure() { 156 val owner = TestLifecycleOwner(Lifecycle.State.STARTED, UnconfinedTestDispatcher()) 157 runBlocking { 158 val action = owner.lifecycleScope.async { throw IllegalArgumentException("why not ?") } 159 val result = kotlin.runCatching { action.await() } 160 assertThat(result.exceptionOrNull()).isInstanceOf(IllegalArgumentException::class.java) 161 } 162 assertThat(runBlocking { owner.lifecycleScope.async { true }.await() }).isTrue() 163 } 164 } 165