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