• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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, Boolean) -> 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", /* isError */ false)
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!", /* isError */ false)
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                     /* isError */ true)
68                 if (i < retryLimit) {
69                     onRetry?.invoke(currState)
70                 }
71             }
72         }
73 
74         val detailedMessage = if (currState != null) {
75             condition.getMessage(currState)
76         } else {
77             condition.toString()
78         }
79         onLog?.invoke("***Waiting for $detailedMessage ... Failed!", /* isError */ true)
80         if (onFailure != null) {
81             require(currState != null) { "Missing last result for failure notification" }
82             onFailure.invoke(currState)
83         }
84         return false
85     }
86 
87     class Builder<T>(
88         private val supplier: () -> T,
89         private var retryLimit: Int
90     ) {
91         private val conditions = mutableListOf<Condition<T>>()
92         private var onFailure: ((T) -> Any)? = null
93         private var onRetry: ((T) -> Any)? = null
94         private var onSuccess: ((T) -> Any)? = null
95         private var onLog: ((String, Boolean) -> Unit)? = null
96 
withConditionnull97         fun withCondition(condition: Condition<T>) =
98             apply { conditions.add(condition) }
99 
withConditionnull100         fun withCondition(message: String, condition: (T) -> Boolean) =
101             apply { withCondition(Condition(message, condition)) }
102 
spreadConditionListnull103         private fun spreadConditionList(): List<Condition<T>> =
104             conditions.flatMap {
105                 if (it is ConditionList<T>) {
106                     it.conditions
107                 } else {
108                     listOf(it)
109                 }
110             }
111 
112         /**
113          * Executes the action when the condition does not satisfy within the time limit. The passed
114          * object to the consumer will be the last result from the supplier.
115          */
onFailurenull116         fun onFailure(onFailure: (T) -> Any): Builder<T> =
117             apply { this.onFailure = onFailure }
118 
onLognull119         fun onLog(onLog: (String, Boolean) -> Unit): Builder<T> =
120             apply { this.onLog = onLog }
121 
onRetrynull122         fun onRetry(onRetry: ((T) -> Any)? = null): Builder<T> =
123             apply { this.onRetry = onRetry }
124 
onSuccessnull125         fun onSuccess(onRetry: ((T) -> Any)? = null): Builder<T> =
126             apply { this.onSuccess = onRetry }
127 
buildnull128         fun build(): WaitCondition<T> =
129             WaitCondition(supplier, ConditionList(spreadConditionList()), retryLimit,
130                 onLog, onFailure, onRetry, onSuccess)
131     }
132 
133     companion object {
134         // TODO(b/112837428): Implement a incremental retry policy to reduce the unnecessary
135         // constant time, currently keep the default as 5*1s because most of the original code
136         // uses it, and some tests might be sensitive to the waiting interval.
137         const val DEFAULT_RETRY_LIMIT = 10
138         const val DEFAULT_RETRY_INTERVAL_MS = 500L
139     }
140 }