1 /* 2 * Copyright (C) 2017 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file 5 * except in compliance with the License. You may obtain a copy of the License at 6 * 7 * http://www.apache.org/licenses/LICENSE-2.0 8 * 9 * Unless required by applicable law or agreed to in writing, software distributed under the 10 * License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 11 * KIND, either express or implied. See the License for the specific language governing 12 * permissions and limitations under the License. 13 */ 14 15 package android.testing; 16 17 import android.annotation.NonNull; 18 import android.annotation.Nullable; 19 import android.os.Handler; 20 import android.os.HandlerThread; 21 import android.os.Looper; 22 import android.os.Message; 23 import android.os.MessageQueue; 24 import android.os.TestLooperManager; 25 import android.util.ArrayMap; 26 27 import androidx.test.InstrumentationRegistry; 28 29 import org.junit.runners.model.FrameworkMethod; 30 31 import java.lang.annotation.ElementType; 32 import java.lang.annotation.Retention; 33 import java.lang.annotation.RetentionPolicy; 34 import java.lang.annotation.Target; 35 import java.lang.reflect.Field; 36 import java.util.Map; 37 import java.util.Objects; 38 import java.util.concurrent.atomic.AtomicBoolean; 39 40 /** 41 * This is a wrapper around {@link TestLooperManager} to make it easier to manage 42 * and provide an easy annotation for use with tests. 43 * 44 * @see TestableLooperTest TestableLooperTest for examples. 45 */ 46 public class TestableLooper { 47 48 /** 49 * Whether to hold onto the main thread through all tests in an attempt to 50 * catch crashes. 51 */ 52 public static final boolean HOLD_MAIN_THREAD = false; 53 private static final Field MESSAGE_QUEUE_MESSAGES_FIELD; 54 private static final Field MESSAGE_NEXT_FIELD; 55 private static final Field MESSAGE_WHEN_FIELD; 56 57 private Looper mLooper; 58 private MessageQueue mQueue; 59 private MessageHandler mMessageHandler; 60 61 private Handler mHandler; 62 private TestLooperManager mQueueWrapper; 63 64 static { 65 try { 66 MESSAGE_QUEUE_MESSAGES_FIELD = MessageQueue.class.getDeclaredField("mMessages"); 67 MESSAGE_QUEUE_MESSAGES_FIELD.setAccessible(true); 68 MESSAGE_NEXT_FIELD = Message.class.getDeclaredField("next"); 69 MESSAGE_NEXT_FIELD.setAccessible(true); 70 MESSAGE_WHEN_FIELD = Message.class.getDeclaredField("when"); 71 MESSAGE_WHEN_FIELD.setAccessible(true); 72 } catch (NoSuchFieldException e) { 73 throw new RuntimeException("Failed to initialize TestableLooper", e); 74 } 75 } 76 TestableLooper(Looper l)77 public TestableLooper(Looper l) throws Exception { 78 this(acquireLooperManager(l), l); 79 } 80 TestableLooper(TestLooperManager wrapper, Looper l)81 private TestableLooper(TestLooperManager wrapper, Looper l) { 82 mQueueWrapper = Objects.requireNonNull(wrapper); 83 setupQueue(l); 84 } 85 TestableLooper(Looper looper, boolean b)86 private TestableLooper(Looper looper, boolean b) { 87 setupQueue(looper); 88 } 89 90 /** 91 * Wrap the given runnable so that it will run blocking on the Looper that will be set up for 92 * the given test. 93 * <p> 94 * This method is required to support any TestRule which needs to run setup and/or teardown code 95 * on the TestableLooper. Whether using {@link AndroidTestingRunner} or 96 * {@link TestWithLooperRule}, the TestRule's Statement evaluates on the test instrumentation 97 * thread, rather than the TestableLooper thread, so access to the TestableLooper is required. 98 * However, {@link #get(Object)} will return {@code null} both before and after the inner 99 * statement is evaluated: 100 * <ul> 101 * <li>Before the test {@link #get} returns {@code null} because while the TestableLooperHolder 102 * is accessible in sLoopers, it has not been initialized with an actual TestableLooper yet. 103 * This method's use of the internal LooperFrameworkMethod ensures that all setup and teardown 104 * of the TestableLooper happen as it would for all other wrapped code blocks. 105 * <li>After the test {@link #get} can return {@code null} because many tests call 106 * {@link #remove} in the teardown method. The fact that this method returns a runnable allows 107 * it to be called before the test (when the TestableLooperHolder is still in sLoopers), and 108 * then executed as teardown after the test. 109 * </ul> 110 * 111 * @param test the test instance (just like passed to {@link #get(Object)}) 112 * @param runnable the operation that should eventually be run on the TestableLooper 113 * @return a runnable that will block the thread on which it is called until the given runnable 114 * is finished. Will be {@code null} if there is no looper for the given test. 115 * @hide 116 */ 117 @Nullable wrapWithRunBlocking( Object test, @NonNull RunnableWithException runnable)118 public static RunnableWithException wrapWithRunBlocking( 119 Object test, @NonNull RunnableWithException runnable) { 120 TestableLooperHolder looperHolder = sLoopers.get(test); 121 if (looperHolder == null) { 122 return null; 123 } 124 try { 125 FrameworkMethod base = new FrameworkMethod(runnable.getClass().getMethod("run")); 126 LooperFrameworkMethod wrapped = new LooperFrameworkMethod(base, looperHolder); 127 return () -> { 128 try { 129 wrapped.invokeExplosively(runnable); 130 } catch (RuntimeException | Error e) { 131 throw e; 132 } catch (Throwable e) { 133 throw new RuntimeException(e); 134 } 135 }; 136 } catch (NoSuchMethodException e) { 137 throw new RuntimeException(e); 138 } 139 } 140 141 public Looper getLooper() { 142 return mLooper; 143 } 144 145 private void setupQueue(Looper l) { 146 mLooper = l; 147 mQueue = mLooper.getQueue(); 148 mHandler = new Handler(mLooper); 149 } 150 151 /** 152 * Must be called to release the looper when the test is complete, otherwise 153 * the looper will not be available for any subsequent tests. This is 154 * automatically handled for tests using {@link RunWithLooper}. 155 */ 156 public void destroy() { 157 mQueueWrapper.release(); 158 if (HOLD_MAIN_THREAD && mLooper == Looper.getMainLooper()) { 159 TestableInstrumentation.releaseMain(); 160 } 161 } 162 163 /** 164 * Sets a callback for all messages processed on this TestableLooper. 165 * 166 * @see {@link MessageHandler} 167 */ 168 public void setMessageHandler(MessageHandler handler) { 169 mMessageHandler = handler; 170 } 171 172 /** 173 * Parse num messages from the message queue. 174 * 175 * @param num Number of messages to parse 176 */ 177 public int processMessages(int num) { 178 return processMessagesInternal(num, null); 179 } 180 181 private int processMessagesInternal(int num, Runnable barrierRunnable) { 182 for (int i = 0; i < num; i++) { 183 if (!processSingleMessage(barrierRunnable)) { 184 return i + 1; 185 } 186 } 187 return num; 188 } 189 190 /** 191 * Process up to a certain number of messages, not blocking if the queue has less messages than 192 * that 193 * @param num the maximum number of messages to process 194 * @return the number of messages processed. This will be at most {@code num}. 195 */ 196 197 public int processMessagesNonBlocking(int num) { 198 final AtomicBoolean reachedBarrier = new AtomicBoolean(false); 199 Runnable barrierRunnable = () -> { 200 reachedBarrier.set(true); 201 }; 202 mHandler.post(barrierRunnable); 203 waitForMessage(mQueueWrapper, mHandler, barrierRunnable); 204 try { 205 return processMessagesInternal(num, barrierRunnable) + (reachedBarrier.get() ? -1 : 0); 206 } finally { 207 mHandler.removeCallbacks(barrierRunnable); 208 } 209 } 210 211 /** 212 * Process messages in the queue until no more are found. 213 */ 214 public void processAllMessages() { 215 while (processQueuedMessages() != 0) ; 216 } 217 218 public void moveTimeForward(long milliSeconds) { 219 try { 220 Message msg = getMessageLinkedList(); 221 while (msg != null) { 222 long updatedWhen = msg.getWhen() - milliSeconds; 223 if (updatedWhen < 0) { 224 updatedWhen = 0; 225 } 226 MESSAGE_WHEN_FIELD.set(msg, updatedWhen); 227 msg = (Message) MESSAGE_NEXT_FIELD.get(msg); 228 } 229 } catch (IllegalAccessException e) { 230 throw new RuntimeException("Access failed in TestableLooper: set - Message.when", e); 231 } 232 } 233 234 private Message getMessageLinkedList() { 235 try { 236 MessageQueue queue = mLooper.getQueue(); 237 return (Message) MESSAGE_QUEUE_MESSAGES_FIELD.get(queue); 238 } catch (IllegalAccessException e) { 239 throw new RuntimeException( 240 "Access failed in TestableLooper: get - MessageQueue.mMessages", 241 e); 242 } 243 } 244 245 private int processQueuedMessages() { 246 int count = 0; 247 Runnable barrierRunnable = () -> { }; 248 mHandler.post(barrierRunnable); 249 waitForMessage(mQueueWrapper, mHandler, barrierRunnable); 250 while (processSingleMessage(barrierRunnable)) count++; 251 return count; 252 } 253 254 private boolean processSingleMessage(Runnable barrierRunnable) { 255 try { 256 Message result = mQueueWrapper.next(); 257 if (result != null) { 258 // This is a break message. 259 if (result.getCallback() == barrierRunnable) { 260 mQueueWrapper.execute(result); 261 mQueueWrapper.recycle(result); 262 return false; 263 } 264 265 if (mMessageHandler != null) { 266 if (mMessageHandler.onMessageHandled(result)) { 267 mQueueWrapper.execute(result); 268 mQueueWrapper.recycle(result); 269 } else { 270 mQueueWrapper.recycle(result); 271 // Message handler indicated it doesn't want us to continue. 272 return false; 273 } 274 } else { 275 mQueueWrapper.execute(result); 276 mQueueWrapper.recycle(result); 277 } 278 } else { 279 // No messages, don't continue parsing 280 return false; 281 } 282 } catch (Exception e) { 283 throw new RuntimeException(e); 284 } 285 return true; 286 } 287 288 /** 289 * Runs an executable with myLooper set and processes all messages added. 290 */ 291 public void runWithLooper(RunnableWithException runnable) throws Exception { 292 new Handler(getLooper()).post(() -> { 293 try { 294 runnable.run(); 295 } catch (Exception e) { 296 throw new RuntimeException(e); 297 } 298 }); 299 processAllMessages(); 300 } 301 302 public interface RunnableWithException { 303 void run() throws Exception; 304 } 305 306 /** 307 * Annotation that tells the {@link AndroidTestingRunner} to create a TestableLooper and 308 * run this test/class on that thread. The {@link TestableLooper} can be acquired using 309 * {@link #get(Object)}. 310 */ 311 @Retention(RetentionPolicy.RUNTIME) 312 @Target({ElementType.METHOD, ElementType.TYPE}) 313 public @interface RunWithLooper { 314 boolean setAsMainLooper() default false; 315 } 316 317 private static void waitForMessage(TestLooperManager queueWrapper, Handler handler, 318 Runnable execute) { 319 for (int i = 0; i < 10; i++) { 320 if (!queueWrapper.hasMessages(handler, null, execute)) { 321 try { 322 Thread.sleep(1); 323 } catch (InterruptedException e) { 324 } 325 } 326 } 327 if (!queueWrapper.hasMessages(handler, null, execute)) { 328 throw new RuntimeException("Message didn't queue..."); 329 } 330 } 331 332 private static TestLooperManager acquireLooperManager(Looper l) { 333 if (HOLD_MAIN_THREAD && l == Looper.getMainLooper()) { 334 TestableInstrumentation.acquireMain(); 335 } 336 return InstrumentationRegistry.getInstrumentation().acquireLooperManager(l); 337 } 338 339 private static final Map<Object, TestableLooperHolder> sLoopers = new ArrayMap<>(); 340 341 /** 342 * For use with {@link RunWithLooper}, used to get the TestableLooper that was 343 * automatically created for this test. 344 */ 345 public static TestableLooper get(Object test) { 346 final TestableLooperHolder looperHolder = sLoopers.get(test); 347 return (looperHolder != null) ? looperHolder.mTestableLooper : null; 348 } 349 350 public static void remove(Object test) { 351 sLoopers.remove(test); 352 } 353 354 /** 355 * Holder object that contains {@link TestableLooper} so that its initialization can be 356 * deferred until a test case is actually run, instead of forcing it to be created at 357 * {@link FrameworkMethod} construction time. 358 * 359 * This deferral is important because some test environments may configure 360 * {@link Looper#getMainLooper()} as part of a {@code Rule} instead of assuming it's globally 361 * initialized and unconditionally available. 362 */ 363 private static class TestableLooperHolder { 364 private final boolean mSetAsMain; 365 private final Object mTest; 366 367 private TestableLooper mTestableLooper; 368 private Looper mLooper; 369 private Handler mHandler; 370 private HandlerThread mHandlerThread; 371 372 public TestableLooperHolder(boolean setAsMain, Object test) { 373 mSetAsMain = setAsMain; 374 mTest = test; 375 } 376 377 public void ensureInit() { 378 if (mLooper != null) return; 379 try { 380 mLooper = mSetAsMain ? Looper.getMainLooper() : createLooper(); 381 mTestableLooper = new TestableLooper(mLooper, false); 382 if (!mSetAsMain) { 383 mTestableLooper.getLooper().getThread().setName(mTest.getClass().getName()); 384 } 385 } catch (Exception e) { 386 throw new RuntimeException(e); 387 } 388 mHandler = new Handler(mLooper); 389 } 390 391 private Looper createLooper() { 392 // TODO: Find way to share these. 393 mHandlerThread = new HandlerThread(TestableLooper.class.getSimpleName()); 394 mHandlerThread.start(); 395 return mHandlerThread.getLooper(); 396 } 397 } 398 399 static class LooperFrameworkMethod extends FrameworkMethod { 400 private TestableLooperHolder mLooperHolder; 401 402 public LooperFrameworkMethod(FrameworkMethod base, TestableLooperHolder looperHolder) { 403 super(base.getMethod()); 404 mLooperHolder = looperHolder; 405 } 406 407 public static FrameworkMethod get(FrameworkMethod base, boolean setAsMain, Object test) { 408 TestableLooperHolder looperHolder = sLoopers.get(test); 409 if (looperHolder == null) { 410 looperHolder = new TestableLooperHolder(setAsMain, test); 411 sLoopers.put(test, looperHolder); 412 } 413 return new LooperFrameworkMethod(base, looperHolder); 414 } 415 416 @Override 417 public Object invokeExplosively(Object target, Object... params) throws Throwable { 418 mLooperHolder.ensureInit(); 419 if (Looper.myLooper() == mLooperHolder.mLooper) { 420 // Already on the right thread from another statement, just execute then. 421 return super.invokeExplosively(target, params); 422 } 423 boolean set = mLooperHolder.mTestableLooper.mQueueWrapper == null; 424 if (set) { 425 mLooperHolder.mTestableLooper.mQueueWrapper = acquireLooperManager( 426 mLooperHolder.mLooper); 427 } 428 try { 429 Object[] ret = new Object[1]; 430 // Run the execution on the looper thread. 431 Runnable execute = () -> { 432 try { 433 ret[0] = super.invokeExplosively(target, params); 434 } catch (Throwable throwable) { 435 throw new LooperException(throwable); 436 } 437 }; 438 Message m = Message.obtain(mLooperHolder.mHandler, execute); 439 440 // Dispatch our message. 441 try { 442 mLooperHolder.mTestableLooper.mQueueWrapper.execute(m); 443 } catch (LooperException e) { 444 throw e.getSource(); 445 } catch (RuntimeException re) { 446 // If the TestLooperManager has to post, it will wrap what it throws in a 447 // RuntimeException, make sure we grab the actual source. 448 if (re.getCause() instanceof LooperException) { 449 throw ((LooperException) re.getCause()).getSource(); 450 } else { 451 throw re.getCause(); 452 } 453 } finally { 454 m.recycle(); 455 } 456 return ret[0]; 457 } finally { 458 if (set) { 459 mLooperHolder.mTestableLooper.mQueueWrapper.release(); 460 mLooperHolder.mTestableLooper.mQueueWrapper = null; 461 if (HOLD_MAIN_THREAD && mLooperHolder.mLooper == Looper.getMainLooper()) { 462 TestableInstrumentation.releaseMain(); 463 } 464 } 465 } 466 } 467 468 @Override 469 protected void finalize() throws Throwable { 470 super.finalize(); 471 if (mLooperHolder.mHandlerThread != null) { 472 mLooperHolder.mHandlerThread.quit(); 473 } 474 } 475 476 private static class LooperException extends RuntimeException { 477 private final Throwable mSource; 478 479 public LooperException(Throwable t) { 480 mSource = t; 481 } 482 483 public Throwable getSource() { 484 return mSource; 485 } 486 } 487 } 488 489 /** 490 * Callback to control the execution of messages on the looper, when set with 491 * {@link #setMessageHandler(MessageHandler)} then {@link #onMessageHandled(Message)} 492 * will get called back for every message processed on the {@link TestableLooper}. 493 */ 494 public interface MessageHandler { 495 /** 496 * Return true to have the message executed and delivered to target. 497 * Return false to not execute the message and stop executing messages. 498 */ 499 boolean onMessageHandled(Message m); 500 } 501 } 502