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 addObservernull93fun <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