Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit fd8f6159 authored by Jason Monk's avatar Jason Monk
Browse files

Revert "Integrate new looper apis into testables"

This reverts commit f715f417.

Change-Id: I4c0c12d8687426cecccf0f8dad710ea42699727b
parent f715f417
Loading
Loading
Loading
Loading
+1 −6
Original line number Diff line number Diff line
@@ -20,9 +20,7 @@ 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;
@@ -33,13 +31,10 @@ 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;
@@ -52,7 +47,7 @@ public class BluetoothControllerImplTest extends SysuiTestCase {

    @Before
    public void setup() throws Exception {
        mTestableLooper = TestableLooper.get(this);
        mTestableLooper = new TestableLooper();
        mMockBluetoothManager = mDependency.injectMockDependency(LocalBluetoothManager.class);
        mDevices = new ArrayList<>();
        mMockDeviceManager = mock(CachedBluetoothDeviceManager.class);
+15 −33
Original line number Diff line number Diff line
@@ -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.LooperFrameworkMethod;
import android.testing.TestableLooper.LooperStatement;
import android.testing.TestableLooper.RunWithLooper;

import org.junit.After;
@@ -30,7 +30,6 @@ 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;

/**
@@ -50,21 +49,28 @@ public class AndroidTestingRunner extends BlockJUnit4ClassRunner {

    @Override
    protected Statement methodInvoker(FrameworkMethod method, Object test) {
        method = looperWrap(method, test, method);
        final Statement statement = super.methodInvoker(method, test);
        return shouldRunOnUiThread(method) ? new UiThreadStatement(statement, true) : statement;
        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);
    }

    protected Statement withBefores(FrameworkMethod method, Object target, Statement statement) {
        List befores = looperWrap(method, target,
                this.getTestClass().getAnnotatedMethods(Before.class));
        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 = looperWrap(method, target,
                this.getTestClass().getAnnotatedMethods(After.class));
        List afters = this.getTestClass().getAnnotatedMethods(After.class);
        return afters.isEmpty() ? statement : new RunAfters(method, statement, afters,
                target);
    }
@@ -82,30 +88,6 @@ 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;
+72 −143
Original line number Diff line number Diff line
@@ -15,21 +15,20 @@
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.FrameworkMethod;
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;

/**
@@ -39,35 +38,65 @@ 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 TestLooperManager mQueueWrapper;

    public TestableLooper(Looper l) throws Exception {
        this(InstrumentationRegistry.getInstrumentation().acquireLooperManager(l), l);
    }

    private TestableLooper(TestLooperManager wrapper, Looper l) throws Exception {
        mQueueWrapper = wrapper;
        setupQueue(l);
    public TestableLooper() throws Exception {
        this(true);
    }

    private TestableLooper(Looper looper, boolean b) throws Exception {
        setupQueue(looper);
    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 setupQueue(Looper l) throws Exception {
        mLooper = l;
    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);
    }
@@ -92,7 +121,9 @@ public class TestableLooper {
     * tests.
     */
    public void destroy() throws NoSuchFieldException, IllegalAccessException {
        mQueueWrapper.release();
        if (Looper.myLooper() == mLooper) {
            clearLooper();
        }
        if (mMain && mOriginalMain != null) {
            Field field = mLooper.getClass().getDeclaredField("sMainLooper");
            field.setAccessible(true);
@@ -133,26 +164,26 @@ public class TestableLooper {

    private boolean parseMessageInt() {
        try {
            Message result = mQueueWrapper.next();
            Message result = (Message) mNext.invoke(mQueue);
            if (result != null) {
                // This is a break message.
                if (result == mEmptyMessage) {
                    mQueueWrapper.recycle(result);
                    mRecycleUnchecked.invoke(result);
                    return false;
                }

                if (mMessageHandler != null) {
                    if (mMessageHandler.onMessageHandled(result)) {
                        result.getTarget().dispatchMessage(result);
                        mQueueWrapper.recycle(result);
                        mRecycleUnchecked.invoke(result);
                    } else {
                        mQueueWrapper.recycle(result);
                        mRecycleUnchecked.invoke(result);
                        // Message handler indicated it doesn't want us to continue.
                        return false;
                    }
                } else {
                    result.getTarget().dispatchMessage(result);
                    mQueueWrapper.recycle(result);
                    mRecycleUnchecked.invoke(result);
                }
            } else {
                // No messages, don't continue parsing
@@ -168,14 +199,10 @@ public class TestableLooper {
     * Runs an executable with myLooper set and processes all messages added.
     */
    public void runWithLooper(RunnableWithException runnable) throws Exception {
        new Handler(getLooper()).post(() -> {
            try {
        boolean set = setForCurrentThread();
        runnable.run();
            } catch (Exception e) {
                throw new RuntimeException(e);
            }
        });
        processAllMessages();
        if (set) clearLooper();
    }

    public interface RunnableWithException {
@@ -194,131 +221,33 @@ public class TestableLooper {
        return sLoopers.get(test);
    }

    public static class LooperFrameworkMethod extends FrameworkMethod {
        private HandlerThread mHandlerThread;
    public static class LooperStatement extends Statement {
        private final boolean mSetAsMain;
        private final Statement mBase;
        private final TestableLooper mLooper;

        private final TestableLooper mTestableLooper;
        private final Looper mLooper;
        private final Handler mHandler;

        public LooperFrameworkMethod(FrameworkMethod base, boolean setAsMain, Object test) {
            super(base.getMethod());
        public LooperStatement(Statement base, boolean setAsMain, Object test) {
            mBase = base;
            try {
                mLooper = setAsMain ? Looper.getMainLooper() : createLooper();
                mTestableLooper = new TestableLooper(mLooper, false);
                mLooper = new TestableLooper(false);
                sLoopers.put(test, mLooper);
                mSetAsMain = setAsMain;
            } 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 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);
        public void evaluate() throws Throwable {
            mLooper.setForCurrentThread();
            if (mSetAsMain) {
                mLooper.setAsMainLooper();
            }
            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);
                    }
                };
                mHandler.post(execute);
                // Try to wait for the message to be queued.
                for (int i = 0; i < 10; i++) {
                    if (!mTestableLooper.mQueueWrapper.hasMessages(mHandler, null, execute)) {
                        Thread.sleep(1);
                    }
                }
                if (!mTestableLooper.mQueueWrapper.hasMessages(mHandler, null, execute)) {
                    throw new RuntimeException("Message didn't queue...");
                }
                Message m = mTestableLooper.mQueueWrapper.next();
                // Parse all other messages until we get to ours.
                while (m.getTarget() != mHandler) {
                    try {
                        mTestableLooper.mQueueWrapper.execute(m);
                    } catch (LooperException e) {
                        throw e.getSource();
                    } finally {
                        mTestableLooper.mQueueWrapper.recycle(m);
                    }
                    m = mTestableLooper.mQueueWrapper.next();
                }
                // 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();
                    }
                mBase.evaluate();
            } finally {
                    mTestableLooper.mQueueWrapper.recycle(m);
                }
                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;
            }

            public Throwable getSource() {
                return mSource;
                mLooper.destroy();
            }
        }
    }
+41 −19
Original line number Diff line number Diff line
@@ -24,16 +24,17 @@ 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 {
@@ -45,6 +46,11 @@ 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();
@@ -127,23 +133,39 @@ 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);
        Runnable r2 = mock(Runnable.class);
        TestableLooper testableLooper = new TestableLooper(Looper.getMainLooper());

        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(r2, never()).run();
        } finally {
            testableLooper.destroy();
        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