xref: /aosp_15_r20/tools/metalava/metalava/src/test/java/com/android/tools/metalava/lint/FlaggedApiLintTest.kt (revision 115816f9299ab6ddd6b9673b81f34e707f6bacab)
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.lint
18 
19 import com.android.tools.metalava.DriverTest
20 import com.android.tools.metalava.cli.common.ARG_HIDE
21 import com.android.tools.metalava.cli.common.ARG_WARNING
22 import com.android.tools.metalava.flaggedApiSource
23 import com.android.tools.metalava.systemApiSource
24 import com.android.tools.metalava.testing.java
25 import org.junit.Test
26 
27 class FlaggedApiLintTest : DriverTest() {
28 
29     private val flagsFile =
30         java(
31             """
32                 package android.foobar;
33 
34                 /** @hide */
35                 public class Flags {
36                     public static final String FLAG_MY_FEATURE = "android.foobar.my_feature";
37                 }
38             """
39         )
40 
41     @Test
Dont require @FlaggedApi on methods that get elided from signature filesnull42     fun `Dont require @FlaggedApi on methods that get elided from signature files`() {
43         check(
44             showAnnotations = arrayOf("android.annotation.SystemApi"),
45             expectedIssues = "",
46             apiLint =
47                 """
48                     package android.foobar {
49                       public class ExistingSystemApi {
50                           ctor public ExistingSystemApi();
51                       }
52                       public class Existing {
53                           method public int existingSystemApi();
54                       }
55                     }
56                 """,
57             sourceFiles =
58                 arrayOf(
59                     java(
60                         """
61                             package android.foobar;
62 
63                             import android.annotation.SystemApi;
64                             import android.annotation.FlaggedApi;
65 
66                             /** @hide */
67                             @SystemApi
68                             public class ExistingSystemApi extends Existing {
69                                 /** exactly matches Object.equals, not emitted */
70                                 @Override
71                                 public boolean equals(Object other) { return false; }
72                                 /** exactly matches Object.hashCode, not emitted */
73                                 @Override
74                                 public int hashCode() { return 0; }
75                                 /** exactly matches ExistingPublicApi.existingPublicApi, not emitted */
76                                 @Override
77                                 public int existingPublicApi() { return 0; }
78                                 @Override
79                                 public int existingSystemApi() { return 0; }
80                             }
81                         """
82                     ),
83                     java(
84                         """
85                             package android.foobar;
86 
87                             import android.annotation.SystemApi;
88                             import android.annotation.FlaggedApi;
89 
90                             public class Existing {
91                                 public int existingPublicApi() { return 0; }
92                                 /** @hide */
93                                 @SystemApi
94                                 public int existingSystemApi() { return 0; }
95                             }
96                         """
97                     ),
98                     flaggedApiSource,
99                     systemApiSource,
100                 ),
101             extraArguments = arrayOf("--warning", "UnflaggedApi")
102         )
103     }
104 
105     @Test
Require @FlaggedApi on new APIsnull106     fun `Require @FlaggedApi on new APIs`() {
107         check(
108             expectedIssues =
109                 """
110                     src/android/foobar/Bad.java:3: warning: New API must be flagged with @FlaggedApi: class android.foobar.Bad [UnflaggedApi]
111                     src/android/foobar/Bad.java:4: warning: New API must be flagged with @FlaggedApi: field android.foobar.Bad.BAD [UnflaggedApi]
112                     src/android/foobar/Bad.java:5: warning: New API must be flagged with @FlaggedApi: method android.foobar.Bad.bad() [UnflaggedApi]
113                     src/android/foobar/Bad.java:6: warning: New API must be flagged with @FlaggedApi: class android.foobar.Bad.BadInterface [UnflaggedApi]
114                     src/android/foobar/Bad.java:7: warning: New API must be flagged with @FlaggedApi: class android.foobar.Bad.BadAnnotation [UnflaggedApi]
115                     src/android/foobar/BadHiddenSuperClass.java:4: warning: New API must be flagged with @FlaggedApi: field android.foobar.Bad.INHERITED_BAD [UnflaggedApi]
116                     src/android/foobar/BadHiddenSuperClass.java:4: warning: New API must be flagged with @FlaggedApi: field android.foobar.ExistingClass.INHERITED_BAD [UnflaggedApi]
117                     src/android/foobar/BadHiddenSuperClass.java:5: warning: New API must be flagged with @FlaggedApi: method android.foobar.Bad.inheritedBad() [UnflaggedApi]
118                     src/android/foobar/BadHiddenSuperClass.java:5: warning: New API must be flagged with @FlaggedApi: method android.foobar.ExistingClass.inheritedBad() [UnflaggedApi]
119                     src/android/foobar/ExistingClass.java:9: warning: New API must be flagged with @FlaggedApi: field android.foobar.ExistingClass.BAD [UnflaggedApi]
120                     src/android/foobar/ExistingClass.java:10: warning: New API must be flagged with @FlaggedApi: method android.foobar.ExistingClass.bad() [UnflaggedApi]
121                 """,
122             apiLint =
123                 """
124                     package android.foobar {
125                       public class ExistingClass {
126                           ctor public ExistingClass();
127                           field public static final String EXISTING_FIELD = "foo";
128                           method public void existingMethod();
129                       }
130                       public interface ExistingInterface {
131                           field public static final String EXISTING_INTERFACE_FIELD = "foo";
132                           method public default void existingInterfaceMethod();
133                       }
134                       public class ExistingSuperClass {
135                           ctor public ExistingSuperClass();
136                           field public static final String EXISTING_SUPER_FIELD = "foo";
137                           method public void existingSuperMethod();
138                       }
139                     }
140                 """,
141             sourceFiles =
142                 arrayOf(
143                     java(
144                         """
145                             package android.foobar;
146 
147                             import android.annotation.FlaggedApi;
148 
149                             public interface ExistingInterface {
150                                 public static final String EXISTING_INTERFACE_FIELD = "foo";
151                                 public default void existingInterfaceMethod() {}
152                             }
153                         """
154                     ),
155                     java(
156                         """
157                             package android.foobar;
158 
159                             import android.annotation.FlaggedApi;
160 
161                             public class ExistingSuperClass {
162                                 public static final String EXISTING_SUPER_FIELD = "foo";
163                                 public void existingSuperMethod() {}
164                             }
165                         """
166                     ),
167                     java(
168                         """
169                             package android.foobar;
170 
171                             import android.annotation.FlaggedApi;
172 
173                             public class ExistingClass extends BadHiddenSuperClass implements BadHiddenSuperInterface {
174                                 public static final String EXISTING_FIELD = "foo";
175                                 public void existingMethod() {}
176 
177                                 public static final String BAD = "bar";
178                                 public void bad() {}
179 
180                                 @FlaggedApi(Flags.FLAG_MY_FEATURE)
181                                 public static final String OK = "baz";
182 
183                                 @FlaggedApi(Flags.FLAG_MY_FEATURE)
184                                 public void ok() {}
185                             }
186                         """
187                     ),
188                     java(
189                         """
190                             package android.foobar;
191 
192                             class BadHiddenSuperClass {
193                                 public static final String INHERITED_BAD = "bar";
194                                 public void inheritedBad() {}
195                             }
196                         """
197                     ),
198                     java(
199                         """
200                             package android.foobar;
201 
202                             interface BadHiddenSuperInterface {
203                                 public static final String INHERITED_BAD = "bar";
204                                 public void inheritedBad() {}
205                             }
206                         """
207                     ),
208                     java(
209                         """
210                             package android.foobar;
211 
212                             public class Bad extends BadHiddenSuperClass implements BadHiddenSuperInterface {
213                                 public static final String BAD = "bar";
214                                 public void bad() {}
215                                 public interface BadInterface {}
216                                 public @interface BadAnnotation {}
217                             }
218                         """
219                     ),
220                     java(
221                         """
222                             package android.foobar;
223 
224                             import android.annotation.FlaggedApi;
225 
226                             @FlaggedApi(Flags.FLAG_MY_FEATURE)
227                             public class Ok extends ExistingSuperClass implements ExistingInterface {
228                                 public static final String OK = "bar";
229                                 public void ok() {}
230                                 public interface OkInterface {}
231                                 public @interface OkAnnotation {}
232                             }
233                         """
234                     ),
235                     flaggedApiSource,
236                     flagsFile,
237                 ),
238             extraArguments = arrayOf(ARG_WARNING, "UnflaggedApi", ARG_HIDE, "HiddenSuperclass")
239         )
240     }
241 
242     @Test
Dont require @FlaggedApi on existing items in nested SystemApi classesnull243     fun `Dont require @FlaggedApi on existing items in nested SystemApi classes`() {
244         check(
245             showAnnotations = arrayOf("android.annotation.SystemApi"),
246             expectedIssues = "",
247             apiLint =
248                 """
249                     package android.foobar {
250                       public class Existing.Inner {
251                           method public int existing();
252                       }
253                     }
254                 """,
255             sourceFiles =
256                 arrayOf(
257                     java(
258                         """
259                             package android.foobar;
260 
261                             import android.annotation.SystemApi;
262 
263                             public class Existing {
264                                 public class Inner {
265                                     /** @hide */
266                                     @SystemApi
267                                     public int existing() {}
268                                 }
269                             }
270                         """
271                     ),
272                     flaggedApiSource,
273                     systemApiSource,
274                 ),
275             extraArguments = arrayOf("--warning", "UnflaggedApi")
276         )
277     }
278 
279     @Test
Dont require @FlaggedApi on existing items inherited into new SystemApi classesnull280     fun `Dont require @FlaggedApi on existing items inherited into new SystemApi classes`() {
281         check(
282             showAnnotations = arrayOf("android.annotation.SystemApi"),
283             expectedIssues =
284                 """
285                     src/android/foobar/BadHiddenSuperClass.java:4: warning: New API must be flagged with @FlaggedApi: field android.foobar.Bad.BAD_INHERITED [UnflaggedApi]
286                     src/android/foobar/BadHiddenSuperClass.java:5: warning: New API must be flagged with @FlaggedApi: method android.foobar.Bad.badInherited() [UnflaggedApi]
287                 """,
288             apiLint =
289                 """
290                     package android.foobar {
291                       public interface ExistingSystemInterface {
292                           field public static final String EXISTING_SYSTEM_INTERFACE_FIELD = "foo";
293                           method public default void existingSystemInterfaceMethod();
294                       }
295                       public class ExistingSystemSuperClass {
296                           ctor public ExistingSystemSuperClass();
297                           field public static final String EXISTING_SYSTEM_SUPER_FIELD = "foo";
298                           method public void existingSystemSuperMethod();
299                       }
300                       public class Existing {
301                       }
302                     }
303                 """,
304             sourceFiles =
305                 arrayOf(
306                     java(
307                         """
308                             package android.foobar;
309 
310                             import android.annotation.FlaggedApi;
311                             import android.annotation.SystemApi;
312 
313                             /** @hide */
314                             @SystemApi
315                             public interface ExistingSystemInterface {
316                                 public static final String EXISTING_SYSTEM_INTERFACE_FIELD = "foo";
317                                 public default void existingSystemInterfaceMethod() {}
318                             }
319                         """
320                     ),
321                     java(
322                         """
323                             package android.foobar;
324 
325                             import android.annotation.FlaggedApi;
326                             import android.annotation.SystemApi;
327 
328                             /** @hide */
329                             @SystemApi
330                             public class ExistingSystemSuperClass {
331                                 public static final String EXISTING_SYSTEM_SUPER_FIELD = "foo";
332                                 public void existingSystemSuperMethod() {}
333                             }
334                         """
335                     ),
336                     java(
337                         """
338                             package android.foobar;
339 
340                             public interface ExistingPublicInterface {
341                                 public static final String EXISTING_PUBLIC_INTERFACE_FIELD = "foo";
342                                 public default void existingPublicInterfaceMethod() {}
343                             }
344                         """
345                     ),
346                     java(
347                         """
348                             package android.foobar;
349 
350                             class BadHiddenSuperClass {
351                                 public static final String BAD_INHERITED = "foo";
352                                 public default void badInherited() {}
353                             }
354                         """
355                     ),
356                     java(
357                         """
358                             package android.foobar;
359 
360                             public class ExistingPublicSuperClass {
361                                 public static final String EXISTING_PUBLIC_SUPER_FIELD = "foo";
362                                 public void existingPublicSuperMethod() {}
363                             }
364                         """
365                     ),
366                     java(
367                         """
368                             package android.foobar;
369 
370                             import android.annotation.SystemApi;
371 
372                             /** @hide */
373                             @SystemApi
374                             @SuppressWarnings("UnflaggedApi")  // Ignore the class itself for this test.
375                             public class Ok extends ExistingSystemSuperClass implements ExistingSystemInterface {
376                                 private Ok() {}
377                             }
378                         """
379                     ),
380                     java(
381                         """
382                             package android.foobar;
383 
384                             import android.annotation.SystemApi;
385 
386                             /** @hide */
387                             @SystemApi
388                             @SuppressWarnings("UnflaggedApi")  // Ignore the class itself for this test.
389                             public class Bad extends BadHiddenSuperClass {
390                                 private Bad() {}
391                             }
392                         """
393                     ),
394                     java(
395                         """
396                             package android.foobar;
397 
398                             import android.annotation.SystemApi;
399 
400                             /** @hide */
401                             @SystemApi
402                             @SuppressWarnings("UnflaggedApi")  // Ignore the class itself for this test.
403                             public class Ok2 extends ExistingPublicSuperClass implements ExistingPublicInterface {
404                                 private Ok2() {}
405                             }
406                         """
407                     ),
408                     java(
409                         """
410                             package android.foobar;
411 
412                             import android.annotation.SystemApi;
413 
414                             /** @hide */
415                             @SystemApi
416                             public class Existing extends ExistingPublicSuperClass implements ExistingPublicInterface {
417                                 private Existing() {}
418                             }
419                         """
420                     ),
421                     flaggedApiSource,
422                     systemApiSource,
423                 ),
424             extraArguments = arrayOf(ARG_WARNING, "UnflaggedApi", ARG_HIDE, "HiddenSuperclass"),
425             checkCompilation = true
426         )
427     }
428 
429     @Test
Require @FlaggedApi to reference generated fieldsnull430     fun `Require @FlaggedApi to reference generated fields`() {
431         check(
432             expectedIssues =
433                 """
434                     src/android/foobar/Bad.java:6: warning: @FlaggedApi contains a string literal, but should reference the field generated by aconfig (android.foobar.Flags.FLAG_MY_FEATURE). (ErrorWhenNew) [FlaggedApiLiteral]
435                     src/android/foobar/Bad.java:8: warning: @FlaggedApi contains a string literal, but should reference the field generated by aconfig (android.foobar.Flags.FLAG_MY_FEATURE). (ErrorWhenNew) [FlaggedApiLiteral]
436                     src/android/foobar/Bad.java:10: warning: @FlaggedApi contains a string literal, but should reference the field generated by aconfig (android.foobar.Flags.FLAG_MY_FEATURE). (ErrorWhenNew) [FlaggedApiLiteral]
437                     src/android/foobar/Bad.java:12: warning: @FlaggedApi contains a string literal, but should reference the field generated by aconfig (android.foobar.Flags.FLAG_MY_FEATURE). (ErrorWhenNew) [FlaggedApiLiteral]
438                     src/android/foobar/Bad.java:14: warning: @FlaggedApi contains a string literal, but should reference the field generated by aconfig (android.foobar.Flags.FLAG_MY_FEATURE). (ErrorWhenNew) [FlaggedApiLiteral]
439                     src/android/foobar/Bad.java:17: warning: @FlaggedApi contains a string literal, but should reference the field generated by aconfig (furthermore, the current flag literal seems to be malformed). (ErrorWhenNew) [FlaggedApiLiteral]
440                     src/android/foobar/Bad.java:19: warning: @FlaggedApi contains a string literal, but should reference the field generated by aconfig (android.foobar.Flags.FLAG_NONEXISTENT_FLAG, however this flag doesn't seem to exist). (ErrorWhenNew) [FlaggedApiLiteral]
441                     src/android/foobar/Bad.java:21: warning: @FlaggedApi contains a string literal, but should reference the field generated by aconfig (android.baz.Flags.FLAG_NON_EXISTENT_PACKAGE, however this flag doesn't seem to exist). (ErrorWhenNew) [FlaggedApiLiteral]
442                 """,
443             apiLint = "",
444             sourceFiles =
445                 arrayOf(
446                     java(
447                         """
448                             package android.foobar;
449 
450                             import android.annotation.FlaggedApi;
451 
452                             @FlaggedApi("android.foobar.my_feature")
453                             public class Bad {
454                                 @FlaggedApi("android.foobar.my_feature")
455                                 public static final String BAD = "bar";
456                                 @FlaggedApi("android.foobar.my_feature")
457                                 public void bad() {}
458                                 @FlaggedApi("android.foobar.my_feature")
459                                 public interface BadInterface {}
460                                 @FlaggedApi("android.foobar.my_feature")
461                                 public @interface BadAnnotation {}
462 
463                                 @FlaggedApi("malformed/flag")
464                                 public void malformed() {}
465                                 @FlaggedApi("android.foobar.nonexistent_flag")
466                                 public void nonexistentFlag() {}
467                                 @FlaggedApi("android.baz.non_existent_package")
468                                 public void nonexistentPackage() {}
469                             }
470                         """
471                     ),
472                     java(
473                         """
474                             package android.foobar;
475 
476                             import android.annotation.FlaggedApi;
477 
478                             @FlaggedApi(android.foobar.Flags.FLAG_MY_FEATURE)
479                             public class Ok {
480                                 @FlaggedApi(android.foobar.Flags.FLAG_MY_FEATURE)
481                                 public static final String OK = "bar";
482                                 @FlaggedApi(android.foobar.Flags.FLAG_MY_FEATURE)
483                                 public void ok() {}
484                                 @FlaggedApi(android.foobar.Flags.FLAG_MY_FEATURE)
485                                 public interface OkInterface {}
486                                 @FlaggedApi(android.foobar.Flags.FLAG_MY_FEATURE)
487                                 public @interface OkAnnotation {}
488                             }
489                         """
490                     ),
491                     flagsFile,
492                     flaggedApiSource
493                 ),
494         )
495     }
496 
497     @Test
Require @FlaggedApi on APIs whose modifiers have changednull498     fun `Require @FlaggedApi on APIs whose modifiers have changed`() {
499         check(
500             expectedIssues =
501                 """
502                     src/test/pkg/Foo.java:3: warning: Changes to modifiers, from 'public abstract' to 'public' must be flagged with @FlaggedApi: class test.pkg.Foo [UnflaggedApi]
503                     src/test/pkg/Foo.java:4: warning: Changes to modifiers, from 'protected' to 'public' must be flagged with @FlaggedApi: constructor test.pkg.Foo() [UnflaggedApi]
504                     src/test/pkg/Foo.java:5: warning: Changes to modifiers, from 'public final' to 'public' must be flagged with @FlaggedApi: method test.pkg.Foo.method() [UnflaggedApi]
505                 """,
506             apiLint =
507                 """
508                     // Signature format: 2.0
509                     package test.pkg {
510                       public abstract class Foo {
511                         ctor protected Foo();
512                         method public final void method();
513                       }
514                     }
515                 """,
516             sourceFiles =
517                 arrayOf(
518                     java(
519                         """
520                             package test.pkg;
521 
522                             public class Foo {
523                                 public Foo() {}
524                                 public void method() {}
525                             }
526                         """
527                     ),
528                     flagsFile,
529                     flaggedApiSource,
530                 ),
531             extraArguments = arrayOf(ARG_WARNING, "UnflaggedApi"),
532         )
533     }
534 
535     @Test
Do not require @FlaggedApi on concrete class methods that override a default interface methodnull536     fun `Do not require @FlaggedApi on concrete class methods that override a default interface method`() {
537         check(
538             expectedIssues = "",
539             apiLint =
540                 """
541                     // Signature format: 2.0
542                     package test.pkg {
543                       public interface Base {
544                         method public default void method();
545                       }
546                       public class Foo implements test.pkg.Base {
547                       }
548                     }
549                 """,
550             sourceFiles =
551                 arrayOf(
552                     java(
553                         """
554                             package test.pkg;
555 
556                             public interface Base {
557                                 default void method() {}
558                             }
559                         """
560                     ),
561                     java(
562                         """
563                             package test.pkg;
564 
565                             public class Foo implements Base {
566                                 private Foo() {}
567                                 public void method() {}
568                             }
569                         """
570                     ),
571                     flagsFile,
572                     flaggedApiSource,
573                 ),
574             extraArguments = arrayOf(ARG_WARNING, "UnflaggedApi"),
575         )
576     }
577 
578     @Test
Require @FlaggedApi on APIs whose deprecated status has changed to deprecatednull579     fun `Require @FlaggedApi on APIs whose deprecated status has changed to deprecated`() {
580         check(
581             expectedIssues =
582                 """
583                     src/test/pkg/Foo.java:6: warning: Changes from not deprecated to deprecated must be flagged with @FlaggedApi: class test.pkg.Foo [UnflaggedApi]
584                 """,
585             apiLint =
586                 """
587                     // Signature format: 2.0
588                     package test.pkg {
589                       public class Foo {
590                         ctor public Foo();
591                       }
592                     }
593                 """,
594             sourceFiles =
595                 arrayOf(
596                     java(
597                         """
598                             package test.pkg;
599 
600                             /**
601                              * @deprecated
602                              */
603                             @Deprecated
604                             public class Foo {
605                             }
606                         """
607                     ),
608                     flagsFile,
609                     flaggedApiSource,
610                 ),
611             extraArguments = arrayOf(ARG_WARNING, "UnflaggedApi"),
612         )
613     }
614 
615     @Test
Require @FlaggedApi on APIs whose deprecated status has changed to not deprecatednull616     fun `Require @FlaggedApi on APIs whose deprecated status has changed to not deprecated`() {
617         check(
618             expectedIssues =
619                 """
620                     src/test/pkg/Foo.java:3: warning: Changes from deprecated to not deprecated must be flagged with @FlaggedApi: class test.pkg.Foo [UnflaggedApi]
621                 """,
622             apiLint =
623                 """
624                     // Signature format: 2.0
625                     package test.pkg {
626                       @Deprecated public class Foo {
627                         ctor public Foo();
628                       }
629                     }
630                 """,
631             sourceFiles =
632                 arrayOf(
633                     java(
634                         """
635                             package test.pkg;
636 
637                             public class Foo {
638                             }
639                         """
640                     ),
641                     flagsFile,
642                     flaggedApiSource,
643                 ),
644             extraArguments = arrayOf(ARG_WARNING, "UnflaggedApi"),
645         )
646     }
647 }
648