• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2023 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.tools.metalava.model
18 
19 import com.android.tools.metalava.model.testing.testTypeString
20 import com.google.common.truth.Truth.assertThat
21 import kotlin.test.assertEquals
22 import kotlin.test.assertIs
23 import kotlin.test.assertNotNull
24 
25 interface Assertions {
26 
27     /**
28      * Get the class from the [Codebase], failing if it does not exist.
29      *
30      * Checks to make sure that returned [ClassItem]'s [ClassItem.emit] property matches
31      * [expectedEmit]. That defaults to `true` as this is usually used to retrieve a class that is
32      * present in the source which have `emit = true` by default.
33      */
assertClassnull34     fun Codebase.assertClass(qualifiedName: String, expectedEmit: Boolean = true): ClassItem {
35         val classItem = findClass(qualifiedName)
36         assertNotNull(classItem, message = "Expected $qualifiedName to be defined")
37         assertEquals(
38             expectedEmit,
39             classItem.emit,
40             message = "Expected $qualifiedName to have emit=$expectedEmit"
41         )
42         return classItem
43     }
44 
45     /**
46      * Resolve the class from the [Codebase], failing if it does not exist.
47      *
48      * Checks to make sure that returned [ClassItem]'s [ClassItem.emit] property matches
49      * [expectedEmit]. That defaults to `true` as this is usually used to retrieve a class that is
50      * present in the source which have `emit = true` by default.
51      */
Codebasenull52     fun Codebase.assertResolvedClass(
53         qualifiedName: String,
54         expectedEmit: Boolean = false
55     ): ClassItem {
56         // Resolve the class which should make it available to assertClass(...) if it could be
57         // found.
58         resolveClass(qualifiedName)
59         // Assert that the class exists and has correct setting of `emit`.
60         return assertClass(qualifiedName, expectedEmit)
61     }
62 
63     /** Get the package from the [Codebase], failing if it does not exist. */
assertPackagenull64     fun Codebase.assertPackage(pkgName: String): PackageItem {
65         val packageItem = findPackage(pkgName)
66         assertNotNull(packageItem, message = "Expected $pkgName to be defined")
67         return packageItem
68     }
69 
70     /** Get the type alias from the [Codebase], failing if it does not exist. */
assertTypeAliasnull71     fun Codebase.assertTypeAlias(qualifiedName: String): TypeAliasItem {
72         val typeAliasItem = findTypeAlias(qualifiedName)
73         assertNotNull(typeAliasItem, message = "Expected $qualifiedName to be a defined type alias")
74         return typeAliasItem
75     }
76 
77     /**
78      * Return a dump of the state of [SelectableItem.selectedApiVariants] across this [Codebase].
79      */
<lambda>null80     private fun Codebase.dumpSelectedApiVariants() = buildString {
81         accept(
82             object :
83                 BaseItemVisitor(
84                     preserveClassNesting = true,
85                 ) {
86                 private var indent = ""
87 
88                 override fun visitSelectableItem(item: SelectableItem) {
89                     append("$indent${item.describe()} - ${item.selectedApiVariants}\n")
90                     indent += "  "
91                 }
92 
93                 override fun afterVisitSelectableItem(item: SelectableItem) {
94                     indent = indent.substring(2)
95                 }
96             }
97         )
98     }
99 
100     /** Assert that the [dumpSelectedApiVariants] matches [expected]. */
assertSelectedApiVariantsnull101     fun Codebase.assertSelectedApiVariants(expected: String, message: String? = null) {
102         val actual = dumpSelectedApiVariants()
103         assertEquals(expected.trimIndent(), actual.trimEnd(), message)
104     }
105 
106     /** Get the field from the [ClassItem], failing if it does not exist. */
ClassItemnull107     fun ClassItem.assertField(fieldName: String): FieldItem {
108         val fieldItem = findField(fieldName)
109         assertNotNull(fieldItem, message = "Expected $fieldName to be defined")
110         return fieldItem
111     }
112 
113     /** Get the method from the [ClassItem], failing if it does not exist. */
ClassItemnull114     fun ClassItem.assertMethod(methodName: String, parameters: String): MethodItem {
115         val methodItem = findMethod(methodName, parameters)
116         assertNotNull(methodItem, message = "Expected $methodName($parameters) to be defined")
117         return methodItem
118     }
119 
120     /** Get the constructor from the [ClassItem], failing if it does not exist. */
ClassItemnull121     fun ClassItem.assertConstructor(parameters: String): ConstructorItem {
122         val constructorItem = findConstructor(parameters)
123         assertNotNull(
124             constructorItem,
125             message = "Expected ${simpleName()}($parameters) to be defined"
126         )
127         return assertIs(constructorItem)
128     }
129 
130     /** Get the property from the [ClassItem], failing if it does not exist. */
ClassItemnull131     fun ClassItem.assertProperty(propertyName: String): PropertyItem {
132         val propertyItem = properties().firstOrNull { it.name() == propertyName }
133         assertNotNull(propertyItem, message = "Expected $propertyName to be defined")
134         return propertyItem
135     }
136 
137     /** Get the annotation from the [Item], failing if it does not exist. */
Itemnull138     fun Item.assertAnnotation(qualifiedName: String): AnnotationItem {
139         val annoItem = modifiers.findAnnotation(qualifiedName)
140         assertNotNull(annoItem, message = "Expected item to be annotated with ($qualifiedName)")
141         return assertIs(annoItem)
142     }
143 
144     /**
145      * Check the [Item.originallyDeprecated] and [Item.effectivelyDeprecated] are
146      * [explicitlyDeprecated] and [implicitlyDeprecated] respectively.
147      */
Itemnull148     private fun Item.assertDeprecatedStatus(
149         explicitlyDeprecated: Boolean,
150         implicitlyDeprecated: Boolean = explicitlyDeprecated,
151     ) {
152         assertEquals(
153             explicitlyDeprecated,
154             originallyDeprecated,
155             message = "$this: originallyDeprecated"
156         )
157         assertEquals(
158             implicitlyDeprecated,
159             effectivelyDeprecated,
160             message = "$this: effectivelyDeprecated"
161         )
162     }
163 
164     /** Make sure that the item is not deprecated explicitly, or implicitly. */
Itemnull165     fun Item.assertNotDeprecated() {
166         assertDeprecatedStatus(explicitlyDeprecated = false)
167     }
168 
169     /** Make sure that the item is explicitly deprecated. */
assertExplicitlyDeprecatednull170     fun Item.assertExplicitlyDeprecated() {
171         assertDeprecatedStatus(explicitlyDeprecated = true)
172     }
173 
174     /**
175      * Make sure that the item is implicitly deprecated, this will fail if the item is explicitly
176      * deprecated.
177      */
Itemnull178     fun Item.assertImplicitlyDeprecated() {
179         assertDeprecatedStatus(
180             explicitlyDeprecated = false,
181             implicitlyDeprecated = true,
182         )
183     }
184 
185     /**
186      * Create a Kotlin like method description. It uses Kotlin structure for a method and Kotlin
187      * style nulls but not Kotlin types.
188      */
<lambda>null189     fun CallableItem.kotlinLikeDescription(): String = buildString {
190         if (isConstructor()) {
191             append("constructor ")
192         } else {
193             append("fun ")
194         }
195         append(name())
196         append("(")
197         parameters().joinTo(this) {
198             "${it.name()}: ${it.type().testTypeString(kotlinStyleNulls = true)}"
199         }
200         append("): ")
201         append(returnType().testTypeString(kotlinStyleNulls = true))
202     }
203 
204     /** Get the [AnnotationAttribute] from the [AnnotationItem], failing if it does not exist. */
AnnotationItemnull205     fun AnnotationItem.assertAttribute(name: String): AnnotationAttribute {
206         val attribute = findAttribute(name)
207         assertNotNull(attribute, message = "Expected $this to contain attribute $name")
208         return attribute
209     }
210 
211     /** Get the list of fully qualified annotation names associated with the [TypeItem]. */
TypeItemnull212     fun TypeItem.annotationNames(): List<String?> {
213         return modifiers.annotations.map { it.qualifiedName }
214     }
215 
216     /** Get the list of fully qualified annotation names associated with the [Item]. */
Itemnull217     fun Item.annotationNames(): List<String?> {
218         return modifiers.annotations().map { it.qualifiedName }
219     }
220 
221     /**
222      * Check to make sure that this [TypeItem] is actually a [VariableTypeItem] whose
223      * [VariableTypeItem.asTypeParameter] references the supplied [typeParameter] and then run the
224      * optional lambda on the [VariableTypeItem].
225      */
assertReferencesTypeParameternull226     fun TypeItem.assertReferencesTypeParameter(
227         typeParameter: TypeParameterItem,
228         body: (VariableTypeItem.() -> Unit)? = null
229     ) {
230         assertVariableTypeItem {
231             assertThat(asTypeParameter).isSameInstanceAs(typeParameter)
232             if (body != null) this.body()
233         }
234     }
235 
236     /**
237      * Check to make sure that this nullable [TypeItem] is actually a [TypeItem] and then run the
238      * optional lambda on the [TypeItem].
239      */
assertNotNullTypeItemnull240     fun <T : TypeItem> T?.assertNotNullTypeItem(body: (T.() -> Unit)? = null) {
241         assertThat(this).isNotNull()
242         if (body != null) this?.body()
243     }
244 
245     /**
246      * Check to make sure that this [TypeItem] is actually a [ArrayTypeItem] and then run the
247      * optional lambda on the [ArrayTypeItem].
248      */
assertArrayTypeItemnull249     fun TypeItem?.assertArrayTypeItem(body: (ArrayTypeItem.() -> Unit)? = null) {
250         assertIsInstanceOf(body ?: {})
251     }
252 
253     /**
254      * Check to make sure that this [TypeItem] is actually a [ClassTypeItem] and then run the
255      * optional lambda on the [ClassTypeItem].
256      */
assertClassTypeItemnull257     fun TypeItem?.assertClassTypeItem(body: (ClassTypeItem.() -> Unit)? = null) {
258         assertIsInstanceOf(body ?: {})
259     }
260 
261     /**
262      * Check to make sure that this [TypeItem] is actually a [PrimitiveTypeItem] and then run the
263      * optional lambda on the [PrimitiveTypeItem].
264      */
assertPrimitiveTypeItemnull265     fun TypeItem?.assertPrimitiveTypeItem(body: (PrimitiveTypeItem.() -> Unit)? = null) {
266         assertIsInstanceOf(body ?: {})
267     }
268 
269     /**
270      * Check to make sure that this [TypeItem] is actually a [LambdaTypeItem] and then run the
271      * optional lambda on the [LambdaTypeItem].
272      */
assertLambdaTypeItemnull273     fun TypeItem?.assertLambdaTypeItem(body: (LambdaTypeItem.() -> Unit)? = null) {
274         assertIsInstanceOf(body ?: {})
275     }
276 
277     /**
278      * Check to make sure that this [TypeItem] is actually a [VariableTypeItem] and then run the
279      * optional lambda on the [VariableTypeItem].
280      */
assertVariableTypeItemnull281     fun TypeItem?.assertVariableTypeItem(body: (VariableTypeItem.() -> Unit)? = null) {
282         assertIsInstanceOf(body ?: {})
283     }
284 
285     /**
286      * Check to make sure that this [TypeItem] is actually a [WildcardTypeItem] and then run the
287      * optional lambda on the [WildcardTypeItem].
288      */
assertWildcardItemnull289     fun TypeItem?.assertWildcardItem(body: (WildcardTypeItem.() -> Unit)? = null) {
290         assertIsInstanceOf(body ?: {})
291     }
292 
293     companion object : Assertions {}
294 }
295 
assertIsInstanceOfnull296 private inline fun <reified T> Any?.assertIsInstanceOf(body: (T).() -> Unit) {
297     assertThat(this).isInstanceOf(T::class.java)
298     (this as T).body()
299 }
300