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