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

Commit 92b2beab authored by Kevin Jeon's avatar Kevin Jeon
Browse files

Additional test APIs for MessageQueue

Adds APIs for resetting the state of a MessageQueue and finding the
message with the latest execution time.

Test: - manual with Roboelectric
      - atest FrameworksCoreTests_messagequeue
Bug: 440336953
Flag: EXEMPT TEST_ONLY

Change-Id: Ib55331495e2fb2d4d7baed6b62dcd6db818bdaa0
parent 109ce60c
Loading
Loading
Loading
Loading
+114 −0
Original line number Diff line number Diff line
@@ -1093,6 +1093,120 @@ public final class MessageQueue {
        }
    }

    /**
     * Returns the last message in the queue in execution order.
     *
     * Caller must ensure that this doesn't race 'next' from the Looper thread.
     * @hide
     */
    public @Nullable Message peekLastMessageForTest() {
        ActivityThread.throwIfNotInstrumenting();
        if (sUseConcurrent) {
            return peekLastMessageConcurrent();
        } else {
            return peekLastMessageLegacy();
        }
    }

    /**
     * Matches no messages, but stores the message with the latest execution time.
     */
    static final class FindLastMessage extends MessageCompare {
        Message lastMsg;
        @Override
        public boolean compareMessage(Message m, Handler h, int what, Object object, Runnable r,
                long when) {
            if (m.target != null && (lastMsg == null || lastMsg.when <= m.when)) {
                lastMsg = m;
            }
            return false;
        }
    }

    private Message peekLastMessageConcurrent() {
        final FindLastMessage findLastMessage = new FindLastMessage();
        findOrRemoveMessages(null, 0, null, null, 0, findLastMessage, false);
        return findLastMessage.lastMsg;
    }

    private Message peekLastMessageLegacy() {
        synchronized (this) {
            Message lastMsg = null;

            Message current = mMessages;
            while (current != null) {
                if (current.target != null && (lastMsg == null || lastMsg.when <= current.when)) {
                    lastMsg = current;
                }
                current = current.next;
            }

            return lastMsg;
        }
    }

    /**
     * Resets this queue's state and allows it to continue being used.
     *
     * @hide
     */
    public void resetForTest() {
        ActivityThread.throwIfNotInstrumenting();
        if (sUseConcurrent) {
            resetConcurrent();
        } else {
            resetLegacy();
        }
    }

    private void resetConcurrent() {
        // This queue is already quitting, so we can't reset its state and continue using it.
        if (getQuitting()) {
            return;
        }
        synchronized (mIdleHandlersLock) {
            mIdleHandlers.clear();
        }
        synchronized (mFileDescriptorRecordsLock) {
            removeAllFdRecords();
        }
        removeAllMessages();

        // We reset the sync barrier tokens to reflect the queue's state reset. This helps ensure
        // that the queue's behavior is deterministic in both individual tests and in a test suite.
        resetSyncBarrierTokens();
    }

    private void resetLegacy() {
        synchronized (this) {
            // This queue is already quitting, so we can't reset its state and continue using it.
            if (mQuitting) {
                return;
            }
            mIdleHandlers.clear();
            removeAllFdRecords();
            removeAllMessagesLocked();
            // We reset the sync barrier tokens to reflect the queue's state reset. This helps
            // ensure that the queue's behavior is deterministic in both individual tests and in a
            // test suite.
            resetSyncBarrierTokens();
            nativeWake(mPtr);
        }
    }

    private void removeAllFdRecords() {
        if (mFileDescriptorRecords != null) {
            while (mFileDescriptorRecords.size() > 0) {
                removeOnFileDescriptorEventListener(mFileDescriptorRecords.valueAt(0).mDescriptor);
            }
        }
    }

    private void resetSyncBarrierTokens() {
        mNextBarrierTokenAtomic.set(1);
        mNextBarrierToken = 0;
    }

    void quit(boolean safe) {
        if (!mQuitAllowed) {
            throw new IllegalStateException("Main thread not allowed to quit.");
+50 −0
Original line number Diff line number Diff line
@@ -647,6 +647,56 @@ public final class MessageQueue {
        return false;
    }

    /**
     * Returns the message with the latest scheduled execution time.
     *
     *
     * Caller must ensure that this doesn't race 'next' from the Looper thread.
     * @hide
     */
    public @Nullable Message peekLastMessageForTest() {
        ActivityThread.throwIfNotInstrumenting();
        return mStack.peekLastMessageForTest();
    }

    /**
     * Resets this queue's state.
     *
     * @hide
     */
    public void resetForTest() {
        ActivityThread.throwIfNotInstrumenting();
        // This queue is already quitting, so we can't reset its state and continue using it.
        if (mWorkerShouldQuit) {
            return;
        }
        synchronized (mIdleHandlersLock) {
            mIdleHandlers.clear();
        }
        synchronized (mFileDescriptorRecordsLock) {
            removeAllFdRecords();
        }
        removeAllMessages();
        mStack.drainFreelist();

        // We reset the sync barrier tokens to reflect the queue's state reset. This helps ensure
        // that the queue's behavior is deterministic in both individual tests and in a test suite.
        resetSyncBarrierTokens();
    }

    private void removeAllFdRecords() {
        if (mFileDescriptorRecords != null) {
            while (mFileDescriptorRecords.size() > 0) {
                removeOnFileDescriptorEventListener(mFileDescriptorRecords.valueAt(0).mDescriptor);
            }
        }
    }

    private void resetSyncBarrierTokens() {
        mNextBarrierTokenAtomic.set(1);
        mNextBarrierToken = 0;
    }

    void quit(boolean safe) {
        if (!mQuitAllowed) {
            throw new IllegalStateException("Main thread not allowed to quit.");
+60 −0
Original line number Diff line number Diff line
@@ -463,6 +463,66 @@ public final class MessageQueue {
        }
    }

    /**
     * Returns the last message in the queue in execution order.
     *
     * Caller must ensure that this doesn't race 'next' from the Looper thread.
     * @hide
     */
    public @Nullable Message peekLastMessageForTest() {
        ActivityThread.throwIfNotInstrumenting();
        synchronized (this) {
            Message lastMsg = null;

            Message current = mMessages;
            while (current != null) {
                if (current.target != null && (lastMsg == null || lastMsg.when <= current.when)) {
                    lastMsg = current;
                }
                current = current.next;
            }

            return lastMsg;
        }
    }

    /**
     * Resets this queue's state and allows it to continue being used.
     *
     * @hide
     */
    public void resetForTest() {
        ActivityThread.throwIfNotInstrumenting();
        synchronized (this) {
            // This queue is already quitting, so we can't reset its state and continue using it.
            if (mQuitting) {
                return;
            }
            mIdleHandlers.clear();
            removeAllFdRecords();
            removeAllMessagesLocked();
            // We reset the sync barrier tokens to reflect the queue's state reset. This helps
            // ensure that the queue's behavior is deterministic in both individual tests and in a
            // test suite.
            resetSyncBarrierTokens();
            nativeWake(mPtr);
        }
    }

    private void removeAllFdRecords() {
        if (mFileDescriptorRecords != null) {
            while (mFileDescriptorRecords.size() > 0) {
                removeOnFileDescriptorEventListener(mFileDescriptorRecords.valueAt(0).mDescriptor);
            }
        }
    }

    private void resetSyncBarrierTokens() {
        // Legacy MQ doesn't use an atomic integer for barrier tokens.
        // mNextBarrierTokenAtomic.set(1);
        mNextBarrierToken = 0;
    }

    void quit(boolean safe) {
        if (!mQuitAllowed) {
            throw new IllegalStateException("Main thread not allowed to quit.");
+12 −0
Original line number Diff line number Diff line
@@ -355,6 +355,18 @@ public final class MessageStack {
        removeMessage(m, /* removeFromHeap= */ true);
    }

    Message peekLastMessageForTest() {
        Message lastMsg = null;
        Message current = (Message) sTop.getAcquire(this);
        while (current != null) {
            if (lastMsg == null || (current.when > lastMsg.when && !lastMsg.isRemoved())) {
                lastMsg = current;
            }
            current = current.next;
        }
        return lastMsg;
    }

    /**
     * Returns the number of non-removed messages in this stack.
     */
+1 −0
Original line number Diff line number Diff line
@@ -866,6 +866,7 @@ test_module_config {
    include_filters: [
        "android.os.MessageHeapTest",
        "android.os.MessageStackTest",
        "android.os.MessageQueueTest",
        "android.os.WaitStateTest",
    ],
}
Loading