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 com.android.tools.metalava.model.testsuite.codebase
18 
19 import com.android.tools.metalava.model.ClassItem
20 import com.android.tools.metalava.model.testsuite.BaseModelTest
21 import com.android.tools.metalava.testing.java
22 import com.android.tools.metalava.testing.kotlin
23 import kotlin.test.assertNotNull
24 import kotlin.test.assertNull
25 import kotlin.test.assertSame
26 import org.junit.Test
27 import org.junit.runners.Parameterized
28 
29 class ParameterizedFindClassTest : BaseModelTest() {
30 
31     @Parameterized.Parameter(0) lateinit var params: TestParams
32 
33     data class TestParams(
34         val className: String,
35         val expectedFound: Boolean,
36         /**
37          * This is only tested when `true` as even though the class might be unknown some models
38          * that work with partial information, e.g. text, will fabricate an instance just in case it
39          * was real.
40          */
41         val expectedResolved: Boolean = expectedFound,
42     ) {
toStringnull43         override fun toString(): String {
44             return className
45         }
46     }
47 
48     companion object {
49         private val params =
50             listOf(
51                 TestParams(
52                     className = "test.pkg.Foo",
53                     expectedFound = true,
54                 ),
55                 TestParams(
56                     className = "test.pkg.Unknown",
57                     expectedFound = false,
58                 ),
59                 TestParams(
60                     // Test to make sure that a class whose name does not match the file name will
61                     // be found.
62                     className = "test.pkg.SecondInFile",
63                     expectedFound = true,
64                 ),
65                 // The following classes will be explicitly loaded. Although these are used
66                 // implicitly the behavior differs between models so is hard to test. By specifying
67                 // them explicitly it makes the tests more consistent.
68                 TestParams(
69                     className = "java.lang.Object",
70                     expectedFound = true,
71                 ),
72                 TestParams(
73                     className = "java.lang.Throwable",
74                     expectedFound = true,
75                 ),
76                 // The following classes are implicitly used, directly, or indirectly and are tested
77                 // to check that the implicit use does not accidentally include them when they
78                 // should not. However, they should all be resolvable.
79                 TestParams(
80                     className = "java.lang.annotation.Annotation",
81                     expectedFound = false,
82                     expectedResolved = true,
83                 ),
84                 TestParams(
85                     className = "java.lang.Enum",
86                     expectedFound = false,
87                     expectedResolved = true,
88                 ),
89                 TestParams(
90                     className = "java.lang.Comparable",
91                     expectedFound = false,
92                     expectedResolved = true,
93                 ),
94                 // The following should not be used implicitly by anything.
95                 TestParams(
96                     className = "java.io.File",
97                     expectedFound = false,
98                     expectedResolved = true,
99                 ),
100             )
101 
paramsnull102         @JvmStatic @Parameterized.Parameters fun params() = params
103     }
104 
105     private fun assertFound(className: String, expectedFound: Boolean, foundClass: ClassItem?) {
106         if (expectedFound) {
107             assertNotNull(foundClass, message = "$className should exist")
108         } else {
109             assertNull(foundClass, message = "$className should not exist")
110         }
111     }
112 
113     @Test
test findClass()null114     fun `test findClass()`() {
115         runCodebaseTest(
116             signature(
117                 """
118                     // Signature format: 2.0
119                     package test.pkg {
120                       public class Foo {
121                         method public Object foo(Throwable) throws Throwable;
122                       }
123                       public class SecondInFile {
124                       }
125                     }
126                 """
127             ),
128             java(
129                 """
130                     package test.pkg;
131                     public class Foo {
132                         private Foo() {}
133                         public Object foo(Throwable t) throws Throwable {throw new Throwable();}
134                     }
135                     public class SecondInFile {
136                     }
137                 """
138             ),
139             kotlin(
140                 """
141                     package test.pkg
142                     class Foo
143                     private constructor() {
144                         @Throws(Throwable::class)
145                         fun foo(t: Throwable): Any {throw Throwable()}
146                     }
147                     class SecondInFile {
148                     }
149                 """
150             ),
151         ) {
152             val fooMethod = codebase.assertClass("test.pkg.Foo").methods().single()
153 
154             // Force loading of the Object classes by resolving the return type which is
155             // java.lang.Object.
156             fooMethod.returnType().asClass()
157 
158             // Force loading of the Throwable classes by resolving the parameter's type which is
159             // java.lang.Object.
160             fooMethod.parameters().single().type().asClass()
161 
162             val className = params.className
163             val foundClass = codebase.findClass(className)
164             assertFound(className, params.expectedFound, foundClass)
165 
166             val resolvedClass = codebase.resolveClass(className)
167             if (foundClass == null) {
168                 // If the class was not found then resolving might have found it.
169                 if (params.expectedResolved) {
170                     assertNotNull(resolvedClass, message = "expected to resolve $className")
171                 }
172 
173                 // If the class was resolved then it must now be found.
174                 if (resolvedClass != null) {
175                     val foundClassAfterResolving = codebase.findClass(className)
176                     assertSame(
177                         resolvedClass,
178                         foundClassAfterResolving,
179                         message = "could not find $className even though it was previously resolved"
180                     )
181                 }
182             } else {
183                 // If the class was found then it must be resolved to the same class.
184                 assertSame(
185                     foundClass,
186                     resolvedClass,
187                     message = "could not resolve $className even though it was previously found"
188                 )
189             }
190         }
191     }
192 }
193