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