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