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