• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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