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 android.platform.test.util;
18 
19 import android.platform.uiautomator_helpers.WaitUtils;
20 
21 import androidx.test.uiautomator.StaleObjectException;
22 
23 import java.time.Duration;
24 import java.util.Optional;
25 import java.util.function.Supplier;
26 
27 /** Helper class for writing health tests. */
28 public class HealthTestingUtils {
29 
30     private static final String TAG = "HealthTestingUtils";
31     private static final int WAIT_TIME_MS = 10000;
32     private static final int DEFAULT_SETTLE_TIME_MS = 3000;
33 
HealthTestingUtils()34     private HealthTestingUtils() {}
35 
36     /** Supplier of a boolean that can throw an exception. */
37     public interface Condition {
isTrue()38         boolean isTrue() throws Throwable;
39     }
40 
41     /**
42      * Waits for a diagnostics to become null within 10 sec.
43      *
44      * @param diagnostics Supplier of the error message. It should return null in case of success.
45      *     The method repeatedly calls this provider while it's returning non-null. If it keeps
46      *     returning non-null after 10 sec, the method throws an exception using the last string
47      *     returned by the provider. Otherwise, it succeeds.
48      */
waitForNullDiag(Supplier<String> diagnostics)49     public static void waitForNullDiag(Supplier<String> diagnostics) {
50         final String[] lastDiag = new String[1];
51         waitForCondition(() -> lastDiag[0], () -> (lastDiag[0] = diagnostics.get()) == null);
52     }
53 
54     /**
55      * Waits for a value producer to produce a result that's isPresent() and fails if it doesn't
56      * happen within 10 sec.
57      *
58      * @param message Supplier of the error message.
59      * @param resultProducer Result producer.
60      * @return The present value returned by the producer.
61      */
waitForValuePresent( Supplier<String> message, Supplier<Optional<T>> resultProducer)62     public static <T> T waitForValuePresent(
63             Supplier<String> message, Supplier<Optional<T>> resultProducer) {
64         class ResultHolder {
65             public T value;
66         }
67         final ResultHolder result = new ResultHolder();
68 
69         waitForCondition(
70                 message,
71                 () -> {
72                     final Optional<T> optionalResult = resultProducer.get();
73                     if (optionalResult.isPresent()) {
74                         result.value = optionalResult.get();
75                         return true;
76                     } else {
77                         return false;
78                     }
79                 });
80 
81         return result.value;
82     }
83 
84     /**
85      * Waits for a result to be produced without throwing a {@link StaleObjectException}.
86      *
87      * <p>This is useful in case of dealing with containers that change or go out of screen during
88      * the test, to reduce flakiness.
89      *
90      * @param errorMessage message thrown when resultProduces fails after the maximum number of
91      *     retries.
92      * @param resultProducer produces the output. Might throw {@link StaleObjectException}.
93      */
waitForValueCatchingStaleObjectExceptions( Supplier<String> errorMessage, Supplier<T> resultProducer)94     public static <T> T waitForValueCatchingStaleObjectExceptions(
95             Supplier<String> errorMessage, Supplier<T> resultProducer) {
96         return waitForValuePresent(
97                 errorMessage,
98                 () -> {
99                     try {
100                         return Optional.ofNullable(resultProducer.get());
101                     } catch (StaleObjectException e) {
102                         return Optional.empty();
103                     }
104                 });
105     }
106 
107     /**
108      * Waits for a condition and fails if it doesn't become true within 10 sec.
109      *
110      * @param message Supplier of the error message.
111      * @param condition Condition.
112      */
113     public static void waitForCondition(Supplier<String> message, Condition condition) {
114         waitForCondition(message, condition, WAIT_TIME_MS);
115     }
116 
117     /**
118      * Waits for a condition and fails if it doesn't become true within specified time period.
119      *
120      * @param message Supplier of the error message.
121      * @param condition Condition.
122      * @param timeoutMs Timeout.
123      */
124     public static void waitForCondition(
125             Supplier<String> message, Condition condition, long timeoutMs) {
126 
127         WaitUtils.ensureThat(
128                 "waitForCondition",
129                 /* timeout= */ Duration.ofMillis(timeoutMs),
130                 /* errorProvider= */ message::get,
131                 /* condition= */ () -> {
132                     try {
133                         return condition.isTrue();
134                     } catch (Throwable t) {
135                         throw new RuntimeException(t);
136                     }
137                 });
138     }
139 
140     /** @see HealthTestingUtils#waitForValueToSettle */
141     public static <T> T waitForValueToSettle(Supplier<String> errorMessage, Supplier<T> supplier) {
142         return waitForValueToSettle(
143                 errorMessage,
144                 supplier,
145                 /* minimumSettleTime= */ DEFAULT_SETTLE_TIME_MS,
146                 /* timeoutMs= */ WAIT_TIME_MS);
147     }
148 
149     /**
150      * Waits for the supplier to return the same value for a specified time period. If the value
151      * changes, the timer gets restarted. Fails when reaching the timeout. The minimum running time
152      * of this method is the settle time.
153      *
154      * @return the settled value. Fails if it doesn't settle.
155      */
156     public static <T> T waitForValueToSettle(
157             Supplier<String> errorMessage,
158             Supplier<T> supplier,
159             long minimumSettleTime,
160             long timeoutMs) {
161         return WaitUtils.waitForNullableValueToSettle(
162                 "waitForValueToSettle",
163                 /* minimumSettleTime= */ Duration.ofMillis(minimumSettleTime),
164                 /* timeout= */ Duration.ofMillis(timeoutMs),
165                 /* errorProvider= */ errorMessage::get,
166                 /* supplier */ supplier::get);
167     }
168 }
169