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 androidx.test.platform.app.InstrumentationRegistry 21 import java.time.Duration 22 import kotlinx.coroutines.DEBUG_PROPERTY_NAME 23 import kotlinx.coroutines.DEBUG_PROPERTY_VALUE_AUTO 24 import kotlinx.coroutines.DEBUG_PROPERTY_VALUE_ON 25 import kotlinx.coroutines.delay 26 import kotlinx.coroutines.runBlocking 27 import kotlinx.coroutines.withTimeout 28 import kotlinx.coroutines.withTimeoutOrNull 29 30 /** A class that facilitates interacting with coroutines. */ 31 object Coroutines { 32 33 /** 34 * The timeout of a test case, typically varies depending on whether the test is running 35 * locally, on pre-submit or post-submit. 36 */ 37 val TEST_TIMEOUT: Duration 38 get() = 39 Duration.ofMillis( 40 InstrumentationRegistry.getArguments().getString("timeout_msec", "60000").toLong() 41 ) 42 43 /** A long timeout, to be used for actions that are expected to complete. */ 44 val TIMEOUT_LONG: Duration 45 get() = TEST_TIMEOUT.dividedBy(2) 46 47 /** A short timeout, to be used for actions that are expected not to complete. */ 48 val TIMEOUT_SHORT: Duration = Duration.ofSeconds(1) 49 50 /** Shorthand for [runBlocking] combined with [withTimeout]. */ runBlockingWithTimeoutnull51 fun <T> runBlockingWithTimeout(timeout: Duration = TIMEOUT_LONG, block: suspend () -> T): T = 52 runBlocking { 53 withTimeout(timeout.toMillis()) { block() } 54 } 55 56 /** Shorthand for [runBlocking] combined with [withTimeoutOrNull] */ runBlockingWithTimeoutOrNullnull57 fun <T> runBlockingWithTimeoutOrNull( 58 timeout: Duration = TIMEOUT_LONG, 59 block: suspend () -> T 60 ): T? = runBlocking { withTimeoutOrNull(timeout.toMillis()) { block() } } 61 62 /** Check a condition using coroutines with a timeout. */ waitForWithTimeoutnull63 fun waitForWithTimeout( 64 timeout: Duration = TIMEOUT_LONG, 65 checkPeriod: Duration = CHECK_PERIOD, 66 condition: () -> Boolean 67 ) { 68 runBlockingWithTimeout(timeout) { waitFor(checkPeriod, condition) } 69 } 70 71 /** Retries a [fallibleAction] until no errors are thrown or a timeout occurs. */ waitForSuccessWithTimeoutnull72 fun waitForSuccessWithTimeout( 73 timeout: Duration = TIMEOUT_LONG, 74 checkPeriod: Duration = CHECK_PERIOD, 75 fallibleAction: () -> Unit 76 ) { 77 waitForWithTimeout(timeout, checkPeriod) { 78 try { 79 fallibleAction() 80 true 81 } catch (ex: Throwable) { 82 Log.w(TAG, "Encountered failure, retrying until timeout: $ex") 83 false 84 } 85 } 86 } 87 88 /** 89 * Enables debug mode for coroutines, in particular this enables stack traces in case of 90 * failures. 91 */ enableDebuggingnull92 fun enableDebugging() { 93 System.setProperty(DEBUG_PROPERTY_NAME, DEBUG_PROPERTY_VALUE_ON) 94 } 95 96 /** Resets the debug mode to its original state. */ resetDebuggingnull97 fun resetDebugging() { 98 System.setProperty(DEBUG_PROPERTY_NAME, debugMode) 99 } 100 101 /** Check a condition using coroutines. */ waitFornull102 private suspend fun waitFor(checkPeriod: Duration = CHECK_PERIOD, condition: () -> Boolean) { 103 while (!condition()) { 104 delay(checkPeriod.toMillis()) 105 } 106 } 107 108 private const val TAG: String = "Coroutines" 109 110 /** A medium period, to be used for conditions that are expected to change. */ 111 private val CHECK_PERIOD: Duration = Duration.ofMillis(250) 112 113 private val debugMode: String? = 114 System.getProperty(DEBUG_PROPERTY_NAME, DEBUG_PROPERTY_VALUE_AUTO) 115 } 116