1 /* 2 * 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.platform.test.rule 18 19 import com.google.common.truth.Truth.assertThat 20 import com.google.common.truth.Truth.assertWithMessage 21 import java.lang.annotation.Inherited 22 import kotlin.reflect.full.memberFunctions 23 import org.junit.Test 24 import org.junit.runner.Description 25 import org.junit.runner.JUnitCore 26 import org.junit.runner.Request 27 import org.junit.runner.RunWith 28 import org.junit.runners.Parameterized 29 30 @Inherited @Retention(AnnotationRetention.RUNTIME) annotation class Flavor(val value: String = "") 31 32 @Retention(AnnotationRetention.RUNTIME) annotation class NotInherited(val value: String = "") 33 34 @Inherited 35 @Retention(AnnotationRetention.RUNTIME) 36 @MetaAnnotation 37 @Flavor("umami") 38 annotation class Umami() 39 descriptionnull40inline fun <reified T> String.description() = 41 Description.createTestDescription( 42 T::class.java, 43 this, 44 *(T::class.memberFunctions.single { it.name == this }.annotations.toTypedArray()), 45 ) 46 47 class TestAnnotationScannerTest { 48 class NoAnnotation { aTestnull49 fun aTest() {} 50 } 51 52 @Test noAnnotationnull53 fun noAnnotation() { 54 val scanner = TestAnnotationScanner() 55 val method = "aTest".description<NoAnnotation>() 56 assertThat(scanner.find<Flavor>(method)).isNull() 57 } 58 59 class AnnotationOnMethod { aTestnull60 @Flavor("spicy") fun aTest() {} 61 } 62 63 @Test annotationOnMethodnull64 fun annotationOnMethod() { 65 val scanner = TestAnnotationScanner() 66 val method = "aTest".description<AnnotationOnMethod>() 67 assertThat(scanner.find<Flavor>(method)?.value).isEqualTo("spicy") 68 } 69 70 @Flavor("sour") 71 class AnnotationOnClass { aTestnull72 fun aTest() {} 73 } 74 75 @Test annotationOnClassnull76 fun annotationOnClass() { 77 val scanner = TestAnnotationScanner() 78 val method = "aTest".description<AnnotationOnClass>() 79 assertThat(scanner.find<Flavor>(method)?.value).isEqualTo("sour") 80 } 81 82 @Flavor("bitter") 83 class AnnotationOnBoth { aTestnull84 @Flavor("sweet") fun aTest() {} 85 } 86 87 @Test methodWinsOnBothnull88 fun methodWinsOnBoth() { 89 val scanner = TestAnnotationScanner() 90 val method = "aTest".description<AnnotationOnBoth>() 91 assertThat(scanner.find<Flavor>(method)?.value).isEqualTo("sweet") 92 } 93 94 @NotInherited("only for this class") 95 @Flavor("tangy") 96 @AllowedDevices(DeviceProduct.CF_PHONE) 97 open class Superclass 98 99 class Subclass : Superclass() { aTestnull100 fun aTest() {} 101 } 102 103 @Test findSuperclassIfInheritednull104 fun findSuperclassIfInherited() { 105 val scanner = TestAnnotationScanner() 106 val method = "aTest".description<Subclass>() 107 assertThat(scanner.find<Flavor>(method)?.value).isEqualTo("tangy") 108 } 109 110 @Test findAllowedDevicesFromSuperclassnull111 fun findAllowedDevicesFromSuperclass() { 112 val scanner = TestAnnotationScanner() 113 val method = "aTest".description<Subclass>() 114 assertThat(scanner.find<AllowedDevices>(method)?.allowed) 115 .isEqualTo(arrayOf(DeviceProduct.CF_PHONE)) 116 } 117 118 @Test dontFindSuperclassIfNotInheritednull119 fun dontFindSuperclassIfNotInherited() { 120 val scanner = TestAnnotationScanner() 121 val method = "aTest".description<Subclass>() 122 assertThat(scanner.find<NotInherited>(method)).isNull() 123 } 124 125 @Umami 126 open class UmamiTest { aTestnull127 fun aTest() {} 128 } 129 130 @Test metaAnnotationnull131 fun metaAnnotation() { 132 val scanner = TestAnnotationScanner() 133 val method = "aTest".description<UmamiTest>() 134 assertThat(scanner.find<Flavor>(method)!!.value).isEqualTo("umami") 135 } 136 137 @RunWith(Parameterized::class) 138 class ParameterizedUmamiSubclassTest : UmamiTest() { 139 companion object { 140 @Parameterized.Parameters(name = "{0}") 141 @JvmStatic getParamsnull142 fun getParams() = listOf("1", "2", "3") 143 } 144 145 @Parameterized.Parameter lateinit var param: String 146 147 @Test fun bTest() {} 148 } 149 150 @Test parameterizedSubclassnull151 fun parameterizedSubclass() { 152 val scanner = TestAnnotationScanner() 153 val request = Request.classes(ParameterizedUmamiSubclassTest::class.java) 154 assertThat(JUnitCore().run(request).failures).isEmpty() 155 val rootDescription = request.runner.description 156 val leaves = rootDescription.leafDescriptions() 157 assertThat(leaves.map { it.methodName }).contains("bTest[1]") 158 val bTestDescription = leaves.first { it.methodName == "bTest[1]" } 159 assertWithMessage(rootDescription.familyTree()) 160 .that(scanner.find<Flavor>(bTestDescription)?.value) 161 .isEqualTo("umami") 162 } 163 } 164 Descriptionnull165private fun Description.familyTree(): String { 166 return toString() + 167 if (isTest) { 168 "" 169 } else { 170 children.map { it.familyTree() }.toString() 171 } 172 } 173 Descriptionnull174private fun Description.leafDescriptions(): List<Description> { 175 return if (isTest) { 176 listOf(this) 177 } else { 178 children.flatMap { it.leafDescriptions() } 179 } 180 } 181