1 /*
2  * Copyright 2023 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 @file:OptIn(kotlinx.coroutines.ExperimentalCoroutinesApi::class)
18 
19 package androidx.lifecycle.util
20 
21 import androidx.arch.core.executor.ArchTaskExecutor
22 import androidx.arch.core.executor.TaskExecutor
23 import androidx.lifecycle.LiveData
24 import androidx.lifecycle.Observer
25 import com.google.common.truth.Truth
26 import kotlinx.coroutines.Dispatchers
27 import kotlinx.coroutines.ExperimentalCoroutinesApi
28 import kotlinx.coroutines.async
29 import kotlinx.coroutines.launch
30 import kotlinx.coroutines.runBlocking
31 import kotlinx.coroutines.test.StandardTestDispatcher
32 import kotlinx.coroutines.test.TestCoroutineScheduler
33 import kotlinx.coroutines.test.TestScope
34 import kotlinx.coroutines.test.resetMain
35 import kotlinx.coroutines.test.setMain
36 import org.junit.rules.TestWatcher
37 import org.junit.runner.Description
38 
39 @OptIn(ExperimentalCoroutinesApi::class)
40 class ScopesRule : TestWatcher() {
41     private val scheduler = TestCoroutineScheduler()
42     private val mainDispatcher = StandardTestDispatcher(scheduler)
43     val mainScope = TestScope(mainDispatcher)
44 
45     private val testDispatcher = StandardTestDispatcher(scheduler)
46     val testScope = TestScope(testDispatcher)
47 
startingnull48     override fun starting(description: Description?) {
49         Dispatchers.setMain(mainDispatcher)
50         ArchTaskExecutor.getInstance()
51             .setDelegate(
52                 object : TaskExecutor() {
53                     override fun executeOnDiskIO(runnable: Runnable) {
54                         error("unsupported")
55                     }
56 
57                     override fun postToMainThread(runnable: Runnable) {
58                         mainScope.launch { runnable.run() }
59                     }
60 
61                     override fun isMainThread(): Boolean {
62                         // we have only one thread in this test.
63                         return true
64                     }
65                 }
66             )
67     }
68 
finishednull69     override fun finished(description: Description?) {
70         advanceTimeBy(100000)
71         ArchTaskExecutor.getInstance().setDelegate(null)
72         Dispatchers.resetMain()
73     }
74 
advanceTimeBynull75     fun advanceTimeBy(time: Long) {
76         scheduler.advanceTimeBy(time)
77         triggerAllActions()
78     }
79 
triggerAllActionsnull80     fun triggerAllActions() {
81         scheduler.runCurrent()
82     }
83 
runOnMainnull84     fun <T> runOnMain(block: () -> T): T {
85         return runBlocking {
86             val async = mainScope.async { block() }
87             mainScope.testScheduler.runCurrent()
88             async.await()
89         }
90     }
91 }
92 
addObservernull93 fun <T> LiveData<T>.addObserver(scopes: ScopesRule): CollectingObserver<T> {
94     return scopes.runOnMain {
95         val observer = CollectingObserver(this, scopes)
96         observeForever(observer)
97         observer
98     }
99 }
100 
101 class CollectingObserver<T>(private val liveData: LiveData<T>, private val scopes: ScopesRule) :
102     Observer<T> {
103     private var items = mutableListOf<T>()
104 
onChangednull105     override fun onChanged(value: T) {
106         items.add(value)
107     }
108 
assertItemsnull109     fun assertItems(vararg expected: T) {
110         Truth.assertThat(items).containsExactly(*expected)
111     }
112 
<lambda>null113     fun unsubscribe() = scopes.runOnMain { liveData.removeObserver(this) }
114 }
115