1 /* 2 * Copyright (C) 2024 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 package com.android.launcher3.util 17 18 import androidx.test.annotation.UiThreadTest 19 import androidx.test.platform.app.InstrumentationRegistry 20 import java.lang.reflect.Method 21 import java.util.concurrent.atomic.AtomicReference 22 import org.junit.runners.model.FrameworkMethod 23 import org.junit.runners.model.Statement 24 import org.robolectric.RobolectricTestRunner 25 import org.robolectric.internal.bytecode.Sandbox 26 import org.robolectric.util.ReflectionHelpers 27 import org.robolectric.util.ReflectionHelpers.ClassParameter 28 29 /** Runner which emulates the provided display before running the actual test */ 30 class RobolectricDeviceRunner(testClass: Class<*>?, private val deviceName: String?) : 31 RobolectricTestRunner(testClass) { 32 <lambda>null33 private val nameSuffix = deviceName?.let { "-$it" } ?: "" 34 getNamenull35 override fun getName() = super.getName() + nameSuffix 36 37 override fun testName(method: FrameworkMethod) = super.testName(method) + nameSuffix 38 39 @Throws(Throwable::class) 40 override fun beforeTest(sandbox: Sandbox, method: FrameworkMethod, bootstrappedMethod: Method) { 41 super.beforeTest(sandbox, method, bootstrappedMethod) 42 43 deviceName ?: return 44 45 val emulator = 46 try { 47 ReflectionHelpers.loadClass( 48 bootstrappedMethod.declaringClass.classLoader, 49 DEVICE_EMULATOR 50 ) 51 } catch (e: Exception) { 52 // Ignore, if the device emulator is not present 53 return 54 } 55 ReflectionHelpers.callStaticMethod<Any>( 56 emulator, 57 "updateDevice", 58 ClassParameter.from(String::class.java, deviceName) 59 ) 60 } 61 getHelperTestRunnernull62 override fun getHelperTestRunner(clazz: Class<*>) = MyHelperTestRunner(clazz) 63 64 class MyHelperTestRunner(clazz: Class<*>) : HelperTestRunner(clazz) { 65 66 override fun methodBlock(method: FrameworkMethod): Statement = 67 // this needs to be run in the test classLoader 68 ReflectionHelpers.callStaticMethod( 69 method.declaringClass.classLoader, 70 RobolectricDeviceRunner::class.qualifiedName, 71 "wrapUiThreadMethod", 72 ClassParameter.from(FrameworkMethod::class.java, method), 73 ClassParameter.from(Statement::class.java, super.methodBlock(method)) 74 ) 75 } 76 77 private class UiThreadStatement(val base: Statement) : Statement() { 78 evaluatenull79 override fun evaluate() { 80 val exceptionRef = AtomicReference<Throwable>() 81 InstrumentationRegistry.getInstrumentation().runOnMainSync { 82 try { 83 base.evaluate() 84 } catch (throwable: Throwable) { 85 exceptionRef.set(throwable) 86 } 87 } 88 exceptionRef.get()?.let { throw it } 89 } 90 } 91 92 companion object { 93 94 private const val DEVICE_EMULATOR = "com.android.launcher3.util.RoboDeviceEmulator" 95 96 @JvmStatic wrapUiThreadMethodnull97 fun wrapUiThreadMethod(method: FrameworkMethod, base: Statement): Statement = 98 if ( 99 method.method.isAnnotationPresent(UiThreadTest::class.java) || 100 method.declaringClass.isAnnotationPresent(UiThreadTest::class.java) 101 ) { 102 UiThreadStatement(base) 103 } else { 104 base 105 } 106 } 107 } 108