From e1e1c7317e5e5d61daf90cd2f2861305f19890b7 Mon Sep 17 00:00:00 2001 From: Etan Cohen Date: Thu, 7 Jan 2016 17:25:09 -0800 Subject: Add/extend MockLooper to handled messages posted with a delay. MockLooperAbstractTime allows moving time forward and dispatches messages per the current time. Change-Id: Ic87a609db86011b7b909353122fabe7cb440b61f --- .../src/com/android/server/wifi/MockLooper.java | 26 +- .../server/wifi/MockLooperAbstractTime.java | 125 ++++++++++ .../server/wifi/MockLooperAbstractTimeTest.java | 261 +++++++++++++++++++++ 3 files changed, 401 insertions(+), 11 deletions(-) create mode 100644 tests/wifitests/src/com/android/server/wifi/MockLooperAbstractTime.java create mode 100644 tests/wifitests/src/com/android/server/wifi/MockLooperAbstractTimeTest.java (limited to 'tests') diff --git a/tests/wifitests/src/com/android/server/wifi/MockLooper.java b/tests/wifitests/src/com/android/server/wifi/MockLooper.java index 0194f3700..2016110a6 100644 --- a/tests/wifitests/src/com/android/server/wifi/MockLooper.java +++ b/tests/wifitests/src/com/android/server/wifi/MockLooper.java @@ -33,7 +33,7 @@ import java.lang.reflect.Method; * Creating a MockLooper will also install it as the looper for the current thread */ public class MockLooper { - private final Looper mLooper; + protected final Looper mLooper; private static final Constructor LOOPER_CONSTRUCTOR; private static final Field THREAD_LOCAL_LOOPER_FIELD; @@ -53,19 +53,23 @@ public class MockLooper { } - public MockLooper() throws Exception { - mLooper = LOOPER_CONSTRUCTOR.newInstance(false); + public MockLooper() { + try { + mLooper = LOOPER_CONSTRUCTOR.newInstance(false); - ThreadLocal threadLocalLooper = - (ThreadLocal) THREAD_LOCAL_LOOPER_FIELD.get(null); - threadLocalLooper.set(mLooper); + ThreadLocal threadLocalLooper = (ThreadLocal) THREAD_LOCAL_LOOPER_FIELD + .get(null); + threadLocalLooper.set(mLooper); + } catch (IllegalAccessException | InstantiationException | InvocationTargetException e) { + throw new RuntimeException("Reflection error constructing or accessing looper", e); + } } public Looper getLooper() { return mLooper; } - private Message messageQueueNext() { + protected Message messageQueueNext() { try { return (Message) MESSAGE_QUEUE_NEXT_METHOD.invoke(mLooper.getQueue()); } catch (IllegalAccessException | InvocationTargetException e) { @@ -76,7 +80,7 @@ public class MockLooper { /** * @return true if there are pending messages in the message queue */ - public boolean hasMessage() { + public boolean isIdle() { return !mLooper.getQueue().isIdle(); } @@ -84,7 +88,7 @@ public class MockLooper { * @return the next message in the Looper's message queue or null if there is none */ public Message nextMessage() { - if (hasMessage()) { + if (isIdle()) { return messageQueueNext(); } else { return null; @@ -96,7 +100,7 @@ public class MockLooper { * Asserts that there is a message in the queue */ public void dispatchNext() { - assertTrue(hasMessage()); + assertTrue(isIdle()); Message msg = messageQueueNext(); if (msg == null) { return; @@ -111,7 +115,7 @@ public class MockLooper { */ public int dispatchAll() { int count = 0; - while (hasMessage()) { + while (isIdle()) { dispatchNext(); ++count; } diff --git a/tests/wifitests/src/com/android/server/wifi/MockLooperAbstractTime.java b/tests/wifitests/src/com/android/server/wifi/MockLooperAbstractTime.java new file mode 100644 index 000000000..17abd2051 --- /dev/null +++ b/tests/wifitests/src/com/android/server/wifi/MockLooperAbstractTime.java @@ -0,0 +1,125 @@ +/* + * Copyright (C) 2016 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.server.wifi; + +import android.os.Message; +import android.os.MessageQueue; +import android.os.SystemClock; + +import java.lang.reflect.Field; +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; + +/** + * Creates a looper whose message queue can be manipulated similarly to that of + * MockLooper. Additionally, can handle abstract time - i.e. can specify to the + * queue time changes to handle messages posted with a delay. + */ +public class MockLooperAbstractTime extends MockLooper { + private static final Field MESSAGE_QUEUE_MESSAGES_FIELD; + private static final Field MESSAGE_NEXT_FIELD; + private static final Field MESSAGE_WHEN_FIELD; + private static final Method MESSAGE_MARK_IN_USE_METHOD; + + static { + try { + MESSAGE_QUEUE_MESSAGES_FIELD = MessageQueue.class.getDeclaredField("mMessages"); + MESSAGE_QUEUE_MESSAGES_FIELD.setAccessible(true); + MESSAGE_NEXT_FIELD = Message.class.getDeclaredField("next"); + MESSAGE_NEXT_FIELD.setAccessible(true); + MESSAGE_WHEN_FIELD = Message.class.getDeclaredField("when"); + MESSAGE_WHEN_FIELD.setAccessible(true); + MESSAGE_MARK_IN_USE_METHOD = Message.class.getDeclaredMethod("markInUse"); + MESSAGE_MARK_IN_USE_METHOD.setAccessible(true); + } catch (NoSuchFieldException | NoSuchMethodException e) { + throw new RuntimeException("Failed to initialize MockLooperAbstractTime", e); + } + } + + private Message getMessageLinkedList() { + try { + MessageQueue queue = mLooper.getQueue(); + return (Message) MESSAGE_QUEUE_MESSAGES_FIELD.get(queue); + } catch (IllegalAccessException e) { + throw new RuntimeException("Access failed in MockLooperAbstractTime", e); + } + } + + public void moveTimeForward(long milliSeconds) { + try { + Message msg = getMessageLinkedList(); + while (msg != null) { + long updatedWhen = msg.getWhen() - milliSeconds; + if (updatedWhen < 0) { + updatedWhen = 0; + } + MESSAGE_WHEN_FIELD.set(msg, updatedWhen); + msg = (Message) MESSAGE_NEXT_FIELD.get(msg); + } + } catch (IllegalAccessException e) { + throw new RuntimeException("Access failed in MockLooperAbstractTime", e); + } + } + + @Override + protected Message messageQueueNext() { + try { + long now = SystemClock.uptimeMillis(); + + Message prevMsg = null; + Message msg = getMessageLinkedList(); + if (msg != null && msg.getTarget() == null) { + // Stalled by a barrier. Find the next asynchronous message in + // the queue. + do { + prevMsg = msg; + msg = (Message) MESSAGE_NEXT_FIELD.get(msg); + } while (msg != null && !msg.isAsynchronous()); + } + if (msg != null) { + if (now >= msg.getWhen()) { + // Got a message. + if (prevMsg != null) { + MESSAGE_NEXT_FIELD.set(prevMsg, MESSAGE_NEXT_FIELD.get(msg)); + } else { + MESSAGE_QUEUE_MESSAGES_FIELD.set(mLooper.getQueue(), + MESSAGE_NEXT_FIELD.get(msg)); + } + MESSAGE_NEXT_FIELD.set(msg, null); + MESSAGE_MARK_IN_USE_METHOD.invoke(msg); + return msg; + } + } + } catch (IllegalAccessException e) { + throw new RuntimeException("Access failed in MockLooperAbstractTime", e); + } catch (InvocationTargetException e) { + throw new RuntimeException("Access failed in MockLooperAbstractTime", e); + } + + return null; + } + + /** + * @return true if there are pending messages in the message queue + */ + @Override + public boolean isIdle() { + Message messageList = getMessageLinkedList(); + + return messageList != null && SystemClock.uptimeMillis() >= messageList.getWhen(); + } +} diff --git a/tests/wifitests/src/com/android/server/wifi/MockLooperAbstractTimeTest.java b/tests/wifitests/src/com/android/server/wifi/MockLooperAbstractTimeTest.java new file mode 100644 index 000000000..8167ead97 --- /dev/null +++ b/tests/wifitests/src/com/android/server/wifi/MockLooperAbstractTimeTest.java @@ -0,0 +1,261 @@ +/* + * Copyright (C) 2016 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License + */ + +package com.android.server.wifi; + +import static org.hamcrest.core.IsEqual.equalTo; +import static org.mockito.Matchers.any; +import static org.mockito.Mockito.inOrder; +import static org.mockito.Mockito.never; +import static org.mockito.Mockito.spy; + +import android.os.Handler; +import android.os.Message; +import android.test.suitebuilder.annotation.SmallTest; + +import org.junit.Before; +import org.junit.Rule; +import org.junit.Test; +import org.junit.rules.ErrorCollector; +import org.mockito.ArgumentCaptor; +import org.mockito.InOrder; +import org.mockito.MockitoAnnotations; + +/** + * Test MockLooperAbstractTime which provides control over "time". Note that + * real-time is being used as well. Therefore small time increments are NOT + * reliable. All tests are in "K" units (i.e. *1000). + */ + +@SmallTest +public class MockLooperAbstractTimeTest { + private MockLooperAbstractTime mMockLooper; + private Handler mHandler; + private Handler mHandlerSpy; + + @Rule + public ErrorCollector collector = new ErrorCollector(); + + @Before + public void setUp() throws Exception { + MockitoAnnotations.initMocks(this); + + mMockLooper = new MockLooperAbstractTime(); + mHandler = new Handler(mMockLooper.getLooper()); + mHandlerSpy = spy(mHandler); + } + + /** + * Basic test with no time stamps: dispatch 4 messages, check that all 4 + * delivered (in correct order). + */ + @Test + public void testNoTimeMovement() { + final int messageA = 1; + final int messageB = 2; + final int messageC = 3; + + InOrder inOrder = inOrder(mHandlerSpy); + ArgumentCaptor messageCaptor = ArgumentCaptor.forClass(Message.class); + + mHandlerSpy.sendMessage(mHandler.obtainMessage(messageA)); + mHandlerSpy.sendMessage(mHandler.obtainMessage(messageA)); + mHandlerSpy.sendMessage(mHandler.obtainMessage(messageB)); + mHandlerSpy.sendMessage(mHandler.obtainMessage(messageC)); + mMockLooper.dispatchAll(); + + inOrder.verify(mHandlerSpy).handleMessage(messageCaptor.capture()); + collector.checkThat("1: messageA", messageA, equalTo(messageCaptor.getValue().what)); + inOrder.verify(mHandlerSpy).handleMessage(messageCaptor.capture()); + collector.checkThat("2: messageA", messageA, equalTo(messageCaptor.getValue().what)); + inOrder.verify(mHandlerSpy).handleMessage(messageCaptor.capture()); + collector.checkThat("3: messageB", messageB, equalTo(messageCaptor.getValue().what)); + inOrder.verify(mHandlerSpy).handleMessage(messageCaptor.capture()); + collector.checkThat("4: messageC", messageC, equalTo(messageCaptor.getValue().what)); + + inOrder.verify(mHandlerSpy, never()).handleMessage(any(Message.class)); + } + + /** + * Test message sequence: A, B, C@5K, A@10K. Don't move time. + *

+ * Expected: only get A, B + */ + @Test + public void testDelayedDispatchNoTimeMove() { + final int messageA = 1; + final int messageB = 2; + final int messageC = 3; + + InOrder inOrder = inOrder(mHandlerSpy); + ArgumentCaptor messageCaptor = ArgumentCaptor.forClass(Message.class); + + mHandlerSpy.sendMessage(mHandler.obtainMessage(messageA)); + mHandlerSpy.sendMessage(mHandler.obtainMessage(messageB)); + mHandlerSpy.sendMessageDelayed(mHandler.obtainMessage(messageC), 5000); + mHandlerSpy.sendMessageDelayed(mHandler.obtainMessage(messageA), 10000); + mMockLooper.dispatchAll(); + + inOrder.verify(mHandlerSpy).handleMessage(messageCaptor.capture()); + collector.checkThat("1: messageA", messageA, equalTo(messageCaptor.getValue().what)); + inOrder.verify(mHandlerSpy).handleMessage(messageCaptor.capture()); + collector.checkThat("2: messageB", messageB, equalTo(messageCaptor.getValue().what)); + + inOrder.verify(mHandlerSpy, never()).handleMessage(any(Message.class)); + } + + /** + * Test message sequence: A, B, C@5K, A@10K, Advance time by 5K. + *

+ * Expected: only get A, B, C + */ + @Test + public void testDelayedDispatchAdvanceTimeOnce() { + final int messageA = 1; + final int messageB = 2; + final int messageC = 3; + + InOrder inOrder = inOrder(mHandlerSpy); + ArgumentCaptor messageCaptor = ArgumentCaptor.forClass(Message.class); + + mHandlerSpy.sendMessage(mHandler.obtainMessage(messageA)); + mHandlerSpy.sendMessage(mHandler.obtainMessage(messageB)); + mHandlerSpy.sendMessageDelayed(mHandler.obtainMessage(messageC), 5000); + mHandlerSpy.sendMessageDelayed(mHandler.obtainMessage(messageA), 10000); + mMockLooper.moveTimeForward(5000); + mMockLooper.dispatchAll(); + + inOrder.verify(mHandlerSpy).handleMessage(messageCaptor.capture()); + collector.checkThat("1: messageA", messageA, equalTo(messageCaptor.getValue().what)); + inOrder.verify(mHandlerSpy).handleMessage(messageCaptor.capture()); + collector.checkThat("2: messageB", messageB, equalTo(messageCaptor.getValue().what)); + inOrder.verify(mHandlerSpy).handleMessage(messageCaptor.capture()); + collector.checkThat("3: messageC", messageC, equalTo(messageCaptor.getValue().what)); + + inOrder.verify(mHandlerSpy, never()).handleMessage(any(Message.class)); + } + + /** + * Test message sequence: A, B, C@5K, Advance time by 4K, A@1K, B@2K Advance + * time by 1K. + *

+ * Expected: get A, B, C, A + */ + @Test + public void testDelayedDispatchAdvanceTimeTwice() { + final int messageA = 1; + final int messageB = 2; + final int messageC = 3; + + InOrder inOrder = inOrder(mHandlerSpy); + ArgumentCaptor messageCaptor = ArgumentCaptor.forClass(Message.class); + + mHandlerSpy.sendMessage(mHandler.obtainMessage(messageA)); + mHandlerSpy.sendMessage(mHandler.obtainMessage(messageB)); + mHandlerSpy.sendMessageDelayed(mHandler.obtainMessage(messageC), 5000); + mMockLooper.moveTimeForward(4000); + mHandlerSpy.sendMessageDelayed(mHandler.obtainMessage(messageA), 1000); + mHandlerSpy.sendMessageDelayed(mHandler.obtainMessage(messageB), 2000); + mMockLooper.moveTimeForward(1000); + mMockLooper.dispatchAll(); + + inOrder.verify(mHandlerSpy).handleMessage(messageCaptor.capture()); + collector.checkThat("1: messageA", messageA, equalTo(messageCaptor.getValue().what)); + inOrder.verify(mHandlerSpy).handleMessage(messageCaptor.capture()); + collector.checkThat("2: messageB", messageB, equalTo(messageCaptor.getValue().what)); + inOrder.verify(mHandlerSpy).handleMessage(messageCaptor.capture()); + collector.checkThat("3: messageC", messageC, equalTo(messageCaptor.getValue().what)); + inOrder.verify(mHandlerSpy).handleMessage(messageCaptor.capture()); + collector.checkThat("4: messageA", messageA, equalTo(messageCaptor.getValue().what)); + + inOrder.verify(mHandlerSpy, never()).handleMessage(any(Message.class)); + } + + /** + * Test message sequence: A, B, C@5K, Advance time by 4K, A@5K, B@2K Advance + * time by 3K. + *

+ * Expected: get A, B, C, B + */ + @Test + public void testDelayedDispatchReverseOrder() { + final int messageA = 1; + final int messageB = 2; + final int messageC = 3; + + InOrder inOrder = inOrder(mHandlerSpy); + ArgumentCaptor messageCaptor = ArgumentCaptor.forClass(Message.class); + + mHandlerSpy.sendMessage(mHandler.obtainMessage(messageA)); + mHandlerSpy.sendMessage(mHandler.obtainMessage(messageB)); + mHandlerSpy.sendMessageDelayed(mHandler.obtainMessage(messageC), 5000); + mMockLooper.moveTimeForward(4000); + mHandlerSpy.sendMessageDelayed(mHandler.obtainMessage(messageA), 5000); + mHandlerSpy.sendMessageDelayed(mHandler.obtainMessage(messageB), 2000); + mMockLooper.moveTimeForward(3000); + mMockLooper.dispatchAll(); + + inOrder.verify(mHandlerSpy).handleMessage(messageCaptor.capture()); + collector.checkThat("1: messageA", messageA, equalTo(messageCaptor.getValue().what)); + inOrder.verify(mHandlerSpy).handleMessage(messageCaptor.capture()); + collector.checkThat("2: messageB", messageB, equalTo(messageCaptor.getValue().what)); + inOrder.verify(mHandlerSpy).handleMessage(messageCaptor.capture()); + collector.checkThat("3: messageC", messageC, equalTo(messageCaptor.getValue().what)); + inOrder.verify(mHandlerSpy).handleMessage(messageCaptor.capture()); + collector.checkThat("4: messageB", messageB, equalTo(messageCaptor.getValue().what)); + + inOrder.verify(mHandlerSpy, never()).handleMessage(any(Message.class)); + } + + /** + * Test message sequence: A, B, C@5K, Advance time by 4K, dispatch all, + * A@5K, B@2K Advance time by 3K, dispatch all. + *

+ * Expected: get A, B after first dispatch; then C, B after second dispatch + */ + @Test + public void testDelayedDispatchAllMultipleTimes() { + final int messageA = 1; + final int messageB = 2; + final int messageC = 3; + + InOrder inOrder = inOrder(mHandlerSpy); + ArgumentCaptor messageCaptor = ArgumentCaptor.forClass(Message.class); + + mHandlerSpy.sendMessage(mHandler.obtainMessage(messageA)); + mHandlerSpy.sendMessage(mHandler.obtainMessage(messageB)); + mHandlerSpy.sendMessageDelayed(mHandler.obtainMessage(messageC), 5000); + mMockLooper.moveTimeForward(4000); + mMockLooper.dispatchAll(); + + inOrder.verify(mHandlerSpy).handleMessage(messageCaptor.capture()); + collector.checkThat("1: messageA", messageA, equalTo(messageCaptor.getValue().what)); + inOrder.verify(mHandlerSpy).handleMessage(messageCaptor.capture()); + collector.checkThat("2: messageB", messageB, equalTo(messageCaptor.getValue().what)); + + mHandlerSpy.sendMessageDelayed(mHandler.obtainMessage(messageA), 5000); + mHandlerSpy.sendMessageDelayed(mHandler.obtainMessage(messageB), 2000); + mMockLooper.moveTimeForward(3000); + mMockLooper.dispatchAll(); + + inOrder.verify(mHandlerSpy).handleMessage(messageCaptor.capture()); + collector.checkThat("3: messageC", messageC, equalTo(messageCaptor.getValue().what)); + inOrder.verify(mHandlerSpy).handleMessage(messageCaptor.capture()); + collector.checkThat("4: messageB", messageB, equalTo(messageCaptor.getValue().what)); + + inOrder.verify(mHandlerSpy, never()).handleMessage(any(Message.class)); + } +} -- cgit v1.2.3