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

Commit 2fc827de authored by Matthew Reynolds's avatar Matthew Reynolds Committed by Android (Google) Code Review
Browse files

Merge "Explicit UiThreadTest, Rule to block @UiThreadTest" into main

parents 6244932b 46c3608b
Loading
Loading
Loading
Loading
+41 −0
Original line number Diff line number Diff line
/*
 * Copyright (C) 2024 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

import android.testing.UiThreadTest
import org.junit.Assert.fail
import org.junit.rules.MethodRule
import org.junit.runners.model.FrameworkMethod
import org.junit.runners.model.Statement

/**
 * A Test rule which prevents us from using the UiThreadTest annotation. See
 * go/android_junit4_uithreadtest (b/352170965)
 */
public class NoUiThreadTestRule : MethodRule {
    override fun apply(base: Statement, method: FrameworkMethod, target: Any): Statement? {
        if (hasUiThreadAnnotation(method, target)) {
            fail("UiThreadTest doesn't actually run on the UiThread")
        }
        return base
    }

    private fun hasUiThreadAnnotation(method: FrameworkMethod, target: Any): Boolean {
        if (method.getAnnotation(UiThreadTest::class.java) != null) {
            return true
        } else {
            return target.javaClass.getAnnotation(UiThreadTest::class.java) != null
        }
    }
}
+75 −0
Original line number Diff line number Diff line
/*
 * Copyright (C) 2024 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

import android.testing.UiThreadTest
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
import java.lang.AssertionError
import org.junit.Assert
import org.junit.Test
import org.junit.runner.RunWith
import org.junit.runners.model.FrameworkMethod
import org.junit.runners.model.Statement

/**
 * Test that NoUiThreadTestRule asserts when it finds a framework method with a UiThreadTest
 * annotation.
 */
@RunWith(AndroidJUnit4::class)
@SmallTest
public class NoUiThreadTestRuleTest : SysuiTestCase() {

    class TestStatement : Statement() {
        override fun evaluate() {}
    }

    inner class TestInner {
        @Test @UiThreadTest fun simpleUiTest() {}

        @Test fun simpleTest() {}
    }

    /**
     * Test that NoUiThreadTestRule throws an asserts false if a test is annotated
     * with @UiThreadTest
     */
    @Test(expected = AssertionError::class)
    fun testNoUiThreadFail() {
        val method = TestInner::class.java.getDeclaredMethod("simpleUiTest")
        val frameworkMethod = FrameworkMethod(method)
        val noUiThreadTestRule = NoUiThreadTestRule()
        val testStatement = TestStatement()
        // target needs to be non-null
        val obj = Object()
        noUiThreadTestRule.apply(testStatement, frameworkMethod, obj)
    }

    /**
     * Test that NoUiThreadTestRule throws an asserts false if a test is annotated
     * with @UiThreadTest
     */
    fun testNoUiThreadOK() {
        val method = TestInner::class.java.getDeclaredMethod("simpleUiTest")
        val frameworkMethod = FrameworkMethod(method)
        val noUiThreadTestRule = NoUiThreadTestRule()
        val testStatement = TestStatement()

        // because target needs to be non-null
        val obj = Object()
        val newStatement = noUiThreadTestRule.apply(testStatement, frameworkMethod, obj)
        Assert.assertEquals(newStatement, testStatement)
    }
}
+58 −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;

import android.os.Looper;
import android.util.Log;

import androidx.test.platform.app.InstrumentationRegistry;

import java.util.concurrent.ExecutionException;
import java.util.concurrent.FutureTask;

/**
 * A class to launch runnables on the UI thread explicitly.
 */
public class UiThread {
    private static final String TAG = "UiThread";

    /**
     * Run a runnable on the UI thread using instrumentation.runOnMainSync.
     *
     * @param runnable code to run on the UI thread.
     * @throws Throwable if the code threw an exception, so it can be reported
     * to the test.
     */
    public static void runOnUiThread(final Runnable runnable) throws Throwable {
        if (Looper.myLooper() == Looper.getMainLooper()) {
            Log.w(
                    TAG,
                    "UiThread.runOnUiThread() should not be called from the "
                        + "main application thread");
            runnable.run();
        } else {
            FutureTask<Void> task = new FutureTask<>(runnable, null);
            InstrumentationRegistry.getInstrumentation().runOnMainSync(task);
            try {
                task.get();
            } catch (ExecutionException e) {
                // Expose the original exception
                throw e.getCause();
            }
        }
    }
}
+44 −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;

import android.os.Looper;

import androidx.test.ext.junit.runners.AndroidJUnit4;
import androidx.test.filters.SmallTest;

import com.android.systemui.SysuiTestCase;

import org.junit.Assert;
import org.junit.Test;
import org.junit.runner.RunWith;


/**
 * Test that UiThread.runOnUiThread() actually runs on the UI Thread.
 */
@RunWith(AndroidJUnit4.class)
@SmallTest
public class UiThreadRunTest extends SysuiTestCase {

    @Test
    public void testUiThread() throws Throwable {
        UiThread.runOnUiThread(() -> {
            Assert.assertEquals(Looper.getMainLooper().getThread(), Thread.currentThread());
        });
    }
}