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

Commit 01b9a95e authored by TreeHugger Robot's avatar TreeHugger Robot Committed by Android (Google) Code Review
Browse files

Merge "Revert "Integrate new looper apis into testables""

parents 837dde71 fd8f6159
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