1 /* 2 * Copyright 2021 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 package androidx.testutils 17 18 import java.util.concurrent.Executor 19 import java.util.concurrent.ExecutorService 20 import java.util.concurrent.Executors 21 import java.util.concurrent.TimeUnit 22 import java.util.concurrent.locks.ReentrantLock 23 import kotlin.concurrent.withLock 24 import kotlinx.coroutines.flow.MutableStateFlow 25 import kotlinx.coroutines.flow.filter 26 import kotlinx.coroutines.flow.first 27 import kotlinx.coroutines.withTimeout 28 29 /** 30 * An executor that can block some known runnables. We use it to slow down database invalidation 31 * events. 32 */ 33 class FilteringExecutor( 34 private val delegate: ExecutorService = Executors.newSingleThreadExecutor() 35 ) : Executor { 36 private val deferred = mutableListOf<Runnable>() 37 private val deferredSize = MutableStateFlow(0) 38 private val lock = ReentrantLock() 39 <lambda>null40 var filterFunction: (Runnable) -> Boolean = { true } 41 set(value) { 42 field = value 43 reEnqueueDeferred() 44 } 45 <lambda>null46 suspend fun awaitDeferredSizeAtLeast(min: Int) = withTestTimeout { 47 deferredSize.filter { it >= min }.first() 48 } 49 reEnqueueDeferrednull50 private fun reEnqueueDeferred() { 51 val copy = 52 lock.withLock { 53 val copy = deferred.toMutableList() 54 deferred.clear() 55 deferredSize.value = 0 56 copy 57 } 58 copy.forEach(this::execute) 59 } 60 deferredSizenull61 fun deferredSize(): Int { 62 return deferred.size 63 } 64 executeAllnull65 fun executeAll() { 66 while (deferred.isNotEmpty()) { 67 deferred.removeFirst().run() 68 } 69 } 70 executeLatestDeferrednull71 fun executeLatestDeferred() { 72 deferred.removeLast().run() 73 } 74 executenull75 override fun execute(command: Runnable) { 76 lock.withLock { 77 if (filterFunction(command)) { 78 delegate.execute(command) 79 } else { 80 deferred.add(command) 81 deferredSize.value += 1 82 } 83 } 84 } 85 } 86 withTestTimeoutnull87suspend fun <T> withTestTimeout(duration: Long = 3, block: suspend () -> T): T { 88 try { 89 return withTimeout(timeMillis = TimeUnit.SECONDS.toMillis(duration)) { block() } 90 } catch (err: Throwable) { 91 throw AssertionError("didn't complete in expected time", err) 92 } 93 } 94