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