1 /* <lambda>null2 * 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 17 package android.tools.flicker.junit 18 19 import android.app.Instrumentation 20 import android.os.Bundle 21 import android.platform.test.util.TestFilter 22 import android.tools.FLICKER_TAG 23 import android.tools.Scenario 24 import android.tools.flicker.legacy.FlickerBuilder 25 import android.tools.flicker.legacy.runner.TransitionRunner 26 import android.tools.withTracing 27 import android.util.Log 28 import androidx.test.platform.app.InstrumentationRegistry 29 import com.google.common.annotations.VisibleForTesting 30 import java.util.Collections 31 import java.util.concurrent.locks.Lock 32 import java.util.concurrent.locks.ReentrantLock 33 import org.junit.FixMethodOrder 34 import org.junit.Ignore 35 import org.junit.internal.AssumptionViolatedException 36 import org.junit.internal.runners.model.EachTestNotifier 37 import org.junit.runner.Description 38 import org.junit.runner.manipulation.Filter 39 import org.junit.runner.manipulation.InvalidOrderingException 40 import org.junit.runner.manipulation.NoTestsRemainException 41 import org.junit.runner.manipulation.Orderable 42 import org.junit.runner.manipulation.Orderer 43 import org.junit.runner.manipulation.Sorter 44 import org.junit.runner.notification.RunNotifier 45 import org.junit.runner.notification.StoppedByUserException 46 import org.junit.runners.model.FrameworkMethod 47 import org.junit.runners.model.InvalidTestClassError 48 import org.junit.runners.model.RunnerScheduler 49 import org.junit.runners.model.Statement 50 import org.junit.runners.model.TestClass 51 import org.junit.runners.parameterized.BlockJUnit4ClassRunnerWithParameters 52 import org.junit.runners.parameterized.TestWithParameters 53 54 /** 55 * Implements the JUnit 4 standard test case class model, parsing from a flicker DSL. 56 * 57 * Supports both assertions in {@link org.junit.Test} and assertions defined in the DSL 58 * 59 * When using this runner the default `atest class#method` command doesn't work. Instead use: -- 60 * --test-arg \ 61 * 62 * ``` 63 * com.android.tradefed.testtype.AndroidJUnitTest:instrumentation-arg:filter-tests:=<TEST_NAME> 64 * ``` 65 * 66 * For example: `atest FlickerTests -- \ 67 * 68 * ``` 69 * --test-arg com.android.tradefed.testtype.AndroidJUnitTest:instrumentation-arg:filter-tests\ 70 * :=com.android.server.wm.flicker.close.\ 71 * CloseAppBackButtonTest#launcherWindowBecomesVisible[ROTATION_90_GESTURAL_NAV]` 72 * ``` 73 */ 74 class LegacyFlickerJUnit4ClassRunner( 75 test: TestWithParameters?, 76 private val scenario: Scenario?, 77 private val arguments: Bundle = InstrumentationRegistry.getArguments(), 78 ) : BlockJUnit4ClassRunnerWithParameters(test), IFlickerJUnitDecorator { 79 private val onlyBlocking 80 get() = 81 scenario?.getConfigValue<Boolean>(Scenario.FAAS_BLOCKING) 82 ?: arguments.getString(Scenario.FAAS_BLOCKING)?.toBoolean() 83 ?: true 84 85 @VisibleForTesting 86 val transitionRunner = 87 object : ITransitionRunner { 88 private val instrumentation: Instrumentation = 89 InstrumentationRegistry.getInstrumentation() 90 91 override fun runTransition(scenario: Scenario, test: Any, description: Description?) { 92 withTracing("LegacyFlickerJUnit4ClassRunner#runTransition") { 93 Log.v(FLICKER_TAG, "Creating flicker object for $scenario") 94 val builder = getFlickerBuilder(test) 95 Log.v(FLICKER_TAG, "Creating flicker object for $scenario") 96 val flicker = builder.build() 97 val runner = TransitionRunner(scenario, instrumentation) 98 Log.v(FLICKER_TAG, "Running transition for $scenario") 99 runner.execute(flicker, description) 100 } 101 } 102 103 private val providerMethod: FrameworkMethod 104 get() = 105 getCandidateProviderMethods(testClass).firstOrNull() 106 ?: error("Provider method not found") 107 108 private fun getFlickerBuilder(test: Any): FlickerBuilder { 109 Log.v(FLICKER_TAG, "Obtaining flicker builder for $testClass") 110 return providerMethod.invokeExplosively(test) as FlickerBuilder 111 } 112 } 113 114 private fun getCandidateProviderMethods(testClass: TestClass): List<FrameworkMethod> = 115 testClass.getAnnotatedMethods(FlickerBuilderProvider::class.java) ?: emptyList() 116 117 @VisibleForTesting 118 val flickerDecorator: LegacyFlickerServiceDecorator = 119 LegacyFlickerServiceDecorator( 120 this.testClass, 121 scenario, 122 transitionRunner, 123 arguments = arguments, 124 skipNonBlocking = onlyBlocking, 125 inner = LegacyFlickerDecorator(this.testClass, scenario, transitionRunner, inner = this), 126 ) 127 128 init { 129 val errors = mutableListOf<Throwable>() 130 flickerDecorator.doValidateInstanceMethods().let { errors.addAll(it) } 131 flickerDecorator.doValidateConstructor().let { errors.addAll(it) } 132 133 if (errors.isNotEmpty()) { 134 throw InvalidTestClassError(testClass.javaClass, errors) 135 } 136 } 137 138 override fun run(notifier: RunNotifier) { 139 val testNotifier = EachTestNotifier(notifier, description) 140 testNotifier.fireTestSuiteStarted() 141 try { 142 val statement = classBlock(notifier) 143 statement.evaluate() 144 } catch (e: AssumptionViolatedException) { 145 testNotifier.addFailedAssumption(e) 146 } catch (e: StoppedByUserException) { 147 throw e 148 } catch (e: Throwable) { 149 testNotifier.addFailure(e) 150 } finally { 151 testNotifier.fireTestSuiteFinished() 152 } 153 } 154 155 /** 156 * Implementation of Filterable and Sortable Based on JUnit's ParentRunner implementation but 157 * with a minor modification to ensure injected FaaS tests are not filtered out. 158 */ 159 @Throws(NoTestsRemainException::class) 160 override fun filter(filter: Filter) { 161 childrenLock.lock() 162 try { 163 val children: MutableList<FrameworkMethod> = getFilteredChildren().toMutableList() 164 val iter: MutableIterator<FrameworkMethod> = children.iterator() 165 while (iter.hasNext()) { 166 val each: FrameworkMethod = iter.next() 167 if (isInjectedFaasTest(each)) { 168 // Don't filter out injected FaaS tests 169 continue 170 } 171 if (shouldRun(filter, each)) { 172 try { 173 filter.apply(each) 174 } catch (e: NoTestsRemainException) { 175 iter.remove() 176 } 177 } else { 178 iter.remove() 179 } 180 } 181 filteredChildren = Collections.unmodifiableList(children) 182 val filteredChildren = filteredChildren 183 if (filteredChildren!!.isEmpty()) { 184 throw NoTestsRemainException() 185 } 186 } finally { 187 childrenLock.unlock() 188 } 189 } 190 191 private fun isInjectedFaasTest(method: FrameworkMethod): Boolean { 192 return method is FlickerServiceCachedTestCase 193 } 194 195 override fun isIgnored(child: FrameworkMethod): Boolean { 196 return child.getAnnotation(Ignore::class.java) != null 197 } 198 199 /** 200 * Returns the methods that run tests. Is ran after validateInstanceMethods, so 201 * flickerBuilderProviderMethod should be set. 202 */ 203 public override fun computeTestMethods(): List<FrameworkMethod> { 204 val result = mutableListOf<FrameworkMethod>() 205 if (scenario != null) { 206 val testInstance = createTest() 207 result.addAll(flickerDecorator.getTestMethods(testInstance)) 208 } else { 209 result.addAll(getTestMethods({} /* placeholder param */)) 210 } 211 return result 212 } 213 214 override fun describeChild(method: FrameworkMethod): Description { 215 return if (scenario != null) { 216 flickerDecorator.getChildDescription(method) 217 } else { 218 getChildDescription(method) 219 } 220 } 221 222 /** {@inheritDoc} */ 223 override fun getChildren(): MutableList<FrameworkMethod> { 224 val validChildren = 225 super.getChildren().filter { 226 val childDescription = describeChild(it) 227 TestFilter.isFilteredOrUnspecified(arguments, childDescription) 228 } 229 return validChildren.toMutableList() 230 } 231 232 override fun methodInvoker(method: FrameworkMethod, test: Any): Statement { 233 return flickerDecorator.getMethodInvoker(method, test) 234 } 235 236 /** IFlickerJunitDecorator implementation */ 237 override fun getTestMethods(test: Any): List<FrameworkMethod> { 238 val tests = mutableListOf<FrameworkMethod>() 239 tests.addAll(super.computeTestMethods()) 240 return tests 241 } 242 243 override fun doValidateInstanceMethods(): List<Throwable> { 244 // NOOP - handled in init of this class otherwise called on initialization of parent class 245 // which means this class is not initialized yet leading to issues 246 val errors = emptyList<Throwable>() 247 super.validateInstanceMethods(errors) 248 return errors 249 } 250 251 override fun doValidateConstructor(): List<Throwable> { 252 // NOOP - handled in init of this class otherwise called on initialization of parent class 253 // which means this class is not initialized yet leading to issues 254 val errors = emptyList<Throwable>() 255 super.validateInstanceMethods(errors) 256 return errors 257 } 258 259 override fun getChildDescription(method: FrameworkMethod): Description { 260 return super.describeChild(method) 261 } 262 263 override fun getMethodInvoker(method: FrameworkMethod, test: Any): Statement { 264 return super.methodInvoker(method, test) 265 } 266 267 override fun shouldRunBeforeOn(method: FrameworkMethod): Boolean = true 268 269 override fun shouldRunAfterOn(method: FrameworkMethod): Boolean = true 270 271 /** 272 * ******************************************************************************************** 273 * START of code copied from ParentRunner to have local access to filteredChildren to ensure 274 * FaaS injected tests are not filtered out. 275 */ 276 277 // Guarded by childrenLock 278 @Volatile private var filteredChildren: List<FrameworkMethod>? = null 279 private val childrenLock: Lock = ReentrantLock() 280 281 @Volatile 282 private var scheduler: RunnerScheduler = 283 object : RunnerScheduler { 284 override fun schedule(childStatement: Runnable) { 285 childStatement.run() 286 } 287 288 override fun finished() { 289 // do nothing 290 } 291 } 292 293 /** 294 * Sets a scheduler that determines the order and parallelization of children. Highly 295 * experimental feature that may change. 296 */ 297 override fun setScheduler(scheduler: RunnerScheduler) { 298 this.scheduler = scheduler 299 } 300 301 private fun shouldRun(filter: Filter, each: FrameworkMethod): Boolean { 302 return filter.shouldRun(describeChild(each)) 303 } 304 305 override fun sort(sorter: Sorter) { 306 if (shouldNotReorder()) { 307 return 308 } 309 childrenLock.lock() 310 filteredChildren = 311 try { 312 for (each in getFilteredChildren()) { 313 sorter.apply(each) 314 } 315 val sortedChildren: List<FrameworkMethod> = 316 ArrayList<FrameworkMethod>(getFilteredChildren()) 317 Collections.sort(sortedChildren, comparator(sorter)) 318 Collections.unmodifiableList(sortedChildren) 319 } finally { 320 childrenLock.unlock() 321 } 322 } 323 324 /** 325 * Implementation of [Orderable.order]. 326 * 327 * @since 4.13 328 */ 329 @Throws(InvalidOrderingException::class) 330 override fun order(orderer: Orderer) { 331 if (shouldNotReorder()) { 332 return 333 } 334 childrenLock.lock() 335 try { 336 var children: List<FrameworkMethod> = getFilteredChildren() 337 // In theory, we could have duplicate Descriptions. De-dup them before ordering, 338 // and add them back at the end. 339 val childMap: MutableMap<Description, MutableList<FrameworkMethod>> = 340 LinkedHashMap(children.size) 341 for (child in children) { 342 val description = describeChild(child) 343 var childrenWithDescription: MutableList<FrameworkMethod>? = childMap[description] 344 if (childrenWithDescription == null) { 345 childrenWithDescription = ArrayList<FrameworkMethod>(1) 346 childMap[description] = childrenWithDescription 347 } 348 childrenWithDescription.add(child) 349 orderer.apply(child) 350 } 351 val inOrder = orderer.order(childMap.keys) 352 children = ArrayList(children.size) 353 for (description in inOrder) { 354 children.addAll(childMap[description]!!) 355 } 356 filteredChildren = Collections.unmodifiableList(children) 357 } finally { 358 childrenLock.unlock() 359 } 360 } 361 362 private fun shouldNotReorder(): Boolean { 363 // If the test specifies a specific order, do not reorder. 364 return description.getAnnotation(FixMethodOrder::class.java) != null 365 } 366 367 private fun getFilteredChildren(): List<FrameworkMethod> { 368 childrenLock.lock() 369 val filteredChildren = 370 try { 371 if (filteredChildren != null) { 372 filteredChildren!! 373 } else { 374 Collections.unmodifiableList(ArrayList<FrameworkMethod>(children)) 375 } 376 } finally { 377 childrenLock.unlock() 378 } 379 return filteredChildren 380 } 381 382 override fun getDescription(): Description { 383 val clazz = testClass.javaClass 384 // if subclass overrides `getName()` then we should use it 385 // to maintain backwards compatibility with JUnit 4.12 386 val description: Description = 387 if (clazz == null || clazz.name != name) { 388 Description.createSuiteDescription(name, *runnerAnnotations) 389 } else { 390 Description.createSuiteDescription(clazz, *runnerAnnotations) 391 } 392 for (child in getFilteredChildren()) { 393 description.addChild(describeChild(child)) 394 } 395 return description 396 } 397 398 /** 399 * Returns a [Statement]: Call [.runChild] on each object returned by [.getChildren] (subject to 400 * any imposed filter and sort) 401 */ 402 override fun childrenInvoker(notifier: RunNotifier): Statement { 403 return object : Statement() { 404 override fun evaluate() { 405 runChildren(notifier) 406 } 407 } 408 } 409 410 private fun runChildren(notifier: RunNotifier) { 411 val currentScheduler = scheduler 412 try { 413 for (each in getFilteredChildren()) { 414 currentScheduler.schedule { this.runChild(each, notifier) } 415 } 416 } finally { 417 currentScheduler.finished() 418 } 419 } 420 421 private fun comparator(sorter: Sorter): Comparator<in FrameworkMethod> { 422 return Comparator { o1, o2 -> sorter.compare(describeChild(o1), describeChild(o2)) } 423 } 424 425 /** 426 * END of code copied from ParentRunner to have local access to filteredChildren to ensure FaaS 427 * injected tests are not filtered out. 428 */ 429 } 430