Loading packages/SystemUI/tests/src/com/android/systemui/DependencyTest.java +6 −1 Original line number Diff line number Diff line Loading @@ -22,8 +22,13 @@ import static org.mockito.Mockito.verify; import android.os.Looper; import com.android.systemui.ConfigurationChangedReceiver; import com.android.systemui.Dependency; import com.android.systemui.Dumpable; import com.android.systemui.SysuiTestCase; import com.android.systemui.statusbar.policy.FlashlightController; import org.junit.Assert; import org.junit.Test; import java.io.PrintWriter; Loading @@ -34,7 +39,7 @@ public class DependencyTest extends SysuiTestCase { public void testClassDependency() { FlashlightController f = mock(FlashlightController.class); injectTestDependency(FlashlightController.class, f); assertEquals(f, Dependency.get(FlashlightController.class)); Assert.assertEquals(f, Dependency.get(FlashlightController.class)); } @Test Loading packages/SystemUI/tests/src/com/android/systemui/SysUIRunner.java 0 → 100644 +87 −0 Original line number Diff line number Diff line /* * Copyright (C) 2017 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.systemui; import android.support.test.internal.runner.junit4.statement.RunAfters; import android.support.test.internal.runner.junit4.statement.RunBefores; import android.support.test.internal.runner.junit4.statement.UiThreadStatement; import com.android.systemui.utils.TestableLooper.LooperStatement; import com.android.systemui.utils.TestableLooper.RunWithLooper; import org.junit.After; import org.junit.Before; import org.junit.Test; import org.junit.internal.runners.statements.FailOnTimeout; import org.junit.runners.BlockJUnit4ClassRunner; import org.junit.runners.model.FrameworkMethod; import org.junit.runners.model.InitializationError; import org.junit.runners.model.Statement; import java.util.List; public class SysUIRunner extends BlockJUnit4ClassRunner { private final long mTimeout; private final Class<?> mKlass; public SysUIRunner(Class<?> klass) throws InitializationError { super(klass); mKlass = klass; // Can't seem to get reference to timeout parameter from here, so set default to 10 mins. mTimeout = 10 * 60 * 1000; } @Override protected Statement methodInvoker(FrameworkMethod method, Object test) { return UiThreadStatement.shouldRunOnUiThread(method) ? new UiThreadStatement( methodInvokerInt(method, test), true) : methodInvokerInt(method, test); } protected Statement methodInvokerInt(FrameworkMethod method, Object test) { RunWithLooper annotation = method.getAnnotation(RunWithLooper.class); if (annotation == null) annotation = mKlass.getAnnotation(RunWithLooper.class); if (annotation != null) { return new LooperStatement(super.methodInvoker(method, test), annotation.setAsMainLooper(), test); } return super.methodInvoker(method, test); } protected Statement withBefores(FrameworkMethod method, Object target, Statement statement) { List befores = this.getTestClass().getAnnotatedMethods(Before.class); return befores.isEmpty() ? statement : new RunBefores(method, statement, befores, target); } protected Statement withAfters(FrameworkMethod method, Object target, Statement statement) { List afters = this.getTestClass().getAnnotatedMethods(After.class); return afters.isEmpty() ? statement : new RunAfters(method, statement, afters, target); } protected Statement withPotentialTimeout(FrameworkMethod method, Object test, Statement next) { long timeout = this.getTimeout(method.getAnnotation(Test.class)); if (timeout <= 0L && mTimeout > 0L) { timeout = mTimeout; } return timeout <= 0L ? next : new FailOnTimeout(next, timeout); } private long getTimeout(Test annotation) { return annotation == null ? 0L : annotation.timeout(); } } packages/SystemUI/tests/src/com/android/systemui/utils/TestableLooper.java 0 → 100644 +260 −0 Original line number Diff line number Diff line /* * Copyright (C) 2017 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.systemui.utils; import android.os.Handler; import android.os.Looper; import android.os.Message; import android.os.MessageQueue; import android.util.ArrayMap; import org.junit.runners.model.Statement; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; import java.lang.reflect.Constructor; import java.lang.reflect.Field; import java.lang.reflect.Method; import java.util.Map; /** * Creates a looper on the current thread with control over if/when messages are * executed. Warning: This class works through some reflection and may break/need * to be updated from time to time. */ public class TestableLooper { private final Method mNext; private final Method mRecycleUnchecked; private Looper mLooper; private MessageQueue mQueue; private boolean mMain; private Object mOriginalMain; private MessageHandler mMessageHandler; private int mParsedCount; private Handler mHandler; private Message mEmptyMessage; public TestableLooper() throws Exception { this(true); } public TestableLooper(boolean setMyLooper) throws Exception { setupQueue(setMyLooper); mNext = mQueue.getClass().getDeclaredMethod("next"); mNext.setAccessible(true); mRecycleUnchecked = Message.class.getDeclaredMethod("recycleUnchecked"); mRecycleUnchecked.setAccessible(true); } public Looper getLooper() { return mLooper; } private void clearLooper() throws NoSuchFieldException, IllegalAccessException { Field field = Looper.class.getDeclaredField("sThreadLocal"); field.setAccessible(true); ThreadLocal<Looper> sThreadLocal = (ThreadLocal<Looper>) field.get(null); sThreadLocal.set(null); } private boolean setForCurrentThread() throws NoSuchFieldException, IllegalAccessException { if (Looper.myLooper() != mLooper) { Field field = Looper.class.getDeclaredField("sThreadLocal"); field.setAccessible(true); ThreadLocal<Looper> sThreadLocal = (ThreadLocal<Looper>) field.get(null); sThreadLocal.set(mLooper); return true; } return false; } private void setupQueue(boolean setMyLooper) throws Exception { if (setMyLooper) { clearLooper(); Looper.prepare(); mLooper = Looper.myLooper(); } else { Constructor<Looper> constructor = Looper.class.getDeclaredConstructor( boolean.class); constructor.setAccessible(true); mLooper = constructor.newInstance(true); } mQueue = mLooper.getQueue(); mHandler = new Handler(mLooper); } public void setAsMainLooper() throws NoSuchFieldException, IllegalAccessException { mMain = true; setAsMainInt(); } private void setAsMainInt() throws NoSuchFieldException, IllegalAccessException { Field field = mLooper.getClass().getDeclaredField("sMainLooper"); field.setAccessible(true); if (mOriginalMain == null) { mOriginalMain = field.get(null); } field.set(null, mLooper); } /** * Must be called if setAsMainLooper is called to restore the main looper when the * test is complete, otherwise the main looper will not be available for any subsequent * tests. */ public void destroy() throws NoSuchFieldException, IllegalAccessException { if (Looper.myLooper() == mLooper) { clearLooper(); } if (mMain && mOriginalMain != null) { Field field = mLooper.getClass().getDeclaredField("sMainLooper"); field.setAccessible(true); field.set(null, mOriginalMain); mOriginalMain = null; } } public void setMessageHandler(MessageHandler handler) { mMessageHandler = handler; } /** * Parse num messages from the message queue. * * @param num Number of messages to parse */ public int processMessages(int num) { for (int i = 0; i < num; i++) { if (!parseMessageInt()) { return i + 1; } } return num; } public void processAllMessages() { while (processQueuedMessages() != 0) ; } private int processQueuedMessages() { int count = 0; mEmptyMessage = mHandler.obtainMessage(1); mHandler.sendMessageDelayed(mEmptyMessage, 1); while (parseMessageInt()) count++; return count; } private boolean parseMessageInt() { try { Message result = (Message) mNext.invoke(mQueue); if (result != null) { // This is a break message. if (result == mEmptyMessage) { mRecycleUnchecked.invoke(result); return false; } if (mMessageHandler != null) { if (mMessageHandler.onMessageHandled(result)) { result.getTarget().dispatchMessage(result); mRecycleUnchecked.invoke(result); } else { mRecycleUnchecked.invoke(result); // Message handler indicated it doesn't want us to continue. return false; } } else { result.getTarget().dispatchMessage(result); mRecycleUnchecked.invoke(result); } } else { // No messages, don't continue parsing return false; } } catch (Exception e) { throw new RuntimeException(e); } return true; } /** * Runs an executable with myLooper set and processes all messages added. */ public void runWithLooper(RunnableWithException runnable) throws Exception { boolean set = setForCurrentThread(); runnable.run(); processAllMessages(); if (set) clearLooper(); } public interface RunnableWithException { void run() throws Exception; } @Retention(RetentionPolicy.RUNTIME) @Target({ElementType.METHOD, ElementType.TYPE}) public @interface RunWithLooper { boolean setAsMainLooper() default false; } private static final Map<Object, TestableLooper> sLoopers = new ArrayMap<>(); public static TestableLooper get(Object test) { return sLoopers.get(test); } public static class LooperStatement extends Statement { private final boolean mSetAsMain; private final Statement mBase; private final TestableLooper mLooper; public LooperStatement(Statement base, boolean setAsMain, Object test) { mBase = base; try { mLooper = new TestableLooper(false); sLoopers.put(test, mLooper); mSetAsMain = setAsMain; } catch (Exception e) { throw new RuntimeException(e); } } @Override public void evaluate() throws Throwable { mLooper.setForCurrentThread(); if (mSetAsMain) { mLooper.setAsMainLooper(); } mBase.evaluate(); mLooper.destroy(); } } public interface MessageHandler { /** * Return true to have the message executed and delivered to target. * Return false to not execute the message and stop executing messages. */ boolean onMessageHandled(Message m); } } packages/SystemUI/tests/src/com/android/systemui/utils/TestableLooperTest.java 0 → 100644 +184 −0 Original line number Diff line number Diff line /* * Copyright (C) 2017 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.systemui.utils; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertNotEquals; import static org.mockito.Matchers.any; import static org.mockito.Matchers.eq; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.never; import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; import android.os.Handler; import android.os.Looper; import android.os.Message; import com.android.systemui.SysUIRunner; import com.android.systemui.SysuiTestCase; import com.android.systemui.utils.TestableLooper.MessageHandler; import com.android.systemui.utils.TestableLooper.RunWithLooper; import org.junit.After; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; @RunWith(SysUIRunner.class) @RunWithLooper public class TestableLooperTest extends SysuiTestCase { private TestableLooper mTestableLooper; @Before public void setup() throws Exception { mTestableLooper = TestableLooper.get(this); } @After public void tearDown() throws Exception { mTestableLooper.destroy(); } @Test public void testMessageExecuted() throws Exception { Handler h = new Handler(); Runnable r = mock(Runnable.class); h.post(r); verify(r, never()).run(); mTestableLooper.processAllMessages(); verify(r).run(); } @Test public void testMessageCallback() throws Exception { Handler h = new Handler(); Message m = h.obtainMessage(3); Runnable r = mock(Runnable.class); MessageHandler messageHandler = mock(MessageHandler.class); when(messageHandler.onMessageHandled(any())).thenReturn(false); mTestableLooper.setMessageHandler(messageHandler); m.sendToTarget(); h.post(r); mTestableLooper.processAllMessages(); verify(messageHandler).onMessageHandled(eq(m)); // This should never be run becaus the mock returns false on the first message, and // the second will get skipped. verify(r, never()).run(); } @Test public void testProcessNumberOfMessages() throws Exception { Handler h = new Handler(); Runnable r = mock(Runnable.class); h.post(r); h.post(r); h.post(r); mTestableLooper.processMessages(2); verify(r, times(2)).run(); } @Test public void testProcessAllMessages() throws Exception { Handler h = new Handler(); Runnable r = mock(Runnable.class); Runnable poster = () -> h.post(r); h.post(poster); mTestableLooper.processAllMessages(); verify(r).run(); } @Test public void test3Chain() throws Exception { Handler h = new Handler(); Runnable r = mock(Runnable.class); Runnable poster = () -> h.post(r); Runnable poster2 = () -> h.post(poster); h.post(poster2); mTestableLooper.processAllMessages(); verify(r).run(); } @Test public void testProcessAllMessages_2Messages() throws Exception { Handler h = new Handler(); Runnable r = mock(Runnable.class); Runnable r2 = mock(Runnable.class); h.post(r); h.post(r2); mTestableLooper.processAllMessages(); verify(r).run(); verify(r2).run(); } @Test public void testMainLooper() throws Exception { assertNotEquals(Looper.myLooper(), Looper.getMainLooper()); Looper originalMain = Looper.getMainLooper(); mTestableLooper.setAsMainLooper(); assertEquals(Looper.myLooper(), Looper.getMainLooper()); Runnable r = mock(Runnable.class); new Handler(Looper.getMainLooper()).post(r); mTestableLooper.processAllMessages(); verify(r).run(); mTestableLooper.destroy(); assertEquals(originalMain, Looper.getMainLooper()); } @Test public void testNotMyLooper() throws Exception { TestableLooper looper = new TestableLooper(false); assertEquals(Looper.myLooper(), mTestableLooper.getLooper()); assertNotEquals(Looper.myLooper(), looper.getLooper()); Runnable r = mock(Runnable.class); Runnable r2 = mock(Runnable.class); new Handler().post(r); new Handler(looper.getLooper()).post(r2); looper.processAllMessages(); verify(r2).run(); verify(r, never()).run(); mTestableLooper.processAllMessages(); verify(r).run(); } @Test public void testNonMainLooperAnnotation() { assertNotEquals(Looper.myLooper(), Looper.getMainLooper()); } @Test @RunWithLooper(setAsMainLooper = true) public void testMainLooperAnnotation() { assertEquals(Looper.myLooper(), Looper.getMainLooper()); } } Loading
packages/SystemUI/tests/src/com/android/systemui/DependencyTest.java +6 −1 Original line number Diff line number Diff line Loading @@ -22,8 +22,13 @@ import static org.mockito.Mockito.verify; import android.os.Looper; import com.android.systemui.ConfigurationChangedReceiver; import com.android.systemui.Dependency; import com.android.systemui.Dumpable; import com.android.systemui.SysuiTestCase; import com.android.systemui.statusbar.policy.FlashlightController; import org.junit.Assert; import org.junit.Test; import java.io.PrintWriter; Loading @@ -34,7 +39,7 @@ public class DependencyTest extends SysuiTestCase { public void testClassDependency() { FlashlightController f = mock(FlashlightController.class); injectTestDependency(FlashlightController.class, f); assertEquals(f, Dependency.get(FlashlightController.class)); Assert.assertEquals(f, Dependency.get(FlashlightController.class)); } @Test Loading
packages/SystemUI/tests/src/com/android/systemui/SysUIRunner.java 0 → 100644 +87 −0 Original line number Diff line number Diff line /* * Copyright (C) 2017 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.systemui; import android.support.test.internal.runner.junit4.statement.RunAfters; import android.support.test.internal.runner.junit4.statement.RunBefores; import android.support.test.internal.runner.junit4.statement.UiThreadStatement; import com.android.systemui.utils.TestableLooper.LooperStatement; import com.android.systemui.utils.TestableLooper.RunWithLooper; import org.junit.After; import org.junit.Before; import org.junit.Test; import org.junit.internal.runners.statements.FailOnTimeout; import org.junit.runners.BlockJUnit4ClassRunner; import org.junit.runners.model.FrameworkMethod; import org.junit.runners.model.InitializationError; import org.junit.runners.model.Statement; import java.util.List; public class SysUIRunner extends BlockJUnit4ClassRunner { private final long mTimeout; private final Class<?> mKlass; public SysUIRunner(Class<?> klass) throws InitializationError { super(klass); mKlass = klass; // Can't seem to get reference to timeout parameter from here, so set default to 10 mins. mTimeout = 10 * 60 * 1000; } @Override protected Statement methodInvoker(FrameworkMethod method, Object test) { return UiThreadStatement.shouldRunOnUiThread(method) ? new UiThreadStatement( methodInvokerInt(method, test), true) : methodInvokerInt(method, test); } protected Statement methodInvokerInt(FrameworkMethod method, Object test) { RunWithLooper annotation = method.getAnnotation(RunWithLooper.class); if (annotation == null) annotation = mKlass.getAnnotation(RunWithLooper.class); if (annotation != null) { return new LooperStatement(super.methodInvoker(method, test), annotation.setAsMainLooper(), test); } return super.methodInvoker(method, test); } protected Statement withBefores(FrameworkMethod method, Object target, Statement statement) { List befores = this.getTestClass().getAnnotatedMethods(Before.class); return befores.isEmpty() ? statement : new RunBefores(method, statement, befores, target); } protected Statement withAfters(FrameworkMethod method, Object target, Statement statement) { List afters = this.getTestClass().getAnnotatedMethods(After.class); return afters.isEmpty() ? statement : new RunAfters(method, statement, afters, target); } protected Statement withPotentialTimeout(FrameworkMethod method, Object test, Statement next) { long timeout = this.getTimeout(method.getAnnotation(Test.class)); if (timeout <= 0L && mTimeout > 0L) { timeout = mTimeout; } return timeout <= 0L ? next : new FailOnTimeout(next, timeout); } private long getTimeout(Test annotation) { return annotation == null ? 0L : annotation.timeout(); } }
packages/SystemUI/tests/src/com/android/systemui/utils/TestableLooper.java 0 → 100644 +260 −0 Original line number Diff line number Diff line /* * Copyright (C) 2017 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.systemui.utils; import android.os.Handler; import android.os.Looper; import android.os.Message; import android.os.MessageQueue; import android.util.ArrayMap; import org.junit.runners.model.Statement; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; import java.lang.reflect.Constructor; import java.lang.reflect.Field; import java.lang.reflect.Method; import java.util.Map; /** * Creates a looper on the current thread with control over if/when messages are * executed. Warning: This class works through some reflection and may break/need * to be updated from time to time. */ public class TestableLooper { private final Method mNext; private final Method mRecycleUnchecked; private Looper mLooper; private MessageQueue mQueue; private boolean mMain; private Object mOriginalMain; private MessageHandler mMessageHandler; private int mParsedCount; private Handler mHandler; private Message mEmptyMessage; public TestableLooper() throws Exception { this(true); } public TestableLooper(boolean setMyLooper) throws Exception { setupQueue(setMyLooper); mNext = mQueue.getClass().getDeclaredMethod("next"); mNext.setAccessible(true); mRecycleUnchecked = Message.class.getDeclaredMethod("recycleUnchecked"); mRecycleUnchecked.setAccessible(true); } public Looper getLooper() { return mLooper; } private void clearLooper() throws NoSuchFieldException, IllegalAccessException { Field field = Looper.class.getDeclaredField("sThreadLocal"); field.setAccessible(true); ThreadLocal<Looper> sThreadLocal = (ThreadLocal<Looper>) field.get(null); sThreadLocal.set(null); } private boolean setForCurrentThread() throws NoSuchFieldException, IllegalAccessException { if (Looper.myLooper() != mLooper) { Field field = Looper.class.getDeclaredField("sThreadLocal"); field.setAccessible(true); ThreadLocal<Looper> sThreadLocal = (ThreadLocal<Looper>) field.get(null); sThreadLocal.set(mLooper); return true; } return false; } private void setupQueue(boolean setMyLooper) throws Exception { if (setMyLooper) { clearLooper(); Looper.prepare(); mLooper = Looper.myLooper(); } else { Constructor<Looper> constructor = Looper.class.getDeclaredConstructor( boolean.class); constructor.setAccessible(true); mLooper = constructor.newInstance(true); } mQueue = mLooper.getQueue(); mHandler = new Handler(mLooper); } public void setAsMainLooper() throws NoSuchFieldException, IllegalAccessException { mMain = true; setAsMainInt(); } private void setAsMainInt() throws NoSuchFieldException, IllegalAccessException { Field field = mLooper.getClass().getDeclaredField("sMainLooper"); field.setAccessible(true); if (mOriginalMain == null) { mOriginalMain = field.get(null); } field.set(null, mLooper); } /** * Must be called if setAsMainLooper is called to restore the main looper when the * test is complete, otherwise the main looper will not be available for any subsequent * tests. */ public void destroy() throws NoSuchFieldException, IllegalAccessException { if (Looper.myLooper() == mLooper) { clearLooper(); } if (mMain && mOriginalMain != null) { Field field = mLooper.getClass().getDeclaredField("sMainLooper"); field.setAccessible(true); field.set(null, mOriginalMain); mOriginalMain = null; } } public void setMessageHandler(MessageHandler handler) { mMessageHandler = handler; } /** * Parse num messages from the message queue. * * @param num Number of messages to parse */ public int processMessages(int num) { for (int i = 0; i < num; i++) { if (!parseMessageInt()) { return i + 1; } } return num; } public void processAllMessages() { while (processQueuedMessages() != 0) ; } private int processQueuedMessages() { int count = 0; mEmptyMessage = mHandler.obtainMessage(1); mHandler.sendMessageDelayed(mEmptyMessage, 1); while (parseMessageInt()) count++; return count; } private boolean parseMessageInt() { try { Message result = (Message) mNext.invoke(mQueue); if (result != null) { // This is a break message. if (result == mEmptyMessage) { mRecycleUnchecked.invoke(result); return false; } if (mMessageHandler != null) { if (mMessageHandler.onMessageHandled(result)) { result.getTarget().dispatchMessage(result); mRecycleUnchecked.invoke(result); } else { mRecycleUnchecked.invoke(result); // Message handler indicated it doesn't want us to continue. return false; } } else { result.getTarget().dispatchMessage(result); mRecycleUnchecked.invoke(result); } } else { // No messages, don't continue parsing return false; } } catch (Exception e) { throw new RuntimeException(e); } return true; } /** * Runs an executable with myLooper set and processes all messages added. */ public void runWithLooper(RunnableWithException runnable) throws Exception { boolean set = setForCurrentThread(); runnable.run(); processAllMessages(); if (set) clearLooper(); } public interface RunnableWithException { void run() throws Exception; } @Retention(RetentionPolicy.RUNTIME) @Target({ElementType.METHOD, ElementType.TYPE}) public @interface RunWithLooper { boolean setAsMainLooper() default false; } private static final Map<Object, TestableLooper> sLoopers = new ArrayMap<>(); public static TestableLooper get(Object test) { return sLoopers.get(test); } public static class LooperStatement extends Statement { private final boolean mSetAsMain; private final Statement mBase; private final TestableLooper mLooper; public LooperStatement(Statement base, boolean setAsMain, Object test) { mBase = base; try { mLooper = new TestableLooper(false); sLoopers.put(test, mLooper); mSetAsMain = setAsMain; } catch (Exception e) { throw new RuntimeException(e); } } @Override public void evaluate() throws Throwable { mLooper.setForCurrentThread(); if (mSetAsMain) { mLooper.setAsMainLooper(); } mBase.evaluate(); mLooper.destroy(); } } public interface MessageHandler { /** * Return true to have the message executed and delivered to target. * Return false to not execute the message and stop executing messages. */ boolean onMessageHandled(Message m); } }
packages/SystemUI/tests/src/com/android/systemui/utils/TestableLooperTest.java 0 → 100644 +184 −0 Original line number Diff line number Diff line /* * Copyright (C) 2017 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.systemui.utils; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertNotEquals; import static org.mockito.Matchers.any; import static org.mockito.Matchers.eq; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.never; import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; import android.os.Handler; import android.os.Looper; import android.os.Message; import com.android.systemui.SysUIRunner; import com.android.systemui.SysuiTestCase; import com.android.systemui.utils.TestableLooper.MessageHandler; import com.android.systemui.utils.TestableLooper.RunWithLooper; import org.junit.After; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; @RunWith(SysUIRunner.class) @RunWithLooper public class TestableLooperTest extends SysuiTestCase { private TestableLooper mTestableLooper; @Before public void setup() throws Exception { mTestableLooper = TestableLooper.get(this); } @After public void tearDown() throws Exception { mTestableLooper.destroy(); } @Test public void testMessageExecuted() throws Exception { Handler h = new Handler(); Runnable r = mock(Runnable.class); h.post(r); verify(r, never()).run(); mTestableLooper.processAllMessages(); verify(r).run(); } @Test public void testMessageCallback() throws Exception { Handler h = new Handler(); Message m = h.obtainMessage(3); Runnable r = mock(Runnable.class); MessageHandler messageHandler = mock(MessageHandler.class); when(messageHandler.onMessageHandled(any())).thenReturn(false); mTestableLooper.setMessageHandler(messageHandler); m.sendToTarget(); h.post(r); mTestableLooper.processAllMessages(); verify(messageHandler).onMessageHandled(eq(m)); // This should never be run becaus the mock returns false on the first message, and // the second will get skipped. verify(r, never()).run(); } @Test public void testProcessNumberOfMessages() throws Exception { Handler h = new Handler(); Runnable r = mock(Runnable.class); h.post(r); h.post(r); h.post(r); mTestableLooper.processMessages(2); verify(r, times(2)).run(); } @Test public void testProcessAllMessages() throws Exception { Handler h = new Handler(); Runnable r = mock(Runnable.class); Runnable poster = () -> h.post(r); h.post(poster); mTestableLooper.processAllMessages(); verify(r).run(); } @Test public void test3Chain() throws Exception { Handler h = new Handler(); Runnable r = mock(Runnable.class); Runnable poster = () -> h.post(r); Runnable poster2 = () -> h.post(poster); h.post(poster2); mTestableLooper.processAllMessages(); verify(r).run(); } @Test public void testProcessAllMessages_2Messages() throws Exception { Handler h = new Handler(); Runnable r = mock(Runnable.class); Runnable r2 = mock(Runnable.class); h.post(r); h.post(r2); mTestableLooper.processAllMessages(); verify(r).run(); verify(r2).run(); } @Test public void testMainLooper() throws Exception { assertNotEquals(Looper.myLooper(), Looper.getMainLooper()); Looper originalMain = Looper.getMainLooper(); mTestableLooper.setAsMainLooper(); assertEquals(Looper.myLooper(), Looper.getMainLooper()); Runnable r = mock(Runnable.class); new Handler(Looper.getMainLooper()).post(r); mTestableLooper.processAllMessages(); verify(r).run(); mTestableLooper.destroy(); assertEquals(originalMain, Looper.getMainLooper()); } @Test public void testNotMyLooper() throws Exception { TestableLooper looper = new TestableLooper(false); assertEquals(Looper.myLooper(), mTestableLooper.getLooper()); assertNotEquals(Looper.myLooper(), looper.getLooper()); Runnable r = mock(Runnable.class); Runnable r2 = mock(Runnable.class); new Handler().post(r); new Handler(looper.getLooper()).post(r2); looper.processAllMessages(); verify(r2).run(); verify(r, never()).run(); mTestableLooper.processAllMessages(); verify(r).run(); } @Test public void testNonMainLooperAnnotation() { assertNotEquals(Looper.myLooper(), Looper.getMainLooper()); } @Test @RunWithLooper(setAsMainLooper = true) public void testMainLooperAnnotation() { assertEquals(Looper.myLooper(), Looper.getMainLooper()); } }