1 /*
<lambda>null2  * 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 android.os.Build
20 import androidx.test.InstrumentationRegistry
21 import com.android.modules.utils.build.UnboundedSdkLevel
22 import java.util.regex.Pattern
23 import org.junit.Assume.assumeTrue
24 import org.junit.rules.TestRule
25 import org.junit.runner.Description
26 import org.junit.runners.model.Statement
27 
28 @Deprecated("Use Build.VERSION_CODES", ReplaceWith("Build.VERSION_CODES.S_V2"))
29 const val SC_V2 = Build.VERSION_CODES.S_V2
30 // TODO: Remove this when Build.VERSION_CODES.VANILLA_ICE_CREAM is available in all branches
31 // where this code builds
32 const val VANILLA_ICE_CREAM = 35 // Bui1ld.VERSION_CODES.VANILLA_ICE_CREAM
33 
34 private val MAX_TARGET_SDK_ANNOTATION_RE = Pattern.compile("MaxTargetSdk([0-9]+)$")
35 private val targetSdk = InstrumentationRegistry.getContext().applicationInfo.targetSdkVersion
36 
37 private fun isDevSdkInRange(minExclusive: String?, maxInclusive: String?): Boolean {
38     return (minExclusive == null || !isAtMost(minExclusive)) &&
39             (maxInclusive == null || isAtMost(maxInclusive))
40 }
41 
isAtMostnull42 private fun isAtMost(sdkVersionOrCodename: String): Boolean {
43     // UnboundedSdkLevel does not support builds < Q, and may stop supporting Q as well since it
44     // is intended for mainline modules that are now R+.
45     if (Build.VERSION.SDK_INT <= Build.VERSION_CODES.Q) {
46         // Assume that any codename passed as argument from current code is a more recent build than
47         // Q: this util did not exist before Q, and codenames are only used before the corresponding
48         // build is finalized. This util could list 28 older codenames to check against (as per
49         // ro.build.version.known_codenames in more recent builds), but this does not seem valuable.
50         val intVersion = sdkVersionOrCodename.toIntOrNull() ?: return true
51         return Build.VERSION.SDK_INT <= intVersion
52     }
53     return UnboundedSdkLevel.isAtMost(sdkVersionOrCodename)
54 }
55 
56 /**
57  * Returns true if the development SDK version of the device is in the provided annotation range.
58  *
59  * If the device is not using a release SDK, the development SDK differs from
60  * [Build.VERSION.SDK_INT], and is indicated by the device codenames; see [UnboundedSdkLevel].
61  */
isDevSdkInRangenull62 fun isDevSdkInRange(
63     ignoreUpTo: DevSdkIgnoreRule.IgnoreUpTo?,
64     ignoreAfter: DevSdkIgnoreRule.IgnoreAfter?
65 ): Boolean {
66     val minExclusive =
67             if (ignoreUpTo?.value == 0) ignoreUpTo.codename
68             else ignoreUpTo?.value?.toString()
69     val maxInclusive =
70             if (ignoreAfter?.value == 0) ignoreAfter.codename
71             else ignoreAfter?.value?.toString()
72     return isDevSdkInRange(minExclusive, maxInclusive)
73 }
74 
getMaxTargetSdknull75 private fun getMaxTargetSdk(description: Description): Int? {
76     return description.annotations.firstNotNullOfOrNull {
77         MAX_TARGET_SDK_ANNOTATION_RE.matcher(it.annotationClass.simpleName).let { m ->
78             if (m.find()) m.group(1).toIntOrNull() else null
79         }
80     }
81 }
82 
83 /**
84  * A test rule to ignore tests based on the development SDK level.
85  *
86  * If the device is not using a release SDK, the development SDK is considered to be higher than
87  * [Build.VERSION.SDK_INT].
88  *
89  * @param ignoreClassUpTo Skip all tests in the class if the device dev SDK is <= this codename or
90  *                        SDK level.
91  * @param ignoreClassAfter Skip all tests in the class if the device dev SDK is > this codename or
92  *                         SDK level.
93  */
94 class DevSdkIgnoreRule @JvmOverloads constructor(
95     private val ignoreClassUpTo: String? = null,
96     private val ignoreClassAfter: String? = null
97 ) : TestRule {
98     /**
99      * @param ignoreClassUpTo Skip all tests in the class if the device dev SDK is <= this value.
100      * @param ignoreClassAfter Skip all tests in the class if the device dev SDK is > this value.
101      */
102     @JvmOverloads
103     constructor(ignoreClassUpTo: Int?, ignoreClassAfter: Int? = null) : this(
104             ignoreClassUpTo?.toString(), ignoreClassAfter?.toString())
105 
applynull106     override fun apply(base: Statement, description: Description): Statement {
107         return IgnoreBySdkStatement(base, description)
108     }
109 
110     /**
111      * Ignore the test for any development SDK that is strictly after [value].
112      *
113      * If the device is not using a release SDK, the development SDK is considered to be higher
114      * than [Build.VERSION.SDK_INT].
115      */
116     annotation class IgnoreAfter(val value: Int = 0, val codename: String = "")
117 
118     /**
119      * Ignore the test for any development SDK that lower than or equal to [value].
120      *
121      * If the device is not using a release SDK, the development SDK is considered to be higher
122      * than [Build.VERSION.SDK_INT].
123      */
124     annotation class IgnoreUpTo(val value: Int = 0, val codename: String = "")
125 
126     private inner class IgnoreBySdkStatement(
127         private val base: Statement,
128         private val description: Description
129     ) : Statement() {
evaluatenull130         override fun evaluate() {
131             val ignoreAfter = description.getAnnotation(IgnoreAfter::class.java)
132             val ignoreUpTo = description.getAnnotation(IgnoreUpTo::class.java)
133 
134             val devSdkMessage = "Skipping test for build ${Build.VERSION.CODENAME} " +
135                     "with SDK ${Build.VERSION.SDK_INT}"
136             assumeTrue(devSdkMessage, isDevSdkInRange(ignoreClassUpTo, ignoreClassAfter))
137             assumeTrue(devSdkMessage, isDevSdkInRange(ignoreUpTo, ignoreAfter))
138 
139             val maxTargetSdk = getMaxTargetSdk(description)
140             if (maxTargetSdk != null) {
141                 assumeTrue("Skipping test, target SDK $targetSdk greater than $maxTargetSdk",
142                         targetSdk <= maxTargetSdk)
143             }
144             base.evaluate()
145         }
146     }
147 }
148