• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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