1 /* 2 * Copyright (C) 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 17 package com.android.server.wm.traces.common 18 19 /** 20 * The utility class to wait a condition with customized options. 21 * The default retry policy is 5 times with interval 1 second. 22 * 23 * @param <T> The type of the object to validate. 24 * 25 * <p>Sample:</p> 26 * <pre> 27 * // Simple case. 28 * if (Condition.waitFor("true value", () -> true)) { 29 * println("Success"); 30 * } 31 * // Wait for customized result with customized validation. 32 * String result = WaitForCondition.Builder(supplier = () -> "Result string") 33 * .withCondition(str -> str.equals("Expected string")) 34 * .withRetryIntervalMs(500) 35 * .withRetryLimit(3) 36 * .onFailure(str -> println("Failed on " + str))) 37 * .build() 38 * .waitFor() 39 * </pre> 40 41 * @param condition If it returns true, that means the condition is satisfied. 42 */ 43 class WaitCondition<T> private constructor( 44 private val supplier: () -> T, 45 private val condition: Condition<T>, 46 private val retryLimit: Int, 47 private val onLog: ((String) -> Unit)?, 48 private val onFailure: ((T) -> Any)?, 49 private val onRetry: ((T) -> Any)?, 50 private val onSuccess: ((T) -> Any)? 51 ) { 52 /** 53 * @return `false` if the condition does not satisfy within the time limit. 54 */ waitFornull55 fun waitFor(): Boolean { 56 onLog?.invoke("***Waiting for $condition") 57 var currState: T? = null 58 for (i in 0..retryLimit) { 59 currState = supplier.invoke() 60 if (condition.isSatisfied(currState)) { 61 onLog?.invoke("***Waiting for $condition ... Success!") 62 onSuccess?.invoke(currState) 63 return true 64 } else { 65 val detailedMessage = condition.getMessage(currState) 66 onLog?.invoke("***Waiting for $detailedMessage... retry=${i + 1}") 67 if (i < retryLimit) { 68 onRetry?.invoke(currState) 69 } 70 } 71 } 72 73 val detailedMessage = if (currState != null) { 74 condition.getMessage(currState) 75 } else { 76 condition.toString() 77 } 78 onLog?.invoke("***Waiting for $detailedMessage ... Failed!") 79 if (onFailure != null) { 80 require(currState != null) { "Missing last result for failure notification" } 81 onFailure.invoke(currState) 82 } 83 return false 84 } 85 86 class Builder<T>( 87 private val supplier: () -> T, 88 private var retryLimit: Int 89 ) { 90 private val conditions = mutableListOf<Condition<T>>() 91 private var onFailure: ((T) -> Any)? = null 92 private var onRetry: ((T) -> Any)? = null 93 private var onSuccess: ((T) -> Any)? = null 94 private var onLog: ((String) -> Unit)? = null 95 withConditionnull96 fun withCondition(condition: Condition<T>) = 97 apply { conditions.add(condition) } 98 withConditionnull99 fun withCondition(message: String, condition: (T) -> Boolean) = 100 apply { withCondition(Condition(message, condition)) } 101 spreadConditionListnull102 private fun spreadConditionList(): List<Condition<T>> = 103 conditions.flatMap { 104 if (it is ConditionList<T>) { 105 it.conditions 106 } else { 107 listOf(it) 108 } 109 } 110 111 /** 112 * Executes the action when the condition does not satisfy within the time limit. The passed 113 * object to the consumer will be the last result from the supplier. 114 */ onFailurenull115 fun onFailure(onFailure: (T) -> Any): Builder<T> = 116 apply { this.onFailure = onFailure } 117 onLognull118 fun onLog(onLog: (String) -> Unit): Builder<T> = 119 apply { this.onLog = onLog } 120 onRetrynull121 fun onRetry(onRetry: ((T) -> Any)? = null): Builder<T> = 122 apply { this.onRetry = onRetry } 123 onSuccessnull124 fun onSuccess(onRetry: ((T) -> Any)? = null): Builder<T> = 125 apply { this.onSuccess = onRetry } 126 buildnull127 fun build(): WaitCondition<T> = 128 WaitCondition(supplier, ConditionList(spreadConditionList()), retryLimit, 129 onLog, onFailure, onRetry, onSuccess) 130 } 131 132 companion object { 133 // TODO(b/112837428): Implement a incremental retry policy to reduce the unnecessary 134 // constant time, currently keep the default as 5*1s because most of the original code 135 // uses it, and some tests might be sensitive to the waiting interval. 136 const val DEFAULT_RETRY_LIMIT = 10 137 const val DEFAULT_RETRY_INTERVAL_MS = 500L 138 } 139 }