1 /*
2  * Copyright (C) 2020 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 com.android.testutils
18 
19 import androidx.test.ext.junit.runners.AndroidJUnit4
20 import com.android.testutils.DevSdkIgnoreRule.IgnoreAfter
21 import com.android.testutils.DevSdkIgnoreRule.IgnoreUpTo
22 import org.junit.runner.Description
23 import org.junit.runner.Runner
24 import org.junit.runner.notification.RunNotifier
25 
26 /**
27  * A runner that can skip tests based on the development SDK as defined in [DevSdkIgnoreRule].
28  *
29  * Generally [DevSdkIgnoreRule] should be used for that purpose (using rules is preferable over
30  * replacing the test runner), however JUnit runners inspect all methods in the test class before
31  * processing test rules. This may cause issues if the test methods are referencing classes that do
32  * not exist on the SDK of the device the test is run on.
33  *
34  * This runner inspects [IgnoreAfter] and [IgnoreUpTo] annotations on the test class, and will skip
35  * the whole class if they do not match the development SDK as defined in [DevSdkIgnoreRule].
36  * Otherwise, it will delegate to [AndroidJUnit4] to run the test as usual.
37  *
38  * Example usage:
39  *
40  *     @RunWith(DevSdkIgnoreRunner::class)
41  *     @IgnoreUpTo(Build.VERSION_CODES.Q)
42  *     class MyTestClass { ... }
43  */
44 class DevSdkIgnoreRunner(private val klass: Class<*>) : Runner() {
45     private val baseRunner = klass.let {
46         val ignoreAfter = it.getAnnotation(IgnoreAfter::class.java)
47         val ignoreUpTo = it.getAnnotation(IgnoreUpTo::class.java)
48 
49         if (isDevSdkInRange(ignoreUpTo?.value, ignoreAfter?.value)) AndroidJUnit4(klass) else null
50     }
51 
52     override fun run(notifier: RunNotifier) {
53         if (baseRunner != null) {
54             baseRunner.run(notifier)
55             return
56         }
57 
58         // Report a single, skipped placeholder test for this class, so that the class is still
59         // visible as skipped in test results.
60         notifier.fireTestIgnored(
61                 Description.createTestDescription(klass, "skippedClassForDevSdkMismatch"))
62     }
63 
64     override fun getDescription(): Description {
65         return baseRunner?.description ?: Description.createSuiteDescription(klass)
66     }
67 
68     override fun testCount(): Int {
69         // When ignoring the tests, a skipped placeholder test is reported, so test count is 1.
70         return baseRunner?.testCount() ?: 1
71     }
72 }