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