• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2017 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
18 
19 import org.junit.Ignore
20 import org.junit.Test
21 import java.io.File
22 import kotlin.text.Charsets.UTF_8
23 
24 class
25 CompatibilityCheckTest : DriverTest() {
26     @Test
Change between class and interfacenull27     fun `Change between class and interface`() {
28         check(
29             warnings = """
30                 TESTROOT/load-api.txt:2: error: Class test.pkg.MyTest1 changed class/interface declaration [ChangedClass]
31                 TESTROOT/load-api.txt:4: error: Class test.pkg.MyTest2 changed class/interface declaration [ChangedClass]
32                 """,
33             compatibilityMode = false,
34             checkCompatibilityApi = """
35                 package test.pkg {
36                   public class MyTest1 {
37                   }
38                   public interface MyTest2 {
39                   }
40                   public class MyTest3 {
41                   }
42                   public interface MyTest4 {
43                   }
44                 }
45                 """,
46             // MyTest1 and MyTest2 reversed from class to interface or vice versa, MyTest3 and MyTest4 unchanged
47             signatureSource = """
48                 package test.pkg {
49                   public interface MyTest1 {
50                   }
51                   public class MyTest2 {
52                   }
53                   public class MyTest3 {
54                   }
55                   public interface MyTest4 {
56                   }
57                 }
58                 """
59         )
60     }
61 
62     @Test
Interfaces should not be droppednull63     fun `Interfaces should not be dropped`() {
64         check(
65             warnings = """
66                 TESTROOT/load-api.txt:2: error: Class test.pkg.MyTest1 changed class/interface declaration [ChangedClass]
67                 TESTROOT/load-api.txt:4: error: Class test.pkg.MyTest2 changed class/interface declaration [ChangedClass]
68                 """,
69             compatibilityMode = false,
70             checkCompatibilityApi = """
71                 package test.pkg {
72                   public class MyTest1 {
73                   }
74                   public interface MyTest2 {
75                   }
76                   public class MyTest3 {
77                   }
78                   public interface MyTest4 {
79                   }
80                 }
81                 """,
82             // MyTest1 and MyTest2 reversed from class to interface or vice versa, MyTest3 and MyTest4 unchanged
83             signatureSource = """
84                 package test.pkg {
85                   public interface MyTest1 {
86                   }
87                   public class MyTest2 {
88                   }
89                   public class MyTest3 {
90                   }
91                   public interface MyTest4 {
92                   }
93                 }
94                 """
95         )
96     }
97 
98     @Test
Ensure warnings for removed APIsnull99     fun `Ensure warnings for removed APIs`() {
100         check(
101             warnings = """
102                 TESTROOT/current-api.txt:3: error: Removed method test.pkg.MyTest1.method(Float) [RemovedMethod]
103                 TESTROOT/current-api.txt:4: error: Removed field test.pkg.MyTest1.field [RemovedField]
104                 TESTROOT/current-api.txt:6: error: Removed class test.pkg.MyTest2 [RemovedClass]
105                 """,
106             compatibilityMode = false,
107             checkCompatibilityApi = """
108                 package test.pkg {
109                   public class MyTest1 {
110                     method public Double method(Float);
111                     field public Double field;
112                   }
113                   public class MyTest2 {
114                     method public Double method(Float);
115                     field public Double field;
116                   }
117                 }
118                 package test.pkg.other {
119                 }
120                 """,
121             signatureSource = """
122                 package test.pkg {
123                   public class MyTest1 {
124                   }
125                 }
126                 """
127         )
128     }
129 
130     @Test
Flag invalid nullness changesnull131     fun `Flag invalid nullness changes`() {
132         check(
133             warnings = """
134                 TESTROOT/load-api.txt:5: error: Attempted to remove @Nullable annotation from method test.pkg.MyTest.convert3(Float) [InvalidNullConversion]
135                 TESTROOT/load-api.txt:5: error: Attempted to remove @Nullable annotation from parameter arg1 in test.pkg.MyTest.convert3(Float arg1) [InvalidNullConversion]
136                 TESTROOT/load-api.txt:6: error: Attempted to remove @NonNull annotation from method test.pkg.MyTest.convert4(Float) [InvalidNullConversion]
137                 TESTROOT/load-api.txt:6: error: Attempted to remove @NonNull annotation from parameter arg1 in test.pkg.MyTest.convert4(Float arg1) [InvalidNullConversion]
138                 TESTROOT/load-api.txt:7: error: Attempted to change parameter from @Nullable to @NonNull: incompatible change for parameter arg1 in test.pkg.MyTest.convert5(Float arg1) [InvalidNullConversion]
139                 TESTROOT/load-api.txt:8: error: Attempted to change method return from @NonNull to @Nullable: incompatible change for method test.pkg.MyTest.convert6(Float) [InvalidNullConversion]
140                 """,
141             compatibilityMode = false,
142             outputKotlinStyleNulls = false,
143             checkCompatibilityApi = """
144                 package test.pkg {
145                   public class MyTest {
146                     method public Double convert1(Float);
147                     method public Double convert2(Float);
148                     method @Nullable public Double convert3(@Nullable Float);
149                     method @NonNull public Double convert4(@NonNull Float);
150                     method @Nullable public Double convert5(@Nullable Float);
151                     method @NonNull public Double convert6(@NonNull Float);
152                     // booleans cannot reasonably be annotated with @Nullable/@NonNull but
153                     // the compiler accepts it and we had a few of these accidentally annotated
154                     // that way in API 28, such as Boolean.getBoolean. Make sure we don't flag
155                     // these as incompatible changes when they're dropped.
156                     method public void convert7(@NonNull boolean);
157                   }
158                 }
159                 """,
160             // Changes: +nullness, -nullness, nullable->nonnull, nonnull->nullable
161             signatureSource = """
162                 package test.pkg {
163                   public class MyTest {
164                     method @Nullable public Double convert1(@Nullable Float);
165                     method @NonNull public Double convert2(@NonNull Float);
166                     method public Double convert3(Float);
167                     method public Double convert4(Float);
168                     method @NonNull public Double convert5(@NonNull Float);
169                     method @Nullable public Double convert6(@Nullable Float);
170                     method public void convert7(boolean);
171                   }
172                 }
173                 """
174         )
175     }
176 
177     @Test
Kotlin Nullnessnull178     fun `Kotlin Nullness`() {
179         check(
180             warnings = """
181                 src/test/pkg/Outer.kt:5: error: Attempted to change method return from @NonNull to @Nullable: incompatible change for method test.pkg.Outer.method2(String,String) [InvalidNullConversion]
182                 src/test/pkg/Outer.kt:5: error: Attempted to change parameter from @Nullable to @NonNull: incompatible change for parameter string in test.pkg.Outer.method2(String string, String maybeString) [InvalidNullConversion]
183                 src/test/pkg/Outer.kt:6: error: Attempted to change parameter from @Nullable to @NonNull: incompatible change for parameter string in test.pkg.Outer.method3(String maybeString, String string) [InvalidNullConversion]
184                 src/test/pkg/Outer.kt:8: error: Attempted to change method return from @NonNull to @Nullable: incompatible change for method test.pkg.Outer.Inner.method2(String,String) [InvalidNullConversion]
185                 src/test/pkg/Outer.kt:8: error: Attempted to change parameter from @Nullable to @NonNull: incompatible change for parameter string in test.pkg.Outer.Inner.method2(String string, String maybeString) [InvalidNullConversion]
186                 src/test/pkg/Outer.kt:9: error: Attempted to change parameter from @Nullable to @NonNull: incompatible change for parameter string in test.pkg.Outer.Inner.method3(String maybeString, String string) [InvalidNullConversion]
187                 """,
188             compatibilityMode = false,
189             inputKotlinStyleNulls = true,
190             outputKotlinStyleNulls = true,
191             checkCompatibilityApi = """
192                     package test.pkg {
193                       public final class Outer {
194                         ctor public Outer();
195                         method public final String? method1(String, String?);
196                         method public final String method2(String?, String);
197                         method public final String? method3(String, String?);
198                       }
199                       public static final class Outer.Inner {
200                         ctor public Outer.Inner();
201                         method public final String method2(String?, String);
202                         method public final String? method3(String, String?);
203                       }
204                     }
205                 """,
206             sourceFiles = *arrayOf(
207                 kotlin(
208                     """
209                     package test.pkg
210 
211                     class Outer {
212                         fun method1(string: String, maybeString: String?): String? = null
213                         fun method2(string: String, maybeString: String?): String? = null
214                         fun method3(maybeString: String?, string : String): String = ""
215                         class Inner {
216                             fun method2(string: String, maybeString: String?): String? = null
217                             fun method3(maybeString: String?, string : String): String = ""
218                         }
219                     }
220                     """
221                 )
222             )
223         )
224     }
225 
226     @Test
Java Parameter Name Changenull227     fun `Java Parameter Name Change`() {
228         check(
229             warnings = """
230                 src/test/pkg/JavaClass.java:6: error: Attempted to remove parameter name from parameter newName in test.pkg.JavaClass.method1 in method test.pkg.JavaClass.method1 [ParameterNameChange]
231                 src/test/pkg/JavaClass.java:7: error: Attempted to change parameter name from secondParameter to newName in method test.pkg.JavaClass.method2 [ParameterNameChange]
232                 """,
233             compatibilityMode = false,
234             checkCompatibilityApi = """
235                 package test.pkg {
236                   public class JavaClass {
237                     ctor public JavaClass();
238                     method public String method1(String parameterName);
239                     method public String method2(String firstParameter, String secondParameter);
240                   }
241                 }
242                 """,
243             sourceFiles = *arrayOf(
244                 java(
245                     """
246                     @Suppress("all")
247                     package test.pkg;
248                     import androidx.annotation.ParameterName;
249 
250                     public class JavaClass {
251                         public String method1(String newName) { return null; }
252                         public String method2(@ParameterName("firstParameter") String s, @ParameterName("newName") String prevName) { return null; }
253                     }
254                     """
255                 ),
256                 supportParameterName
257             ),
258             extraArguments = arrayOf(ARG_HIDE_PACKAGE, "androidx.annotation")
259         )
260     }
261 
262     @Test
Kotlin Parameter Name Changenull263     fun `Kotlin Parameter Name Change`() {
264         check(
265             warnings = """
266                 src/test/pkg/KotlinClass.kt:4: error: Attempted to change parameter name from prevName to newName in method test.pkg.KotlinClass.method1 [ParameterNameChange]
267                 """,
268             compatibilityMode = false,
269             inputKotlinStyleNulls = true,
270             outputKotlinStyleNulls = true,
271             checkCompatibilityApi = """
272                 package test.pkg {
273                   public final class KotlinClass {
274                     ctor public KotlinClass();
275                     method public final String? method1(String prevName);
276                   }
277                 }
278                 """,
279             sourceFiles = *arrayOf(
280                 kotlin(
281                     """
282                     package test.pkg
283 
284                     class KotlinClass {
285                         fun method1(newName: String): String? = null
286                     }
287                     """
288                 )
289             )
290         )
291     }
292 
293     @Test
Kotlin Coroutinesnull294     fun `Kotlin Coroutines`() {
295         check(
296             warnings = "",
297             compatibilityMode = false,
298             inputKotlinStyleNulls = true,
299             outputKotlinStyleNulls = true,
300             checkCompatibilityApi = """
301                 package test.pkg {
302                   public final class TestKt {
303                     ctor public TestKt();
304                     method public static suspend inline java.lang.Object hello(kotlin.coroutines.experimental.Continuation<? super kotlin.Unit>);
305                   }
306                 }
307                 """,
308             signatureSource = """
309                 package test.pkg {
310                   public final class TestKt {
311                     ctor public TestKt();
312                     method public static suspend inline Object hello(@NonNull kotlin.coroutines.Continuation<? super kotlin.Unit> p);
313                   }
314                 }
315                 """
316         )
317     }
318 
319     @Test
Add flag new methods but not overrides from platformnull320     fun `Add flag new methods but not overrides from platform`() {
321         check(
322             warnings = """
323                 src/test/pkg/MyClass.java:6: error: Added method test.pkg.MyClass.method2(String) [AddedMethod]
324                 src/test/pkg/MyClass.java:7: error: Added field test.pkg.MyClass.newField [AddedField]
325                 """,
326             compatibilityMode = false,
327             checkCompatibilityApi = """
328                 package test.pkg {
329                   public class MyClass {
330                     method public String method1(String);
331                   }
332                 }
333                 """,
334             sourceFiles = *arrayOf(
335                 java(
336                     """
337                     package test.pkg;
338 
339                     public class MyClass  {
340                         private MyClass() { }
341                         public String method1(String newName) { return null; }
342                         public String method2(String newName) { return null; }
343                         public int newField = 5;
344                         public String toString() { return "Hello World"; }
345                     }
346                     """
347                 )
348             )
349         )
350     }
351 
352     @Test
Remove operatornull353     fun `Remove operator`() {
354         check(
355             warnings = """
356                 src/test/pkg/Foo.kt:4: error: Cannot remove `operator` modifier from method test.pkg.Foo.plus(String): Incompatible change [OperatorRemoval]
357                 """,
358             compatibilityMode = false,
359             checkCompatibilityApi = """
360                 package test.pkg {
361                   public final class Foo {
362                     ctor public Foo();
363                     method public final operator void plus(String s);
364                   }
365                 }
366                 """,
367             sourceFiles = *arrayOf(
368                 kotlin(
369                     """
370                     package test.pkg
371 
372                     class Foo {
373                         fun plus(s: String) { }
374                     }
375                     """
376                 )
377             )
378         )
379     }
380 
381     @Test
Remove varargnull382     fun `Remove vararg`() {
383         check(
384             warnings = """
385                 src/test/pkg/test.kt:3: error: Changing from varargs to array is an incompatible change: parameter x in test.pkg.TestKt.method2(int[] x) [VarargRemoval]
386                 """,
387             compatibilityMode = false,
388             checkCompatibilityApi = """
389                 package test.pkg {
390                   public final class TestKt {
391                     ctor public TestKt();
392                     method public static final void method1(int[] x);
393                     method public static final void method2(int... x);
394                   }
395                 }
396                 """,
397             sourceFiles = *arrayOf(
398                 kotlin(
399                     """
400                     package test.pkg
401                     fun method1(vararg x: Int) { }
402                     fun method2(x: IntArray) { }
403                     """
404                 )
405             )
406         )
407     }
408 
409     @Test
Add finalnull410     fun `Add final`() {
411         // Adding final on class or method is incompatible; adding it on a parameter is fine.
412         // Field is iffy.
413         check(
414             warnings = """
415                 src/test/pkg/Java.java:4: error: Method test.pkg.Java.method has added 'final' qualifier [AddedFinal]
416                 src/test/pkg/Kotlin.kt:4: error: Method test.pkg.Kotlin.method has added 'final' qualifier [AddedFinal]
417                 """,
418             compatibilityMode = false,
419             checkCompatibilityApi = """
420                 package test.pkg {
421                   public class Java {
422                     method public void method(int);
423                   }
424                   public class Kotlin {
425                     ctor public Kotlin();
426                     method public void method(String s);
427                   }
428                 }
429                 """,
430             sourceFiles = *arrayOf(
431                 kotlin(
432                     """
433                     package test.pkg
434 
435                     open class Kotlin {
436                         fun method(s: String) { }
437                     }
438                     """
439                 ),
440                 java(
441                     """
442                         package test.pkg;
443                         public class Java {
444                             private Java() { }
445                             public final void method(final int parameter) { }
446                         }
447                         """
448                 )
449             )
450         )
451     }
452 
453     @Test
Inherited finalnull454     fun `Inherited final`() {
455         // Make sure that we correctly compare effectively final (inherited from surrounding class)
456         // between the signature file codebase and the real codebase
457         check(
458             warnings = """
459                 """,
460             compatibilityMode = false,
461             checkCompatibilityApi = """
462                 package test.pkg {
463                   public final class Cls extends test.pkg.Parent {
464                   }
465                   public class Parent {
466                     method public void method(int);
467                   }
468                 }
469                 """,
470             sourceFiles = *arrayOf(
471                 java(
472                     """
473                         package test.pkg;
474                         public final class Cls extends Parent {
475                             private Cls() { }
476                             @Override public void method(final int parameter) { }
477                         }
478                         """
479                 ),
480                 java(
481                     """
482                         package test.pkg;
483                         public class Parent {
484                             private Parent() { }
485                             public void method(final int parameter) { }
486                         }
487                         """
488                 )
489             )
490         )
491     }
492 
493     @Test
Implicit concretenull494     fun `Implicit concrete`() {
495         // Doclava signature files sometimes leave out overridden methods of
496         // abstract methods. We don't want to list these as having changed
497         // their abstractness.
498         check(
499             warnings = """
500                 """,
501             compatibilityMode = false,
502             checkCompatibilityApi = """
503                 package test.pkg {
504                   public final class Cls extends test.pkg.Parent {
505                   }
506                   public class Parent {
507                     method public abstract void method(int);
508                   }
509                 }
510                 """,
511             sourceFiles = *arrayOf(
512                 java(
513                     """
514                         package test.pkg;
515                         public final class Cls extends Parent {
516                             private Cls() { }
517                             @Override public void method(final int parameter) { }
518                         }
519                         """
520                 ),
521                 java(
522                     """
523                         package test.pkg;
524                         public class Parent {
525                             private Parent() { }
526                             public abstract void method(final int parameter);
527                         }
528                         """
529                 )
530             )
531         )
532     }
533 
534     @Test
Implicit modifiers from inherited super classesnull535     fun `Implicit modifiers from inherited super classes`() {
536         check(
537             warnings = """
538                 """,
539             compatibilityMode = false,
540             checkCompatibilityApi = """
541                 package test.pkg {
542                   public final class Cls implements test.pkg.Interface {
543                     method public void method(int);
544                     method public final void method2(int);
545                   }
546                   public interface Interface {
547                     method public void method2(int);
548                   }
549                 }
550                 """,
551             sourceFiles = *arrayOf(
552                 java(
553                     """
554                         package test.pkg;
555                         public final class Cls extends HiddenParent implements Interface {
556                             private Cls() { }
557                             @Override public void method(final int parameter) { }
558                         }
559                         """
560                 ),
561                 java(
562                     """
563                         package test.pkg;
564                         class HiddenParent {
565                             private HiddenParent() { }
566                             public abstract void method(final int parameter) { }
567                             public final void method2(final int parameter) { }
568                         }
569                         """
570                 ),
571                 java(
572                     """
573                         package test.pkg;
574                         public interface Interface {
575                             void method2(final int parameter) { }
576                         }
577                         """
578                 )
579             )
580         )
581     }
582 
583     @Test
Wildcard comparisonsnull584     fun `Wildcard comparisons`() {
585         // Doclava signature files sometimes leave out overridden methods of
586         // abstract methods. We don't want to list these as having changed
587         // their abstractness.
588         check(
589             warnings = """
590                 """,
591             compatibilityMode = false,
592             checkCompatibilityApi = """
593                 package test.pkg {
594                   public abstract class AbstractMap<K, V> implements java.util.Map {
595                     method public java.util.Set<K> keySet();
596                     method public V put(K, V);
597                     method public void putAll(java.util.Map<? extends K, ? extends V>);
598                   }
599                   public abstract class EnumMap<K extends java.lang.Enum<K>, V> extends test.pkg.AbstractMap  {
600                   }
601                 }
602                 """,
603             sourceFiles = *arrayOf(
604                 java(
605                     """
606                         package test.pkg;
607                         @SuppressWarnings({"ConstantConditions", "NullableProblems"})
608                         public abstract class AbstractMap<K, V> implements java.util.Map {
609                             private AbstractMap() { }
610                             public V put(K k, V v) { return null; }
611                             public java.util.Set<K> keySet() { return null; }
612                             public V put(K k, V v) { return null; }
613                             public void putAll(java.util.Map<? extends K, ? extends V> x) { }
614                         }
615                         """
616                 ),
617                 java(
618                     """
619                         package test.pkg;
620                         public abstract class EnumMap<K extends java.lang.Enum<K>, V> extends test.pkg.AbstractMap  {
621                             private EnumMap() { }
622                             public V put(K k, V v) { return null; }
623                         }
624                         """
625                 )
626             )
627         )
628     }
629 
630     @Test
Added constructornull631     fun `Added constructor`() {
632         // Regression test for issue 116619591
633         check(
634             warnings = "src/test/pkg/AbstractMap.java:2: error: Added constructor test.pkg.AbstractMap() [AddedMethod]",
635             compatibilityMode = false,
636             checkCompatibilityApi = """
637                 package test.pkg {
638                   public abstract class AbstractMap<K, V> implements java.util.Map {
639                   }
640                 }
641                 """,
642             sourceFiles = *arrayOf(
643                 java(
644                     """
645                         package test.pkg;
646                         @SuppressWarnings({"ConstantConditions", "NullableProblems"})
647                         public abstract class AbstractMap<K, V> implements java.util.Map {
648                         }
649                         """
650                 )
651             )
652         )
653     }
654 
655     @Test
Remove infixnull656     fun `Remove infix`() {
657         check(
658             warnings = """
659                 src/test/pkg/Foo.kt:5: error: Cannot remove `infix` modifier from method test.pkg.Foo.add2(String): Incompatible change [InfixRemoval]
660                 """,
661             compatibilityMode = false,
662             checkCompatibilityApi = """
663                 package test.pkg {
664                   public final class Foo {
665                     ctor public Foo();
666                     method public final void add1(String s);
667                     method public final infix void add2(String s);
668                     method public final infix void add3(String s);
669                   }
670                 }
671                 """,
672             sourceFiles = *arrayOf(
673                 kotlin(
674                     """
675                     package test.pkg
676 
677                     class Foo {
678                         infix fun add1(s: String) { }
679                         fun add2(s: String) { }
680                         infix fun add3(s: String) { }
681                     }
682                     """
683                 )
684             )
685         )
686     }
687 
688     @Test
Add sealnull689     fun `Add seal`() {
690         check(
691             warnings = """
692                 src/test/pkg/Foo.kt: error: Cannot add 'sealed' modifier to class test.pkg.Foo: Incompatible change [AddSealed]
693                 """,
694             compatibilityMode = false,
695             checkCompatibilityApi = """
696                 package test.pkg {
697                   public class Foo {
698                   }
699                 }
700                 """,
701             sourceFiles = *arrayOf(
702                 kotlin(
703                     """
704                     package test.pkg
705                     sealed class Foo
706                     """
707                 )
708             )
709         )
710     }
711 
712     @Test
Remove default parameternull713     fun `Remove default parameter`() {
714         check(
715             warnings = """
716                 src/test/pkg/Foo.kt:7: error: Attempted to remove default value from parameter s1 in test.pkg.Foo.method4 in method test.pkg.Foo.method4 [DefaultValueChange]
717                 """,
718             compatibilityMode = false,
719             inputKotlinStyleNulls = true,
720             checkCompatibilityApi = """
721                 package test.pkg {
722                   public final class Foo {
723                     ctor public Foo();
724                     method public final void method1(boolean b, String? s1);
725                     method public final void method2(boolean b, String? s1);
726                     method public final void method3(boolean b, String? s1 = "null");
727                     method public final void method4(boolean b, String? s1 = "null");
728                   }
729                 }
730                 """,
731             sourceFiles = *arrayOf(
732                 kotlin(
733                     """
734                     package test.pkg
735 
736                     class Foo {
737                         fun method1(b: Boolean, s1: String?) { }         // No change
738                         fun method2(b: Boolean, s1: String? = null) { }  // Adding: OK
739                         fun method3(b: Boolean, s1: String? = null) { }  // No change
740                         fun method4(b: Boolean, s1: String?) { }         // Removed
741                     }
742                     """
743                 )
744             )
745         )
746     }
747 
748     @Test
Removing method or field when still available via inheritance is OKnull749     fun `Removing method or field when still available via inheritance is OK`() {
750         check(
751             warnings = """
752                 """,
753             checkCompatibilityApi = """
754                 package test.pkg {
755                   public class Child extends test.pkg.Parent {
756                     ctor public Child();
757                     field public int field1;
758                     method public void method1();
759                   }
760                   public class Parent {
761                     ctor public Parent();
762                     field public int field1;
763                     field public int field2;
764                     method public void method1();
765                     method public void method2();
766                   }
767                 }
768                 """,
769             sourceFiles = *arrayOf(
770                 java(
771                     """
772                     package test.pkg;
773 
774                     public class Parent {
775                         public int field1 = 0;
776                         public int field2 = 0;
777                         public void method1() { }
778                         public void method2() { }
779                     }
780                     """
781                 ),
782                 java(
783                     """
784                     package test.pkg;
785 
786                     public class Child extends Parent {
787                         public int field1 = 0;
788                         @Override public void method1() { } // NO CHANGE
789                         //@Override public void method2() { } // REMOVED OK: Still inherited
790                     }
791                     """
792                 )
793             )
794         )
795     }
796 
797     @Test
Change field constant value, change field typenull798     fun `Change field constant value, change field type`() {
799         check(
800             warnings = """
801                 src/test/pkg/Parent.java:5: error: Field test.pkg.Parent.field2 has changed value from 2 to 42 [ChangedValue]
802                 src/test/pkg/Parent.java:6: error: Field test.pkg.Parent.field3 has changed type from int to char [ChangedType]
803                 src/test/pkg/Parent.java:7: error: Field test.pkg.Parent.field4 has added 'final' qualifier [AddedFinal]
804                 src/test/pkg/Parent.java:8: error: Field test.pkg.Parent.field5 has changed 'static' qualifier [ChangedStatic]
805                 src/test/pkg/Parent.java:9: error: Field test.pkg.Parent.field6 has changed 'transient' qualifier [ChangedTransient]
806                 src/test/pkg/Parent.java:10: error: Field test.pkg.Parent.field7 has changed 'volatile' qualifier [ChangedVolatile]
807                 src/test/pkg/Parent.java:11: error: Field test.pkg.Parent.field8 has changed deprecation state true --> false [ChangedDeprecated]
808                 src/test/pkg/Parent.java:12: error: Field test.pkg.Parent.field9 has changed deprecation state false --> true [ChangedDeprecated]
809                 src/test/pkg/Parent.java:19: error: Field test.pkg.Parent.field94 has changed value from 1 to 42 [ChangedValue]
810                 """,
811             checkCompatibilityApi = """
812                 package test.pkg {
813                   public class Parent {
814                     ctor public Parent();
815                     field public static final int field1 = 1; // 0x1
816                     field public static final int field2 = 2; // 0x2
817                     field public int field3;
818                     field public int field4 = 4; // 0x4
819                     field public int field5;
820                     field public int field6;
821                     field public int field7;
822                     field public deprecated int field8;
823                     field public int field9;
824                     field public static final int field91 = 1; // 0x1
825                     field public static final int field92 = 1; // 0x1
826                     field public static final int field93 = 1; // 0x1
827                     field public static final int field94 = 1; // 0x1
828                   }
829                 }
830                 """,
831             sourceFiles = *arrayOf(
832                 java(
833                     """
834                     package test.pkg;
835                     import android.annotation.SuppressLint;
836                     public class Parent {
837                         public static final int field1 = 1;  // UNCHANGED
838                         public static final int field2 = 42; // CHANGED VALUE
839                         public char field3 = 3;              // CHANGED TYPE
840                         public final int field4 = 4;         // ADDED FINAL
841                         public static int field5 = 5;        // ADDED STATIC
842                         public transient int field6 = 6;     // ADDED TRANSIENT
843                         public volatile int field7 = 7;      // ADDED VOLATILE
844                         public int field8 = 8;               // REMOVED DEPRECATED
845                         /** @deprecated */ @Deprecated public int field9 = 8;  // ADDED DEPRECATED
846                         @SuppressLint("ChangedValue")
847                         public static final int field91 = 42;// CHANGED VALUE: Suppressed
848                         @SuppressLint("ChangedValue:Field test.pkg.Parent.field92 has changed value from 1 to 42")
849                         public static final int field92 = 42;// CHANGED VALUE: Suppressed with same message
850                         @SuppressLint("ChangedValue: Field test.pkg.Parent.field93 has changed value from 1 to 42")
851                         public static final int field93 = 42;// CHANGED VALUE: Suppressed with same message
852                         @SuppressLint("ChangedValue:Field test.pkg.Parent.field94 has changed value from 10 to 1")
853                         public static final int field94 = 42;// CHANGED VALUE: Suppressed but with different message
854                     }
855                     """
856                 ),
857                 suppressLintSource
858             ),
859             extraArguments = arrayOf(ARG_HIDE_PACKAGE, "android.annotation")
860         )
861     }
862 
863     @Test
Change annotation default method value changenull864     fun `Change annotation default method value change`() {
865         check(
866             inputKotlinStyleNulls = true,
867             warnings = """
868                 src/test/pkg/ExportedProperty.java:15: error: Method test.pkg.ExportedProperty.category has changed value from "" to nothing [ChangedValue]
869                 src/test/pkg/ExportedProperty.java:14: error: Method test.pkg.ExportedProperty.floating has changed value from 1.0f to 1.1f [ChangedValue]
870                 src/test/pkg/ExportedProperty.java:16: error: Method test.pkg.ExportedProperty.formatToHexString has changed value from nothing to false [ChangedValue]
871                 src/test/pkg/ExportedProperty.java:13: error: Method test.pkg.ExportedProperty.prefix has changed value from "" to "hello" [ChangedValue]
872                 """,
873             checkCompatibilityApi = """
874                 package test.pkg {
875                   public @interface ExportedProperty {
876                     method public abstract boolean resolveId() default false;
877                     method public abstract float floating() default 1.0f;
878                     method public abstract String! prefix() default "";
879                     method public abstract String! category() default "";
880                     method public abstract boolean formatToHexString();
881                   }
882                 }
883                 """,
884             sourceFiles = *arrayOf(
885                 java(
886                     """
887                     package test.pkg;
888 
889                     import java.lang.annotation.ElementType;
890                     import java.lang.annotation.Retention;
891                     import java.lang.annotation.RetentionPolicy;
892                     import java.lang.annotation.Target;
893                     import static java.lang.annotation.RetentionPolicy.SOURCE;
894 
895                     @Target({ElementType.FIELD, ElementType.METHOD})
896                     @Retention(RetentionPolicy.RUNTIME)
897                     public @interface ExportedProperty {
898                         boolean resolveId() default false;            // UNCHANGED
899                         String prefix() default "hello";              // CHANGED VALUE
900                         float floating() default 1.1f;                // CHANGED VALUE
901                         String category();                            // REMOVED VALUE
902                         boolean formatToHexString() default false;    // ADDED VALUE
903                     }
904                     """
905                 )
906             )
907         )
908     }
909 
910     @Test
Incompatible class change -- class to interfacenull911     fun `Incompatible class change -- class to interface`() {
912         check(
913             warnings = """
914                 src/test/pkg/Parent.java:3: error: Class test.pkg.Parent changed class/interface declaration [ChangedClass]
915                 """,
916             checkCompatibilityApi = """
917                 package test.pkg {
918                   public class Parent {
919                   }
920                 }
921                 """,
922             sourceFiles = *arrayOf(
923                 java(
924                     """
925                     package test.pkg;
926 
927                     public interface Parent {
928                     }
929                     """
930                 )
931             )
932         )
933     }
934 
935     @Test
Incompatible class change -- change implemented interfacesnull936     fun `Incompatible class change -- change implemented interfaces`() {
937         check(
938             warnings = """
939                 src/test/pkg/Parent.java:3: error: Class test.pkg.Parent no longer implements java.io.Closeable [RemovedInterface]
940                 src/test/pkg/Parent.java:3: error: Added interface java.util.List to class class test.pkg.Parent [AddedInterface]
941                 """,
942             checkCompatibilityApi = """
943                 package test.pkg {
944                   public abstract class Parent implements java.io.Closeable, java.util.Map {
945                   }
946                 }
947                 """,
948             sourceFiles = *arrayOf(
949                 java(
950                     """
951                     package test.pkg;
952 
953                     public abstract class Parent implements java.util.Map, java.util.List {
954                         private Parent() {}
955                     }
956                     """
957                 )
958             )
959         )
960     }
961 
962     @Test
Incompatible class change -- change qualifiersnull963     fun `Incompatible class change -- change qualifiers`() {
964         check(
965             warnings = """
966                 src/test/pkg/Parent.java:3: error: Class test.pkg.Parent changed 'abstract' qualifier [ChangedAbstract]
967                 src/test/pkg/Parent.java:3: error: Class test.pkg.Parent changed 'static' qualifier [ChangedStatic]
968                 """,
969             checkCompatibilityApi = """
970                 package test.pkg {
971                   public class Parent {
972                   }
973                 }
974                 """,
975             sourceFiles = *arrayOf(
976                 java(
977                     """
978                     package test.pkg;
979 
980                     public abstract static class Parent {
981                         private Parent() {}
982                     }
983                     """
984                 )
985             )
986         )
987     }
988 
989     @Test
Incompatible class change -- finalnull990     fun `Incompatible class change -- final`() {
991         check(
992             warnings = """
993                 src/test/pkg/Class1.java:3: error: Class test.pkg.Class1 added 'final' qualifier [AddedFinal]
994                 TESTROOT/current-api.txt:3: error: Removed constructor test.pkg.Class1() [RemovedMethod]
995                 src/test/pkg/Class2.java:3: error: Class test.pkg.Class2 added 'final' qualifier but was previously uninstantiable and therefore could not be subclassed [AddedFinalUninstantiable]
996                 src/test/pkg/Class3.java:3: error: Class test.pkg.Class3 removed 'final' qualifier [RemovedFinal]
997                 """,
998             checkCompatibilityApi = """
999                 package test.pkg {
1000                   public class Class1 {
1001                       ctor public Class1();
1002                   }
1003                   public class Class2 {
1004                   }
1005                   public final class Class3 {
1006                   }
1007                 }
1008                 """,
1009             sourceFiles = *arrayOf(
1010                 java(
1011                     """
1012                     package test.pkg;
1013 
1014                     public final class Class1 {
1015                         private Class1() {}
1016                     }
1017                     """
1018                 ),
1019                 java(
1020                     """
1021                     package test.pkg;
1022 
1023                     public final class Class2 {
1024                         private Class2() {}
1025                     }
1026                     """
1027                 ),
1028                 java(
1029                     """
1030                     package test.pkg;
1031 
1032                     public class Class3 {
1033                         private Class3() {}
1034                     }
1035                     """
1036                 )
1037             )
1038         )
1039     }
1040 
1041     @Test
Incompatible class change -- visibilitynull1042     fun `Incompatible class change -- visibility`() {
1043         check(
1044             warnings = """
1045                 src/test/pkg/Class1.java:3: error: Class test.pkg.Class1 changed visibility from protected to public [ChangedScope]
1046                 src/test/pkg/Class2.java:3: error: Class test.pkg.Class2 changed visibility from public to protected [ChangedScope]
1047                 """,
1048             checkCompatibilityApi = """
1049                 package test.pkg {
1050                   protected class Class1 {
1051                   }
1052                   public class Class2 {
1053                   }
1054                 }
1055                 """,
1056             sourceFiles = *arrayOf(
1057                 java(
1058                     """
1059                     package test.pkg;
1060 
1061                     public class Class1 {
1062                         private Class1() {}
1063                     }
1064                     """
1065                 ),
1066                 java(
1067                     """
1068                     package test.pkg;
1069 
1070                     protected class Class2 {
1071                         private Class2() {}
1072                     }
1073                     """
1074                 )
1075             )
1076         )
1077     }
1078 
1079     @Test
Incompatible class change -- deprecationnull1080     fun `Incompatible class change -- deprecation`() {
1081         check(
1082             warnings = """
1083                 src/test/pkg/Class1.java:3: error: Class test.pkg.Class1 has changed deprecation state false --> true [ChangedDeprecated]
1084                 """,
1085             checkCompatibilityApi = """
1086                 package test.pkg {
1087                   public class Class1 {
1088                   }
1089                 }
1090                 """,
1091             sourceFiles = *arrayOf(
1092                 java(
1093                     """
1094                     package test.pkg;
1095 
1096                     /** @deprecated */
1097                     @Deprecated public class Class1 {
1098                         private Class1() {}
1099                     }
1100                     """
1101                 )
1102             )
1103         )
1104     }
1105 
1106     @Test
Incompatible class change -- superclassnull1107     fun `Incompatible class change -- superclass`() {
1108         check(
1109             warnings = """
1110                 src/test/pkg/Class3.java:3: error: Class test.pkg.Class3 superclass changed from java.lang.Char to java.lang.Number [ChangedSuperclass]
1111                 """,
1112             checkCompatibilityApi = """
1113                 package test.pkg {
1114                   public abstract class Class1 {
1115                   }
1116                   public abstract class Class2 extends java.lang.Number {
1117                   }
1118                   public abstract class Class3 extends java.lang.Char {
1119                   }
1120                 }
1121                 """,
1122             sourceFiles = *arrayOf(
1123                 java(
1124                     """
1125                     package test.pkg;
1126 
1127                     public abstract class Class1 extends java.lang.Short {
1128                         private Class1() {}
1129                     }
1130                     """
1131                 ),
1132                 java(
1133                     """
1134                     package test.pkg;
1135 
1136                     public abstract class Class2 extends java.lang.Float {
1137                         private Class2() {}
1138                     }
1139                     """
1140                 ),
1141                 java(
1142                     """
1143                     package test.pkg;
1144 
1145                     public abstract class Class3 extends java.lang.Number {
1146                         private Class3() {}
1147                     }
1148                     """
1149                 )
1150             )
1151         )
1152     }
1153 
1154     @Test
Incompatible class change -- type variablesnull1155     fun `Incompatible class change -- type variables`() {
1156         check(
1157             warnings = """
1158                 src/test/pkg/Class1.java:3: error: Class test.pkg.Class1 changed number of type parameters from 1 to 2 [ChangedType]
1159                 """,
1160             checkCompatibilityApi = """
1161                 package test.pkg {
1162                   public class Class1<X> {
1163                   }
1164                 }
1165                 """,
1166             sourceFiles = *arrayOf(
1167                 java(
1168                     """
1169                     package test.pkg;
1170 
1171                     public class Class1<X,Y> {
1172                         private Class1() {}
1173                     }
1174                     """
1175                 )
1176             )
1177         )
1178     }
1179 
1180     @Test
Incompatible method change -- modifiersnull1181     fun `Incompatible method change -- modifiers`() {
1182         check(
1183             warnings = """
1184                 src/test/pkg/MyClass.java:5: error: Method test.pkg.MyClass.myMethod2 has changed 'abstract' qualifier [ChangedAbstract]
1185                 src/test/pkg/MyClass.java:6: error: Method test.pkg.MyClass.myMethod3 has changed 'static' qualifier [ChangedStatic]
1186                 src/test/pkg/MyClass.java:7: error: Method test.pkg.MyClass.myMethod4 has changed deprecation state true --> false [ChangedDeprecated]
1187                 """,
1188             checkCompatibilityApi = """
1189                 package test.pkg {
1190                   public abstract class MyClass {
1191                       method public void myMethod2();
1192                       method public void myMethod3();
1193                       method deprecated public void myMethod4();
1194                   }
1195                 }
1196                 """,
1197             sourceFiles = *arrayOf(
1198                 java(
1199                     """
1200                     package test.pkg;
1201 
1202                     public abstract class MyClass {
1203                         private MyClass() {}
1204                         public native abstract void myMethod2(); // Note that Errors.CHANGE_NATIVE is hidden by default
1205                         public static void myMethod3() {}
1206                         public void myMethod4() {}
1207                     }
1208                     """
1209                 )
1210             )
1211         )
1212     }
1213 
1214     @Test
Incompatible method change -- finalnull1215     fun `Incompatible method change -- final`() {
1216         check(
1217             warnings = """
1218                 src/test/pkg/Outer.java:7: error: Method test.pkg.Outer.Class1.method1 has added 'final' qualifier [AddedFinal]
1219                 src/test/pkg/Outer.java:19: error: Method test.pkg.Outer.Class4.method4 has removed 'final' qualifier [RemovedFinal]
1220                 """,
1221             checkCompatibilityApi = """
1222                 package test.pkg {
1223                   public abstract class Outer {
1224                   }
1225                   public class Outer.Class1 {
1226                     method public void method1();
1227                   }
1228                   public final class Outer.Class2 {
1229                     method public void method2();
1230                   }
1231                   public final class Outer.Class3 {
1232                     method public void method3();
1233                   }
1234                   public class Outer.Class4 {
1235                     method public final void method4();
1236                   }
1237                 }
1238                 """,
1239             sourceFiles = *arrayOf(
1240                 java(
1241                     """
1242                     package test.pkg;
1243 
1244                     public abstract class Outer {
1245                         private Outer() {}
1246                         public class Class1 {
1247                             private Class1() {}
1248                             public final void method1() { } // Added final
1249                         }
1250                         public final class Class2 {
1251                             private Class2() {}
1252                             public final void method2() { } // Added final but class is effectively final so no change
1253                         }
1254                         public final class Class3 {
1255                             private Class3() {}
1256                             public void method3() { } // Removed final but is still effectively final
1257                         }
1258                         public class Class4 {
1259                             private Class4() {}
1260                             public void method4() { } // Removed final
1261                         }
1262                     }
1263                     """
1264                 )
1265             )
1266         )
1267     }
1268 
1269     @Test
Incompatible method change -- visibilitynull1270     fun `Incompatible method change -- visibility`() {
1271         check(
1272             warnings = """
1273                 src/test/pkg/MyClass.java:5: error: Method test.pkg.MyClass.myMethod1 changed visibility from protected to public [ChangedScope]
1274                 src/test/pkg/MyClass.java:6: error: Method test.pkg.MyClass.myMethod2 changed visibility from public to protected [ChangedScope]
1275                 """,
1276             checkCompatibilityApi = """
1277                 package test.pkg {
1278                   public abstract class MyClass {
1279                       method protected void myMethod1();
1280                       method public void myMethod2();
1281                   }
1282                 }
1283                 """,
1284             sourceFiles = *arrayOf(
1285                 java(
1286                     """
1287                     package test.pkg;
1288 
1289                     public abstract class MyClass {
1290                         private MyClass() {}
1291                         public void myMethod1() {}
1292                         protected void myMethod2() {}
1293                     }
1294                     """
1295                 )
1296             )
1297         )
1298     }
1299 
1300     @Test
Incompatible method change -- throws listnull1301     fun `Incompatible method change -- throws list`() {
1302         check(
1303             warnings = """
1304                 src/test/pkg/MyClass.java:7: error: Method test.pkg.MyClass.method1 added thrown exception java.io.IOException [ChangedThrows]
1305                 src/test/pkg/MyClass.java:8: error: Method test.pkg.MyClass.method2 no longer throws exception java.io.IOException [ChangedThrows]
1306                 src/test/pkg/MyClass.java:9: error: Method test.pkg.MyClass.method3 no longer throws exception java.io.IOException [ChangedThrows]
1307                 src/test/pkg/MyClass.java:9: error: Method test.pkg.MyClass.method3 no longer throws exception java.lang.NumberFormatException [ChangedThrows]
1308                 src/test/pkg/MyClass.java:9: error: Method test.pkg.MyClass.method3 added thrown exception java.lang.UnsupportedOperationException [ChangedThrows]
1309                 """,
1310             checkCompatibilityApi = """
1311                 package test.pkg {
1312                   public abstract class MyClass {
1313                       method public void finalize() throws java.lang.Throwable;
1314                       method public void method1();
1315                       method public void method2() throws java.io.IOException;
1316                       method public void method3() throws java.io.IOException, java.lang.NumberFormatException;
1317                   }
1318                 }
1319                 """,
1320             sourceFiles = *arrayOf(
1321                 java(
1322                     """
1323                     package test.pkg;
1324 
1325                     @SuppressWarnings("RedundantThrows")
1326                     public abstract class MyClass {
1327                         private MyClass() {}
1328                         public void finalize() {}
1329                         public void method1() throws java.io.IOException {}
1330                         public void method2() {}
1331                         public void method3() throws java.lang.UnsupportedOperationException {}
1332                     }
1333                     """
1334                 )
1335             )
1336         )
1337     }
1338 
1339     @Test
Incompatible method change -- return typesnull1340     fun `Incompatible method change -- return types`() {
1341         check(
1342             warnings = """
1343                 src/test/pkg/MyClass.java:5: error: Method test.pkg.MyClass.method1 has changed return type from float to int [ChangedType]
1344                 src/test/pkg/MyClass.java:6: error: Method test.pkg.MyClass.method2 has changed return type from java.util.List<Number> to java.util.List<java.lang.Integer> [ChangedType]
1345                 src/test/pkg/MyClass.java:7: error: Method test.pkg.MyClass.method3 has changed return type from java.util.List<Integer> to java.util.List<java.lang.Number> [ChangedType]
1346                 src/test/pkg/MyClass.java:8: error: Method test.pkg.MyClass.method4 has changed return type from String to String[] [ChangedType]
1347                 src/test/pkg/MyClass.java:9: error: Method test.pkg.MyClass.method5 has changed return type from String[] to String[][] [ChangedType]
1348                 src/test/pkg/MyClass.java:10: error: Method test.pkg.MyClass.method6 has changed return type from T (extends java.lang.Object) to U (extends java.lang.Number) [ChangedType]
1349                 src/test/pkg/MyClass.java:11: error: Method test.pkg.MyClass.method7 has changed return type from T to Number [ChangedType]
1350                 src/test/pkg/MyClass.java:13: error: Method test.pkg.MyClass.method9 has changed return type from X (extends java.lang.Throwable) to U (extends java.lang.Number) [ChangedType]
1351                 """,
1352             checkCompatibilityApi = """
1353                 package test.pkg {
1354                   public abstract class MyClass<T extends Number> {
1355                       method public float method1();
1356                       method public java.util.List<Number> method2();
1357                       method public java.util.List<Integer> method3();
1358                       method public String method4();
1359                       method public String[] method5();
1360                       method public <X extends java.lang.Throwable> T method6(java.util.function.Supplier<? extends X>);
1361                       method public <X extends java.lang.Throwable> T method7(java.util.function.Supplier<? extends X>);
1362                       method public <X extends java.lang.Throwable> Number method8(java.util.function.Supplier<? extends X>);
1363                       method public <X extends java.lang.Throwable> X method9(java.util.function.Supplier<? extends X>);
1364                   }
1365                 }
1366                 """,
1367             sourceFiles = *arrayOf(
1368                 java(
1369                     """
1370                     package test.pkg;
1371 
1372                     public abstract class MyClass<U extends Number> { // Changing type variable name is fine/compatible
1373                         private MyClass() {}
1374                         public int method1() { return 0; }
1375                         public java.util.List<Integer> method2() { return null; }
1376                         public java.util.List<Number> method3() { return null; }
1377                         public String[] method4() { return null; }
1378                         public String[][] method5() { return null; }
1379                         public <X extends java.lang.Throwable> U method6(java.util.function.Supplier<? extends X> arg) { return null; }
1380                         public <X extends java.lang.Throwable> Number method7(java.util.function.Supplier<? extends X> arg) { return null; }
1381                         public <X extends java.lang.Throwable> U method8(java.util.function.Supplier<? extends X> arg) { return null; }
1382                         public <X extends java.lang.Throwable> U method9(java.util.function.Supplier<? extends X> arg) { return null; }
1383                     }
1384                     """
1385                 )
1386             )
1387         )
1388     }
1389 
1390     @Test
Incompatible field change -- visibility and removing finalnull1391     fun `Incompatible field change -- visibility and removing final`() {
1392         check(
1393             warnings = """
1394                 src/test/pkg/MyClass.java:5: error: Field test.pkg.MyClass.myField1 changed visibility from protected to public [ChangedScope]
1395                 src/test/pkg/MyClass.java:6: error: Field test.pkg.MyClass.myField2 changed visibility from public to protected [ChangedScope]
1396                 src/test/pkg/MyClass.java:7: error: Field test.pkg.MyClass.myField3 has removed 'final' qualifier [RemovedFinal]
1397                 """,
1398             checkCompatibilityApi = """
1399                 package test.pkg {
1400                   public abstract class MyClass {
1401                       field protected int myField1;
1402                       field public int myField2;
1403                       field public final int myField3;
1404                   }
1405                 }
1406                 """,
1407             sourceFiles = *arrayOf(
1408                 java(
1409                     """
1410                     package test.pkg;
1411 
1412                     public abstract class MyClass {
1413                         private MyClass() {}
1414                         public int myField1 = 1;
1415                         protected int myField2 = 1;
1416                         public int myField3 = 1;
1417                     }
1418                     """
1419                 )
1420             )
1421         )
1422     }
1423 
1424     @Test
Adding classes, interfaces and packages, and removing thesenull1425     fun `Adding classes, interfaces and packages, and removing these`() {
1426         check(
1427             warnings = """
1428                 src/test/pkg/MyClass.java:3: error: Added class test.pkg.MyClass [AddedClass]
1429                 src/test/pkg/MyInterface.java:3: error: Added class test.pkg.MyInterface [AddedInterface]
1430                 TESTROOT/current-api.txt:2: error: Removed class test.pkg.MyOldClass [RemovedClass]
1431                 error: Added package test.pkg2 [AddedPackage]
1432                 TESTROOT/current-api.txt:5: error: Removed package test.pkg3 [RemovedPackage]
1433                 """,
1434             checkCompatibilityApi = """
1435                 package test.pkg {
1436                   public abstract class MyOldClass {
1437                   }
1438                 }
1439                 package test.pkg3 {
1440                   public abstract class MyOldClass {
1441                   }
1442                 }
1443                 """,
1444             sourceFiles = *arrayOf(
1445                 java(
1446                     """
1447                     package test.pkg;
1448 
1449                     public abstract class MyClass {
1450                         private MyClass() {}
1451                     }
1452                     """
1453                 ),
1454                 java(
1455                     """
1456                     package test.pkg;
1457 
1458                     public interface MyInterface {
1459                     }
1460                     """
1461                 ),
1462                 java(
1463                     """
1464                     package test.pkg2;
1465 
1466                     public abstract class MyClass2 {
1467                         private MyClass2() {}
1468                     }
1469                     """
1470                 )
1471             )
1472         )
1473     }
1474 
1475     @Test
Test removing public constructornull1476     fun `Test removing public constructor`() {
1477         check(
1478             warnings = """
1479                 TESTROOT/current-api.txt:3: error: Removed constructor test.pkg.MyClass() [RemovedMethod]
1480                 """,
1481             checkCompatibilityApi = """
1482                 package test.pkg {
1483                   public abstract class MyClass {
1484                     ctor public MyClass();
1485                   }
1486                 }
1487                 """,
1488             sourceFiles = *arrayOf(
1489                 java(
1490                     """
1491                     package test.pkg;
1492 
1493                     public abstract class MyClass {
1494                         private MyClass() {}
1495                     }
1496                     """
1497                 )
1498             )
1499         )
1500     }
1501 
1502     @Test
Test type variables from text signature filesnull1503     fun `Test type variables from text signature files`() {
1504         check(
1505             warnings = """
1506                 src/test/pkg/MyClass.java:8: error: Method test.pkg.MyClass.myMethod4 has changed return type from S (extends java.lang.Object) to S (extends java.lang.Float) [ChangedType]
1507                 """,
1508             checkCompatibilityApi = """
1509                 package test.pkg {
1510                   public abstract class MyClass<T extends test.pkg.Number,T_SPLITR> {
1511                     method public T myMethod1();
1512                     method public <S extends test.pkg.Number> S myMethod2();
1513                     method public <S> S myMethod3();
1514                     method public <S> S myMethod4();
1515                     method public java.util.List<byte[]> myMethod5();
1516                     method public T_SPLITR[] myMethod6();
1517                   }
1518                   public class Number {
1519                     ctor public Number();
1520                   }
1521                 }
1522                 """,
1523             sourceFiles = *arrayOf(
1524                 java(
1525                     """
1526                     package test.pkg;
1527 
1528                     public abstract class MyClass<T extends Number,T_SPLITR> {
1529                         private MyClass() {}
1530                         public T myMethod1() { return null; }
1531                         public <S extends Number> S myMethod2() { return null; }
1532                         public <S> S myMethod3() { return null; }
1533                         public <S extends Float> S myMethod4() { return null; }
1534                         public java.util.List<byte[]> myMethod5() { return null; }
1535                         public T_SPLITR[] myMethod6() { return null; }
1536                     }
1537                     """
1538                 ),
1539                 java(
1540                     """
1541                     package test.pkg;
1542                     public class Number {
1543                     }
1544                     """
1545                 )
1546             )
1547         )
1548     }
1549 
1550     @Test
Test Kotlin extensionsnull1551     fun `Test Kotlin extensions`() {
1552         check(
1553             inputKotlinStyleNulls = true,
1554             outputKotlinStyleNulls = true,
1555             omitCommonPackages = true,
1556             compatibilityMode = false,
1557             warnings = "",
1558             checkCompatibilityApi = """
1559                 package androidx.content {
1560                   public final class ContentValuesKt {
1561                     ctor public ContentValuesKt();
1562                     method public static android.content.ContentValues contentValuesOf(kotlin.Pair<String,?>... pairs);
1563                   }
1564                 }
1565                 """,
1566             sourceFiles = *arrayOf(
1567                 kotlin(
1568                     "src/androidx/content/ContentValues.kt",
1569                     """
1570                     package androidx.content
1571 
1572                     import android.content.ContentValues
1573 
1574                     fun contentValuesOf(vararg pairs: Pair<String, Any?>) = ContentValues(pairs.size).apply {
1575                         for ((key, value) in pairs) {
1576                             when (value) {
1577                                 null -> putNull(key)
1578                                 is String -> put(key, value)
1579                                 is Int -> put(key, value)
1580                                 is Long -> put(key, value)
1581                                 is Boolean -> put(key, value)
1582                                 is Float -> put(key, value)
1583                                 is Double -> put(key, value)
1584                                 is ByteArray -> put(key, value)
1585                                 is Byte -> put(key, value)
1586                                 is Short -> put(key, value)
1587                                 else -> {
1588                                     val valueType = value.javaClass.canonicalName
1589                                     throw IllegalArgumentException("Illegal value type")
1590                                 }
1591                             }
1592                         }
1593                     }
1594                     """
1595                 )
1596             )
1597         )
1598     }
1599 
1600     @Test
Test Kotlin type boundsnull1601     fun `Test Kotlin type bounds`() {
1602         check(
1603             inputKotlinStyleNulls = false,
1604             outputKotlinStyleNulls = true,
1605             omitCommonPackages = true,
1606             compatibilityMode = false,
1607             warnings = "",
1608             checkCompatibilityApi = """
1609                 package androidx.navigation {
1610                   public final class NavDestination {
1611                     ctor public NavDestination();
1612                   }
1613                   public class NavDestinationBuilder<D extends androidx.navigation.NavDestination> {
1614                     ctor public NavDestinationBuilder(int id);
1615                     method public D build();
1616                   }
1617                 }
1618                 """,
1619             sourceFiles = *arrayOf(
1620                 kotlin(
1621                     """
1622                     package androidx.navigation
1623 
1624                     open class NavDestinationBuilder<out D : NavDestination>(
1625                             id: Int
1626                     ) {
1627                         open fun build(): D {
1628                             TODO()
1629                         }
1630                     }
1631 
1632                     class NavDestination
1633                     """
1634                 )
1635             )
1636         )
1637     }
1638 
1639     @Test
Test inherited methodsnull1640     fun `Test inherited methods`() {
1641         check(
1642             warnings = """
1643                 """,
1644             checkCompatibilityApi = """
1645                 package test.pkg {
1646                   public class Child1 extends test.pkg.Parent {
1647                   }
1648                   public class Child2 extends test.pkg.Parent {
1649                     method public void method0(java.lang.String, int);
1650                     method public void method4(java.lang.String, int);
1651                   }
1652                   public class Child3 extends test.pkg.Parent {
1653                     method public void method1(java.lang.String, int);
1654                     method public void method2(java.lang.String, int);
1655                   }
1656                   public class Parent {
1657                     method public void method1(java.lang.String, int);
1658                     method public void method2(java.lang.String, int);
1659                     method public void method3(java.lang.String, int);
1660                   }
1661                 }
1662                 """,
1663             sourceFiles = *arrayOf(
1664                 java(
1665                     """
1666                     package test.pkg;
1667 
1668                     public class Child1 extends Parent {
1669                         private Child1() {}
1670                         @Override
1671                         public void method1(String first, int second) {
1672                         }
1673                         @Override
1674                         public void method2(String first, int second) {
1675                         }
1676                         @Override
1677                         public void method3(String first, int second) {
1678                         }
1679                     }
1680                     """
1681                 ),
1682                 java(
1683                     """
1684                     package test.pkg;
1685 
1686                     public class Child2 extends Parent {
1687                         private Child2() {}
1688                         @Override
1689                         public void method0(String first, int second) {
1690                         }
1691                         @Override
1692                         public void method1(String first, int second) {
1693                         }
1694                         @Override
1695                         public void method2(String first, int second) {
1696                         }
1697                         @Override
1698                         public void method3(String first, int second) {
1699                         }
1700                         @Override
1701                         public void method4(String first, int second) {
1702                         }
1703                     }
1704                     """
1705                 ),
1706                 java(
1707                     """
1708                     package test.pkg;
1709 
1710                     public class Child3 extends Parent {
1711                         private Child3() {}
1712                         @Override
1713                         public void method1(String first, int second) {
1714                         }
1715                     }
1716                     """
1717                 ),
1718                 java(
1719                     """
1720                     package test.pkg;
1721                     public class Parent {
1722                         private Parent() { }
1723                         public void method1(String first, int second) {
1724                         }
1725                         public void method2(String first, int second) {
1726                         }
1727                         public void method3(String first, int second) {
1728                         }
1729                     }
1730                     """
1731                 )
1732             )
1733         )
1734     }
1735 
1736     @Test
Partial text file which references inner classes not listed elsewherenull1737     fun `Partial text file which references inner classes not listed elsewhere`() {
1738         // This happens in system and test files where we only include APIs that differ
1739         // from the base IDE. When parsing these code bases we need to gracefully handle
1740         // references to inner classes.
1741         check(
1742             includeSystemApiAnnotations = true,
1743             warnings = """
1744                 src/test/pkg/Bar.java:17: error: Added method test.pkg.Bar.Inner1.Inner2.addedMethod() to the system API [AddedMethod]
1745                 TESTROOT/current-api.txt:4: error: Removed method test.pkg.Bar.Inner1.Inner2.removedMethod() [RemovedMethod]
1746                 """,
1747             sourceFiles = *arrayOf(
1748                 java(
1749                     """
1750                     package other.pkg;
1751 
1752                     public class MyClass {
1753                         public class MyInterface {
1754                             public void test() { }
1755                         }
1756                     }
1757                     """
1758                 ).indented(),
1759                 java(
1760                     """
1761                     package test.pkg;
1762                     import android.annotation.SystemApi;
1763 
1764                     public class Bar {
1765                         public class Inner1 {
1766                             private Inner1() { }
1767                             @SuppressWarnings("JavaDoc")
1768                             public class Inner2 {
1769                                 private Inner2() { }
1770 
1771                                 /**
1772                                  * @hide
1773                                  */
1774                                 @SystemApi
1775                                 public void method() { }
1776 
1777                                 /**
1778                                  * @hide
1779                                  */
1780                                 @SystemApi
1781                                 public void addedMethod() { }
1782                             }
1783                         }
1784                     }
1785                     """
1786                 ),
1787                 systemApiSource
1788             ),
1789 
1790             extraArguments = arrayOf(
1791                 ARG_SHOW_ANNOTATION, "android.annotation.TestApi",
1792                 ARG_HIDE_PACKAGE, "android.annotation",
1793                 ARG_HIDE_PACKAGE, "android.support.annotation"
1794             ),
1795 
1796             checkCompatibilityApi =
1797             """
1798                 package test.pkg {
1799                   public class Bar.Inner1.Inner2 {
1800                     method public void method();
1801                     method public void removedMethod();
1802                   }
1803                 }
1804                 """
1805         )
1806     }
1807 
1808     @Test
Partial text file which adds methods to show-annotation APInull1809     fun `Partial text file which adds methods to show-annotation API`() {
1810         // This happens in system and test files where we only include APIs that differ
1811         // from the base IDE. When parsing these code bases we need to gracefully handle
1812         // references to inner classes.
1813         check(
1814             includeSystemApiAnnotations = true,
1815             warnings = """
1816                 TESTROOT/current-api.txt:4: error: Removed method android.rolecontrollerservice.RoleControllerService.onClearRoleHolders() [RemovedMethod]
1817                 src/android/rolecontrollerservice/RoleControllerService.java:7: warning: Added method android.rolecontrollerservice.RoleControllerService.onGrantDefaultRoles() to the system API [AddedAbstractMethod]
1818                 """,
1819             sourceFiles = *arrayOf(
1820                 java(
1821                     """
1822                     package android.rolecontrollerservice;
1823 
1824                     public class Service {
1825                     }
1826                     """
1827                 ).indented(),
1828                 java(
1829                     """
1830                     package android.rolecontrollerservice;
1831                     import android.annotation.SystemApi;
1832 
1833                     /** @hide */
1834                     @SystemApi
1835                     public abstract class RoleControllerService extends Service {
1836                         public abstract void onGrantDefaultRoles();
1837                     }
1838                     """
1839                 ),
1840                 systemApiSource
1841             ),
1842 
1843             extraArguments = arrayOf(
1844                 ARG_SHOW_ANNOTATION, "android.annotation.TestApi",
1845                 ARG_HIDE_PACKAGE, "android.annotation",
1846                 ARG_HIDE_PACKAGE, "android.support.annotation"
1847             ),
1848 
1849             checkCompatibilityApi =
1850                 """
1851                 package android.rolecontrollerservice {
1852                   public abstract class RoleControllerService extends android.rolecontrollerservice.Service {
1853                     ctor public RoleControllerService();
1854                     method public abstract void onClearRoleHolders();
1855                   }
1856                 }
1857                 """
1858         )
1859     }
1860 
1861     @Test
Test verifying simple removed APInull1862     fun `Test verifying simple removed API`() {
1863         check(
1864             warnings = """
1865                 src/test/pkg/Bar.java:8: error: Added method test.pkg.Bar.newlyRemoved() to the removed API [AddedMethod]
1866                 """,
1867             checkCompatibilityRemovedApiCurrent = """
1868                 package test.pkg {
1869                   public class Bar {
1870                     ctor public Bar();
1871                     method public void removedMethod();
1872                   }
1873                   public class Bar.Inner {
1874                     ctor public Bar.Inner();
1875                   }
1876                 }
1877                 """,
1878             sourceFiles = *arrayOf(
1879                 java(
1880                     """
1881                     package test.pkg;
1882                     @SuppressWarnings("JavaDoc")
1883                     public class Bar {
1884                         /** @removed */
1885                         public Bar() { }
1886                         // No longer removed: /** @removed */
1887                         public void removedMethod() { }
1888                         /** @removed */
1889                         public void newlyRemoved() { }
1890 
1891                         public void newlyAdded() { }
1892 
1893                         /** @removed */
1894                         public class Inner { }
1895                     }
1896                     """
1897                 )
1898             )
1899         )
1900     }
1901 
1902     @Test
Test verifying removed APInull1903     fun `Test verifying removed API`() {
1904         check(
1905             warnings = """
1906                 """,
1907             checkCompatibilityRemovedApiCurrent = """
1908                 package test.pkg {
1909                   public class Bar {
1910                     ctor public Bar();
1911                     method public void removedMethod();
1912                     field public int removedField;
1913                   }
1914                   public class Bar.Inner {
1915                     ctor public Bar.Inner();
1916                   }
1917                   public class Bar.Inner2.Inner3.Inner4 {
1918                     ctor public Bar.Inner2.Inner3.Inner4();
1919                   }
1920                   public class Bar.Inner5.Inner6.Inner7 {
1921                     field public int removed;
1922                   }
1923                 }
1924                 """,
1925             sourceFiles = *arrayOf(
1926                 java(
1927                     """
1928                     package test.pkg;
1929                     @SuppressWarnings("JavaDoc")
1930                     public class Bar {
1931                         /** @removed */
1932                         public Bar() { }
1933                         public int field;
1934                         public void test() { }
1935                         /** @removed */
1936                         public int removedField;
1937                         /** @removed */
1938                         public void removedMethod() { }
1939                         /** @removed and @hide - should not be listed */
1940                         public int hiddenField;
1941 
1942                         /** @removed */
1943                         public class Inner { }
1944 
1945                         public class Inner2 {
1946                             public class Inner3 {
1947                                 /** @removed */
1948                                 public class Inner4 { }
1949                             }
1950                         }
1951 
1952                         public class Inner5 {
1953                             public class Inner6 {
1954                                 public class Inner7 {
1955                                     /** @removed */
1956                                     public int removed;
1957                                 }
1958                             }
1959                         }
1960                     }
1961                     """
1962                 )
1963             )
1964         )
1965     }
1966 
1967     @Test
Regression test for bug 120847535null1968     fun `Regression test for bug 120847535`() {
1969         // Regression test for
1970         // 120847535: check-api doesn't fail on method that is in current.txt, but marked @hide @TestApi
1971         check(
1972             warnings = """
1973                 TESTROOT/current-api.txt:6: error: Removed method test.view.ViewTreeObserver.registerFrameCommitCallback(Runnable) [RemovedMethod]
1974                 """,
1975             sourceFiles = *arrayOf(
1976                 java(
1977                     """
1978                     package test.view;
1979                     import android.annotation.TestApi;
1980                     public final class ViewTreeObserver {
1981                          /**
1982                          * @hide
1983                          */
1984                         @TestApi
1985                         public void registerFrameCommitCallback(Runnable callback) {
1986                         }
1987                     }
1988                     """
1989                 ).indented(),
1990                 java(
1991                     """
1992                     package test.view;
1993                     public final class View {
1994                         private View() { }
1995                     }
1996                     """
1997                 ).indented(),
1998                 testApiSource
1999             ),
2000 
2001             api = """
2002                 package test.view {
2003                   public final class View {
2004                   }
2005                   public final class ViewTreeObserver {
2006                     ctor public ViewTreeObserver();
2007                   }
2008                 }
2009             """,
2010             extraArguments = arrayOf(
2011                 ARG_HIDE_PACKAGE, "android.annotation",
2012                 ARG_HIDE_PACKAGE, "android.support.annotation"
2013             ),
2014 
2015             checkCompatibilityApi = """
2016                 package test.view {
2017                   public final class View {
2018                   }
2019                   public final class ViewTreeObserver {
2020                     ctor public ViewTreeObserver();
2021                     method public void registerFrameCommitCallback(java.lang.Runnable);
2022                   }
2023                 }
2024                 """
2025         )
2026     }
2027 
2028     @Test
Test release compatibility checkingnull2029     fun `Test release compatibility checking`() {
2030         // Different checks are enforced for current vs release API comparisons:
2031         // we don't flag AddedClasses etc. Removed classes *are* enforced.
2032         check(
2033             warnings = """
2034                 src/test/pkg/Class1.java:3: error: Class test.pkg.Class1 added 'final' qualifier [AddedFinal]
2035                 TESTROOT/released-api.txt:3: error: Removed constructor test.pkg.Class1() [RemovedMethod]
2036                 src/test/pkg/MyClass.java:5: warning: Method test.pkg.MyClass.myMethod2 has changed 'abstract' qualifier [ChangedAbstract]
2037                 src/test/pkg/MyClass.java:6: error: Method test.pkg.MyClass.myMethod3 has changed 'static' qualifier [ChangedStatic]
2038                 TESTROOT/released-api.txt:14: error: Removed class test.pkg.MyOldClass [RemovedClass]
2039                 TESTROOT/released-api.txt:17: error: Removed package test.pkg3 [RemovedPackage]
2040                 """,
2041             checkCompatibilityApiReleased = """
2042                 package test.pkg {
2043                   public class Class1 {
2044                       ctor public Class1();
2045                   }
2046                   public class Class2 {
2047                   }
2048                   public final class Class3 {
2049                   }
2050                   public abstract class MyClass {
2051                       method public void myMethod2();
2052                       method public void myMethod3();
2053                       method deprecated public void myMethod4();
2054                   }
2055                   public abstract class MyOldClass {
2056                   }
2057                 }
2058                 package test.pkg3 {
2059                   public abstract class MyOldClass {
2060                   }
2061                 }
2062                 """,
2063             sourceFiles = *arrayOf(
2064                 java(
2065                     """
2066                     package test.pkg;
2067 
2068                     public final class Class1 {
2069                         private Class1() {}
2070                     }
2071                     """
2072                 ),
2073                 java(
2074                     """
2075                     package test.pkg;
2076 
2077                     public final class Class2 {
2078                         private Class2() {}
2079                     }
2080                     """
2081                 ),
2082                 java(
2083                     """
2084                     package test.pkg;
2085 
2086                     public class Class3 {
2087                         private Class3() {}
2088                     }
2089                     """
2090                 ),
2091                 java(
2092                     """
2093                     package test.pkg;
2094 
2095                     public abstract class MyNewClass {
2096                         private MyNewClass() {}
2097                     }
2098                     """
2099                 ),
2100                 java(
2101                     """
2102                     package test.pkg;
2103 
2104                     public abstract class MyClass {
2105                         private MyClass() {}
2106                         public native abstract void myMethod2(); // Note that Errors.CHANGE_NATIVE is hidden by default
2107                         public static void myMethod3() {}
2108                         public void myMethod4() {}
2109                     }
2110                     """
2111                 )
2112             )
2113         )
2114     }
2115 
2116     @Test
Implicit nullnessnull2117     fun `Implicit nullness`() {
2118         check(
2119             compatibilityMode = false,
2120             inputKotlinStyleNulls = true,
2121             checkCompatibilityApi = """
2122                 // Signature format: 2.0
2123                 package androidx.annotation {
2124                   @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.CLASS) @java.lang.annotation.Target({java.lang.annotation.ElementType.ANNOTATION_TYPE, java.lang.annotation.ElementType.TYPE, java.lang.annotation.ElementType.METHOD, java.lang.annotation.ElementType.CONSTRUCTOR, java.lang.annotation.ElementType.FIELD, java.lang.annotation.ElementType.PACKAGE}) public @interface RestrictTo {
2125                     method public abstract androidx.annotation.RestrictTo.Scope[] value();
2126                   }
2127 
2128                   public enum RestrictTo.Scope {
2129                     enum_constant @Deprecated public static final androidx.annotation.RestrictTo.Scope GROUP_ID;
2130                     enum_constant public static final androidx.annotation.RestrictTo.Scope LIBRARY;
2131                     enum_constant public static final androidx.annotation.RestrictTo.Scope LIBRARY_GROUP;
2132                     enum_constant public static final androidx.annotation.RestrictTo.Scope SUBCLASSES;
2133                     enum_constant public static final androidx.annotation.RestrictTo.Scope TESTS;
2134                   }
2135                 }
2136                 """,
2137 
2138             sourceFiles = *arrayOf(
2139                 restrictToSource
2140             )
2141         )
2142     }
2143 
2144     @Test
Implicit nullness in compat formatnull2145     fun `Implicit nullness in compat format`() {
2146         // Make sure we put "static" in enum modifier lists when in v1/compat mode
2147         check(
2148             compatibilityMode = true,
2149             inputKotlinStyleNulls = true,
2150             checkCompatibilityApi = """
2151                 package androidx.annotation {
2152                   public abstract class RestrictTo implements java.lang.annotation.Annotation {
2153                     method public abstract androidx.annotation.RestrictTo.Scope[] value();
2154                   }
2155 
2156                   public static final class RestrictTo.Scope extends java.lang.Enum {
2157                     enum_constant deprecated public static final androidx.annotation.RestrictTo.Scope GROUP_ID;
2158                     enum_constant public static final androidx.annotation.RestrictTo.Scope LIBRARY;
2159                     enum_constant public static final androidx.annotation.RestrictTo.Scope LIBRARY_GROUP;
2160                     enum_constant public static final androidx.annotation.RestrictTo.Scope SUBCLASSES;
2161                     enum_constant public static final androidx.annotation.RestrictTo.Scope TESTS;
2162                   }
2163                 }
2164                 """,
2165 
2166             sourceFiles = *arrayOf(
2167                 restrictToSource
2168             )
2169         )
2170     }
2171 
2172     @Test
Java String constantsnull2173     fun `Java String constants`() {
2174         check(
2175             compatibilityMode = false,
2176             inputKotlinStyleNulls = true,
2177             checkCompatibilityApi = """
2178                 package androidx.browser.browseractions {
2179                   public class BrowserActionsIntent {
2180                     field public static final String EXTRA_APP_ID = "androidx.browser.browseractions.APP_ID";
2181                   }
2182                 }
2183                 """,
2184 
2185             sourceFiles = *arrayOf(
2186                 java(
2187                     """
2188                      package androidx.browser.browseractions;
2189                      public class BrowserActionsIntent {
2190                         private BrowserActionsIntent() { }
2191                         public static final String EXTRA_APP_ID = "androidx.browser.browseractions.APP_ID";
2192 
2193                      }
2194                     """
2195                 ).indented()
2196             )
2197         )
2198     }
2199 
2200     @Test
Classes with mapsnull2201     fun `Classes with maps`() {
2202         check(
2203             compatibilityMode = false,
2204             inputKotlinStyleNulls = true,
2205             checkCompatibilityApi = """
2206                 // Signature format: 2.0
2207                 package androidx.collection {
2208                   public class SimpleArrayMap<K, V> {
2209                   }
2210                 }
2211                 """,
2212 
2213             sourceFiles = *arrayOf(
2214                 java(
2215                     """
2216                     package androidx.collection;
2217 
2218                     public class SimpleArrayMap<K, V> {
2219                         private SimpleArrayMap() { }
2220                     }
2221                     """
2222                 ).indented()
2223             )
2224         )
2225     }
2226 
2227     @Test
Referencing type parameters in typesnull2228     fun `Referencing type parameters in types`() {
2229         check(
2230             compatibilityMode = false,
2231             inputKotlinStyleNulls = true,
2232             checkCompatibilityApi = """
2233                 // Signature format: 2.0
2234                 package androidx.collection {
2235                   public class MyMap<Key, Value> {
2236                     ctor public MyMap();
2237                     field public Key! myField;
2238                     method public Key! getReplacement(Key!);
2239                   }
2240                 }
2241                 """,
2242 
2243             sourceFiles = *arrayOf(
2244                 java(
2245                     """
2246                     package androidx.collection;
2247 
2248                     public class MyMap<Key, Value> {
2249                         public Key getReplacement(Key key) { return null; }
2250                         public Key myField = null;
2251                     }
2252                     """
2253                 ).indented()
2254             )
2255         )
2256     }
2257 
2258     @Test
Comparing annotations with methods with v1 signature filesnull2259     fun `Comparing annotations with methods with v1 signature files`() {
2260         check(
2261             compatibilityMode = true,
2262             checkCompatibilityApi = """
2263                 package androidx.annotation {
2264                   public abstract class RestrictTo implements java.lang.annotation.Annotation {
2265                   }
2266                   public static final class RestrictTo.Scope extends java.lang.Enum {
2267                     method public static androidx.annotation.RestrictTo.Scope valueOf(java.lang.String);
2268                     method public static final androidx.annotation.RestrictTo.Scope[] values();
2269                     enum_constant public static final deprecated androidx.annotation.RestrictTo.Scope GROUP_ID;
2270                     enum_constant public static final androidx.annotation.RestrictTo.Scope LIBRARY;
2271                     enum_constant public static final androidx.annotation.RestrictTo.Scope LIBRARY_GROUP;
2272                     enum_constant public static final androidx.annotation.RestrictTo.Scope SUBCLASSES;
2273                     enum_constant public static final androidx.annotation.RestrictTo.Scope TESTS;
2274                   }
2275                 }
2276                 """,
2277 
2278             sourceFiles = *arrayOf(
2279                 restrictToSource
2280             )
2281         )
2282     }
2283 
2284     @Test
Insignificant type formatting differencesnull2285     fun `Insignificant type formatting differences`() {
2286         check(
2287             checkCompatibilityApi = """
2288                 package test.pkg {
2289                   public final class UsageStatsManager {
2290                     method public java.util.Map<java.lang.String, java.lang.Integer> getAppStandbyBuckets();
2291                     method public void setAppStandbyBuckets(java.util.Map<java.lang.String, java.lang.Integer>);
2292                     field public java.util.Map<java.lang.String, java.lang.Integer> map;
2293                   }
2294                 }
2295                 """,
2296             signatureSource = """
2297                 package test.pkg {
2298                   public final class UsageStatsManager {
2299                     method public java.util.Map<java.lang.String,java.lang.Integer> getAppStandbyBuckets();
2300                     method public void setAppStandbyBuckets(java.util.Map<java.lang.String,java.lang.Integer>);
2301                     field public java.util.Map<java.lang.String,java.lang.Integer> map;
2302                   }
2303                 }
2304                 """
2305         )
2306     }
2307 
2308     @Test
Compare signatures with Kotlin nullability from signaturenull2309     fun `Compare signatures with Kotlin nullability from signature`() {
2310         check(
2311             warnings = """
2312             TESTROOT/load-api.txt:5: error: Attempted to remove @NonNull annotation from parameter str in test.pkg.Foo.method1(int p, Integer int2, int p1, String str, java.lang.String... args) [InvalidNullConversion]
2313             TESTROOT/load-api.txt:7: error: Attempted to change parameter from @Nullable to @NonNull: incompatible change for parameter str in test.pkg.Foo.method3(String str, int p, int int2) [InvalidNullConversion]
2314             """.trimIndent(),
2315             format = FileFormat.V3,
2316             checkCompatibilityApi = """
2317                 // Signature format: 3.0
2318                 package test.pkg {
2319                   public final class Foo {
2320                     ctor public Foo();
2321                     method public void method1(int p = 42, Integer? int2 = null, int p1 = 42, String str = "hello world", java.lang.String... args);
2322                     method public void method2(int p, int int2 = (2 * int) * some.other.pkg.Constants.Misc.SIZE);
2323                     method public void method3(String? str, int p, int int2 = double(int) + str.length);
2324                     field public static final test.pkg.Foo.Companion! Companion;
2325                   }
2326                 }
2327                 """,
2328             signatureSource = """
2329                 // Signature format: 3.0
2330                 package test.pkg {
2331                   public final class Foo {
2332                     ctor public Foo();
2333                     method public void method1(int p = 42, Integer? int2 = null, int p1 = 42, String! str = "hello world", java.lang.String... args);
2334                     method public void method2(int p, int int2 = (2 * int) * some.other.pkg.Constants.Misc.SIZE);
2335                     method public void method3(String str, int p, int int2 = double(int) + str.length);
2336                     field public static final test.pkg.Foo.Companion! Companion;
2337                   }
2338                 }
2339                 """
2340         )
2341     }
2342 
2343     @Test
Compare signatures with Kotlin nullability from sourcenull2344     fun `Compare signatures with Kotlin nullability from source`() {
2345         check(
2346             warnings = """
2347             src/test/pkg/test.kt:4: error: Attempted to change parameter from @Nullable to @NonNull: incompatible change for parameter str1 in test.pkg.TestKt.fun1(String str1, String str2, java.util.List<java.lang.String> list) [InvalidNullConversion]
2348             """.trimIndent(),
2349             format = FileFormat.V3,
2350             checkCompatibilityApi = """
2351                 // Signature format: 3.0
2352                 package test.pkg {
2353                   public final class TestKt {
2354                     ctor public TestKt();
2355                     method public static void fun1(String? str1, String str2, java.util.List<java.lang.String!> list);
2356                   }
2357                 }
2358                 """,
2359             sourceFiles = *arrayOf(
2360                 kotlin(
2361                     """
2362                         package test.pkg
2363                         import java.util.List
2364 
2365                         fun fun1(str1: String, str2: String?, list: List<String?>) { }
2366 
2367                     """.trimIndent()
2368                 )
2369             )
2370         )
2371     }
2372 
2373     @Test
Adding and removing reifiednull2374     fun `Adding and removing reified`() {
2375         check(
2376             compatibilityMode = false,
2377             inputKotlinStyleNulls = true,
2378             warnings = """
2379             src/test/pkg/test.kt:5: error: Method test.pkg.TestKt.add made type variable T reified: incompatible change [ChangedThrows]
2380             src/test/pkg/test.kt:8: error: Method test.pkg.TestKt.two made type variable S reified: incompatible change [ChangedThrows]
2381             """,
2382             checkCompatibilityApi = """
2383                 package test.pkg {
2384                   public final class TestKt {
2385                     ctor public TestKt();
2386                     method public static inline <T> void add(T! t);
2387                     method public static inline <reified T> void remove(T! t);
2388                     method public static inline <reified T> void unchanged(T! t);
2389                     method public static inline <S, reified T> void two(S! s, T! t);
2390                   }
2391                 }
2392                 """,
2393 
2394             sourceFiles = *arrayOf(
2395                 kotlin(
2396                     """
2397                     @file:Suppress("NOTHING_TO_INLINE", "RedundantVisibilityModifier", "unused")
2398 
2399                     package test.pkg
2400 
2401                     inline fun <reified T> add(t: T) { }
2402                     inline fun <T> remove(t: T) { }
2403                     inline fun <reified T> unchanged(t: T) { }
2404                     inline fun <reified S, T> two(s: S, t: T) { }
2405                     """
2406                 ).indented()
2407             )
2408         )
2409     }
2410 
2411     @Ignore("Not currently working: we're getting the wrong PSI results; I suspect caching across the two codebases")
2412     @Test
Test All Android API levelsnull2413     fun `Test All Android API levels`() {
2414         // Checks API across Android SDK versions and makes sure the results are
2415         // intentional (to help shake out bugs in the API compatibility checker)
2416 
2417         // Expected migration warnings (the map value) when migrating to the target key level from the previous level
2418         val expected = mapOf(
2419             5 to "warning: Method android.view.Surface.lockCanvas added thrown exception java.lang.IllegalArgumentException [ChangedThrows]",
2420             6 to """
2421                 warning: Method android.accounts.AbstractAccountAuthenticator.confirmCredentials added thrown exception android.accounts.NetworkErrorException [ChangedThrows]
2422                 warning: Method android.accounts.AbstractAccountAuthenticator.updateCredentials added thrown exception android.accounts.NetworkErrorException [ChangedThrows]
2423                 warning: Field android.view.WindowManager.LayoutParams.TYPE_STATUS_BAR_PANEL has changed value from 2008 to 2014 [ChangedValue]
2424                 """,
2425             7 to """
2426                 error: Removed field android.view.ViewGroup.FLAG_USE_CHILD_DRAWING_ORDER [RemovedField]
2427                 """,
2428 
2429             // setOption getting removed here is wrong! Seems to be a PSI loading bug.
2430             8 to """
2431                 warning: Constructor android.net.SSLCertificateSocketFactory no longer throws exception java.security.KeyManagementException [ChangedThrows]
2432                 warning: Constructor android.net.SSLCertificateSocketFactory no longer throws exception java.security.NoSuchAlgorithmException [ChangedThrows]
2433                 error: Removed method java.net.DatagramSocketImpl.getOption(int) [RemovedMethod]
2434                 error: Removed method java.net.DatagramSocketImpl.setOption(int,Object) [RemovedMethod]
2435                 warning: Constructor java.nio.charset.Charset no longer throws exception java.nio.charset.IllegalCharsetNameException [ChangedThrows]
2436                 warning: Method java.nio.charset.Charset.forName no longer throws exception java.nio.charset.IllegalCharsetNameException [ChangedThrows]
2437                 warning: Method java.nio.charset.Charset.forName no longer throws exception java.nio.charset.UnsupportedCharsetException [ChangedThrows]
2438                 warning: Method java.nio.charset.Charset.isSupported no longer throws exception java.nio.charset.IllegalCharsetNameException [ChangedThrows]
2439                 warning: Method java.util.regex.Matcher.appendReplacement no longer throws exception java.lang.IllegalStateException [ChangedThrows]
2440                 warning: Method java.util.regex.Matcher.start no longer throws exception java.lang.IllegalStateException [ChangedThrows]
2441                 warning: Method java.util.regex.Pattern.compile no longer throws exception java.util.regex.PatternSyntaxException [ChangedThrows]
2442                 warning: Class javax.xml.XMLConstants added final qualifier [AddedFinal]
2443                 error: Removed constructor javax.xml.XMLConstants() [RemovedMethod]
2444                 warning: Method javax.xml.parsers.DocumentBuilder.isXIncludeAware no longer throws exception java.lang.UnsupportedOperationException [ChangedThrows]
2445                 warning: Method javax.xml.parsers.DocumentBuilderFactory.newInstance no longer throws exception javax.xml.parsers.FactoryConfigurationError [ChangedThrows]
2446                 warning: Method javax.xml.parsers.SAXParser.isXIncludeAware no longer throws exception java.lang.UnsupportedOperationException [ChangedThrows]
2447                 warning: Method javax.xml.parsers.SAXParserFactory.newInstance no longer throws exception javax.xml.parsers.FactoryConfigurationError [ChangedThrows]
2448                 warning: Method org.w3c.dom.Element.getAttributeNS added thrown exception org.w3c.dom.DOMException [ChangedThrows]
2449                 warning: Method org.w3c.dom.Element.getAttributeNodeNS added thrown exception org.w3c.dom.DOMException [ChangedThrows]
2450                 warning: Method org.w3c.dom.Element.getElementsByTagNameNS added thrown exception org.w3c.dom.DOMException [ChangedThrows]
2451                 warning: Method org.w3c.dom.Element.hasAttributeNS added thrown exception org.w3c.dom.DOMException [ChangedThrows]
2452                 warning: Method org.w3c.dom.NamedNodeMap.getNamedItemNS added thrown exception org.w3c.dom.DOMException [ChangedThrows]
2453                 """,
2454 
2455             18 to """
2456                 warning: Class android.os.Looper added final qualifier but was previously uninstantiable and therefore could not be subclassed [AddedFinalUninstantiable]
2457                 warning: Class android.os.MessageQueue added final qualifier but was previously uninstantiable and therefore could not be subclassed [AddedFinalUninstantiable]
2458                 error: Removed field android.os.Process.BLUETOOTH_GID [RemovedField]
2459                 error: Removed class android.renderscript.Program [RemovedClass]
2460                 error: Removed class android.renderscript.ProgramStore [RemovedClass]
2461                 """,
2462             19 to """
2463                 warning: Method android.app.Notification.Style.build has changed 'abstract' qualifier [ChangedAbstract]
2464                 error: Removed method android.os.Debug.MemoryInfo.getOtherLabel(int) [RemovedMethod]
2465                 error: Removed method android.os.Debug.MemoryInfo.getOtherPrivateDirty(int) [RemovedMethod]
2466                 error: Removed method android.os.Debug.MemoryInfo.getOtherPss(int) [RemovedMethod]
2467                 error: Removed method android.os.Debug.MemoryInfo.getOtherSharedDirty(int) [RemovedMethod]
2468                 warning: Field android.view.animation.Transformation.TYPE_ALPHA has changed value from nothing/not constant to 1 [ChangedValue]
2469                 warning: Field android.view.animation.Transformation.TYPE_ALPHA has added 'final' qualifier [AddedFinal]
2470                 warning: Field android.view.animation.Transformation.TYPE_BOTH has changed value from nothing/not constant to 3 [ChangedValue]
2471                 warning: Field android.view.animation.Transformation.TYPE_BOTH has added 'final' qualifier [AddedFinal]
2472                 warning: Field android.view.animation.Transformation.TYPE_IDENTITY has changed value from nothing/not constant to 0 [ChangedValue]
2473                 warning: Field android.view.animation.Transformation.TYPE_IDENTITY has added 'final' qualifier [AddedFinal]
2474                 warning: Field android.view.animation.Transformation.TYPE_MATRIX has changed value from nothing/not constant to 2 [ChangedValue]
2475                 warning: Field android.view.animation.Transformation.TYPE_MATRIX has added 'final' qualifier [AddedFinal]
2476                 warning: Method java.nio.CharBuffer.subSequence has changed return type from CharSequence to java.nio.CharBuffer [ChangedType]
2477                 """, // The last warning above is not right; seems to be a PSI jar loading bug. It returns the wrong return type!
2478 
2479             20 to """
2480                 error: Removed method android.util.TypedValue.complexToDimensionNoisy(int,android.util.DisplayMetrics) [RemovedMethod]
2481                 warning: Method org.json.JSONObject.keys has changed return type from java.util.Iterator to java.util.Iterator<java.lang.String> [ChangedType]
2482                 warning: Field org.xmlpull.v1.XmlPullParserFactory.features has changed type from java.util.HashMap to java.util.HashMap<java.lang.String, java.lang.Boolean> [ChangedType]
2483                 """,
2484             26 to """
2485                 warning: Field android.app.ActivityManager.RunningAppProcessInfo.IMPORTANCE_PERCEPTIBLE has changed value from 130 to 230 [ChangedValue]
2486                 warning: Field android.content.pm.PermissionInfo.PROTECTION_MASK_FLAGS has changed value from 4080 to 65520 [ChangedValue]
2487                 """,
2488             27 to ""
2489         )
2490 
2491         val suppressLevels = mapOf(
2492             1 to "AddedPackage,AddedClass,AddedMethod,AddedInterface,AddedField,ChangedDeprecated",
2493             7 to "AddedPackage,AddedClass,AddedMethod,AddedInterface,AddedField,ChangedDeprecated",
2494             18 to "AddedPackage,AddedClass,AddedMethod,AddedInterface,AddedField,RemovedMethod,ChangedDeprecated,ChangedThrows,AddedFinal,ChangedType,RemovedDeprecatedClass",
2495             26 to "AddedPackage,AddedClass,AddedMethod,AddedInterface,AddedField,RemovedMethod,ChangedDeprecated,ChangedThrows,AddedFinal,RemovedClass,RemovedDeprecatedClass",
2496             27 to "AddedPackage,AddedClass,AddedMethod,AddedInterface,AddedField,RemovedMethod,ChangedDeprecated,ChangedThrows,AddedFinal"
2497         )
2498 
2499         val loadPrevAsSignature = false
2500 
2501         for (apiLevel in 5..27) {
2502             if (!expected.containsKey(apiLevel)) {
2503                 continue
2504             }
2505             println("Checking compatibility from API level ${apiLevel - 1} to $apiLevel...")
2506             val current = getAndroidJar(apiLevel)
2507             if (current == null) {
2508                 println("Couldn't find $current: Check that pwd for test is correct. Skipping this test.")
2509                 return
2510             }
2511 
2512             val previous = getAndroidJar(apiLevel - 1)
2513             if (previous == null) {
2514                 println("Couldn't find $previous: Check that pwd for test is correct. Skipping this test.")
2515                 return
2516             }
2517             val previousApi = previous.path
2518 
2519             // PSI based check
2520 
2521             check(
2522                 checkDoclava1 = false,
2523                 extraArguments = arrayOf(
2524                     "--omit-locations",
2525                     ARG_HIDE,
2526                     suppressLevels[apiLevel]
2527                         ?: "AddedPackage,AddedClass,AddedMethod,AddedInterface,AddedField,ChangedDeprecated,RemovedField,RemovedClass,RemovedDeprecatedClass" +
2528                         (if ((apiLevel == 19 || apiLevel == 20) && loadPrevAsSignature) ",ChangedType" else "")
2529 
2530                 ),
2531                 warnings = expected[apiLevel]?.trimIndent() ?: "",
2532                 checkCompatibilityApi = previousApi,
2533                 apiJar = current
2534             )
2535 
2536             // Signature based check
2537             if (apiLevel >= 21) {
2538                 // Check signature file checks. We have .txt files for API level 14 and up, but there are a
2539                 // BUNCH of problems in older signature files that make the comparisons not work --
2540                 // missing type variables in class declarations, missing generics in method signatures, etc.
2541                 val signatureFile = File("../../prebuilts/sdk/${apiLevel - 1}/public/api/android.txt")
2542                 if (!(signatureFile.isFile)) {
2543                     println("Couldn't find $signatureFile: Check that pwd for test is correct. Skipping this test.")
2544                     return
2545                 }
2546                 val previousSignatureApi = signatureFile.readText(UTF_8)
2547 
2548                 check(
2549                     checkDoclava1 = false,
2550                     extraArguments = arrayOf(
2551                         "--omit-locations",
2552                         ARG_HIDE,
2553                         suppressLevels[apiLevel]
2554                             ?: "AddedPackage,AddedClass,AddedMethod,AddedInterface,AddedField,ChangedDeprecated,RemovedField,RemovedClass,RemovedDeprecatedClass"
2555                     ),
2556                     warnings = expected[apiLevel]?.trimIndent() ?: "",
2557                     checkCompatibilityApi = previousSignatureApi,
2558                     apiJar = current
2559                 )
2560             }
2561         }
2562     }
2563 
2564     @Test
Ignore hidden referencesnull2565     fun `Ignore hidden references`() {
2566         check(
2567             warnings = """
2568                 """,
2569             compatibilityMode = false,
2570             checkCompatibilityApi = """
2571                 package test.pkg {
2572                   public class MyClass {
2573                     ctor public MyClass();
2574                     method public void method1(test.pkg.Hidden);
2575                   }
2576                 }
2577                 """,
2578             sourceFiles = *arrayOf(
2579                 java(
2580                     """
2581                     package test.pkg;
2582 
2583                     public class MyClass {
2584                         public void method1(Hidden hidden) { }
2585                     }
2586                     """
2587                 ),
2588                 java(
2589                     """
2590                     package test.pkg;
2591                     /** @hide */
2592                     public class Hidden {
2593                     }
2594                     """
2595                 )
2596             ),
2597             extraArguments = arrayOf(
2598                 ARG_HIDE, "ReferencesHidden",
2599                 ARG_HIDE, "UnavailableSymbol",
2600                 ARG_HIDE, "HiddenTypeParameter"
2601             )
2602         )
2603     }
2604 
2605     @Test
Fail on compatible changes that affect signature file contentsnull2606     fun `Fail on compatible changes that affect signature file contents`() {
2607         // Regression test for 122916999
2608         check(
2609             extraArguments = arrayOf(ARG_NO_NATIVE_DIFF),
2610             allowCompatibleDifferences = false,
2611             expectedFail = """
2612                 Aborting: Your changes have resulted in differences in the signature file
2613                 for the public API.
2614 
2615                 The changes may be compatible, but the signature file needs to be updated.
2616 
2617                 Diffs:
2618                 @@ -5 +5
2619                       ctor public MyClass();
2620                 -     method public void method2();
2621                       method public void method1();
2622                 @@ -7 +6
2623                       method public void method1();
2624                 +     method public void method2();
2625                       method public void method3();
2626             """.trimIndent(),
2627             compatibilityMode = false,
2628             // Methods in order
2629             checkCompatibilityApi = """
2630                 package test.pkg {
2631 
2632                   public class MyClass {
2633                     ctor public MyClass();
2634                     method public void method2();
2635                     method public void method1();
2636                     method public void method3();
2637                     method public void method4();
2638                   }
2639 
2640                 }
2641                 """,
2642             sourceFiles = *arrayOf(
2643                 java(
2644                     """
2645                     package test.pkg;
2646 
2647                     public class MyClass {
2648                         public void method1() { }
2649                         public void method2() { }
2650                         public void method3() { }
2651                         public native void method4();
2652                     }
2653                     """
2654                 )
2655             )
2656         )
2657     }
2658 
2659     @Test
Empty bundle filesnull2660     fun `Empty bundle files`() {
2661         // Regression test for 124333557
2662         // Makes sure we properly handle conflicting definitions of a java file in separate source roots
2663         check(
2664             warnings = "",
2665             compatibilityMode = false,
2666             checkCompatibilityApi = """
2667                 // Signature format: 3.0
2668                 package com.android.location.provider {
2669                   public class LocationProviderBase1 {
2670                     ctor public LocationProviderBase1();
2671                     method public void onGetStatus(android.os.Bundle!);
2672                   }
2673                   public class LocationProviderBase2 {
2674                     ctor public LocationProviderBase2();
2675                     method public void onGetStatus(android.os.Bundle!);
2676                   }
2677                 }
2678                 """,
2679             sourceFiles = *arrayOf(
2680                 java(
2681                     "src2/com/android/location/provider/LocationProviderBase1.java",
2682                     """
2683                     /** Something */
2684                     package com.android.location.provider;
2685                     """
2686                 ),
2687                 java(
2688                     "src/com/android/location/provider/LocationProviderBase1.java",
2689                     """
2690                     package com.android.location.provider;
2691                     import android.os.Bundle;
2692 
2693                     public class LocationProviderBase1 {
2694                         public void onGetStatus(Bundle bundle) { }
2695                     }
2696                     """
2697                 ),
2698                 // Try both combinations (empty java file both first on the source path
2699                 // and second on the source path)
2700                 java(
2701                     "src/com/android/location/provider/LocationProviderBase2.java",
2702                     """
2703                     /** Something */
2704                     package com.android.location.provider;
2705                     """
2706                 ),
2707                 java(
2708                     "src/com/android/location/provider/LocationProviderBase2.java",
2709                     """
2710                     package com.android.location.provider;
2711                     import android.os.Bundle;
2712 
2713                     public class LocationProviderBase2 {
2714                         public void onGetStatus(Bundle bundle) { }
2715                     }
2716                     """
2717                 )
2718             )
2719         )
2720     }
2721 
2722     // TODO: Check method signatures changing incompatibly (look especially out for adding new overloaded
2723     // methods and comparator getting confused!)
2724     //   ..equals on the method items should actually be very useful!
2725 }
2726