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 }