summaryrefslogtreecommitdiff
path: root/common/testutils/devicetests/com/android/testutils/DevSdkIgnoreRule.kt
blob: 35f22b9e587d45771b05382d7a3ceeeb2050d7e6 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
/*
 * Copyright (C) 2020 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package com.android.testutils

import android.os.Build
import androidx.test.InstrumentationRegistry
import com.android.modules.utils.build.UnboundedSdkLevel
import java.util.regex.Pattern
import org.junit.Assume.assumeTrue
import org.junit.rules.TestRule
import org.junit.runner.Description
import org.junit.runners.model.Statement

@Deprecated("Use Build.VERSION_CODES", ReplaceWith("Build.VERSION_CODES.S_V2"))
const val SC_V2 = Build.VERSION_CODES.S_V2

private val MAX_TARGET_SDK_ANNOTATION_RE = Pattern.compile("MaxTargetSdk([0-9]+)$")
private val targetSdk = InstrumentationRegistry.getContext().applicationInfo.targetSdkVersion

private fun isDevSdkInRange(minExclusive: String?, maxInclusive: String?): Boolean {
    return (minExclusive == null || !isAtMost(minExclusive)) &&
            (maxInclusive == null || isAtMost(maxInclusive))
}

private fun isAtMost(sdkVersionOrCodename: String): Boolean {
    // UnboundedSdkLevel does not support builds < Q, and may stop supporting Q as well since it
    // is intended for mainline modules that are now R+.
    if (Build.VERSION.SDK_INT <= Build.VERSION_CODES.Q) {
        // Assume that any codename passed as argument from current code is a more recent build than
        // Q: this util did not exist before Q, and codenames are only used before the corresponding
        // build is finalized. This util could list 28 older codenames to check against (as per
        // ro.build.version.known_codenames in more recent builds), but this does not seem valuable.
        val intVersion = sdkVersionOrCodename.toIntOrNull() ?: return true
        return Build.VERSION.SDK_INT <= intVersion
    }
    return UnboundedSdkLevel.isAtMost(sdkVersionOrCodename)
}

/**
 * Returns true if the development SDK version of the device is in the provided annotation range.
 *
 * If the device is not using a release SDK, the development SDK differs from
 * [Build.VERSION.SDK_INT], and is indicated by the device codenames; see [UnboundedSdkLevel].
 */
fun isDevSdkInRange(
    ignoreUpTo: DevSdkIgnoreRule.IgnoreUpTo?,
    ignoreAfter: DevSdkIgnoreRule.IgnoreAfter?
): Boolean {
    val minExclusive =
            if (ignoreUpTo?.value == 0) ignoreUpTo.codename
            else ignoreUpTo?.value?.toString()
    val maxInclusive =
            if (ignoreAfter?.value == 0) ignoreAfter.codename
            else ignoreAfter?.value?.toString()
    return isDevSdkInRange(minExclusive, maxInclusive)
}

private fun getMaxTargetSdk(description: Description): Int? {
    return description.annotations.firstNotNullOfOrNull {
        MAX_TARGET_SDK_ANNOTATION_RE.matcher(it.annotationClass.simpleName).let { m ->
            if (m.find()) m.group(1).toIntOrNull() else null
        }
    }
}

/**
 * A test rule to ignore tests based on the development SDK level.
 *
 * If the device is not using a release SDK, the development SDK is considered to be higher than
 * [Build.VERSION.SDK_INT].
 *
 * @param ignoreClassUpTo Skip all tests in the class if the device dev SDK is <= this codename or
 *                        SDK level.
 * @param ignoreClassAfter Skip all tests in the class if the device dev SDK is > this codename or
 *                         SDK level.
 */
class DevSdkIgnoreRule @JvmOverloads constructor(
    private val ignoreClassUpTo: String? = null,
    private val ignoreClassAfter: String? = null
) : TestRule {
    /**
     * @param ignoreClassUpTo Skip all tests in the class if the device dev SDK is <= this value.
     * @param ignoreClassAfter Skip all tests in the class if the device dev SDK is > this value.
     */
    @JvmOverloads
    constructor(ignoreClassUpTo: Int?, ignoreClassAfter: Int? = null) : this(
            ignoreClassUpTo?.toString(), ignoreClassAfter?.toString())

    override fun apply(base: Statement, description: Description): Statement {
        return IgnoreBySdkStatement(base, description)
    }

    /**
     * Ignore the test for any development SDK that is strictly after [value].
     *
     * If the device is not using a release SDK, the development SDK is considered to be higher
     * than [Build.VERSION.SDK_INT].
     */
    annotation class IgnoreAfter(val value: Int = 0, val codename: String = "")

    /**
     * Ignore the test for any development SDK that lower than or equal to [value].
     *
     * If the device is not using a release SDK, the development SDK is considered to be higher
     * than [Build.VERSION.SDK_INT].
     */
    annotation class IgnoreUpTo(val value: Int = 0, val codename: String = "")

    private inner class IgnoreBySdkStatement(
        private val base: Statement,
        private val description: Description
    ) : Statement() {
        override fun evaluate() {
            val ignoreAfter = description.getAnnotation(IgnoreAfter::class.java)
            val ignoreUpTo = description.getAnnotation(IgnoreUpTo::class.java)

            val devSdkMessage = "Skipping test for build ${Build.VERSION.CODENAME} " +
                    "with SDK ${Build.VERSION.SDK_INT}"
            assumeTrue(devSdkMessage, isDevSdkInRange(ignoreClassUpTo, ignoreClassAfter))
            assumeTrue(devSdkMessage, isDevSdkInRange(ignoreUpTo, ignoreAfter))

            val maxTargetSdk = getMaxTargetSdk(description)
            if (maxTargetSdk != null) {
                assumeTrue("Skipping test, target SDK $targetSdk greater than $maxTargetSdk",
                        targetSdk <= maxTargetSdk)
            }
            base.evaluate()
        }
    }
}