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

Commit d368eea8 authored by Jeff DeCew's avatar Jeff DeCew Committed by Android (Google) Code Review
Browse files

Merge "Fix nondeterminism in NotificationLockscreenUserManagerTest due to use...

Merge "Fix nondeterminism in NotificationLockscreenUserManagerTest due to use of a real looper" into main
parents c98b6775 4f4f6dbd
Loading
Loading
Loading
Loading
+29 −18
Original line number Diff line number Diff line
@@ -26,14 +26,10 @@ import static android.os.Flags.FLAG_ALLOW_PRIVATE_PROFILE;
import static android.os.UserHandle.USER_ALL;
import static android.provider.Settings.Secure.LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS;
import static android.provider.Settings.Secure.LOCK_SCREEN_SHOW_NOTIFICATIONS;

import static com.android.systemui.util.concurrency.MockExecutorHandlerKt.mockExecutorHandler;
import static junit.framework.Assert.assertEquals;
import static junit.framework.Assert.assertFalse;
import static junit.framework.Assert.assertNotNull;
import static junit.framework.Assert.assertTrue;

import static org.junit.Assume.assumeFalse;
import static org.junit.Assume.assumeTrue;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.ArgumentMatchers.eq;
@@ -57,8 +53,6 @@ import android.content.pm.UserInfo;
import android.database.ContentObserver;
import android.net.Uri;
import android.os.Bundle;
import android.os.Handler;
import android.os.Looper;
import android.os.UserHandle;
import android.os.UserManager;
import android.provider.Settings;
@@ -75,6 +69,7 @@ import com.android.systemui.broadcast.BroadcastDispatcher;
import com.android.systemui.dump.DumpManager;
import com.android.systemui.flags.FakeFeatureFlagsClassic;
import com.android.systemui.flags.Flags;
import com.android.systemui.log.LogWtfHandlerRule;
import com.android.systemui.plugins.statusbar.StatusBarStateController;
import com.android.systemui.recents.OverviewProxyService;
import com.android.systemui.settings.UserTracker;
@@ -85,11 +80,14 @@ import com.android.systemui.statusbar.notification.collection.notifcollection.Co
import com.android.systemui.statusbar.notification.collection.render.NotificationVisibilityProvider;
import com.android.systemui.statusbar.policy.DeviceProvisionedController;
import com.android.systemui.statusbar.policy.KeyguardStateController;
import com.android.systemui.util.concurrency.FakeExecutor;
import com.android.systemui.util.settings.FakeSettings;

import com.android.systemui.util.time.FakeSystemClock;
import com.google.android.collect.Lists;

import org.junit.After;
import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Mock;
@@ -144,7 +142,11 @@ public class NotificationLockscreenUserManagerTest extends SysuiTestCase {
    private NotificationEntry mSecondaryUserNotif;
    private NotificationEntry mWorkProfileNotif;
    private final FakeFeatureFlagsClassic mFakeFeatureFlags = new FakeFeatureFlagsClassic();
    private Executor mBackgroundExecutor = Runnable::run; // Direct executor
    private final FakeSystemClock mFakeSystemClock = new FakeSystemClock();
    private final FakeExecutor mBackgroundExecutor = new FakeExecutor(mFakeSystemClock);
    private final Executor mMainExecutor = Runnable::run; // Direct executor

    @Rule public final LogWtfHandlerRule wtfHandlerRule = new LogWtfHandlerRule();

    @Before
    public void setUp() {
@@ -175,7 +177,7 @@ public class NotificationLockscreenUserManagerTest extends SysuiTestCase {
        when(mUserManager.getProfilesIncludingCommunal(mSecondaryUser.id)).thenReturn(
                Lists.newArrayList(mSecondaryUser, mCommunalUser));
        mDependency.injectTestDependency(Dependency.MAIN_HANDLER,
                Handler.createAsync(Looper.myLooper()));
                mockExecutorHandler(mMainExecutor));

        Notification notifWithPrivateVisibility = new Notification();
        notifWithPrivateVisibility.visibility = VISIBILITY_PRIVATE;
@@ -209,6 +211,14 @@ public class NotificationLockscreenUserManagerTest extends SysuiTestCase {

        mLockscreenUserManager = new TestNotificationLockscreenUserManager(mContext);
        mLockscreenUserManager.setUpWithPresenter(mPresenter);

        mBackgroundExecutor.runAllReady();
    }

    @After
    public void tearDown() {
        // Validate that all tests processed all background posted code
        assertEquals(0, mBackgroundExecutor.numPending());
    }

    private void changeSetting(String setting) {
@@ -443,28 +453,28 @@ public class NotificationLockscreenUserManagerTest extends SysuiTestCase {

        // first call explicitly sets user 0 to not public; notifies
        mLockscreenUserManager.updatePublicMode();
        TestableLooper.get(this).processAllMessages();
        mBackgroundExecutor.runAllReady();
        assertFalse(mLockscreenUserManager.isLockscreenPublicMode(0));
        verify(listener).onNotificationStateChanged();
        clearInvocations(listener);

        // calling again has no changes; does not notify
        mLockscreenUserManager.updatePublicMode();
        TestableLooper.get(this).processAllMessages();
        mBackgroundExecutor.runAllReady();
        assertFalse(mLockscreenUserManager.isLockscreenPublicMode(0));
        verify(listener, never()).onNotificationStateChanged();

        // Calling again with keyguard now showing makes user 0 public; notifies
        when(mKeyguardStateController.isShowing()).thenReturn(true);
        mLockscreenUserManager.updatePublicMode();
        TestableLooper.get(this).processAllMessages();
        mBackgroundExecutor.runAllReady();
        assertTrue(mLockscreenUserManager.isLockscreenPublicMode(0));
        verify(listener).onNotificationStateChanged();
        clearInvocations(listener);

        // calling again has no changes; does not notify
        mLockscreenUserManager.updatePublicMode();
        TestableLooper.get(this).processAllMessages();
        mBackgroundExecutor.runAllReady();
        assertTrue(mLockscreenUserManager.isLockscreenPublicMode(0));
        verify(listener, never()).onNotificationStateChanged();
    }
@@ -742,6 +752,9 @@ public class NotificationLockscreenUserManagerTest extends SysuiTestCase {
        intent.putExtra(Intent.EXTRA_USER_HANDLE, newUserId);
        broadcastReceiver.onReceive(mContext, intent);

        // One background task to run which will setup the new user
        assertEquals(1, mBackgroundExecutor.runAllReady());

        verify(mDevicePolicyManager, atMost(1)).getKeyguardDisabledFeatures(any(), eq(newUserId));

        assertTrue(mLockscreenUserManager.userAllowsNotificationsInPublic(newUserId));
@@ -821,10 +834,8 @@ public class NotificationLockscreenUserManagerTest extends SysuiTestCase {
                    (() -> mOverviewProxyService),
                    NotificationLockscreenUserManagerTest.this.mKeyguardManager,
                    mStatusBarStateController,
                    Handler.createAsync(TestableLooper.get(
                            NotificationLockscreenUserManagerTest.this).getLooper()),
                    Handler.createAsync(TestableLooper.get(
                            NotificationLockscreenUserManagerTest.this).getLooper()),
                    mockExecutorHandler(mMainExecutor),
                    mockExecutorHandler(mBackgroundExecutor),
                    mBackgroundExecutor,
                    mDeviceProvisionedController,
                    mKeyguardStateController,
+34 −0
Original line number Diff line number Diff line
@@ -128,6 +128,40 @@ public class FakeExecutorTest extends SysuiTestCase {
        assertEquals(3, runnable.mRunCount);
    }

    /**
     * Test FakeExecutor that is told to delay execution on items.
     */
    @Test
    public void testAtTime() {
        FakeSystemClock clock = new FakeSystemClock();
        FakeExecutor fakeExecutor = new FakeExecutor(clock);
        RunnableImpl runnable = new RunnableImpl();

        // Add three delayed runnables.
        fakeExecutor.executeAtTime(runnable, 10001);
        fakeExecutor.executeAtTime(runnable, 10050);
        fakeExecutor.executeAtTime(runnable, 10100);
        assertEquals(0, runnable.mRunCount);
        assertEquals(10000, clock.uptimeMillis());
        assertEquals(3, fakeExecutor.numPending());
        // Delayed runnables should not advance the clock and therefore should not run.
        assertFalse(fakeExecutor.runNextReady());
        assertEquals(0, fakeExecutor.runAllReady());
        assertEquals(3, fakeExecutor.numPending());

        // Advance the clock to the next runnable. One runnable should execute.
        assertEquals(1, fakeExecutor.advanceClockToNext());
        assertEquals(1, fakeExecutor.runAllReady());
        assertEquals(2, fakeExecutor.numPending());
        assertEquals(1, runnable.mRunCount);
        // Advance the clock to the last runnable.
        assertEquals(99, fakeExecutor.advanceClockToLast());
        assertEquals(2, fakeExecutor.runAllReady());
        // Now all remaining runnables should execute.
        assertEquals(0, fakeExecutor.numPending());
        assertEquals(3, runnable.mRunCount);
    }

    /**
     * Test FakeExecutor that is told to delay execution on items.
     */
+163 −0
Original line number Diff line number Diff line
/*
 * Copyright (C) 2019 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 com.android.systemui.util.concurrency

import android.testing.AndroidTestingRunner
import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
import com.android.systemui.util.time.FakeSystemClock
import org.junit.Assert.assertEquals
import org.junit.Assert.assertFalse
import org.junit.Assert.assertTrue
import org.junit.Test
import org.junit.runner.RunWith

@SmallTest
@RunWith(AndroidTestingRunner::class)
class MockExecutorHandlerTest : SysuiTestCase() {
    /** Test FakeExecutor that receives non-delayed items to execute. */
    @Test
    fun testNoDelay() {
        val clock = FakeSystemClock()
        val fakeExecutor = FakeExecutor(clock)
        val handler = mockExecutorHandler(fakeExecutor)
        val runnable = RunnableImpl()
        assertEquals(10000, clock.uptimeMillis())
        assertEquals(0, runnable.mRunCount)

        // Execute two runnables. They should not run and should be left pending.
        handler.post(runnable)
        assertEquals(0, runnable.mRunCount)
        assertEquals(10000, clock.uptimeMillis())
        assertEquals(1, fakeExecutor.numPending())
        handler.post(runnable)
        assertEquals(0, runnable.mRunCount)
        assertEquals(10000, clock.uptimeMillis())
        assertEquals(2, fakeExecutor.numPending())

        // Run one pending runnable.
        assertTrue(fakeExecutor.runNextReady())
        assertEquals(1, runnable.mRunCount)
        assertEquals(10000, clock.uptimeMillis())
        assertEquals(1, fakeExecutor.numPending())
        // Run a second pending runnable.
        assertTrue(fakeExecutor.runNextReady())
        assertEquals(2, runnable.mRunCount)
        assertEquals(10000, clock.uptimeMillis())
        assertEquals(0, fakeExecutor.numPending())

        // No more runnables to run.
        assertFalse(fakeExecutor.runNextReady())

        // Add two more runnables.
        handler.post(runnable)
        handler.post(runnable)
        assertEquals(2, runnable.mRunCount)
        assertEquals(10000, clock.uptimeMillis())
        assertEquals(2, fakeExecutor.numPending())
        // Execute all pending runnables in batch.
        assertEquals(2, fakeExecutor.runAllReady())
        assertEquals(4, runnable.mRunCount)
        assertEquals(10000, clock.uptimeMillis())
        assertEquals(0, fakeExecutor.runAllReady())
    }

    /** Test FakeExecutor that is told to delay execution on items. */
    @Test
    fun testDelayed() {
        val clock = FakeSystemClock()
        val fakeExecutor = FakeExecutor(clock)
        val handler = mockExecutorHandler(fakeExecutor)
        val runnable = RunnableImpl()

        // Add three delayed runnables.
        handler.postDelayed(runnable, 1)
        handler.postDelayed(runnable, 50)
        handler.postDelayed(runnable, 100)
        assertEquals(0, runnable.mRunCount)
        assertEquals(10000, clock.uptimeMillis())
        assertEquals(3, fakeExecutor.numPending())
        // Delayed runnables should not advance the clock and therefore should not run.
        assertFalse(fakeExecutor.runNextReady())
        assertEquals(0, fakeExecutor.runAllReady())
        assertEquals(3, fakeExecutor.numPending())

        // Advance the clock to the next runnable. One runnable should execute.
        assertEquals(1, fakeExecutor.advanceClockToNext())
        assertEquals(1, fakeExecutor.runAllReady())
        assertEquals(2, fakeExecutor.numPending())
        assertEquals(1, runnable.mRunCount)
        // Advance the clock to the last runnable.
        assertEquals(99, fakeExecutor.advanceClockToLast())
        assertEquals(2, fakeExecutor.runAllReady())
        // Now all remaining runnables should execute.
        assertEquals(0, fakeExecutor.numPending())
        assertEquals(3, runnable.mRunCount)
    }

    /** Test FakeExecutor that is told to delay execution on items. */
    @Test
    fun testAtTime() {
        val clock = FakeSystemClock()
        val fakeExecutor = FakeExecutor(clock)
        val handler = mockExecutorHandler(fakeExecutor)
        val runnable = RunnableImpl()

        // Add three delayed runnables.
        handler.postAtTime(runnable, 10001)
        handler.postAtTime(runnable, 10050)
        handler.postAtTime(runnable, 10100)
        assertEquals(0, runnable.mRunCount)
        assertEquals(10000, clock.uptimeMillis())
        assertEquals(3, fakeExecutor.numPending())
        // Delayed runnables should not advance the clock and therefore should not run.
        assertFalse(fakeExecutor.runNextReady())
        assertEquals(0, fakeExecutor.runAllReady())
        assertEquals(3, fakeExecutor.numPending())

        // Advance the clock to the next runnable. One runnable should execute.
        assertEquals(1, fakeExecutor.advanceClockToNext())
        assertEquals(1, fakeExecutor.runAllReady())
        assertEquals(2, fakeExecutor.numPending())
        assertEquals(1, runnable.mRunCount)
        // Advance the clock to the last runnable.
        assertEquals(99, fakeExecutor.advanceClockToLast())
        assertEquals(2, fakeExecutor.runAllReady())
        // Now all remaining runnables should execute.
        assertEquals(0, fakeExecutor.numPending())
        assertEquals(3, runnable.mRunCount)
    }

    /**
     * Verifies that `Handler.removeMessages`, which doesn't make sense with executor backing,
     * causes an error in the test (rather than failing silently like most mocks).
     */
    @Test(expected = RuntimeException::class)
    fun testRemoveMessages_fails() {
        val clock = FakeSystemClock()
        val fakeExecutor = FakeExecutor(clock)
        val handler = mockExecutorHandler(fakeExecutor)

        handler.removeMessages(1)
    }

    private class RunnableImpl : Runnable {
        var mRunCount = 0
        override fun run() {
            mRunCount++
        }
    }
}
+121 −0
Original line number Diff line number Diff line
/*
 * Copyright (C) 2023 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 com.android.systemui.log

import android.util.Log
import android.util.Log.TerribleFailureHandler
import org.junit.rules.TestRule
import org.junit.runner.Description
import org.junit.runners.model.Statement

class LogWtfHandlerRule : TestRule {

    private var started = false
    private var handler = ThrowAndFailAtEnd

    override fun apply(base: Statement, description: Description): Statement {
        return object : Statement() {
            override fun evaluate() {
                started = true
                val originalWtfHandler = Log.setWtfHandler(handler)
                var failure: Throwable? = null
                try {
                    base.evaluate()
                } catch (ex: Throwable) {
                    failure = ex.runAndAddSuppressed { handler.onTestFailure(ex) }
                } finally {
                    failure = failure.runAndAddSuppressed { handler.onTestFinished() }
                    Log.setWtfHandler(originalWtfHandler)
                }
                if (failure != null) {
                    throw failure
                }
            }
        }
    }

    fun Throwable?.runAndAddSuppressed(block: () -> Unit): Throwable? {
        try {
            block()
        } catch (t: Throwable) {
            if (this == null) {
                return t
            }
            addSuppressed(t)
        }
        return this
    }

    fun setWtfHandler(handler: TerribleFailureTestHandler) {
        check(!started) { "Should only be called before the test starts" }
        this.handler = handler
    }

    fun interface TerribleFailureTestHandler : TerribleFailureHandler {
        fun onTestFailure(failure: Throwable) {}
        fun onTestFinished() {}
    }

    companion object Handlers {
        val ThrowAndFailAtEnd
            get() =
                object : TerribleFailureTestHandler {
                    val failures = mutableListOf<Log.TerribleFailure>()

                    override fun onTerribleFailure(
                        tag: String,
                        what: Log.TerribleFailure,
                        system: Boolean
                    ) {
                        failures.add(what)
                        throw what
                    }

                    override fun onTestFailure(failure: Throwable) {
                        super.onTestFailure(failure)
                    }

                    override fun onTestFinished() {
                        if (failures.isNotEmpty()) {
                            throw AssertionError("Unexpected Log.wtf calls: $failures", failures[0])
                        }
                    }
                }

        val JustThrow = TerribleFailureTestHandler { _, what, _ -> throw what }

        val JustFailAtEnd
            get() =
                object : TerribleFailureTestHandler {
                    val failures = mutableListOf<Log.TerribleFailure>()

                    override fun onTerribleFailure(
                        tag: String,
                        what: Log.TerribleFailure,
                        system: Boolean
                    ) {
                        failures.add(what)
                    }

                    override fun onTestFinished() {
                        if (failures.isNotEmpty()) {
                            throw AssertionError("Unexpected Log.wtf calls: $failures", failures[0])
                        }
                    }
                }
    }
}
+66 −0
Original line number Diff line number Diff line
/*
 * Copyright (C) 2023 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 com.android.systemui.util.concurrency

import android.os.Handler
import java.util.concurrent.Executor
import org.mockito.ArgumentMatchers.any
import org.mockito.ArgumentMatchers.anyLong
import org.mockito.Mockito
import org.mockito.Mockito.doAnswer
import org.mockito.invocation.InvocationOnMock
import org.mockito.stubbing.Answer

/**
 * Wrap an [Executor] in a mock [Handler] that execute when [Handler.post] is called, and throws an
 * exception otherwise. This is useful when a class requires a Handler only because Handlers are
 * used by ContentObserver, and no other methods are used.
 */
fun mockExecutorHandler(executor: Executor): Handler {
    val handlerMock = Mockito.mock(Handler::class.java, RuntimeExceptionAnswer())
    doAnswer { invocation: InvocationOnMock ->
            executor.execute(invocation.getArgument(0))
            true
        }
        .`when`(handlerMock)
        .post(any())
    if (executor is DelayableExecutor) {
        doAnswer { invocation: InvocationOnMock ->
                val runnable = invocation.getArgument<Runnable>(0)
                val uptimeMillis = invocation.getArgument<Long>(1)
                executor.executeAtTime(runnable, uptimeMillis)
                true
            }
            .`when`(handlerMock)
            .postAtTime(any(), anyLong())
        doAnswer { invocation: InvocationOnMock ->
                val runnable = invocation.getArgument<Runnable>(0)
                val delayInMillis = invocation.getArgument<Long>(1)
                executor.executeDelayed(runnable, delayInMillis)
                true
            }
            .`when`(handlerMock)
            .postDelayed(any(), anyLong())
    }
    return handlerMock
}

private class RuntimeExceptionAnswer : Answer<Any> {
    override fun answer(invocation: InvocationOnMock): Any {
        throw RuntimeException(invocation.method.name + " is not stubbed")
    }
}