1 /* 2 * Copyright (C) 2022 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 com.android.safetycenter.testing 18 19 import android.util.Log 20 import java.time.Duration 21 import kotlinx.coroutines.DEBUG_PROPERTY_NAME 22 import kotlinx.coroutines.DEBUG_PROPERTY_VALUE_AUTO 23 import kotlinx.coroutines.DEBUG_PROPERTY_VALUE_ON 24 import kotlinx.coroutines.delay 25 import kotlinx.coroutines.runBlocking 26 import kotlinx.coroutines.withTimeout 27 import kotlinx.coroutines.withTimeoutOrNull 28 29 /** A class that facilitates interacting with coroutines. */ 30 object Coroutines { 31 32 /** A long timeout, to be used for actions that are expected to complete. */ 33 val TIMEOUT_LONG: Duration = Duration.ofSeconds(25) 34 35 /** A short timeout, to be used for actions that are expected not to complete. */ 36 val TIMEOUT_SHORT: Duration = Duration.ofSeconds(1) 37 38 /** Shorthand for [runBlocking] combined with [withTimeout]. */ runBlockingWithTimeoutnull39 fun <T> runBlockingWithTimeout(timeout: Duration = TIMEOUT_LONG, block: suspend () -> T): T = 40 runBlocking { 41 withTimeout(timeout.toMillis()) { block() } 42 } 43 44 /** Shorthand for [runBlocking] combined with [withTimeoutOrNull] */ runBlockingWithTimeoutOrNullnull45 fun <T> runBlockingWithTimeoutOrNull( 46 timeout: Duration = TIMEOUT_LONG, 47 block: suspend () -> T 48 ): T? = runBlocking { withTimeoutOrNull(timeout.toMillis()) { block() } } 49 50 /** Check a condition using coroutines with a timeout. */ waitForWithTimeoutnull51 fun waitForWithTimeout( 52 timeout: Duration = TIMEOUT_LONG, 53 checkPeriod: Duration = CHECK_PERIOD, 54 condition: () -> Boolean 55 ) { 56 runBlockingWithTimeout(timeout) { waitFor(checkPeriod, condition) } 57 } 58 59 /** Retries a [fallibleAction] until no errors are thrown or a timeout occurs. */ waitForSuccessWithTimeoutnull60 fun waitForSuccessWithTimeout( 61 timeout: Duration = TIMEOUT_LONG, 62 checkPeriod: Duration = CHECK_PERIOD, 63 fallibleAction: () -> Unit 64 ) { 65 waitForWithTimeout(timeout, checkPeriod) { 66 try { 67 fallibleAction() 68 true 69 } catch (ex: Throwable) { 70 Log.w(TAG, "Encountered failure, retrying until timeout: $ex") 71 false 72 } 73 } 74 } 75 76 /** 77 * Enables debug mode for coroutines, in particular this enables stack traces in case of 78 * failures. 79 */ enableDebuggingnull80 fun enableDebugging() { 81 System.setProperty(DEBUG_PROPERTY_NAME, DEBUG_PROPERTY_VALUE_ON) 82 } 83 84 /** Resets the debug mode to its original state. */ resetDebuggingnull85 fun resetDebugging() { 86 System.setProperty(DEBUG_PROPERTY_NAME, debugMode) 87 } 88 89 /** Check a condition using coroutines. */ waitFornull90 private suspend fun waitFor(checkPeriod: Duration = CHECK_PERIOD, condition: () -> Boolean) { 91 while (!condition()) { 92 delay(checkPeriod.toMillis()) 93 } 94 } 95 96 private const val TAG: String = "Coroutines" 97 98 /** A medium period, to be used for conditions that are expected to change. */ 99 private val CHECK_PERIOD: Duration = Duration.ofMillis(250) 100 101 private val debugMode: String? = 102 System.getProperty(DEBUG_PROPERTY_NAME, DEBUG_PROPERTY_VALUE_AUTO) 103 } 104