1 /*
2  * Copyright (C) 2018 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.backup.testing;
18 
19 import static com.google.common.truth.Truth.assertThat;
20 import static com.google.common.truth.Truth.assertWithMessage;
21 
22 import static org.robolectric.Shadows.shadowOf;
23 
24 import static java.util.stream.Collectors.toSet;
25 
26 import android.os.Looper;
27 import android.os.Message;
28 import android.os.MessageQueue;
29 import android.os.SystemClock;
30 
31 import com.android.server.testing.shadows.ShadowEventLog;
32 
33 import org.robolectric.shadows.ShadowLog;
34 import org.robolectric.shadows.ShadowLooper;
35 
36 import java.util.Arrays;
37 import java.util.concurrent.Callable;
38 import java.util.concurrent.TimeUnit;
39 import java.util.concurrent.TimeoutException;
40 import java.util.function.Predicate;
41 import java.util.function.Supplier;
42 import java.util.stream.IntStream;
43 
44 public class TestUtils {
45     private static final long TIMEOUT_MS = 3000;
46     private static final long STEP_MS = 50;
47 
48     /**
49      * Counts the number of messages in the looper {@code looper} that satisfy {@code
50      * messageFilter}.
51      */
messagesInLooper(Looper looper, Predicate<Message> messageFilter)52     public static int messagesInLooper(Looper looper, Predicate<Message> messageFilter) {
53         MessageQueue queue = looper.getQueue();
54         int i = 0;
55         for (Message m = shadowOf(queue).getHead(); m != null; m = shadowOf(m).getNext()) {
56             if (messageFilter.test(m)) {
57                 i += 1;
58             }
59         }
60         return i;
61     }
62 
waitUntil(Supplier<Boolean> condition)63     public static void waitUntil(Supplier<Boolean> condition)
64             throws InterruptedException, TimeoutException {
65         waitUntil(condition, STEP_MS, TIMEOUT_MS);
66     }
67 
waitUntil(Supplier<Boolean> condition, long stepMs, long timeoutMs)68     public static void waitUntil(Supplier<Boolean> condition, long stepMs, long timeoutMs)
69             throws InterruptedException, TimeoutException {
70         long deadline = System.nanoTime() + TimeUnit.MILLISECONDS.toNanos(timeoutMs);
71         while (true) {
72             if (condition.get()) {
73                 return;
74             }
75             if (System.nanoTime() > deadline) {
76                 throw new TimeoutException("Test timed-out waiting for condition");
77             }
78             Thread.sleep(stepMs);
79         }
80     }
81 
82     /** Version of {@link ShadowLooper#runToEndOfTasks()} that also advances the system clock. */
runToEndOfTasks(Looper looper)83     public static void runToEndOfTasks(Looper looper) {
84         ShadowLooper shadowLooper = shadowOf(looper);
85         shadowLooper.runToEndOfTasks();
86         // Handler instances have their own clock, so advancing looper (with runToEndOfTasks())
87         // above does NOT advance the handlers' clock, hence whenever a handler post messages with
88         // specific time to the looper the time of those messages will be before the looper's time.
89         // To fix this we advance SystemClock as well since that is from where the handlers read
90         // time.
91         SystemClock.setCurrentTimeMillis(shadowLooper.getScheduler().getCurrentTime());
92     }
93 
94     /**
95      * Reset logcat with {@link ShadowLog#reset()} before the test case if you do anything that uses
96      * logcat before that.
97      */
assertLogcatAtMost(String tag, int level)98     public static void assertLogcatAtMost(String tag, int level) {
99         assertWithMessage("All logs <= " + level).that(
100                 ShadowLog.getLogsForTag(tag).stream().allMatch(logItem -> logItem.type <= level))
101                 .isTrue();
102     }
103 
104     /**
105      * Reset logcat with {@link ShadowLog#reset()} before the test case if you do anything that uses
106      * logcat before that.
107      */
assertLogcatAtLeast(String tag, int level)108     public static void assertLogcatAtLeast(String tag, int level) {
109         assertWithMessage("Any log >= " + level).that(
110                 ShadowLog.getLogsForTag(tag).stream().anyMatch(logItem -> logItem.type >= level))
111                 .isTrue();
112     }
113 
114     /**
115      * Verifies that logcat has produced log items as specified per level in {@code logs} (with
116      * repetition).
117      *
118      * <p>So, if you call {@code assertLogcat(TAG, Log.ERROR, Log.ERROR)}, you assert that there are
119      * exactly 2 log items, each with level ERROR.
120      *
121      * <p>Reset logcat with {@link ShadowLog#reset()} before the test case if you do anything
122      * that uses logcat before that.
123      */
assertLogcat(String tag, int... logs)124     public static void assertLogcat(String tag, int... logs) {
125         assertWithMessage("Log items (specified per level)").that(
126                         ShadowLog.getLogsForTag(tag).stream()
127                                 .map(logItem -> logItem.type)
128                                 .collect(toSet()))
129                 .containsExactly(IntStream.of(logs).boxed().toArray());
130     }
131 
assertLogcatContains(String tag, Predicate<ShadowLog.LogItem> predicate)132     public static void assertLogcatContains(String tag, Predicate<ShadowLog.LogItem> predicate) {
133         assertThat(ShadowLog.getLogsForTag(tag).stream().anyMatch(predicate)).isTrue();
134     }
135 
136     /** Declare shadow {@link ShadowEventLog} to use this. */
assertEventLogged(int tag, Object... values)137     public static void assertEventLogged(int tag, Object... values) {
138         assertWithMessage("Event logs").that(ShadowEventLog.getEntries())
139                 .contains(new ShadowEventLog.Entry(tag, Arrays.asList(values)));
140     }
141 
142     /** Declare shadow {@link ShadowEventLog} to use this. */
assertEventNotLogged(int tag, Object... values)143     public static void assertEventNotLogged(int tag, Object... values) {
144         assertWithMessage("Event logs").that(ShadowEventLog.getEntries())
145                 .doesNotContain(new ShadowEventLog.Entry(tag, Arrays.asList(values)));
146     }
147 
148     /**
149      * Calls {@link Runnable#run()} and returns if no exception is thrown. Otherwise, if the
150      * exception is unchecked, rethrow it; if it's checked wrap in a {@link RuntimeException} and
151      * throw.
152      *
153      * <p><b>Warning:</b>DON'T use this outside tests. A wrapped checked exception is just a failure
154      * in a test.
155      */
uncheck(ThrowingRunnable runnable)156     public static void uncheck(ThrowingRunnable runnable) {
157         try {
158             runnable.runOrThrow();
159         } catch (Exception e) {
160             throw wrapIfChecked(e);
161         }
162     }
163 
164     /**
165      * Calls {@link Callable#call()} and returns the value if no exception is thrown. Otherwise, if
166      * the exception is unchecked, rethrow it; if it's checked wrap in a {@link RuntimeException}
167      * and throw.
168      *
169      * <p><b>Warning:</b>DON'T use this outside tests. A wrapped checked exception is just a failure
170      * in a test.
171      */
uncheck(Callable<T> callable)172     public static <T> T uncheck(Callable<T> callable) {
173         try {
174             return callable.call();
175         } catch (Exception e) {
176             throw wrapIfChecked(e);
177         }
178     }
179 
180     /**
181      * Wrap {@code e} in a {@link RuntimeException} only if it's not one already, in which case it's
182      * returned.
183      */
wrapIfChecked(Exception e)184     public static RuntimeException wrapIfChecked(Exception e) {
185         if (e instanceof RuntimeException) {
186             return (RuntimeException) e;
187         }
188         return new RuntimeException(e);
189     }
190 
191     /** An equivalent of {@link Runnable} that allows throwing checked exceptions. */
192     @FunctionalInterface
193     public interface ThrowingRunnable {
runOrThrow()194         void runOrThrow() throws Exception;
195     }
196 
TestUtils()197     private TestUtils() {}
198 }
199