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.testsuite.methoditem
18 
19 import com.android.tools.metalava.model.provider.InputFormat
20 import com.android.tools.metalava.model.testsuite.BaseModelTest
21 import com.android.tools.metalava.testing.KnownSourceFiles
22 import com.android.tools.metalava.testing.java
23 import com.android.tools.metalava.testing.kotlin
24 import com.google.common.truth.Truth.assertWithMessage
25 import org.junit.Assert.assertEquals
26 import org.junit.Assert.assertNull
27 import org.junit.Test
28 
29 /** Common tests for implementations of [ParameterItem]. */
30 class CommonParameterItemTest : BaseModelTest() {
31 
32     @Test
Test deprecated parameter by annotationnull33     fun `Test deprecated parameter by annotation`() {
34         runCodebaseTest(
35             signature(
36                 """
37                     // Signature format: 2.0
38                     package test.pkg {
39                       public class Bar {
40                         method public void foo(@Deprecated int);
41                       }
42                     }
43                 """
44             ),
45             java(
46                 """
47                     package test.pkg;
48 
49                     public class Bar {
50                         public void foo(@Deprecated int i) {}
51                     }
52                 """
53             ),
54             kotlin(
55                 """
56                     package test.pkg
57 
58                     class Bar {
59                         fun foo(@Deprecated i: Int) {}
60                     }
61                 """
62             ),
63         ) {
64             val parameterItem =
65                 codebase.assertClass("test.pkg.Bar").methods().single().parameters().single()
66             val annotation = parameterItem.modifiers.annotations().single()
67             if (inputFormat == InputFormat.KOTLIN) {
68                 assertEquals("kotlin.Deprecated", annotation.qualifiedName)
69             } else {
70                 assertEquals("java.lang.Deprecated", annotation.qualifiedName)
71             }
72             assertEquals("originallyDeprecated", true, parameterItem.originallyDeprecated)
73             assertEquals("effectivelyDeprecated", true, parameterItem.effectivelyDeprecated)
74         }
75     }
76 
77     @Test
Test not deprecated parameternull78     fun `Test not deprecated parameter`() {
79         runCodebaseTest(
80             signature(
81                 """
82                     // Signature format: 2.0
83                     package test.pkg {
84                       public class Bar {
85                         method public void foo(int);
86                       }
87                     }
88                 """
89             ),
90             java(
91                 """
92                     package test.pkg;
93 
94                     public class Bar {
95                         public void foo(int i) {}
96                     }
97                 """
98             ),
99             kotlin(
100                 """
101                     package test.pkg
102 
103                     class Bar {
104                         fun foo(i: Int) {}
105                     }
106                 """
107             ),
108         ) {
109             val parameterItem =
110                 codebase.assertClass("test.pkg.Bar").methods().single().parameters().single()
111             assertEquals("originallyDeprecated", false, parameterItem.originallyDeprecated)
112             assertEquals("effectivelyDeprecated", false, parameterItem.effectivelyDeprecated)
113         }
114     }
115 
116     @Test
Test publicName reports correct name when specifiednull117     fun `Test publicName reports correct name when specified`() {
118         runCodebaseTest(
119             inputSet(
120                 signature(
121                     """
122                         // Signature format: 2.0
123                         package test.pkg {
124                           public class Bar {
125                             method public void foo(int baz);
126                           }
127                         }
128                     """
129                 ),
130             ),
131             inputSet(
132                 KnownSourceFiles.supportParameterName,
133                 java(
134                     """
135                         package test.pkg;
136 
137                         import androidx.annotation.ParameterName;
138 
139                         public class Bar {
140                             public void foo(@ParameterName("baz") int baz) {}
141                         }
142                     """
143                 ),
144             ),
145             inputSet(
146                 kotlin(
147                     """
148                         package test.pkg
149 
150                         class Bar {
151                             fun foo(baz: Int) {}
152                         }
153                     """
154                 ),
155             ),
156         ) {
157             val parameterItem =
158                 codebase.assertClass("test.pkg.Bar").methods().single().parameters().single()
159             assertEquals("name()", "baz", parameterItem.name())
160             assertEquals("publicName()", "baz", parameterItem.publicName())
161         }
162     }
163 
164     @Test
Test publicName reports correct name when not specifiednull165     fun `Test publicName reports correct name when not specified`() {
166         runCodebaseTest(
167             signature(
168                 """
169                     // Signature format: 2.0
170                     package test.pkg {
171                       public class Bar {
172                         method public void foo(int);
173                       }
174                     }
175                 """
176             ),
177             java(
178                 """
179                     package test.pkg;
180 
181                     public class Bar {
182                         public void foo(int baz) {}
183                     }
184                 """
185             ),
186             // Kotlin treats all parameter names as public.
187         ) {
188             val parameterItem =
189                 codebase.assertClass("test.pkg.Bar").methods().single().parameters().single()
190             assertNull("publicName()", parameterItem.publicName())
191         }
192     }
193 
194     @Test
Test publicName reports correct name when called on binary class - Object#equalsnull195     fun `Test publicName reports correct name when called on binary class - Object#equals`() {
196         runCodebaseTest(
197             java(
198                 """
199                     package test.pkg;
200 
201                     public abstract class Bar {
202                     }
203                 """
204             ),
205             // No need to check any other sources as the source is not being tested, only used to
206             // trigger the test run.
207         ) {
208             val parameterItem =
209                 codebase
210                     .assertResolvedClass("java.lang.Object")
211                     .assertMethod("equals", "java.lang.Object")
212                     .parameters()
213                     .single()
214             // For some reason Object.equals(Object obj) provides the actual parameter name.
215             // Probably, because it was compiled with a late enough version of javac, and/or with
216             // the appropriate options to record the parameter name.
217             assertEquals("name()", "obj", parameterItem.name())
218             assertEquals("publicName()", "obj", parameterItem.publicName())
219         }
220     }
221 
222     @Test
Test publicName reports correct name when called on binary class - ViewGroup#onLayoutnull223     fun `Test publicName reports correct name when called on binary class - ViewGroup#onLayout`() {
224         runCodebaseTest(
225             java(
226                 """
227                     package test.pkg;
228 
229                     public abstract class Bar extends android.view.ViewGroup {
230                     }
231                 """
232             ),
233             // No need to check any other sources as the source is not being tested, only used to
234             // trigger the test run.
235         ) {
236             val parameterItems =
237                 codebase
238                     .assertResolvedClass("android.view.ViewGroup")
239                     .assertMethod("onLayout", "boolean, int, int, int, int")
240                     .parameters()
241             // For some reason ViewGroup.onLayout(boolean, int, int, int, int) does not provide the
242             // actual parameter name. Probably, because it was compiled with an older version of
243             // javac, and/or without the appropriate options to record the parameter name.
244             val expectedNames = listOf("p", "p1", "p2", "p3", "p4")
245             for (i in parameterItems.indices) {
246                 val parameterItem = parameterItems[i]
247                 val expectedName = expectedNames[i]
248                 assertEquals("$i:name()", expectedName, parameterItem.name())
249                 assertNull("$i:publicName()$parameterItem", parameterItem.publicName())
250             }
251         }
252     }
253 
254     @Test
Test nullability of parameter annotated with @not-type-use-NonNullnull255     fun `Test nullability of parameter annotated with @not-type-use-NonNull`() {
256         runCodebaseTest(
257             inputSet(
258                 KnownSourceFiles.notTypeUseNonNullSource,
259                 java(
260                     """
261                         package test.pkg;
262                         import java.util.Map;
263                         import not.type.use.NonNull;
264 
265                         public class Foo<T> {
266                             public void method1(@NonNull String p);
267                             public void method2(@NonNull String[] p);
268                             public void method3(@NonNull String[][] p);
269                             public void method4(@NonNull T p);
270                             public void method5(@NonNull Map.Entry<T, String> p);
271                         }
272                     """
273                 ),
274             ),
275             inputSet(
276                 signature(
277                     """
278                         // Signature format: 2.0
279                         package test.pkg {
280                           public class Foo<T> {
281                             method public void method1(@NonNull String);
282                             method public void method2(@NonNull String[]);
283                             method public void method3(@NonNull String[][]);
284                             method public void method4(@NonNull T);
285                             method public void method5(@NonNull java.util.Map.Entry<T, String>);
286                           }
287                         }
288                     """
289                 ),
290             ),
291             // Kotlin does not care about different nullability annotations.
292         ) {
293             val expectedTypes =
294                 mapOf(
295                     "method1" to "java.lang.String",
296                     "method2" to "java.lang.String![]",
297                     "method3" to "java.lang.String![]![]",
298                     "method4" to "T",
299                     "method5" to "java.util.Map.Entry<T!,java.lang.String!>",
300                 )
301             for (method in codebase.assertClass("test.pkg.Foo").methods()) {
302                 val name = method.name()
303                 val expectedType = expectedTypes[name]!!
304                 // Compare the kotlin style format of the parameter to ensure that only the
305                 // outermost type is affected by the not-type-use nullability annotation.
306                 val type = method.parameters().single().type()
307                 assertWithMessage(name)
308                     .that(type.toTypeString(kotlinStyleNulls = true))
309                     .isEqualTo(expectedType)
310             }
311         }
312     }
313 
314     @Test
Test nullability of parameter annotated with @not-type-use-Nullablenull315     fun `Test nullability of parameter annotated with @not-type-use-Nullable`() {
316         runCodebaseTest(
317             inputSet(
318                 KnownSourceFiles.notTypeUseNullableSource,
319                 java(
320                     """
321                         package test.pkg;
322                         import java.util.Map;
323                         import not.type.use.Nullable;
324 
325                         public class Foo<T> {
326                             public void method1(@Nullable String p);
327                             public void method2(@Nullable String[] p);
328                             public void method3(@Nullable String[][] p);
329                             public void method4(@Nullable T p);
330                             public void method5(@Nullable Map.Entry<T, String> p);
331                         }
332                     """
333                 ),
334             ),
335             inputSet(
336                 signature(
337                     """
338                         // Signature format: 2.0
339                         package test.pkg {
340                           public class Foo<T> {
341                             method public void method1(@Nullable String);
342                             method public void method2(@Nullable String[]);
343                             method public void method3(@Nullable String[][]);
344                             method public void method4(@Nullable T);
345                             method public void method5(@Nullable java.util.Map.Entry<T, String>);
346                           }
347                         }
348                     """
349                 ),
350             ),
351             // Kotlin does not care about different nullability annotations.
352         ) {
353             val expectedTypes =
354                 mapOf(
355                     "method1" to "java.lang.String?",
356                     "method2" to "java.lang.String![]?",
357                     "method3" to "java.lang.String![]![]?",
358                     "method4" to "T?",
359                     "method5" to "java.util.Map.Entry<T!,java.lang.String!>?",
360                 )
361             for (method in codebase.assertClass("test.pkg.Foo").methods()) {
362                 val name = method.name()
363                 val expectedType = expectedTypes[name]!!
364                 // Compare the kotlin style format of the parameter to ensure that only the
365                 // outermost type is affected by the not-type-use nullability annotation.
366                 val type = method.parameters().single().type()
367                 assertWithMessage(name)
368                     .that(type.toTypeString(kotlinStyleNulls = true))
369                     .isEqualTo(expectedType)
370             }
371         }
372     }
373 
374     @Test
Test nullability of non-Kotlin varargsnull375     fun `Test nullability of non-Kotlin varargs`() {
376         runCodebaseTest(
377             inputSet(
378                 KnownSourceFiles.notTypeUseNonNullSource,
379                 KnownSourceFiles.notTypeUseNullableSource,
380                 java(
381                     """
382                         package test.pkg;
383                         import not.type.use.NonNull;
384                         import not.type.use.Nullable;
385 
386                         public class Foo {
387                             public void nullable(@Nullable String... p);
388                             public void nonNull(@NonNull String... p);
389                             public void platform(String... p);
390                         }
391                     """
392                 ),
393             ),
394             inputSet(
395                 signature(
396                     """
397                         // Signature format: 2.0
398                         package test.pkg {
399                           public class Foo {
400                             method public void nullable(@Nullable String... p);
401                             method public void nonNull(@NonNull String... p);
402                             method public void platform(String... p);
403                           }
404                         }
405                     """
406                 ),
407             ),
408             // Kotlin does not care about different nullability annotations.
409         ) {
410             val expectedTypes =
411                 mapOf(
412                     "nullable" to "java.lang.String!...?",
413                     "nonNull" to "java.lang.String!...",
414                     "platform" to "java.lang.String!...!",
415                 )
416             for (method in codebase.assertClass("test.pkg.Foo").methods()) {
417                 val name = method.name()
418                 val expectedType = expectedTypes[name]!!
419                 // Compare the kotlin style format of the parameter to ensure that only the
420                 // outermost type is affected by the not-type-use nullability annotation.
421                 val type = method.parameters().single().type()
422                 assertWithMessage(name)
423                     .that(type.toTypeString(kotlinStyleNulls = true))
424                     .isEqualTo(expectedType)
425             }
426         }
427     }
428 
429     @Test
Test nullability of Kotlin varargs lastnull430     fun `Test nullability of Kotlin varargs last`() {
431         runCodebaseTest(
432             kotlin(
433                 """
434                     package test.pkg
435 
436                     class Foo {
437                         fun nullable(vararg p: String?)
438                         fun nonNull(vararg p: String)
439                     }
440                 """
441             ),
442             signature(
443                 """
444                     // Signature format: 5.0
445                     package test.pkg {
446                       public class Foo {
447                         method public void nullable(String?... p);
448                         method public void nonNull(String... p);
449                       }
450                     }
451                 """
452             ),
453             // Kotlin does not care about different nullability annotations.
454         ) {
455             val expectedTypes =
456                 mapOf(
457                     "nullable" to "java.lang.String?...",
458                     "nonNull" to "java.lang.String...",
459                 )
460             for (method in codebase.assertClass("test.pkg.Foo").methods()) {
461                 val name = method.name()
462                 val parameterItem = method.parameters().single()
463 
464                 // Make sure that it is modelled as a varargs parameter.
465                 assertWithMessage("$name isVarArgs").that(parameterItem.isVarArgs()).isTrue()
466 
467                 // Compare the kotlin style format of the parameter to ensure that only the
468                 // outermost type is affected by the not-type-use nullability annotation.
469                 val type = parameterItem.type()
470                 val expectedType = expectedTypes[name]!!
471                 assertWithMessage("$name type")
472                     .that(type.toTypeString(kotlinStyleNulls = true))
473                     .isEqualTo(expectedType)
474             }
475         }
476     }
477 
478     @Test
Test nullability of Kotlin varargs not-lastnull479     fun `Test nullability of Kotlin varargs not-last`() {
480         runCodebaseTest(
481             kotlin(
482                 """
483                     package test.pkg
484 
485                     fun nullable(vararg p: Any?, i: String = "") {}
486                     fun nonNull(vararg p: Any, i: String = "") {}
487                 """
488             ),
489         ) {
490             val expectedTypes =
491                 mapOf(
492                     "nullable" to "java.lang.Object?[]",
493                     "nonNull" to "java.lang.Object[]",
494                 )
495             for (method in codebase.assertClass("test.pkg.TestKt").methods()) {
496                 val name = method.name()
497                 val parameterItem = method.parameters().first()
498 
499                 // Make sure that it is modelled as a varargs parameter.
500                 assertWithMessage("$name isVarArgs").that(parameterItem.isVarArgs()).isTrue()
501 
502                 // Compare the kotlin style format of the parameter to ensure that only the
503                 // outermost type is affected by the not-type-use nullability annotation.
504                 val type = parameterItem.type()
505                 val expectedType = expectedTypes[name]!!
506                 assertWithMessage(name)
507                     .that(type.toTypeString(kotlinStyleNulls = true))
508                     .isEqualTo(expectedType)
509             }
510         }
511     }
512 
513     @Test
Test nullability of Kotlin varargs last in inline reified funnull514     fun `Test nullability of Kotlin varargs last in inline reified fun`() {
515         runCodebaseTest(
516             kotlin(
517                 """
518                     package test.pkg
519 
520                     inline fun <reified T> nullable(vararg elements: T?) = listOf(*elements)
521                     inline fun <reified T> nonNull(vararg elements: T) = listOf(*elements)
522                 """
523             ),
524         ) {
525             val expectedTypes =
526                 mapOf(
527                     "nullable" to "T?...",
528                     "nonNull" to "T...",
529                 )
530             for (method in codebase.assertClass("test.pkg.TestKt").methods()) {
531                 val name = method.name()
532                 val parameterItem = method.parameters().single()
533 
534                 // Make sure that it is modelled as a varargs parameter.
535                 assertWithMessage("$name isVarArgs").that(parameterItem.isVarArgs()).isTrue()
536 
537                 // Compare the kotlin style format of the parameter to ensure that only the
538                 // outermost type is affected by the not-type-use nullability annotation.
539                 val type = parameterItem.type()
540                 val expectedType = expectedTypes[name]!!
541                 assertWithMessage("$name type")
542                     .that(type.toTypeString(kotlinStyleNulls = true))
543                     .isEqualTo(expectedType)
544             }
545         }
546     }
547 }
548