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

Commit 499b9d06 authored by Sunny Goyal's avatar Sunny Goyal Committed by Android (Google) Code Review
Browse files

Merge "Creating a custom runner for multivalent tests with Launcher specific features" into main

parents bdbb4367 0f4bc926
Loading
Loading
Loading
Loading
+4 −1
Original line number Diff line number Diff line
@@ -23,7 +23,8 @@ filegroup {
    srcs: [
        "src/**/*.java",
        "src/**/*.kt",
        ":launcher3-robo-src",
        "multivalentTests/src/**/*.java",
        "multivalentTests/src/**/*.kt",
    ],
    exclude_srcs: [
        ":launcher-non-quickstep-tests-src",
@@ -37,6 +38,8 @@ filegroup {
    srcs: [
        "multivalentTests/src/**/*.java",
        "multivalentTests/src/**/*.kt",
        "src_deviceless/**/*.java",
        "src_deviceless/**/*.kt",
    ],
}

+2 −8
Original line number Diff line number Diff line
@@ -37,15 +37,12 @@ import android.view.animation.DecelerateInterpolator;
import android.view.animation.PathInterpolator;

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

import com.android.launcher3.util.rule.RobolectricUiThreadRule;
import com.android.launcher3.util.LauncherMultivalentJUnit;

import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;
import org.junit.rules.TestRule;
import org.junit.runner.RunWith;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
@@ -55,14 +52,11 @@ import org.mockito.Spy;
 * Tests for FastBitmapDrawable.
 */
@SmallTest
@RunWith(AndroidJUnit4.class)
@RunWith(LauncherMultivalentJUnit.class)
@UiThreadTest
public class FastBitmapDrawableTest {
    private static final float EPSILON = 0.00001f;

    @Rule
    public final TestRule roboUiThreadRule = new RobolectricUiThreadRule();

    @Spy
    FastBitmapDrawable mFastBitmapDrawable =
            spy(new FastBitmapDrawable(Bitmap.createBitmap(100, 100, Bitmap.Config.ARGB_8888)));
+2 −6
Original line number Diff line number Diff line
@@ -2,22 +2,18 @@ package com.android.launcher3.logging

import androidx.core.util.isEmpty
import androidx.test.annotation.UiThreadTest
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
import com.android.launcher3.util.rule.RobolectricUiThreadRule
import com.android.launcher3.util.LauncherMultivalentJUnit
import com.google.common.truth.Truth.assertThat
import org.junit.Before
import org.junit.Rule
import org.junit.Test
import org.junit.runner.RunWith

/** Unit test for [ColdRebootStartupLatencyLogger]. */
@SmallTest
@RunWith(AndroidJUnit4::class)
@RunWith(LauncherMultivalentJUnit::class)
class StartupLatencyLoggerTest {

    @get:Rule val roboUiThreadRule = RobolectricUiThreadRule()

    private val underTest = ColdRebootStartupLatencyLogger()

    @Before
+14 −11
Original line number Diff line number Diff line
@@ -24,23 +24,24 @@ import org.junit.runner.Runner
import org.junit.runners.Suite

/**
 * A custom runner which emulates multiple devices when running in robolectric framework. Runs
 * normally when running on device
 * A custom runner for multivalent tests with launcher specific features
 * 1) Adds support for @UiThread annotations in deviceless tests
 * 2) Allows emulating multiple devices when running in deviceless mode
 */
class EmulatedDeviceAndroidJUnit(klass: Class<*>?) : Suite(klass, ImmutableList.of()) {
class LauncherMultivalentJUnit(klass: Class<*>?) : Suite(klass, ImmutableList.of()) {

    val runners: List<Runner> =
        testClass.getAnnotation(Devices::class.java)?.value?.let { devices ->
            if (devices.isEmpty() || !isRunningInRobolectric) {
        (testClass.getAnnotation(EmulatedDevices::class.java)?.value ?: emptyArray()).let { devices
            ->
            if (!isRunningInRobolectric) {
                return@let null
            }
            try {
                (testClass.javaClass.classLoader.loadClass(ROBOLECTRIC_RUNNER) as Class<Runner>)
                    .getConstructor(Class::class.java, String::class.java)
                    .let { ctor ->
                        devices.map { deviceName ->
                            ctor.newInstance(testClass.javaClass, deviceName)
                        }
                        if (devices.isEmpty()) listOf(ctor.newInstance(testClass.javaClass, null))
                        else devices.map { ctor.newInstance(testClass.javaClass, it) }
                    }
            } catch (e: Exception) {
                null
@@ -50,11 +51,13 @@ class EmulatedDeviceAndroidJUnit(klass: Class<*>?) : Suite(klass, ImmutableList.

    override fun getChildren() = runners

    @Retention(RUNTIME) @Target(CLASS) annotation class Devices(val value: Array<String>)
    /**
     * Annotation to be added to a test so run it on a list of emulated devices for deviceless test
     */
    @Retention(RUNTIME) @Target(CLASS) annotation class EmulatedDevices(val value: Array<String>)

    companion object {
        private const val ROBOLECTRIC_RUNNER =
            "com.android.launcher3.util.RobolectricEmulatedDeviceRunner"
        private const val ROBOLECTRIC_RUNNER = "com.android.launcher3.util.RobolectricDeviceRunner"

        val isRunningInRobolectric: Boolean
            get() =
+107 −0
Original line number Diff line number Diff line
@@ -13,49 +13,65 @@
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package com.android.launcher3.util.rule
package com.android.launcher3.util

import androidx.test.annotation.UiThreadTest
import androidx.test.platform.app.InstrumentationRegistry
import com.android.launcher3.util.EmulatedDeviceAndroidJUnit.Companion.isRunningInRobolectric
import java.lang.reflect.Method
import java.util.concurrent.atomic.AtomicReference
import org.junit.rules.TestRule
import org.junit.runner.Description
import org.junit.runners.model.FrameworkMethod
import org.junit.runners.model.Statement
import org.robolectric.RobolectricTestRunner
import org.robolectric.internal.bytecode.Sandbox
import org.robolectric.util.ReflectionHelpers
import org.robolectric.util.ReflectionHelpers.ClassParameter

/**
 * A test rule to add support for @UiThreadTest annotations when running in robolectric until is it
 * natively supported by the robolectric runner:
 * https://github.com/robolectric/robolectric/issues/9026
 */
class RobolectricUiThreadRule : TestRule {
/** Runner which emulates the provided display before running the actual test */
class RobolectricDeviceRunner(testClass: Class<*>?, private val deviceName: String?) :
    RobolectricTestRunner(testClass) {

    override fun apply(base: Statement, description: Description): Statement =
        if (!shouldRunOnUiThread(description)) base else UiThreadStatement(base)
    private val nameSuffix = deviceName?.let { "-$it" } ?: ""

    private fun shouldRunOnUiThread(description: Description): Boolean {
        if (!isRunningInRobolectric) {
            // If not running in robolectric, let the default runner handle this
            return false
        }
        var clazz = description.testClass
    override fun getName() = super.getName() + nameSuffix

    override fun testName(method: FrameworkMethod) = super.testName(method) + nameSuffix

    @Throws(Throwable::class)
    override fun beforeTest(sandbox: Sandbox, method: FrameworkMethod, bootstrappedMethod: Method) {
        super.beforeTest(sandbox, method, bootstrappedMethod)

        deviceName ?: return

        val emulator =
            try {
            if (
                clazz
                    .getDeclaredMethod(description.methodName)
                    .getAnnotation(UiThreadTest::class.java) != null
            ) {
                return true
                ReflectionHelpers.loadClass(
                    bootstrappedMethod.declaringClass.classLoader,
                    DEVICE_EMULATOR
                )
            } catch (e: Exception) {
                // Ignore, if the device emulator is not present
                return
            }
        } catch (_: Exception) {
            // Ignore
        ReflectionHelpers.callStaticMethod<Any>(
            emulator,
            "updateDevice",
            ClassParameter.from(String::class.java, deviceName)
        )
    }

        while (!clazz.isAnnotationPresent(UiThreadTest::class.java)) {
            clazz = clazz.superclass ?: return false
        }
        return true
    override fun getHelperTestRunner(clazz: Class<*>) = MyHelperTestRunner(clazz)

    class MyHelperTestRunner(clazz: Class<*>) : HelperTestRunner(clazz) {

        override fun methodBlock(method: FrameworkMethod): Statement =
            // this needs to be run in the test classLoader
            ReflectionHelpers.callStaticMethod(
                method.declaringClass.classLoader,
                RobolectricDeviceRunner::class.qualifiedName,
                "wrapUiThreadMethod",
                ClassParameter.from(FrameworkMethod::class.java, method),
                ClassParameter.from(Statement::class.java, super.methodBlock(method))
            )
    }

    private class UiThreadStatement(val base: Statement) : Statement() {
@@ -72,4 +88,20 @@ class RobolectricUiThreadRule : TestRule {
            exceptionRef.get()?.let { throw it }
        }
    }

    companion object {

        private const val DEVICE_EMULATOR = "com.android.launcher3.util.RoboDeviceEmulator"

        @JvmStatic
        fun wrapUiThreadMethod(method: FrameworkMethod, base: Statement): Statement =
            if (
                method.method.isAnnotationPresent(UiThreadTest::class.java) ||
                    method.declaringClass.isAnnotationPresent(UiThreadTest::class.java)
            ) {
                UiThreadStatement(base)
            } else {
                base
            }
    }
}
Loading