Merge "Revert "Revert "Integrate new looper apis into testables""" into oc-dev

This commit is contained in:
TreeHugger Robot
2017-04-25 20:27:18 +00:00
committed by Android (Google) Code Review
5 changed files with 206 additions and 171 deletions

View File

@@ -20,7 +20,9 @@ import static org.mockito.Mockito.when;
import android.bluetooth.BluetoothAdapter;
import android.bluetooth.BluetoothProfile;
import android.testing.AndroidTestingRunner;
import android.testing.TestableLooper;
import android.testing.TestableLooper.RunWithLooper;
import com.android.settingslib.bluetooth.BluetoothEventManager;
import com.android.settingslib.bluetooth.CachedBluetoothDevice;
@@ -31,10 +33,13 @@ import com.android.systemui.SysuiTestCase;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import java.util.ArrayList;
import java.util.List;
@RunWith(AndroidTestingRunner.class)
@RunWithLooper
public class BluetoothControllerImplTest extends SysuiTestCase {
private LocalBluetoothManager mMockBluetoothManager;
@@ -47,7 +52,7 @@ public class BluetoothControllerImplTest extends SysuiTestCase {
@Before
public void setup() throws Exception {
mTestableLooper = new TestableLooper();
mTestableLooper = TestableLooper.get(this);
mMockBluetoothManager = mDependency.injectMockDependency(LocalBluetoothManager.class);
mDevices = new ArrayList<>();
mMockDeviceManager = mock(CachedBluetoothDeviceManager.class);

View File

@@ -53,9 +53,10 @@ import android.os.Process;
import android.os.UserHandle;
import android.service.notification.NotificationListenerService;
import android.service.notification.StatusBarNotification;
import android.support.test.annotation.UiThreadTest;
import android.support.test.InstrumentationRegistry;
import android.testing.AndroidTestingRunner;
import android.testing.TestableLooper;
import android.testing.TestableLooper.RunWithLooper;
import java.util.ArrayList;
import java.util.Arrays;
@@ -63,6 +64,7 @@ import java.util.List;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Mock;
import org.mockito.Mockito;
import org.mockito.MockitoAnnotations;
@@ -70,6 +72,8 @@ import org.mockito.MockitoAnnotations;
import com.android.server.lights.Light;
import com.android.server.lights.LightsManager;
@RunWith(AndroidTestingRunner.class)
@RunWithLooper
public class NotificationManagerServiceTest {
private static final long WAIT_FOR_IDLE_TIMEOUT = 2;
private static final String TEST_CHANNEL_ID = "NotificationManagerServiceTestChannelId";
@@ -109,7 +113,6 @@ public class NotificationManagerServiceTest {
}
@Before
@UiThreadTest
public void setUp() throws Exception {
MockitoAnnotations.initMocks(this);
mNotificationManagerService = new TestableNotificationManagerService(mContext);
@@ -124,7 +127,7 @@ public class NotificationManagerServiceTest {
final LightsManager mockLightsManager = mock(LightsManager.class);
when(mockLightsManager.getLight(anyInt())).thenReturn(mock(Light.class));
// Use this testable looper.
mTestableLooper = new TestableLooper(false);
mTestableLooper = TestableLooper.get(this);
mListener = mNotificationListeners.new ManagedServiceInfo(
null, new ComponentName(PKG, "test_class"), uid, true, null, 0);
@@ -165,7 +168,6 @@ public class NotificationManagerServiceTest {
}
@Test
@UiThreadTest
public void testCreateNotificationChannels_SingleChannel() throws Exception {
final NotificationChannel channel =
new NotificationChannel("id", "name", NotificationManager.IMPORTANCE_DEFAULT);
@@ -177,7 +179,6 @@ public class NotificationManagerServiceTest {
}
@Test
@UiThreadTest
public void testCreateNotificationChannels_NullChannelThrowsException() throws Exception {
try {
mBinderService.createNotificationChannels("test_pkg",
@@ -189,7 +190,6 @@ public class NotificationManagerServiceTest {
}
@Test
@UiThreadTest
public void testCreateNotificationChannels_TwoChannels() throws Exception {
final NotificationChannel channel1 =
new NotificationChannel("id1", "name", NotificationManager.IMPORTANCE_DEFAULT);
@@ -202,7 +202,6 @@ public class NotificationManagerServiceTest {
}
@Test
@UiThreadTest
public void testCreateNotificationChannels_SecondCreateDoesNotChangeImportance()
throws Exception {
final NotificationChannel channel =
@@ -221,7 +220,6 @@ public class NotificationManagerServiceTest {
}
@Test
@UiThreadTest
public void testCreateNotificationChannels_IdenticalChannelsInListIgnoresSecond()
throws Exception {
final NotificationChannel channel1 =
@@ -236,7 +234,6 @@ public class NotificationManagerServiceTest {
}
@Test
@UiThreadTest
public void testBlockedNotifications_suspended() throws Exception {
NotificationUsageStats usageStats = mock(NotificationUsageStats.class);
when(mPackageManager.isPackageSuspendedForUser(anyString(), anyInt())).thenReturn(true);
@@ -249,7 +246,6 @@ public class NotificationManagerServiceTest {
}
@Test
@UiThreadTest
public void testBlockedNotifications_blockedChannel() throws Exception {
NotificationUsageStats usageStats = mock(NotificationUsageStats.class);
when(mPackageManager.isPackageSuspendedForUser(anyString(), anyInt())).thenReturn(false);
@@ -263,7 +259,6 @@ public class NotificationManagerServiceTest {
}
@Test
@UiThreadTest
public void testBlockedNotifications_blockedApp() throws Exception {
NotificationUsageStats usageStats = mock(NotificationUsageStats.class);
when(mPackageManager.isPackageSuspendedForUser(anyString(), anyInt())).thenReturn(false);
@@ -277,7 +272,6 @@ public class NotificationManagerServiceTest {
}
@Test
@UiThreadTest
public void testEnqueueNotificationWithTag_PopulatesGetActiveNotifications() throws Exception {
mBinderService.enqueueNotificationWithTag(PKG, "opPkg", "tag", 0,
generateNotificationRecord(null).getNotification(), 0);
@@ -288,7 +282,6 @@ public class NotificationManagerServiceTest {
}
@Test
@UiThreadTest
public void testCancelNotificationImmediatelyAfterEnqueue() throws Exception {
mBinderService.enqueueNotificationWithTag(PKG, "opPkg", "tag", 0,
generateNotificationRecord(null).getNotification(), 0);
@@ -300,7 +293,6 @@ public class NotificationManagerServiceTest {
}
@Test
@UiThreadTest
public void testCancelNotificationWhilePostedAndEnqueued() throws Exception {
mBinderService.enqueueNotificationWithTag(PKG, "opPkg", "tag", 0,
generateNotificationRecord(null).getNotification(), 0);
@@ -315,7 +307,6 @@ public class NotificationManagerServiceTest {
}
@Test
@UiThreadTest
public void testCancelNotificationsFromListenerImmediatelyAfterEnqueue() throws Exception {
final StatusBarNotification sbn = generateNotificationRecord(null).sbn;
mBinderService.enqueueNotificationWithTag(PKG, "opPkg", "tag",
@@ -328,7 +319,6 @@ public class NotificationManagerServiceTest {
}
@Test
@UiThreadTest
public void testCancelAllNotificationsImmediatelyAfterEnqueue() throws Exception {
final StatusBarNotification sbn = generateNotificationRecord(null).sbn;
mBinderService.enqueueNotificationWithTag(PKG, "opPkg", "tag",
@@ -341,7 +331,6 @@ public class NotificationManagerServiceTest {
}
@Test
@UiThreadTest
public void testCancelAllNotifications_IgnoreForegroundService() throws Exception {
final StatusBarNotification sbn = generateNotificationRecord(null).sbn;
sbn.getNotification().flags |= Notification.FLAG_FOREGROUND_SERVICE;
@@ -355,7 +344,6 @@ public class NotificationManagerServiceTest {
}
@Test
@UiThreadTest
public void testCancelAllNotifications_IgnoreOtherPackages() throws Exception {
final StatusBarNotification sbn = generateNotificationRecord(null).sbn;
sbn.getNotification().flags |= Notification.FLAG_FOREGROUND_SERVICE;
@@ -369,7 +357,6 @@ public class NotificationManagerServiceTest {
}
@Test
@UiThreadTest
public void testCancelAllNotifications_NullPkgRemovesAll() throws Exception {
final StatusBarNotification sbn = generateNotificationRecord(null).sbn;
mBinderService.enqueueNotificationWithTag(PKG, "opPkg", "tag",
@@ -382,7 +369,6 @@ public class NotificationManagerServiceTest {
}
@Test
@UiThreadTest
public void testCancelAllNotifications_NullPkgIgnoresUserAllNotifications() throws Exception {
final StatusBarNotification sbn = generateNotificationRecord(null).sbn;
mBinderService.enqueueNotificationWithTag(PKG, "opPkg", "tag",
@@ -396,7 +382,6 @@ public class NotificationManagerServiceTest {
}
@Test
@UiThreadTest
public void testRemoveForegroundServiceFlag_ImmediatelyAfterEnqueue() throws Exception {
final StatusBarNotification sbn = generateNotificationRecord(null).sbn;
sbn.getNotification().flags |= Notification.FLAG_FOREGROUND_SERVICE;
@@ -411,7 +396,6 @@ public class NotificationManagerServiceTest {
}
@Test
@UiThreadTest
public void testTvExtenderChannelOverride_onTv() throws Exception {
mNotificationManagerService.setIsTelevision(true);
mNotificationManagerService.setRankingHelper(mRankingHelper);
@@ -427,7 +411,6 @@ public class NotificationManagerServiceTest {
}
@Test
@UiThreadTest
public void testTvExtenderChannelOverride_notOnTv() throws Exception {
mNotificationManagerService.setIsTelevision(false);
mNotificationManagerService.setRankingHelper(mRankingHelper);
@@ -443,7 +426,6 @@ public class NotificationManagerServiceTest {
}
@Test
@UiThreadTest
public void testCreateChannelNotifyListener() throws Exception {
List<String> associations = new ArrayList<>();
associations.add("a");
@@ -469,7 +451,6 @@ public class NotificationManagerServiceTest {
}
@Test
@UiThreadTest
public void testCreateChannelGroupNotifyListener() throws Exception {
List<String> associations = new ArrayList<>();
associations.add("a");
@@ -490,7 +471,6 @@ public class NotificationManagerServiceTest {
}
@Test
@UiThreadTest
public void testUpdateChannelNotifyListener() throws Exception {
List<String> associations = new ArrayList<>();
associations.add("a");
@@ -509,7 +489,6 @@ public class NotificationManagerServiceTest {
}
@Test
@UiThreadTest
public void testDeleteChannelNotifyListener() throws Exception {
List<String> associations = new ArrayList<>();
associations.add("a");
@@ -526,7 +505,6 @@ public class NotificationManagerServiceTest {
}
@Test
@UiThreadTest
public void testDeleteChannelGroupNotifyListener() throws Exception {
List<String> associations = new ArrayList<>();
associations.add("a");
@@ -543,7 +521,6 @@ public class NotificationManagerServiceTest {
}
@Test
@UiThreadTest
public void testUpdateNotificationChannelFromPrivilegedListener_success() throws Exception {
mNotificationManagerService.setRankingHelper(mRankingHelper);
List<String> associations = new ArrayList<>();
@@ -561,7 +538,6 @@ public class NotificationManagerServiceTest {
}
@Test
@UiThreadTest
public void testUpdateNotificationChannelFromPrivilegedListener_noAccess() throws Exception {
mNotificationManagerService.setRankingHelper(mRankingHelper);
List<String> associations = new ArrayList<>();
@@ -583,7 +559,6 @@ public class NotificationManagerServiceTest {
}
@Test
@UiThreadTest
public void testUpdateNotificationChannelFromPrivilegedListener_badUser() throws Exception {
mNotificationManagerService.setRankingHelper(mRankingHelper);
List<String> associations = new ArrayList<>();
@@ -609,7 +584,6 @@ public class NotificationManagerServiceTest {
}
@Test
@UiThreadTest
public void testGetNotificationChannelFromPrivilegedListener_success() throws Exception {
mNotificationManagerService.setRankingHelper(mRankingHelper);
List<String> associations = new ArrayList<>();
@@ -624,7 +598,6 @@ public class NotificationManagerServiceTest {
}
@Test
@UiThreadTest
public void testGetNotificationChannelFromPrivilegedListener_noAccess() throws Exception {
mNotificationManagerService.setRankingHelper(mRankingHelper);
List<String> associations = new ArrayList<>();
@@ -643,7 +616,6 @@ public class NotificationManagerServiceTest {
}
@Test
@UiThreadTest
public void testGetNotificationChannelFromPrivilegedListener_badUser() throws Exception {
mNotificationManagerService.setRankingHelper(mRankingHelper);
List<String> associations = new ArrayList<>();
@@ -666,7 +638,6 @@ public class NotificationManagerServiceTest {
}
@Test
@UiThreadTest
public void testGetNotificationChannelGroupsFromPrivilegedListener_success() throws Exception {
mNotificationManagerService.setRankingHelper(mRankingHelper);
List<String> associations = new ArrayList<>();
@@ -680,7 +651,6 @@ public class NotificationManagerServiceTest {
}
@Test
@UiThreadTest
public void testGetNotificationChannelGroupsFromPrivilegedListener_noAccess() throws Exception {
mNotificationManagerService.setRankingHelper(mRankingHelper);
List<String> associations = new ArrayList<>();
@@ -698,7 +668,6 @@ public class NotificationManagerServiceTest {
}
@Test
@UiThreadTest
public void testGetNotificationChannelGroupsFromPrivilegedListener_badUser() throws Exception {
mNotificationManagerService.setRankingHelper(mRankingHelper);
List<String> associations = new ArrayList<>();
@@ -719,7 +688,6 @@ public class NotificationManagerServiceTest {
}
@Test
@UiThreadTest
public void testHasCompanionDevice_failure() throws Exception {
when(mCompanionMgr.getAssociations(anyString(), anyInt())).thenThrow(
new IllegalArgumentException());
@@ -727,7 +695,6 @@ public class NotificationManagerServiceTest {
}
@Test
@UiThreadTest
public void testHasCompanionDevice_noService() throws Exception {
mNotificationManagerService = new TestableNotificationManagerService(mContext);

View File

@@ -18,7 +18,7 @@ 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 android.testing.TestableLooper.LooperStatement;
import android.testing.TestableLooper.LooperFrameworkMethod;
import android.testing.TestableLooper.RunWithLooper;
import org.junit.After;
@@ -30,6 +30,7 @@ import org.junit.runners.model.FrameworkMethod;
import org.junit.runners.model.InitializationError;
import org.junit.runners.model.Statement;
import java.util.ArrayList;
import java.util.List;
/**
@@ -49,28 +50,21 @@ public class AndroidTestingRunner extends BlockJUnit4ClassRunner {
@Override
protected Statement methodInvoker(FrameworkMethod method, Object test) {
return 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);
method = looperWrap(method, test, method);
final Statement statement = super.methodInvoker(method, test);
return shouldRunOnUiThread(method) ? new UiThreadStatement(statement, true) : statement;
}
protected Statement withBefores(FrameworkMethod method, Object target, Statement statement) {
List befores = this.getTestClass().getAnnotatedMethods(Before.class);
List befores = looperWrap(method, target,
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);
List afters = looperWrap(method, target,
this.getTestClass().getAnnotatedMethods(After.class));
return afters.isEmpty() ? statement : new RunAfters(method, statement, afters,
target);
}
@@ -88,6 +82,30 @@ public class AndroidTestingRunner extends BlockJUnit4ClassRunner {
return annotation == null ? 0L : annotation.timeout();
}
protected List<FrameworkMethod> looperWrap(FrameworkMethod method, Object test,
List<FrameworkMethod> methods) {
RunWithLooper annotation = method.getAnnotation(RunWithLooper.class);
if (annotation == null) annotation = mKlass.getAnnotation(RunWithLooper.class);
if (annotation != null) {
methods = new ArrayList<>(methods);
for (int i = 0; i < methods.size(); i++) {
methods.set(i, LooperFrameworkMethod.get(methods.get(i),
annotation.setAsMainLooper(), test));
}
}
return methods;
}
protected FrameworkMethod looperWrap(FrameworkMethod method, Object test,
FrameworkMethod base) {
RunWithLooper annotation = method.getAnnotation(RunWithLooper.class);
if (annotation == null) annotation = mKlass.getAnnotation(RunWithLooper.class);
if (annotation != null) {
return LooperFrameworkMethod.get(base, annotation.setAsMainLooper(), test);
}
return base;
}
public boolean shouldRunOnUiThread(FrameworkMethod method) {
if (mKlass.getAnnotation(UiThreadTest.class) != null) {
return true;

View File

@@ -15,20 +15,21 @@
package android.testing;
import android.os.Handler;
import android.os.HandlerThread;
import android.os.Looper;
import android.os.Message;
import android.os.MessageQueue;
import android.os.TestLooperManager;
import android.support.test.InstrumentationRegistry;
import android.util.ArrayMap;
import org.junit.runners.model.Statement;
import org.junit.runners.model.FrameworkMethod;
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;
/**
@@ -38,65 +39,35 @@ import java.util.Map;
*/
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;
private Runnable mEmptyMessage;
private TestLooperManager mQueueWrapper;
public TestableLooper() throws Exception {
this(true);
public TestableLooper(Looper l) throws Exception {
this(InstrumentationRegistry.getInstrumentation().acquireLooperManager(l), l);
}
public TestableLooper(boolean setMyLooper) throws Exception {
setupQueue(setMyLooper);
mNext = mQueue.getClass().getDeclaredMethod("next");
mNext.setAccessible(true);
mRecycleUnchecked = Message.class.getDeclaredMethod("recycleUnchecked");
mRecycleUnchecked.setAccessible(true);
private TestableLooper(TestLooperManager wrapper, Looper l) throws Exception {
mQueueWrapper = wrapper;
setupQueue(l);
}
private TestableLooper(Looper looper, boolean b) throws Exception {
setupQueue(looper);
}
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);
}
private void setupQueue(Looper l) throws Exception {
mLooper = l;
mQueue = mLooper.getQueue();
mHandler = new Handler(mLooper);
}
@@ -121,9 +92,7 @@ public class TestableLooper {
* tests.
*/
public void destroy() throws NoSuchFieldException, IllegalAccessException {
if (Looper.myLooper() == mLooper) {
clearLooper();
}
mQueueWrapper.release();
if (mMain && mOriginalMain != null) {
Field field = mLooper.getClass().getDeclaredField("sMainLooper");
field.setAccessible(true);
@@ -156,34 +125,35 @@ public class TestableLooper {
private int processQueuedMessages() {
int count = 0;
mEmptyMessage = mHandler.obtainMessage(1);
mHandler.sendMessageDelayed(mEmptyMessage, 1);
mEmptyMessage = () -> { };
mHandler.post(mEmptyMessage);
waitForMessage(mQueueWrapper, mHandler, mEmptyMessage);
while (parseMessageInt()) count++;
return count;
}
private boolean parseMessageInt() {
try {
Message result = (Message) mNext.invoke(mQueue);
Message result = mQueueWrapper.next();
if (result != null) {
// This is a break message.
if (result == mEmptyMessage) {
mRecycleUnchecked.invoke(result);
if (result.getCallback() == mEmptyMessage) {
mQueueWrapper.recycle(result);
return false;
}
if (mMessageHandler != null) {
if (mMessageHandler.onMessageHandled(result)) {
result.getTarget().dispatchMessage(result);
mRecycleUnchecked.invoke(result);
mQueueWrapper.recycle(result);
} else {
mRecycleUnchecked.invoke(result);
mQueueWrapper.recycle(result);
// Message handler indicated it doesn't want us to continue.
return false;
}
} else {
result.getTarget().dispatchMessage(result);
mRecycleUnchecked.invoke(result);
mQueueWrapper.recycle(result);
}
} else {
// No messages, don't continue parsing
@@ -199,10 +169,14 @@ public class TestableLooper {
* Runs an executable with myLooper set and processes all messages added.
*/
public void runWithLooper(RunnableWithException runnable) throws Exception {
boolean set = setForCurrentThread();
runnable.run();
new Handler(getLooper()).post(() -> {
try {
runnable.run();
} catch (Exception e) {
throw new RuntimeException(e);
}
});
processAllMessages();
if (set) clearLooper();
}
public interface RunnableWithException {
@@ -215,39 +189,132 @@ public class TestableLooper {
boolean setAsMainLooper() default false;
}
private static void waitForMessage(TestLooperManager queueWrapper, Handler handler,
Runnable execute) {
for (int i = 0; i < 10; i++) {
if (!queueWrapper.hasMessages(handler, null, execute)) {
try {
Thread.sleep(1);
} catch (InterruptedException e) {
}
}
}
if (!queueWrapper.hasMessages(handler, null, execute)) {
throw new RuntimeException("Message didn't queue...");
}
}
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 static class LooperFrameworkMethod extends FrameworkMethod {
private HandlerThread mHandlerThread;
public LooperStatement(Statement base, boolean setAsMain, Object test) {
mBase = base;
private final TestableLooper mTestableLooper;
private final Looper mLooper;
private final Handler mHandler;
public LooperFrameworkMethod(FrameworkMethod base, boolean setAsMain, Object test) {
super(base.getMethod());
try {
mLooper = new TestableLooper(false);
sLoopers.put(test, mLooper);
mSetAsMain = setAsMain;
mLooper = setAsMain ? Looper.getMainLooper() : createLooper();
mTestableLooper = new TestableLooper(mLooper, false);
} catch (Exception e) {
throw new RuntimeException(e);
}
sLoopers.put(test, mTestableLooper);
mHandler = new Handler(mLooper);
}
public LooperFrameworkMethod(TestableLooper other, FrameworkMethod base) {
super(base.getMethod());
mLooper = other.mLooper;
mTestableLooper = other;
mHandler = new Handler(mLooper);
}
public static FrameworkMethod get(FrameworkMethod base, boolean setAsMain, Object test) {
if (sLoopers.containsKey(test)) {
return new LooperFrameworkMethod(sLoopers.get(test), base);
}
return new LooperFrameworkMethod(base, setAsMain, test);
}
@Override
public void evaluate() throws Throwable {
mLooper.setForCurrentThread();
if (mSetAsMain) {
mLooper.setAsMainLooper();
public Object invokeExplosively(Object target, Object... params) throws Throwable {
if (Looper.myLooper() == mLooper) {
// Already on the right thread from another statement, just execute then.
return super.invokeExplosively(target, params);
}
boolean set = mTestableLooper.mQueueWrapper == null;
if (set) {
mTestableLooper.mQueueWrapper = InstrumentationRegistry.getInstrumentation()
.acquireLooperManager(mLooper);
}
try {
Object[] ret = new Object[1];
// Run the execution on the looper thread.
Runnable execute = () -> {
try {
ret[0] = super.invokeExplosively(target, params);
} catch (Throwable throwable) {
throw new LooperException(throwable);
}
};
Message m = Message.obtain(mHandler, execute);
// Dispatch our message.
try {
mTestableLooper.mQueueWrapper.execute(m);
} catch (LooperException e) {
throw e.getSource();
} catch (RuntimeException re) {
// If the TestLooperManager has to post, it will wrap what it throws in a
// RuntimeException, make sure we grab the actual source.
if (re.getCause() instanceof LooperException) {
throw ((LooperException) re.getCause()).getSource();
} else {
throw re.getCause();
}
} finally {
m.recycle();
}
return ret[0];
} finally {
if (set) {
mTestableLooper.mQueueWrapper.release();
mTestableLooper.mQueueWrapper = null;
}
}
}
private Looper createLooper() {
// TODO: Find way to share these.
mHandlerThread = new HandlerThread(TestableLooper.class.getSimpleName());
mHandlerThread.start();
return mHandlerThread.getLooper();
}
@Override
protected void finalize() throws Throwable {
super.finalize();
if (mHandlerThread != null) {
mHandlerThread.quit();
}
}
private static class LooperException extends RuntimeException {
private final Throwable mSource;
public LooperException(Throwable t) {
mSource = t;
}
try {
mBase.evaluate();
} finally {
mLooper.destroy();
public Throwable getSource() {
return mSource;
}
}
}

View File

@@ -24,17 +24,16 @@ import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import android.os.Handler;
import android.os.Looper;
import android.os.Message;
import android.testing.TestableLooper.MessageHandler;
import android.testing.TestableLooper.RunWithLooper;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
@RunWith(AndroidTestingRunner.class)
@RunWithLooper
public class TestableLooperTest {
@@ -46,11 +45,6 @@ public class TestableLooperTest {
mTestableLooper = TestableLooper.get(this);
}
@After
public void tearDown() throws Exception {
mTestableLooper.destroy();
}
@Test
public void testMessageExecuted() throws Exception {
Handler h = new Handler();
@@ -133,39 +127,23 @@ public class TestableLooperTest {
@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);
TestableLooper testableLooper = new TestableLooper(Looper.getMainLooper());
looper.processAllMessages();
verify(r2).run();
verify(r, never()).run();
try {
testableLooper.setMessageHandler(m -> {
if (m.getCallback() == r) return true;
return false;
});
new Handler(Looper.getMainLooper()).post(r);
testableLooper.processAllMessages();
mTestableLooper.processAllMessages();
verify(r).run();
verify(r).run();
verify(r2, never()).run();
} finally {
testableLooper.destroy();
}
}
@Test