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