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

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

Merge "Create frameworks-base-testutils lib"

parents 60d3e8f6 07630f6b
Loading
Loading
Loading
Loading
+30 −0
Original line number Diff line number Diff line
#
# Copyright (C) 2016 The Android Open Source Project
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
#      http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
#

LOCAL_PATH := $(call my-dir)

include $(CLEAR_VARS)

LOCAL_MODULE := frameworks-base-testutils
LOCAL_MODULE_TAG := tests

LOCAL_SRC_FILES := $(call all-java-files-under,java)

LOCAL_STATIC_JAVA_LIBRARIES := \
    android-support-test \
    mockito-target

include $(BUILD_STATIC_JAVA_LIBRARY)
+62 −0
Original line number Diff line number Diff line
/*
 * Copyright (C) 2016 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package android.app.test;

import org.mockito.invocation.InvocationOnMock;
import org.mockito.stubbing.Answer;

import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.Arrays;

/**
 * Utilities for creating Answers for mock objects
 */
public class MockAnswerUtil {

    /**
     * Answer that calls the method in the Answer called "answer" that matches the type signature of
     * the method being answered. An error will be thrown at runtime if the signature does not match
     * exactly.
     */
    public static class AnswerWithArguments implements Answer<Object> {
        @Override
        public final Object answer(InvocationOnMock invocation) throws Throwable {
            Method method = invocation.getMethod();
            try {
                Method implementation = getClass().getMethod("answer", method.getParameterTypes());
                if (!implementation.getReturnType().equals(method.getReturnType())) {
                    throw new RuntimeException("Found answer method does not have expected return "
                            + "type. Expected: " + method.getReturnType() + ", got "
                            + implementation.getReturnType());
                }
                Object[] args = invocation.getArguments();
                try {
                    return implementation.invoke(this, args);
                } catch (IllegalAccessException e) {
                    throw new RuntimeException("Error invoking answer method", e);
                } catch (InvocationTargetException e) {
                    throw e.getCause();
                }
            } catch (NoSuchMethodException e) {
                throw new RuntimeException("Could not find answer method with the expected args "
                        + Arrays.toString(method.getParameterTypes()), e);
            }
        }
    }

}
+188 −0
Original line number Diff line number Diff line
/*
 * Copyright (C) 2015 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 android.app.test;

import static org.mockito.Mockito.any;
import static org.mockito.Mockito.anyInt;
import static org.mockito.Mockito.anyLong;
import static org.mockito.Mockito.anyString;
import static org.mockito.Mockito.doAnswer;
import static org.mockito.Mockito.mock;

import android.app.AlarmManager;
import android.app.test.MockAnswerUtil.AnswerWithArguments;
import android.os.Handler;

import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import java.util.Objects;

/**
 * Creates an AlarmManager whose alarm dispatch can be controlled
 * Currently only supports alarm listeners
 *
 * Alarm listeners will be dispatched to the handler provided or will
 * be dispatched immediately if they would have been sent to the main
 * looper (handler was null).
 */
public class TestAlarmManager {
    private final AlarmManager mAlarmManager;
    private final List<PendingAlarm> mPendingAlarms;

    public TestAlarmManager() throws Exception {
        mPendingAlarms = new ArrayList<>();

        mAlarmManager = mock(AlarmManager.class);
        doAnswer(new SetListenerAnswer()).when(mAlarmManager).set(anyInt(), anyLong(), anyString(),
                any(AlarmManager.OnAlarmListener.class), any(Handler.class));
        doAnswer(new SetListenerAnswer()).when(mAlarmManager).setExact(anyInt(), anyLong(),
                anyString(), any(AlarmManager.OnAlarmListener.class), any(Handler.class));
        doAnswer(new CancelListenerAnswer())
                .when(mAlarmManager).cancel(any(AlarmManager.OnAlarmListener.class));
    }

    public AlarmManager getAlarmManager() {
        return mAlarmManager;
    }

    /**
     * Dispatch a pending alarm with the given tag
     * @return if any alarm was dispatched
     */
    public boolean dispatch(String tag) {
        for (int i = 0; i < mPendingAlarms.size(); ++i) {
            PendingAlarm alarm = mPendingAlarms.get(i);
            if (Objects.equals(tag, alarm.getTag())) {
                mPendingAlarms.remove(i);
                alarm.dispatch();
                return true;
            }
        }
        return false;
    }

    /**
     * @return if an alarm with the given tag is pending
     */
    public boolean isPending(String tag) {
        for (int i = 0; i < mPendingAlarms.size(); ++i) {
            PendingAlarm alarm = mPendingAlarms.get(i);
            if (Objects.equals(tag, alarm.getTag())) {
                return true;
            }
        }
        return false;
    }

    /**
     * @return trigger time of an pending alarm with the given tag
     *         -1 if no pending alarm with the given tag
     */
    public long getTriggerTimeMillis(String tag) {
        for (int i = 0; i < mPendingAlarms.size(); ++i) {
            PendingAlarm alarm = mPendingAlarms.get(i);
            if (Objects.equals(tag, alarm.getTag())) {
                return alarm.getTriggerTimeMillis();
            }
        }
        return -1;
    }

    private static class PendingAlarm {
        private final int mType;
        private final long mTriggerAtMillis;
        private final String mTag;
        private final Runnable mCallback;

        public PendingAlarm(int type, long triggerAtMillis, String tag, Runnable callback) {
            mType = type;
            mTriggerAtMillis = triggerAtMillis;
            mTag = tag;
            mCallback = callback;
        }

        public void dispatch() {
            if (mCallback != null) {
                mCallback.run();
            }
        }

        public Runnable getCallback() {
            return mCallback;
        }

        public String getTag() {
            return mTag;
        }

        public long getTriggerTimeMillis() {
            return mTriggerAtMillis;
        }
    }

    private class SetListenerAnswer extends AnswerWithArguments {
        public void answer(int type, long triggerAtMillis, String tag,
                AlarmManager.OnAlarmListener listener, Handler handler) {
            mPendingAlarms.add(new PendingAlarm(type, triggerAtMillis, tag,
                            new AlarmListenerRunnable(listener, handler)));
        }
    }

    private class CancelListenerAnswer extends AnswerWithArguments {
        public void answer(AlarmManager.OnAlarmListener listener) {
            Iterator<PendingAlarm> alarmItr = mPendingAlarms.iterator();
            while (alarmItr.hasNext()) {
                PendingAlarm alarm = alarmItr.next();
                if (alarm.getCallback() instanceof AlarmListenerRunnable) {
                    AlarmListenerRunnable alarmCallback =
                            (AlarmListenerRunnable) alarm.getCallback();
                    if (alarmCallback.getListener() == listener) {
                        alarmItr.remove();
                    }
                }
            }
        }
    }

    private static class AlarmListenerRunnable implements Runnable {
        private final AlarmManager.OnAlarmListener mListener;
        private final Handler mHandler;
        public AlarmListenerRunnable(AlarmManager.OnAlarmListener listener, Handler handler) {
            mListener = listener;
            mHandler = handler;
        }

        public AlarmManager.OnAlarmListener getListener() {
            return mListener;
        }

        @Override
        public void run() {
            if (mHandler != null) {
                mHandler.post(new Runnable() {
                        @Override
                        public void run() {
                            mListener.onAlarm();
                        }
                    });
            } else { // normally gets dispatched in main looper
                mListener.onAlarm();
            }
        }
    }
}
+283 −0
Original line number Diff line number Diff line
/*
 * Copyright (C) 2015 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 android.os.test;

import static org.junit.Assert.assertTrue;

import android.os.Looper;
import android.os.Message;
import android.os.MessageQueue;
import android.os.SystemClock;
import android.util.Log;

import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;

/**
 * Creates a looper whose message queue can be manipulated
 * This allows testing code that uses a looper to dispatch messages in a deterministic manner
 * Creating a TestLooper will also install it as the looper for the current thread
 */
public class TestLooper {
    protected final Looper mLooper;

    private static final Constructor<Looper> LOOPER_CONSTRUCTOR;
    private static final Field THREAD_LOCAL_LOOPER_FIELD;
    private static final Field MESSAGE_QUEUE_MESSAGES_FIELD;
    private static final Field MESSAGE_NEXT_FIELD;
    private static final Field MESSAGE_WHEN_FIELD;
    private static final Method MESSAGE_MARK_IN_USE_METHOD;
    private static final String TAG = "TestLooper";

    private AutoDispatchThread mAutoDispatchThread;

    static {
        try {
            LOOPER_CONSTRUCTOR = Looper.class.getDeclaredConstructor(Boolean.TYPE);
            LOOPER_CONSTRUCTOR.setAccessible(true);
            THREAD_LOCAL_LOOPER_FIELD = Looper.class.getDeclaredField("sThreadLocal");
            THREAD_LOCAL_LOOPER_FIELD.setAccessible(true);
            MESSAGE_QUEUE_MESSAGES_FIELD = MessageQueue.class.getDeclaredField("mMessages");
            MESSAGE_QUEUE_MESSAGES_FIELD.setAccessible(true);
            MESSAGE_NEXT_FIELD = Message.class.getDeclaredField("next");
            MESSAGE_NEXT_FIELD.setAccessible(true);
            MESSAGE_WHEN_FIELD = Message.class.getDeclaredField("when");
            MESSAGE_WHEN_FIELD.setAccessible(true);
            MESSAGE_MARK_IN_USE_METHOD = Message.class.getDeclaredMethod("markInUse");
            MESSAGE_MARK_IN_USE_METHOD.setAccessible(true);
        } catch (NoSuchFieldException | NoSuchMethodException e) {
            throw new RuntimeException("Failed to initialize TestLooper", e);
        }
    }


    public TestLooper() {
        try {
            mLooper = LOOPER_CONSTRUCTOR.newInstance(false);

            ThreadLocal<Looper> threadLocalLooper = (ThreadLocal<Looper>) THREAD_LOCAL_LOOPER_FIELD
                    .get(null);
            threadLocalLooper.set(mLooper);
        } catch (IllegalAccessException | InstantiationException | InvocationTargetException e) {
            throw new RuntimeException("Reflection error constructing or accessing looper", e);
        }
    }

    public Looper getLooper() {
        return mLooper;
    }

    private Message getMessageLinkedList() {
        try {
            MessageQueue queue = mLooper.getQueue();
            return (Message) MESSAGE_QUEUE_MESSAGES_FIELD.get(queue);
        } catch (IllegalAccessException e) {
            throw new RuntimeException("Access failed in TestLooper: get - MessageQueue.mMessages",
                    e);
        }
    }

    public void moveTimeForward(long milliSeconds) {
        try {
            Message msg = getMessageLinkedList();
            while (msg != null) {
                long updatedWhen = msg.getWhen() - milliSeconds;
                if (updatedWhen < 0) {
                    updatedWhen = 0;
                }
                MESSAGE_WHEN_FIELD.set(msg, updatedWhen);
                msg = (Message) MESSAGE_NEXT_FIELD.get(msg);
            }
        } catch (IllegalAccessException e) {
            throw new RuntimeException("Access failed in TestLooper: set - Message.when", e);
        }
    }

    private Message messageQueueNext() {
        try {
            long now = SystemClock.uptimeMillis();

            Message prevMsg = null;
            Message msg = getMessageLinkedList();
            if (msg != null && msg.getTarget() == null) {
                // Stalled by a barrier. Find the next asynchronous message in
                // the queue.
                do {
                    prevMsg = msg;
                    msg = (Message) MESSAGE_NEXT_FIELD.get(msg);
                } while (msg != null && !msg.isAsynchronous());
            }
            if (msg != null) {
                if (now >= msg.getWhen()) {
                    // Got a message.
                    if (prevMsg != null) {
                        MESSAGE_NEXT_FIELD.set(prevMsg, MESSAGE_NEXT_FIELD.get(msg));
                    } else {
                        MESSAGE_QUEUE_MESSAGES_FIELD.set(mLooper.getQueue(),
                                MESSAGE_NEXT_FIELD.get(msg));
                    }
                    MESSAGE_NEXT_FIELD.set(msg, null);
                    MESSAGE_MARK_IN_USE_METHOD.invoke(msg);
                    return msg;
                }
            }
        } catch (IllegalAccessException | InvocationTargetException e) {
            throw new RuntimeException("Access failed in TestLooper", e);
        }

        return null;
    }

    /**
     * @return true if there are pending messages in the message queue
     */
    public synchronized boolean isIdle() {
        Message messageList = getMessageLinkedList();

        return messageList != null && SystemClock.uptimeMillis() >= messageList.getWhen();
    }

    /**
     * @return the next message in the Looper's message queue or null if there is none
     */
    public synchronized Message nextMessage() {
        if (isIdle()) {
            return messageQueueNext();
        } else {
            return null;
        }
    }

    /**
     * Dispatch the next message in the queue
     * Asserts that there is a message in the queue
     */
    public synchronized void dispatchNext() {
        assertTrue(isIdle());
        Message msg = messageQueueNext();
        if (msg == null) {
            return;
        }
        msg.getTarget().dispatchMessage(msg);
    }

    /**
     * Dispatch all messages currently in the queue
     * Will not fail if there are no messages pending
     * @return the number of messages dispatched
     */
    public synchronized int dispatchAll() {
        int count = 0;
        while (isIdle()) {
            dispatchNext();
            ++count;
        }
        return count;
    }

    /**
     * Thread used to dispatch messages when the main thread is blocked waiting for a response.
     */
    private class AutoDispatchThread extends Thread {
        private static final int MAX_LOOPS = 100;
        private static final int LOOP_SLEEP_TIME_MS = 10;

        private RuntimeException mAutoDispatchException = null;

        /**
         * Run method for the auto dispatch thread.
         * The thread loops a maximum of MAX_LOOPS times with a 10ms sleep between loops.
         * The thread continues looping and attempting to dispatch all messages until at
         * least one message has been dispatched.
         */
        @Override
        public void run() {
            int dispatchCount = 0;
            for (int i = 0; i < MAX_LOOPS; i++) {
                try {
                    dispatchCount = dispatchAll();
                } catch (RuntimeException e) {
                    mAutoDispatchException = e;
                }
                Log.d(TAG, "dispatched " + dispatchCount + " messages");
                if (dispatchCount > 0) {
                    return;
                }
                try {
                    Thread.sleep(LOOP_SLEEP_TIME_MS);
                } catch (InterruptedException e) {
                    mAutoDispatchException = new IllegalStateException(
                            "stopAutoDispatch called before any messages were dispatched.");
                    return;
                }
            }
            Log.e(TAG, "AutoDispatchThread did not dispatch any messages.");
            mAutoDispatchException = new IllegalStateException(
                    "TestLooper did not dispatch any messages before exiting.");
        }

        /**
         * Method allowing the TestLooper to pass any exceptions thrown by the thread to be passed
         * to the main thread.
         *
         * @return RuntimeException Exception created by stopping without dispatching a message
         */
        public RuntimeException getException() {
            return mAutoDispatchException;
        }
    }

    /**
     * Create and start a new AutoDispatchThread if one is not already running.
     */
    public void startAutoDispatch() {
        if (mAutoDispatchThread != null) {
            throw new IllegalStateException(
                    "startAutoDispatch called with the AutoDispatchThread already running.");
        }
        mAutoDispatchThread = new AutoDispatchThread();
        mAutoDispatchThread.start();
    }

    /**
     * If an AutoDispatchThread is currently running, stop and clean up.
     */
    public void stopAutoDispatch() {
        if (mAutoDispatchThread != null) {
            if (mAutoDispatchThread.isAlive()) {
                mAutoDispatchThread.interrupt();
            }
            try {
                mAutoDispatchThread.join();
            } catch (InterruptedException e) {
                // Catch exception from join.
            }

            RuntimeException e = mAutoDispatchThread.getException();
            mAutoDispatchThread = null;
            if (e != null) {
                throw e;
            }
        } else {
            // stopAutoDispatch was called when startAutoDispatch has not created a new thread.
            throw new IllegalStateException(
                    "stopAutoDispatch called without startAutoDispatch.");
        }
    }
}
+371 −0

File added.

Preview size limit exceeded, changes collapsed.

Loading