MockLooper.java revision 72c639e8b97067e948eca8be50dfea3173121090
1/* 2 * Copyright (C) 2015 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 17package com.android.server.wifi; 18 19import static org.junit.Assert.assertTrue; 20 21import android.os.Looper; 22import android.os.Message; 23import android.os.MessageQueue; 24 25import java.lang.reflect.Constructor; 26import java.lang.reflect.Field; 27import java.lang.reflect.InvocationTargetException; 28import java.lang.reflect.Method; 29 30/** 31 * Creates a looper whose message queue can be manipulated 32 * This allows testing code that uses a looper to dispatch messages in a deterministic manner 33 * Creating a MockLooper will also install it as the looper for the current thread 34 */ 35public class MockLooper { 36 private final Looper mLooper; 37 38 private static final Constructor<Looper> LOOPER_CONSTRUCTOR; 39 private static final Field THREAD_LOCAL_LOOPER_FIELD; 40 private static final Method MESSAGE_QUEUE_NEXT_METHOD; 41 42 static { 43 try { 44 LOOPER_CONSTRUCTOR = Looper.class.getDeclaredConstructor(Boolean.TYPE); 45 LOOPER_CONSTRUCTOR.setAccessible(true); 46 THREAD_LOCAL_LOOPER_FIELD = Looper.class.getDeclaredField("sThreadLocal"); 47 THREAD_LOCAL_LOOPER_FIELD.setAccessible(true); 48 MESSAGE_QUEUE_NEXT_METHOD = MessageQueue.class.getDeclaredMethod("next"); 49 MESSAGE_QUEUE_NEXT_METHOD.setAccessible(true); 50 } catch (NoSuchFieldException | NoSuchMethodException e) { 51 throw new RuntimeException("Failed to initialize MockLooper", e); 52 } 53 } 54 55 56 public MockLooper() throws Exception { 57 mLooper = LOOPER_CONSTRUCTOR.newInstance(false); 58 59 ThreadLocal<Looper> threadLocalLooper = 60 (ThreadLocal<Looper>) THREAD_LOCAL_LOOPER_FIELD.get(null); 61 threadLocalLooper.set(mLooper); 62 } 63 64 public Looper getLooper() { 65 return mLooper; 66 } 67 68 private Message messageQueueNext() { 69 try { 70 return (Message) MESSAGE_QUEUE_NEXT_METHOD.invoke(mLooper.getQueue()); 71 } catch (IllegalAccessException | InvocationTargetException e) { 72 throw new RuntimeException("Reflection error when getting next message", e); 73 } 74 } 75 76 /** 77 * @return true if there are pending messages in the message queue 78 */ 79 public boolean hasMessage() { 80 return !mLooper.getQueue().isIdle(); 81 } 82 83 /** 84 * @return the next message in the Looper's message queue or null if there is none 85 */ 86 public Message nextMessage() { 87 if (hasMessage()) { 88 return messageQueueNext(); 89 } else { 90 return null; 91 } 92 } 93 94 /** 95 * Dispatch the next message in the queue 96 * Asserts that there is a message in the queue 97 */ 98 public void dispatchNext() { 99 assertTrue(hasMessage()); 100 Message msg = messageQueueNext(); 101 if (msg == null) { 102 return; 103 } 104 msg.getTarget().dispatchMessage(msg); 105 } 106 107 /** 108 * Dispatch all messages currently in the queue 109 * Will not fail if there are no messages pending 110 * @return the number of messages dispatched 111 */ 112 public int dispatchAll() { 113 int count = 0; 114 while (hasMessage()) { 115 dispatchNext(); 116 ++count; 117 } 118 return count; 119 } 120} 121