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