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.google.common.truth.Truth.assertThat
20 import kotlin.test.assertEquals
21 import kotlin.test.assertIs
22 import kotlin.test.assertNotNull
23 
24 interface Assertions {
25 
26     /** Get the class from the [Codebase], failing if it does not exist. */
assertClassnull27     fun Codebase.assertClass(qualifiedName: String): ClassItem {
28         val classItem = findClass(qualifiedName)
29         assertNotNull(classItem, message = "Expected $qualifiedName to be defined")
30         return classItem
31     }
32 
33     /** Resolve the class from the [Codebase], failing if it does not exist. */
Codebasenull34     fun Codebase.assertResolvedClass(qualifiedName: String): ClassItem {
35         val classItem = resolveClass(qualifiedName)
36         assertNotNull(classItem, message = "Expected $qualifiedName to be defined")
37         return classItem
38     }
39 
40     /** Get the package from the [Codebase], failing if it does not exist. */
assertPackagenull41     fun Codebase.assertPackage(pkgName: String): PackageItem {
42         val packageItem = findPackage(pkgName)
43         assertNotNull(packageItem, message = "Expected $pkgName to be defined")
44         return packageItem
45     }
46 
47     /** Get the field from the [ClassItem], failing if it does not exist. */
ClassItemnull48     fun ClassItem.assertField(fieldName: String): FieldItem {
49         val fieldItem = findField(fieldName)
50         assertNotNull(fieldItem, message = "Expected $fieldName to be defined")
51         return fieldItem
52     }
53 
54     /** Get the method from the [ClassItem], failing if it does not exist. */
ClassItemnull55     fun ClassItem.assertMethod(methodName: String, parameters: String): MethodItem {
56         val methodItem = findMethod(methodName, parameters)
57         assertNotNull(methodItem, message = "Expected $methodName($parameters) to be defined")
58         return methodItem
59     }
60 
61     /** Get the constructor from the [ClassItem], failing if it does not exist. */
ClassItemnull62     fun ClassItem.assertConstructor(parameters: String): ConstructorItem {
63         val methodItem = findMethod(simpleName(), parameters)
64         assertNotNull(methodItem, message = "Expected ${simpleName()}($parameters) to be defined")
65         return assertIs(methodItem)
66     }
67 
68     /** Get the property from the [ClassItem], failing if it does not exist. */
ClassItemnull69     fun ClassItem.assertProperty(propertyName: String): PropertyItem {
70         val propertyItem = properties().firstOrNull { it.name() == propertyName }
71         assertNotNull(propertyItem, message = "Expected $propertyName to be defined")
72         return propertyItem
73     }
74 
75     /** Get the annotation from the [Item], failing if it does not exist. */
Itemnull76     fun Item.assertAnnotation(qualifiedName: String): AnnotationItem {
77         val annoItem = modifiers.findAnnotation(qualifiedName)
78         assertNotNull(annoItem, message = "Expected item to be annotated with ($qualifiedName)")
79         return assertIs(annoItem)
80     }
81 
82     /**
83      * Check the [Item.originallyDeprecated] and [Item.effectivelyDeprecated] are
84      * [explicitlyDeprecated] and [implicitlyDeprecated] respectively.
85      */
Itemnull86     private fun Item.assertDeprecatedStatus(
87         explicitlyDeprecated: Boolean,
88         implicitlyDeprecated: Boolean = explicitlyDeprecated,
89     ) {
90         assertEquals(
91             explicitlyDeprecated,
92             originallyDeprecated,
93             message = "$this: originallyDeprecated"
94         )
95         assertEquals(
96             implicitlyDeprecated,
97             effectivelyDeprecated,
98             message = "$this: effectivelyDeprecated"
99         )
100     }
101 
102     /** Make sure that the item is not deprecated explicitly, or implicitly. */
Itemnull103     fun Item.assertNotDeprecated() {
104         assertDeprecatedStatus(explicitlyDeprecated = false)
105     }
106 
107     /** Make sure that the item is explicitly deprecated. */
assertExplicitlyDeprecatednull108     fun Item.assertExplicitlyDeprecated() {
109         assertDeprecatedStatus(explicitlyDeprecated = true)
110     }
111 
112     /**
113      * Make sure that the item is implicitly deprecated, this will fail if the item is explicitly
114      * deprecated.
115      */
Itemnull116     fun Item.assertImplicitlyDeprecated() {
117         assertDeprecatedStatus(
118             explicitlyDeprecated = false,
119             implicitlyDeprecated = true,
120         )
121     }
122 
123     /**
124      * Create a Kotlin like method description. It uses Kotlin structure for a method and Kotlin
125      * style nulls but not Kotlin types.
126      */
<lambda>null127     fun MethodItem.kotlinLikeDescription(): String = buildString {
128         if (isConstructor()) {
129             append("constructor ")
130         } else {
131             append("fun ")
132         }
133         append(name())
134         append("(")
135         parameters().joinTo(this) {
136             "${it.name()}: ${it.type().toTypeString(kotlinStyleNulls = true)}"
137         }
138         append("): ")
139         append(returnType().toTypeString(kotlinStyleNulls = true))
140     }
141 
142     /** Get the list of fully qualified annotation names associated with the [TypeItem]. */
TypeItemnull143     fun TypeItem.annotationNames(): List<String?> {
144         return modifiers.annotations().map { it.qualifiedName }
145     }
146 
147     /** Get the list of fully qualified annotation names associated with the [Item]. */
Itemnull148     fun Item.annotationNames(): List<String?> {
149         return modifiers.annotations().map { it.qualifiedName }
150     }
151 
152     /**
153      * Check to make sure that this [TypeItem] is actually a [VariableTypeItem] whose
154      * [VariableTypeItem.asTypeParameter] references the supplied [typeParameter] and then run the
155      * optional lambda on the [VariableTypeItem].
156      */
assertReferencesTypeParameternull157     fun TypeItem.assertReferencesTypeParameter(
158         typeParameter: TypeParameterItem,
159         body: (VariableTypeItem.() -> Unit)? = null
160     ) {
161         assertVariableTypeItem {
162             assertThat(asTypeParameter).isSameInstanceAs(typeParameter)
163             if (body != null) this.body()
164         }
165     }
166 
167     /**
168      * Check to make sure that this nullable [TypeItem] is actually a [TypeItem] and then run the
169      * optional lambda on the [TypeItem].
170      */
assertNotNullTypeItemnull171     fun <T : TypeItem> T?.assertNotNullTypeItem(body: (T.() -> Unit)? = null) {
172         assertThat(this).isNotNull()
173         if (body != null) this?.body()
174     }
175 
176     /**
177      * Check to make sure that this [TypeItem] is actually a [ArrayTypeItem] and then run the
178      * optional lambda on the [ArrayTypeItem].
179      */
assertArrayTypeItemnull180     fun TypeItem?.assertArrayTypeItem(body: (ArrayTypeItem.() -> Unit)? = null) {
181         assertIsInstanceOf(body ?: {})
182     }
183 
184     /**
185      * Check to make sure that this [TypeItem] is actually a [ClassTypeItem] and then run the
186      * optional lambda on the [ClassTypeItem].
187      */
assertClassTypeItemnull188     fun TypeItem?.assertClassTypeItem(body: (ClassTypeItem.() -> Unit)? = null) {
189         assertIsInstanceOf(body ?: {})
190     }
191 
192     /**
193      * Check to make sure that this [TypeItem] is actually a [PrimitiveTypeItem] and then run the
194      * optional lambda on the [PrimitiveTypeItem].
195      */
assertPrimitiveTypeItemnull196     fun TypeItem?.assertPrimitiveTypeItem(body: (PrimitiveTypeItem.() -> Unit)? = null) {
197         assertIsInstanceOf(body ?: {})
198     }
199 
200     /**
201      * Check to make sure that this [TypeItem] is actually a [LambdaTypeItem] and then run the
202      * optional lambda on the [LambdaTypeItem].
203      */
assertLambdaTypeItemnull204     fun TypeItem?.assertLambdaTypeItem(body: (LambdaTypeItem.() -> Unit)? = null) {
205         assertIsInstanceOf(body ?: {})
206     }
207 
208     /**
209      * Check to make sure that this [TypeItem] is actually a [VariableTypeItem] and then run the
210      * optional lambda on the [VariableTypeItem].
211      */
assertVariableTypeItemnull212     fun TypeItem?.assertVariableTypeItem(body: (VariableTypeItem.() -> Unit)? = null) {
213         assertIsInstanceOf(body ?: {})
214     }
215 
216     /**
217      * Check to make sure that this [TypeItem] is actually a [WildcardTypeItem] and then run the
218      * optional lambda on the [WildcardTypeItem].
219      */
assertWildcardItemnull220     fun TypeItem?.assertWildcardItem(body: (WildcardTypeItem.() -> Unit)? = null) {
221         assertIsInstanceOf(body ?: {})
222     }
223 }
224 
assertIsInstanceOfnull225 private inline fun <reified T> Any?.assertIsInstanceOf(body: (T).() -> Unit) {
226     assertThat(this).isInstanceOf(T::class.java)
227     (this as T).body()
228 }
229