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 expectedIssues = """ 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 expectedIssues = """ 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 expectedIssues = """ 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 expectedIssues = """ 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 expectedIssues = """ 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 expectedIssues = """ 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 expectedIssues = """ 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 expectedIssues = "", 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 expectedIssues = """ 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 expectedIssues = """ 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 expectedIssues = """ 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 method public static final void method1(int[] x); 392 method public static final void method2(int... x); 393 } 394 } 395 """, 396 sourceFiles = arrayOf( 397 kotlin( 398 """ 399 package test.pkg 400 fun method1(vararg x: Int) { } 401 fun method2(x: IntArray) { } 402 """ 403 ) 404 ) 405 ) 406 } 407 408 @Test Add finalnull409 fun `Add final`() { 410 // Adding final on class or method is incompatible; adding it on a parameter is fine. 411 // Field is iffy. 412 check( 413 expectedIssues = """ 414 src/test/pkg/Java.java:4: error: Method test.pkg.Java.method has added 'final' qualifier [AddedFinal] 415 src/test/pkg/Kotlin.kt:4: error: Method test.pkg.Kotlin.method has added 'final' qualifier [AddedFinal] 416 """, 417 compatibilityMode = false, 418 checkCompatibilityApi = """ 419 package test.pkg { 420 public class Java { 421 method public void method(int); 422 } 423 public class Kotlin { 424 ctor public Kotlin(); 425 method public void method(String s); 426 } 427 } 428 """, 429 sourceFiles = arrayOf( 430 kotlin( 431 """ 432 package test.pkg 433 434 open class Kotlin { 435 fun method(s: String) { } 436 } 437 """ 438 ), 439 java( 440 """ 441 package test.pkg; 442 public class Java { 443 private Java() { } 444 public final void method(final int parameter) { } 445 } 446 """ 447 ) 448 ) 449 ) 450 } 451 452 @Test Inherited finalnull453 fun `Inherited final`() { 454 // Make sure that we correctly compare effectively final (inherited from surrounding class) 455 // between the signature file codebase and the real codebase 456 check( 457 expectedIssues = """ 458 """, 459 compatibilityMode = false, 460 checkCompatibilityApi = """ 461 package test.pkg { 462 public final class Cls extends test.pkg.Parent { 463 } 464 public class Parent { 465 method public void method(int); 466 } 467 } 468 """, 469 sourceFiles = arrayOf( 470 java( 471 """ 472 package test.pkg; 473 public final class Cls extends Parent { 474 private Cls() { } 475 @Override public void method(final int parameter) { } 476 } 477 """ 478 ), 479 java( 480 """ 481 package test.pkg; 482 public class Parent { 483 private Parent() { } 484 public void method(final int parameter) { } 485 } 486 """ 487 ) 488 ) 489 ) 490 } 491 492 @Test Implicit concretenull493 fun `Implicit concrete`() { 494 // Doclava signature files sometimes leave out overridden methods of 495 // abstract methods. We don't want to list these as having changed 496 // their abstractness. 497 check( 498 expectedIssues = """ 499 """, 500 compatibilityMode = false, 501 checkCompatibilityApi = """ 502 package test.pkg { 503 public final class Cls extends test.pkg.Parent { 504 } 505 public class Parent { 506 method public abstract void method(int); 507 } 508 } 509 """, 510 sourceFiles = arrayOf( 511 java( 512 """ 513 package test.pkg; 514 public final class Cls extends Parent { 515 private Cls() { } 516 @Override public void method(final int parameter) { } 517 } 518 """ 519 ), 520 java( 521 """ 522 package test.pkg; 523 public class Parent { 524 private Parent() { } 525 public abstract void method(final int parameter); 526 } 527 """ 528 ) 529 ) 530 ) 531 } 532 533 @Test Implicit modifiers from inherited super classesnull534 fun `Implicit modifiers from inherited super classes`() { 535 check( 536 expectedIssues = """ 537 """, 538 compatibilityMode = false, 539 checkCompatibilityApi = """ 540 package test.pkg { 541 public final class Cls implements test.pkg.Interface { 542 method public void method(int); 543 method public final void method2(int); 544 } 545 public interface Interface { 546 method public void method2(int); 547 } 548 } 549 """, 550 sourceFiles = arrayOf( 551 java( 552 """ 553 package test.pkg; 554 public final class Cls extends HiddenParent implements Interface { 555 private Cls() { } 556 @Override public void method(final int parameter) { } 557 } 558 """ 559 ), 560 java( 561 """ 562 package test.pkg; 563 class HiddenParent { 564 private HiddenParent() { } 565 public abstract void method(final int parameter) { } 566 public final void method2(final int parameter) { } 567 } 568 """ 569 ), 570 java( 571 """ 572 package test.pkg; 573 public interface Interface { 574 void method2(final int parameter) { } 575 } 576 """ 577 ) 578 ) 579 ) 580 } 581 582 @Test Wildcard comparisonsnull583 fun `Wildcard comparisons`() { 584 // Doclava signature files sometimes leave out overridden methods of 585 // abstract methods. We don't want to list these as having changed 586 // their abstractness. 587 check( 588 expectedIssues = """ 589 """, 590 compatibilityMode = false, 591 checkCompatibilityApi = """ 592 package test.pkg { 593 public abstract class AbstractMap<K, V> implements java.util.Map { 594 method public java.util.Set<K> keySet(); 595 method public V put(K, V); 596 method public void putAll(java.util.Map<? extends K, ? extends V>); 597 } 598 public abstract class EnumMap<K extends java.lang.Enum<K>, V> extends test.pkg.AbstractMap { 599 } 600 } 601 """, 602 sourceFiles = arrayOf( 603 java( 604 """ 605 package test.pkg; 606 @SuppressWarnings({"ConstantConditions", "NullableProblems"}) 607 public abstract class AbstractMap<K, V> implements java.util.Map { 608 private AbstractMap() { } 609 public V put(K k, V v) { return null; } 610 public java.util.Set<K> keySet() { return null; } 611 public void putAll(java.util.Map<? extends K, ? extends V> x) { } 612 } 613 """ 614 ), 615 java( 616 """ 617 package test.pkg; 618 public abstract class EnumMap<K extends java.lang.Enum<K>, V> extends test.pkg.AbstractMap { 619 private EnumMap() { } 620 public V put(K k, V v) { return null; } 621 } 622 """ 623 ) 624 ) 625 ) 626 } 627 628 @Test Added constructornull629 fun `Added constructor`() { 630 // Regression test for issue 116619591 631 check( 632 expectedIssues = "src/test/pkg/AbstractMap.java:2: error: Added constructor test.pkg.AbstractMap() [AddedMethod]", 633 compatibilityMode = false, 634 checkCompatibilityApi = """ 635 package test.pkg { 636 public abstract class AbstractMap<K, V> implements java.util.Map { 637 } 638 } 639 """, 640 sourceFiles = arrayOf( 641 java( 642 """ 643 package test.pkg; 644 @SuppressWarnings({"ConstantConditions", "NullableProblems"}) 645 public abstract class AbstractMap<K, V> implements java.util.Map { 646 } 647 """ 648 ) 649 ) 650 ) 651 } 652 653 @Test Remove infixnull654 fun `Remove infix`() { 655 check( 656 expectedIssues = """ 657 src/test/pkg/Foo.kt:5: error: Cannot remove `infix` modifier from method test.pkg.Foo.add2(String): Incompatible change [InfixRemoval] 658 """, 659 compatibilityMode = false, 660 checkCompatibilityApi = """ 661 package test.pkg { 662 public final class Foo { 663 ctor public Foo(); 664 method public final void add1(String s); 665 method public final infix void add2(String s); 666 method public final infix void add3(String s); 667 } 668 } 669 """, 670 sourceFiles = arrayOf( 671 kotlin( 672 """ 673 package test.pkg 674 675 class Foo { 676 infix fun add1(s: String) { } 677 fun add2(s: String) { } 678 infix fun add3(s: String) { } 679 } 680 """ 681 ) 682 ) 683 ) 684 } 685 686 @Test Add sealnull687 fun `Add seal`() { 688 check( 689 expectedIssues = """ 690 src/test/pkg/Foo.kt:2: error: Cannot add 'sealed' modifier to class test.pkg.Foo: Incompatible change [AddSealed] 691 """, 692 compatibilityMode = false, 693 checkCompatibilityApi = """ 694 package test.pkg { 695 public class Foo { 696 } 697 } 698 """, 699 sourceFiles = arrayOf( 700 kotlin( 701 """ 702 package test.pkg 703 sealed class Foo 704 """ 705 ) 706 ) 707 ) 708 } 709 710 @Test Remove default parameternull711 fun `Remove default parameter`() { 712 check( 713 expectedIssues = """ 714 src/test/pkg/Foo.kt:3: error: Attempted to remove default value from parameter s1 in test.pkg.Foo in constructor test.pkg.Foo [DefaultValueChange] [See https://s.android.com/api-guidelines#default-value-removal] 715 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] [See https://s.android.com/api-guidelines#default-value-removal] 716 717 """, 718 compatibilityMode = false, 719 inputKotlinStyleNulls = true, 720 checkCompatibilityApi = """ 721 package test.pkg { 722 public final class Foo { 723 ctor public Foo(String? s1 = null); 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(s1: String?) { 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 Remove optional parameternull749 fun `Remove optional parameter`() { 750 check( 751 expectedIssues = """ 752 src/test/pkg/Foo.kt:3: error: Attempted to remove default value from parameter s1 in test.pkg.Foo in constructor test.pkg.Foo [DefaultValueChange] [See https://s.android.com/api-guidelines#default-value-removal] 753 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] [See https://s.android.com/api-guidelines#default-value-removal] 754 """, 755 compatibilityMode = false, 756 inputKotlinStyleNulls = true, 757 format = FileFormat.V4, 758 checkCompatibilityApi = """ 759 package test.pkg { 760 public final class Foo { 761 ctor public Foo(optional String? s1); 762 method public final void method1(boolean b, String? s1); 763 method public final void method2(boolean b, String? s1); 764 method public final void method3(boolean b, optional String? s1); 765 method public final void method4(boolean b, optional String? s1); 766 } 767 } 768 """, 769 sourceFiles = arrayOf( 770 kotlin( 771 """ 772 package test.pkg 773 774 class Foo(s1: String?) { // Removed 775 fun method1(b: Boolean, s1: String?) { } // No change 776 fun method2(b: Boolean, s1: String? = null) { } // Adding: OK 777 fun method3(b: Boolean, s1: String? = null) { } // No change 778 fun method4(b: Boolean, s1: String?) { } // Removed 779 } 780 """ 781 ) 782 ) 783 ) 784 } 785 786 @Test Removing method or field when still available via inheritance is OKnull787 fun `Removing method or field when still available via inheritance is OK`() { 788 check( 789 expectedIssues = """ 790 """, 791 checkCompatibilityApi = """ 792 package test.pkg { 793 public class Child extends test.pkg.Parent { 794 ctor public Child(); 795 field public int field1; 796 method public void method1(); 797 } 798 public class Parent { 799 ctor public Parent(); 800 field public int field1; 801 field public int field2; 802 method public void method1(); 803 method public void method2(); 804 } 805 } 806 """, 807 sourceFiles = arrayOf( 808 java( 809 """ 810 package test.pkg; 811 812 public class Parent { 813 public int field1 = 0; 814 public int field2 = 0; 815 public void method1() { } 816 public void method2() { } 817 } 818 """ 819 ), 820 java( 821 """ 822 package test.pkg; 823 824 public class Child extends Parent { 825 public int field1 = 0; 826 @Override public void method1() { } // NO CHANGE 827 //@Override public void method2() { } // REMOVED OK: Still inherited 828 } 829 """ 830 ) 831 ) 832 ) 833 } 834 835 @Test Change field constant value, change field typenull836 fun `Change field constant value, change field type`() { 837 check( 838 expectedIssues = """ 839 src/test/pkg/Parent.java:5: error: Field test.pkg.Parent.field2 has changed value from 2 to 42 [ChangedValue] 840 src/test/pkg/Parent.java:6: error: Field test.pkg.Parent.field3 has changed type from int to char [ChangedType] 841 src/test/pkg/Parent.java:7: error: Field test.pkg.Parent.field4 has added 'final' qualifier [AddedFinal] 842 src/test/pkg/Parent.java:8: error: Field test.pkg.Parent.field5 has changed 'static' qualifier [ChangedStatic] 843 src/test/pkg/Parent.java:9: error: Field test.pkg.Parent.field6 has changed 'transient' qualifier [ChangedTransient] 844 src/test/pkg/Parent.java:10: error: Field test.pkg.Parent.field7 has changed 'volatile' qualifier [ChangedVolatile] 845 src/test/pkg/Parent.java:11: error: Field test.pkg.Parent.field8 has changed deprecation state true --> false [ChangedDeprecated] 846 src/test/pkg/Parent.java:12: error: Field test.pkg.Parent.field9 has changed deprecation state false --> true [ChangedDeprecated] 847 src/test/pkg/Parent.java:19: error: Field test.pkg.Parent.field94 has changed value from 1 to 42 [ChangedValue] 848 """, 849 checkCompatibilityApi = """ 850 package test.pkg { 851 public class Parent { 852 ctor public Parent(); 853 field public static final int field1 = 1; // 0x1 854 field public static final int field2 = 2; // 0x2 855 field public int field3; 856 field public int field4 = 4; // 0x4 857 field public int field5; 858 field public int field6; 859 field public int field7; 860 field public deprecated int field8; 861 field public int field9; 862 field public static final int field91 = 1; // 0x1 863 field public static final int field92 = 1; // 0x1 864 field public static final int field93 = 1; // 0x1 865 field public static final int field94 = 1; // 0x1 866 } 867 } 868 """, 869 sourceFiles = arrayOf( 870 java( 871 """ 872 package test.pkg; 873 import android.annotation.SuppressLint; 874 public class Parent { 875 public static final int field1 = 1; // UNCHANGED 876 public static final int field2 = 42; // CHANGED VALUE 877 public char field3 = 3; // CHANGED TYPE 878 public final int field4 = 4; // ADDED FINAL 879 public static int field5 = 5; // ADDED STATIC 880 public transient int field6 = 6; // ADDED TRANSIENT 881 public volatile int field7 = 7; // ADDED VOLATILE 882 public int field8 = 8; // REMOVED DEPRECATED 883 /** @deprecated */ @Deprecated public int field9 = 8; // ADDED DEPRECATED 884 @SuppressLint("ChangedValue") 885 public static final int field91 = 42;// CHANGED VALUE: Suppressed 886 @SuppressLint("ChangedValue:Field test.pkg.Parent.field92 has changed value from 1 to 42") 887 public static final int field92 = 42;// CHANGED VALUE: Suppressed with same message 888 @SuppressLint("ChangedValue: Field test.pkg.Parent.field93 has changed value from 1 to 42") 889 public static final int field93 = 42;// CHANGED VALUE: Suppressed with same message 890 @SuppressLint("ChangedValue:Field test.pkg.Parent.field94 has changed value from 10 to 1") 891 public static final int field94 = 42;// CHANGED VALUE: Suppressed but with different message 892 } 893 """ 894 ), 895 suppressLintSource 896 ), 897 extraArguments = arrayOf(ARG_HIDE_PACKAGE, "android.annotation") 898 ) 899 } 900 901 @Test Change annotation default method value changenull902 fun `Change annotation default method value change`() { 903 check( 904 inputKotlinStyleNulls = true, 905 expectedIssues = """ 906 src/test/pkg/ExportedProperty.java:15: error: Method test.pkg.ExportedProperty.category has changed value from "" to nothing [ChangedValue] 907 src/test/pkg/ExportedProperty.java:14: error: Method test.pkg.ExportedProperty.floating has changed value from 1.0f to 1.1f [ChangedValue] 908 src/test/pkg/ExportedProperty.java:13: error: Method test.pkg.ExportedProperty.prefix has changed value from "" to "hello" [ChangedValue] 909 """, 910 checkCompatibilityApi = """ 911 package test.pkg { 912 public @interface ExportedProperty { 913 method public abstract boolean resolveId() default false; 914 method public abstract float floating() default 1.0f; 915 method public abstract String! prefix() default ""; 916 method public abstract String! category() default ""; 917 method public abstract boolean formatToHexString(); 918 } 919 } 920 """, 921 sourceFiles = arrayOf( 922 java( 923 """ 924 package test.pkg; 925 926 import java.lang.annotation.ElementType; 927 import java.lang.annotation.Retention; 928 import java.lang.annotation.RetentionPolicy; 929 import java.lang.annotation.Target; 930 import static java.lang.annotation.RetentionPolicy.SOURCE; 931 932 @Target({ElementType.FIELD, ElementType.METHOD}) 933 @Retention(RetentionPolicy.RUNTIME) 934 public @interface ExportedProperty { 935 boolean resolveId() default false; // UNCHANGED 936 String prefix() default "hello"; // CHANGED VALUE 937 float floating() default 1.1f; // CHANGED VALUE 938 String category(); // REMOVED VALUE 939 boolean formatToHexString() default false; // ADDED VALUE 940 } 941 """ 942 ) 943 ) 944 ) 945 } 946 947 @Test Incompatible class change -- class to interfacenull948 fun `Incompatible class change -- class to interface`() { 949 check( 950 expectedIssues = """ 951 src/test/pkg/Parent.java:3: error: Class test.pkg.Parent changed class/interface declaration [ChangedClass] 952 """, 953 checkCompatibilityApi = """ 954 package test.pkg { 955 public class Parent { 956 } 957 } 958 """, 959 sourceFiles = arrayOf( 960 java( 961 """ 962 package test.pkg; 963 964 public interface Parent { 965 } 966 """ 967 ) 968 ) 969 ) 970 } 971 972 @Test Incompatible class change -- change implemented interfacesnull973 fun `Incompatible class change -- change implemented interfaces`() { 974 check( 975 expectedIssues = """ 976 src/test/pkg/Parent.java:3: error: Class test.pkg.Parent no longer implements java.io.Closeable [RemovedInterface] 977 src/test/pkg/Parent.java:3: error: Added interface java.util.List to class class test.pkg.Parent [AddedInterface] 978 """, 979 checkCompatibilityApi = """ 980 package test.pkg { 981 public abstract class Parent implements java.io.Closeable, java.util.Map { 982 } 983 } 984 """, 985 sourceFiles = arrayOf( 986 java( 987 """ 988 package test.pkg; 989 990 public abstract class Parent implements java.util.Map, java.util.List { 991 private Parent() {} 992 } 993 """ 994 ) 995 ) 996 ) 997 } 998 999 @Test Incompatible class change -- change qualifiersnull1000 fun `Incompatible class change -- change qualifiers`() { 1001 check( 1002 expectedIssues = """ 1003 src/test/pkg/Parent.java:3: error: Class test.pkg.Parent changed 'abstract' qualifier [ChangedAbstract] 1004 src/test/pkg/Parent.java:3: error: Class test.pkg.Parent changed 'static' qualifier [ChangedStatic] 1005 """, 1006 checkCompatibilityApi = """ 1007 package test.pkg { 1008 public class Parent { 1009 } 1010 } 1011 """, 1012 sourceFiles = arrayOf( 1013 java( 1014 """ 1015 package test.pkg; 1016 1017 public abstract static class Parent { 1018 private Parent() {} 1019 } 1020 """ 1021 ) 1022 ) 1023 ) 1024 } 1025 1026 @Test Incompatible class change -- finalnull1027 fun `Incompatible class change -- final`() { 1028 check( 1029 expectedIssues = """ 1030 src/test/pkg/Class1.java:3: error: Class test.pkg.Class1 added 'final' qualifier [AddedFinal] 1031 TESTROOT/current-api.txt:3: error: Removed constructor test.pkg.Class1() [RemovedMethod] 1032 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] 1033 src/test/pkg/Class3.java:3: error: Class test.pkg.Class3 removed 'final' qualifier [RemovedFinal] 1034 """, 1035 checkCompatibilityApi = """ 1036 package test.pkg { 1037 public class Class1 { 1038 ctor public Class1(); 1039 } 1040 public class Class2 { 1041 } 1042 public final class Class3 { 1043 } 1044 } 1045 """, 1046 sourceFiles = arrayOf( 1047 java( 1048 """ 1049 package test.pkg; 1050 1051 public final class Class1 { 1052 private Class1() {} 1053 } 1054 """ 1055 ), 1056 java( 1057 """ 1058 package test.pkg; 1059 1060 public final class Class2 { 1061 private Class2() {} 1062 } 1063 """ 1064 ), 1065 java( 1066 """ 1067 package test.pkg; 1068 1069 public class Class3 { 1070 private Class3() {} 1071 } 1072 """ 1073 ) 1074 ) 1075 ) 1076 } 1077 1078 @Test Incompatible class change -- visibilitynull1079 fun `Incompatible class change -- visibility`() { 1080 check( 1081 expectedIssues = """ 1082 src/test/pkg/Class1.java:3: error: Class test.pkg.Class1 changed visibility from protected to public [ChangedScope] 1083 src/test/pkg/Class2.java:3: error: Class test.pkg.Class2 changed visibility from public to protected [ChangedScope] 1084 """, 1085 checkCompatibilityApi = """ 1086 package test.pkg { 1087 protected class Class1 { 1088 } 1089 public class Class2 { 1090 } 1091 } 1092 """, 1093 sourceFiles = arrayOf( 1094 java( 1095 """ 1096 package test.pkg; 1097 1098 public class Class1 { 1099 private Class1() {} 1100 } 1101 """ 1102 ), 1103 java( 1104 """ 1105 package test.pkg; 1106 1107 protected class Class2 { 1108 private Class2() {} 1109 } 1110 """ 1111 ) 1112 ) 1113 ) 1114 } 1115 1116 @Test Incompatible class change -- deprecationnull1117 fun `Incompatible class change -- deprecation`() { 1118 check( 1119 expectedIssues = """ 1120 src/test/pkg/Class1.java:3: error: Class test.pkg.Class1 has changed deprecation state false --> true [ChangedDeprecated] 1121 """, 1122 checkCompatibilityApi = """ 1123 package test.pkg { 1124 public class Class1 { 1125 } 1126 } 1127 """, 1128 sourceFiles = arrayOf( 1129 java( 1130 """ 1131 package test.pkg; 1132 1133 /** @deprecated */ 1134 @Deprecated public class Class1 { 1135 private Class1() {} 1136 } 1137 """ 1138 ) 1139 ) 1140 ) 1141 } 1142 1143 @Test Incompatible class change -- superclassnull1144 fun `Incompatible class change -- superclass`() { 1145 check( 1146 expectedIssues = """ 1147 src/test/pkg/Class3.java:3: error: Class test.pkg.Class3 superclass changed from java.lang.Char to java.lang.Number [ChangedSuperclass] 1148 """, 1149 checkCompatibilityApi = """ 1150 package test.pkg { 1151 public abstract class Class1 { 1152 } 1153 public abstract class Class2 extends java.lang.Number { 1154 } 1155 public abstract class Class3 extends java.lang.Char { 1156 } 1157 } 1158 """, 1159 sourceFiles = arrayOf( 1160 java( 1161 """ 1162 package test.pkg; 1163 1164 public abstract class Class1 extends java.lang.Short { 1165 private Class1() {} 1166 } 1167 """ 1168 ), 1169 java( 1170 """ 1171 package test.pkg; 1172 1173 public abstract class Class2 extends java.lang.Float { 1174 private Class2() {} 1175 } 1176 """ 1177 ), 1178 java( 1179 """ 1180 package test.pkg; 1181 1182 public abstract class Class3 extends java.lang.Number { 1183 private Class3() {} 1184 } 1185 """ 1186 ) 1187 ) 1188 ) 1189 } 1190 1191 @Test Incompatible class change -- type variablesnull1192 fun `Incompatible class change -- type variables`() { 1193 check( 1194 expectedIssues = """ 1195 src/test/pkg/Class1.java:3: error: Class test.pkg.Class1 changed number of type parameters from 1 to 2 [ChangedType] 1196 """, 1197 checkCompatibilityApi = """ 1198 package test.pkg { 1199 public class Class1<X> { 1200 } 1201 } 1202 """, 1203 sourceFiles = arrayOf( 1204 java( 1205 """ 1206 package test.pkg; 1207 1208 public class Class1<X,Y> { 1209 private Class1() {} 1210 } 1211 """ 1212 ) 1213 ) 1214 ) 1215 } 1216 1217 @Test Incompatible method change -- modifiersnull1218 fun `Incompatible method change -- modifiers`() { 1219 check( 1220 expectedIssues = """ 1221 src/test/pkg/MyClass.java:5: error: Method test.pkg.MyClass.myMethod2 has changed 'abstract' qualifier [ChangedAbstract] 1222 src/test/pkg/MyClass.java:6: error: Method test.pkg.MyClass.myMethod3 has changed 'static' qualifier [ChangedStatic] 1223 src/test/pkg/MyClass.java:7: error: Method test.pkg.MyClass.myMethod4 has changed deprecation state true --> false [ChangedDeprecated] 1224 """, 1225 checkCompatibilityApi = """ 1226 package test.pkg { 1227 public abstract class MyClass { 1228 method public void myMethod2(); 1229 method public void myMethod3(); 1230 method deprecated public void myMethod4(); 1231 } 1232 } 1233 """, 1234 sourceFiles = arrayOf( 1235 java( 1236 """ 1237 package test.pkg; 1238 1239 public abstract class MyClass { 1240 private MyClass() {} 1241 public native abstract void myMethod2(); // Note that Errors.CHANGE_NATIVE is hidden by default 1242 public static void myMethod3() {} 1243 public void myMethod4() {} 1244 } 1245 """ 1246 ) 1247 ) 1248 ) 1249 } 1250 1251 @Test Incompatible method change -- finalnull1252 fun `Incompatible method change -- final`() { 1253 check( 1254 expectedIssues = """ 1255 src/test/pkg/Outer.java:7: error: Method test.pkg.Outer.Class1.method1 has added 'final' qualifier [AddedFinal] 1256 src/test/pkg/Outer.java:19: error: Method test.pkg.Outer.Class4.method4 has removed 'final' qualifier [RemovedFinal] 1257 """, 1258 checkCompatibilityApi = """ 1259 package test.pkg { 1260 public abstract class Outer { 1261 } 1262 public class Outer.Class1 { 1263 method public void method1(); 1264 } 1265 public final class Outer.Class2 { 1266 method public void method2(); 1267 } 1268 public final class Outer.Class3 { 1269 method public void method3(); 1270 } 1271 public class Outer.Class4 { 1272 method public final void method4(); 1273 } 1274 } 1275 """, 1276 sourceFiles = arrayOf( 1277 java( 1278 """ 1279 package test.pkg; 1280 1281 public abstract class Outer { 1282 private Outer() {} 1283 public class Class1 { 1284 private Class1() {} 1285 public final void method1() { } // Added final 1286 } 1287 public final class Class2 { 1288 private Class2() {} 1289 public final void method2() { } // Added final but class is effectively final so no change 1290 } 1291 public final class Class3 { 1292 private Class3() {} 1293 public void method3() { } // Removed final but is still effectively final 1294 } 1295 public class Class4 { 1296 private Class4() {} 1297 public void method4() { } // Removed final 1298 } 1299 } 1300 """ 1301 ) 1302 ) 1303 ) 1304 } 1305 1306 @Test Incompatible method change -- visibilitynull1307 fun `Incompatible method change -- visibility`() { 1308 check( 1309 expectedIssues = """ 1310 src/test/pkg/MyClass.java:5: error: Method test.pkg.MyClass.myMethod1 changed visibility from protected to public [ChangedScope] 1311 src/test/pkg/MyClass.java:6: error: Method test.pkg.MyClass.myMethod2 changed visibility from public to protected [ChangedScope] 1312 """, 1313 checkCompatibilityApi = """ 1314 package test.pkg { 1315 public abstract class MyClass { 1316 method protected void myMethod1(); 1317 method public void myMethod2(); 1318 } 1319 } 1320 """, 1321 sourceFiles = arrayOf( 1322 java( 1323 """ 1324 package test.pkg; 1325 1326 public abstract class MyClass { 1327 private MyClass() {} 1328 public void myMethod1() {} 1329 protected void myMethod2() {} 1330 } 1331 """ 1332 ) 1333 ) 1334 ) 1335 } 1336 1337 @Test Incompatible method change -- throws listnull1338 fun `Incompatible method change -- throws list`() { 1339 check( 1340 expectedIssues = """ 1341 src/test/pkg/MyClass.java:7: error: Method test.pkg.MyClass.method1 added thrown exception java.io.IOException [ChangedThrows] 1342 src/test/pkg/MyClass.java:8: error: Method test.pkg.MyClass.method2 no longer throws exception java.io.IOException [ChangedThrows] 1343 src/test/pkg/MyClass.java:9: error: Method test.pkg.MyClass.method3 no longer throws exception java.io.IOException [ChangedThrows] 1344 src/test/pkg/MyClass.java:9: error: Method test.pkg.MyClass.method3 no longer throws exception java.lang.NumberFormatException [ChangedThrows] 1345 src/test/pkg/MyClass.java:9: error: Method test.pkg.MyClass.method3 added thrown exception java.lang.UnsupportedOperationException [ChangedThrows] 1346 """, 1347 checkCompatibilityApi = """ 1348 package test.pkg { 1349 public abstract class MyClass { 1350 method public void finalize() throws java.lang.Throwable; 1351 method public void method1(); 1352 method public void method2() throws java.io.IOException; 1353 method public void method3() throws java.io.IOException, java.lang.NumberFormatException; 1354 } 1355 } 1356 """, 1357 sourceFiles = arrayOf( 1358 java( 1359 """ 1360 package test.pkg; 1361 1362 @SuppressWarnings("RedundantThrows") 1363 public abstract class MyClass { 1364 private MyClass() {} 1365 public void finalize() {} 1366 public void method1() throws java.io.IOException {} 1367 public void method2() {} 1368 public void method3() throws java.lang.UnsupportedOperationException {} 1369 } 1370 """ 1371 ) 1372 ) 1373 ) 1374 } 1375 1376 @Test Incompatible method change -- return typesnull1377 fun `Incompatible method change -- return types`() { 1378 check( 1379 expectedIssues = """ 1380 src/test/pkg/MyClass.java:5: error: Method test.pkg.MyClass.method1 has changed return type from float to int [ChangedType] 1381 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] 1382 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] 1383 src/test/pkg/MyClass.java:8: error: Method test.pkg.MyClass.method4 has changed return type from String to String[] [ChangedType] 1384 src/test/pkg/MyClass.java:9: error: Method test.pkg.MyClass.method5 has changed return type from String[] to String[][] [ChangedType] 1385 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] 1386 src/test/pkg/MyClass.java:11: error: Method test.pkg.MyClass.method7 has changed return type from T to Number [ChangedType] 1387 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] 1388 """, 1389 checkCompatibilityApi = """ 1390 package test.pkg { 1391 public abstract class MyClass<T extends Number> { 1392 method public float method1(); 1393 method public java.util.List<Number> method2(); 1394 method public java.util.List<Integer> method3(); 1395 method public String method4(); 1396 method public String[] method5(); 1397 method public <X extends java.lang.Throwable> T method6(java.util.function.Supplier<? extends X>); 1398 method public <X extends java.lang.Throwable> T method7(java.util.function.Supplier<? extends X>); 1399 method public <X extends java.lang.Throwable> Number method8(java.util.function.Supplier<? extends X>); 1400 method public <X extends java.lang.Throwable> X method9(java.util.function.Supplier<? extends X>); 1401 } 1402 } 1403 """, 1404 sourceFiles = arrayOf( 1405 java( 1406 """ 1407 package test.pkg; 1408 1409 public abstract class MyClass<U extends Number> { // Changing type variable name is fine/compatible 1410 private MyClass() {} 1411 public int method1() { return 0; } 1412 public java.util.List<Integer> method2() { return null; } 1413 public java.util.List<Number> method3() { return null; } 1414 public String[] method4() { return null; } 1415 public String[][] method5() { return null; } 1416 public <X extends java.lang.Throwable> U method6(java.util.function.Supplier<? extends X> arg) { return null; } 1417 public <X extends java.lang.Throwable> Number method7(java.util.function.Supplier<? extends X> arg) { return null; } 1418 public <X extends java.lang.Throwable> U method8(java.util.function.Supplier<? extends X> arg) { return null; } 1419 public <X extends java.lang.Throwable> U method9(java.util.function.Supplier<? extends X> arg) { return null; } 1420 } 1421 """ 1422 ) 1423 ) 1424 ) 1425 } 1426 1427 @Test Incompatible field change -- visibility and removing finalnull1428 fun `Incompatible field change -- visibility and removing final`() { 1429 check( 1430 expectedIssues = """ 1431 src/test/pkg/MyClass.java:5: error: Field test.pkg.MyClass.myField1 changed visibility from protected to public [ChangedScope] 1432 src/test/pkg/MyClass.java:6: error: Field test.pkg.MyClass.myField2 changed visibility from public to protected [ChangedScope] 1433 src/test/pkg/MyClass.java:7: error: Field test.pkg.MyClass.myField3 has removed 'final' qualifier [RemovedFinal] 1434 """, 1435 checkCompatibilityApi = """ 1436 package test.pkg { 1437 public abstract class MyClass { 1438 field protected int myField1; 1439 field public int myField2; 1440 field public final int myField3; 1441 } 1442 } 1443 """, 1444 sourceFiles = arrayOf( 1445 java( 1446 """ 1447 package test.pkg; 1448 1449 public abstract class MyClass { 1450 private MyClass() {} 1451 public int myField1 = 1; 1452 protected int myField2 = 1; 1453 public int myField3 = 1; 1454 } 1455 """ 1456 ) 1457 ) 1458 ) 1459 } 1460 1461 @Test Adding classes, interfaces and packages, and removing thesenull1462 fun `Adding classes, interfaces and packages, and removing these`() { 1463 check( 1464 expectedIssues = """ 1465 src/test/pkg/MyClass.java:3: error: Added class test.pkg.MyClass [AddedClass] 1466 src/test/pkg/MyInterface.java:3: error: Added class test.pkg.MyInterface [AddedInterface] 1467 TESTROOT/current-api.txt:2: error: Removed class test.pkg.MyOldClass [RemovedClass] 1468 error: Added package test.pkg2 [AddedPackage] 1469 TESTROOT/current-api.txt:5: error: Removed package test.pkg3 [RemovedPackage] 1470 """, 1471 checkCompatibilityApi = """ 1472 package test.pkg { 1473 public abstract class MyOldClass { 1474 } 1475 } 1476 package test.pkg3 { 1477 public abstract class MyOldClass { 1478 } 1479 } 1480 """, 1481 sourceFiles = arrayOf( 1482 java( 1483 """ 1484 package test.pkg; 1485 1486 public abstract class MyClass { 1487 private MyClass() {} 1488 } 1489 """ 1490 ), 1491 java( 1492 """ 1493 package test.pkg; 1494 1495 public interface MyInterface { 1496 } 1497 """ 1498 ), 1499 java( 1500 """ 1501 package test.pkg2; 1502 1503 public abstract class MyClass2 { 1504 private MyClass2() {} 1505 } 1506 """ 1507 ) 1508 ) 1509 ) 1510 } 1511 1512 @Test Test removing public constructornull1513 fun `Test removing public constructor`() { 1514 check( 1515 expectedIssues = """ 1516 TESTROOT/current-api.txt:3: error: Removed constructor test.pkg.MyClass() [RemovedMethod] 1517 """, 1518 checkCompatibilityApi = """ 1519 package test.pkg { 1520 public abstract class MyClass { 1521 ctor public MyClass(); 1522 } 1523 } 1524 """, 1525 sourceFiles = arrayOf( 1526 java( 1527 """ 1528 package test.pkg; 1529 1530 public abstract class MyClass { 1531 private MyClass() {} 1532 } 1533 """ 1534 ) 1535 ) 1536 ) 1537 } 1538 1539 @Test Test type variables from text signature filesnull1540 fun `Test type variables from text signature files`() { 1541 check( 1542 expectedIssues = """ 1543 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] 1544 """, 1545 checkCompatibilityApi = """ 1546 package test.pkg { 1547 public abstract class MyClass<T extends test.pkg.Number,T_SPLITR> { 1548 method public T myMethod1(); 1549 method public <S extends test.pkg.Number> S myMethod2(); 1550 method public <S> S myMethod3(); 1551 method public <S> S myMethod4(); 1552 method public java.util.List<byte[]> myMethod5(); 1553 method public T_SPLITR[] myMethod6(); 1554 } 1555 public class Number { 1556 ctor public Number(); 1557 } 1558 } 1559 """, 1560 sourceFiles = arrayOf( 1561 java( 1562 """ 1563 package test.pkg; 1564 1565 public abstract class MyClass<T extends Number,T_SPLITR> { 1566 private MyClass() {} 1567 public T myMethod1() { return null; } 1568 public <S extends Number> S myMethod2() { return null; } 1569 public <S> S myMethod3() { return null; } 1570 public <S extends Float> S myMethod4() { return null; } 1571 public java.util.List<byte[]> myMethod5() { return null; } 1572 public T_SPLITR[] myMethod6() { return null; } 1573 } 1574 """ 1575 ), 1576 java( 1577 """ 1578 package test.pkg; 1579 public class Number { 1580 } 1581 """ 1582 ) 1583 ) 1584 ) 1585 } 1586 1587 @Test Test Kotlin extensionsnull1588 fun `Test Kotlin extensions`() { 1589 check( 1590 inputKotlinStyleNulls = true, 1591 outputKotlinStyleNulls = true, 1592 omitCommonPackages = true, 1593 compatibilityMode = false, 1594 expectedIssues = "", 1595 checkCompatibilityApi = """ 1596 package androidx.content { 1597 public final class ContentValuesKt { 1598 method public static android.content.ContentValues contentValuesOf(kotlin.Pair<String,?>... pairs); 1599 } 1600 } 1601 """, 1602 sourceFiles = arrayOf( 1603 kotlin( 1604 "src/androidx/content/ContentValues.kt", 1605 """ 1606 package androidx.content 1607 1608 import android.content.ContentValues 1609 1610 fun contentValuesOf(vararg pairs: Pair<String, Any?>) = ContentValues(pairs.size).apply { 1611 for ((key, value) in pairs) { 1612 when (value) { 1613 null -> putNull(key) 1614 is String -> put(key, value) 1615 is Int -> put(key, value) 1616 is Long -> put(key, value) 1617 is Boolean -> put(key, value) 1618 is Float -> put(key, value) 1619 is Double -> put(key, value) 1620 is ByteArray -> put(key, value) 1621 is Byte -> put(key, value) 1622 is Short -> put(key, value) 1623 else -> { 1624 val valueType = value.javaClass.canonicalName 1625 throw IllegalArgumentException("Illegal value type") 1626 } 1627 } 1628 } 1629 } 1630 """ 1631 ) 1632 ) 1633 ) 1634 } 1635 1636 @Test Test Kotlin type boundsnull1637 fun `Test Kotlin type bounds`() { 1638 check( 1639 inputKotlinStyleNulls = false, 1640 outputKotlinStyleNulls = true, 1641 omitCommonPackages = true, 1642 compatibilityMode = false, 1643 expectedIssues = "", 1644 checkCompatibilityApi = """ 1645 package androidx.navigation { 1646 public final class NavDestination { 1647 ctor public NavDestination(); 1648 } 1649 public class NavDestinationBuilder<D extends androidx.navigation.NavDestination> { 1650 ctor public NavDestinationBuilder(int id); 1651 method public D build(); 1652 } 1653 } 1654 """, 1655 sourceFiles = arrayOf( 1656 kotlin( 1657 """ 1658 package androidx.navigation 1659 1660 open class NavDestinationBuilder<out D : NavDestination>( 1661 id: Int 1662 ) { 1663 open fun build(): D { 1664 TODO() 1665 } 1666 } 1667 1668 class NavDestination 1669 """ 1670 ) 1671 ) 1672 ) 1673 } 1674 1675 @Test Test inherited methodsnull1676 fun `Test inherited methods`() { 1677 check( 1678 expectedIssues = """ 1679 """, 1680 checkCompatibilityApi = """ 1681 package test.pkg { 1682 public class Child1 extends test.pkg.Parent { 1683 } 1684 public class Child2 extends test.pkg.Parent { 1685 method public void method0(java.lang.String, int); 1686 method public void method4(java.lang.String, int); 1687 } 1688 public class Child3 extends test.pkg.Parent { 1689 method public void method1(java.lang.String, int); 1690 method public void method2(java.lang.String, int); 1691 } 1692 public class Parent { 1693 method public void method1(java.lang.String, int); 1694 method public void method2(java.lang.String, int); 1695 method public void method3(java.lang.String, int); 1696 } 1697 } 1698 """, 1699 sourceFiles = arrayOf( 1700 java( 1701 """ 1702 package test.pkg; 1703 1704 public class Child1 extends Parent { 1705 private Child1() {} 1706 @Override 1707 public void method1(String first, int second) { 1708 } 1709 @Override 1710 public void method2(String first, int second) { 1711 } 1712 @Override 1713 public void method3(String first, int second) { 1714 } 1715 } 1716 """ 1717 ), 1718 java( 1719 """ 1720 package test.pkg; 1721 1722 public class Child2 extends Parent { 1723 private Child2() {} 1724 @Override 1725 public void method0(String first, int second) { 1726 } 1727 @Override 1728 public void method1(String first, int second) { 1729 } 1730 @Override 1731 public void method2(String first, int second) { 1732 } 1733 @Override 1734 public void method3(String first, int second) { 1735 } 1736 @Override 1737 public void method4(String first, int second) { 1738 } 1739 } 1740 """ 1741 ), 1742 java( 1743 """ 1744 package test.pkg; 1745 1746 public class Child3 extends Parent { 1747 private Child3() {} 1748 @Override 1749 public void method1(String first, int second) { 1750 } 1751 } 1752 """ 1753 ), 1754 java( 1755 """ 1756 package test.pkg; 1757 public class Parent { 1758 private Parent() { } 1759 public void method1(String first, int second) { 1760 } 1761 public void method2(String first, int second) { 1762 } 1763 public void method3(String first, int second) { 1764 } 1765 } 1766 """ 1767 ) 1768 ) 1769 ) 1770 } 1771 1772 @Test Partial text file which references inner classes not listed elsewherenull1773 fun `Partial text file which references inner classes not listed elsewhere`() { 1774 // This happens in system and test files where we only include APIs that differ 1775 // from the base API. When parsing these code bases we need to gracefully handle 1776 // references to inner classes. 1777 check( 1778 includeSystemApiAnnotations = true, 1779 expectedIssues = """ 1780 TESTROOT/current-api.txt:4: error: Removed method test.pkg.Bar.Inner1.Inner2.removedMethod() [RemovedMethod] 1781 """, 1782 sourceFiles = arrayOf( 1783 java( 1784 """ 1785 package other.pkg; 1786 1787 public class MyClass { 1788 public class MyInterface { 1789 public void test() { } 1790 } 1791 } 1792 """ 1793 ).indented(), 1794 java( 1795 """ 1796 package test.pkg; 1797 import android.annotation.SystemApi; 1798 1799 public class Bar { 1800 public class Inner1 { 1801 private Inner1() { } 1802 @SuppressWarnings("JavaDoc") 1803 public class Inner2 { 1804 private Inner2() { } 1805 1806 /** 1807 * @hide 1808 */ 1809 @SystemApi 1810 public void method() { } 1811 1812 /** 1813 * @hide 1814 */ 1815 @SystemApi 1816 public void addedMethod() { } 1817 } 1818 } 1819 } 1820 """ 1821 ), 1822 systemApiSource 1823 ), 1824 1825 extraArguments = arrayOf( 1826 ARG_SHOW_ANNOTATION, "android.annotation.SystemApi", 1827 ARG_HIDE_PACKAGE, "android.annotation", 1828 ARG_HIDE_PACKAGE, "android.support.annotation" 1829 ), 1830 1831 checkCompatibilityApi = 1832 """ 1833 package test.pkg { 1834 public class Bar.Inner1.Inner2 { 1835 method public void method(); 1836 method public void removedMethod(); 1837 } 1838 } 1839 """ 1840 ) 1841 } 1842 1843 @Test Partial text file which adds methods to show-annotation APInull1844 fun `Partial text file which adds methods to show-annotation API`() { 1845 // This happens in system and test files where we only include APIs that differ 1846 // from the base IDE. When parsing these code bases we need to gracefully handle 1847 // references to inner classes. 1848 check( 1849 includeSystemApiAnnotations = true, 1850 expectedIssues = """ 1851 TESTROOT/current-api.txt:4: error: Removed method android.rolecontrollerservice.RoleControllerService.onClearRoleHolders() [RemovedMethod] 1852 """, 1853 sourceFiles = arrayOf( 1854 java( 1855 """ 1856 package android.rolecontrollerservice; 1857 1858 public class Service { 1859 } 1860 """ 1861 ).indented(), 1862 java( 1863 """ 1864 package android.rolecontrollerservice; 1865 import android.annotation.SystemApi; 1866 1867 /** @hide */ 1868 @SystemApi 1869 public abstract class RoleControllerService extends Service { 1870 public abstract void onGrantDefaultRoles(); 1871 } 1872 """ 1873 ), 1874 systemApiSource 1875 ), 1876 1877 extraArguments = arrayOf( 1878 ARG_SHOW_ANNOTATION, "android.annotation.TestApi", 1879 ARG_HIDE_PACKAGE, "android.annotation", 1880 ARG_HIDE_PACKAGE, "android.support.annotation" 1881 ), 1882 1883 checkCompatibilityApi = 1884 """ 1885 package android.rolecontrollerservice { 1886 public abstract class RoleControllerService extends android.rolecontrollerservice.Service { 1887 ctor public RoleControllerService(); 1888 method public abstract void onClearRoleHolders(); 1889 } 1890 } 1891 """ 1892 ) 1893 } 1894 1895 @Test Partial text file where type previously did not existnull1896 fun `Partial text file where type previously did not exist`() { 1897 check( 1898 expectedIssues = """ 1899 """, 1900 sourceFiles = arrayOf( 1901 java( 1902 """ 1903 package test.pkg; 1904 import android.annotation.SystemApi; 1905 1906 /** 1907 * @hide 1908 */ 1909 @SystemApi 1910 public class SampleException1 extends java.lang.Exception { 1911 } 1912 """ 1913 ).indented(), 1914 java( 1915 """ 1916 package test.pkg; 1917 import android.annotation.SystemApi; 1918 1919 /** 1920 * @hide 1921 */ 1922 @SystemApi 1923 public class SampleException2 extends java.lang.Throwable { 1924 } 1925 """ 1926 ).indented(), 1927 java( 1928 """ 1929 package test.pkg; 1930 import android.annotation.SystemApi; 1931 1932 /** 1933 * @hide 1934 */ 1935 @SystemApi 1936 public class Utils { 1937 public void method1() throws SampleException1 { } 1938 public void method2() throws SampleException2 { } 1939 } 1940 """ 1941 ), 1942 systemApiSource 1943 ), 1944 1945 extraArguments = arrayOf( 1946 ARG_SHOW_ANNOTATION, "android.annotation.SystemApi", 1947 ARG_HIDE_PACKAGE, "android.annotation", 1948 ARG_HIDE_PACKAGE, "android.support.annotation" 1949 ), 1950 1951 checkCompatibilityApiReleased = 1952 """ 1953 package test.pkg { 1954 public class Utils { 1955 ctor public Utils(); 1956 // We don't define SampleException1 or SampleException in this file, 1957 // in this partial signature, so we don't need to validate that they 1958 // have not been changed 1959 method public void method1() throws test.pkg.SampleException1; 1960 method public void method2() throws test.pkg.SampleException2; 1961 } 1962 } 1963 """ 1964 ) 1965 } 1966 1967 @Test Test verifying simple removed APInull1968 fun `Test verifying simple removed API`() { 1969 check( 1970 expectedIssues = """ 1971 TESTROOT/removed-current-api.txt:5: error: Removed method test.pkg.Bar.removedMethod2() [RemovedMethod] 1972 """, 1973 checkCompatibilityRemovedApiCurrent = """ 1974 package test.pkg { 1975 public class Bar { 1976 ctor public Bar(); 1977 method public void removedMethod(); 1978 method public void removedMethod2(); 1979 } 1980 public class Bar.Inner { 1981 ctor public Bar.Inner(); 1982 } 1983 } 1984 """, 1985 sourceFiles = arrayOf( 1986 java( 1987 """ 1988 package test.pkg; 1989 @SuppressWarnings("JavaDoc") 1990 public class Bar { 1991 /** @removed */ // still part of the removed api 1992 public Bar() { } 1993 // no longer part of the removed api 1994 public void removedMethod() { } 1995 /** @removed */ 1996 public void newlyRemoved() { } 1997 1998 public void newlyAdded() { } 1999 2000 /** @removed */ // still part of the removed api 2001 public class Inner { } 2002 } 2003 """ 2004 ) 2005 ) 2006 ) 2007 } 2008 2009 @Test Test verifying removed APInull2010 fun `Test verifying removed API`() { 2011 check( 2012 expectedIssues = """ 2013 """, 2014 checkCompatibilityRemovedApiCurrent = """ 2015 package test.pkg { 2016 public class Bar { 2017 ctor public Bar(); 2018 method public void removedMethod(); 2019 field public int removedField; 2020 } 2021 public class Bar.Inner { 2022 ctor public Bar.Inner(); 2023 } 2024 public class Bar.Inner2.Inner3.Inner4 { 2025 ctor public Bar.Inner2.Inner3.Inner4(); 2026 } 2027 public class Bar.Inner5.Inner6.Inner7 { 2028 field public int removed; 2029 } 2030 } 2031 """, 2032 sourceFiles = arrayOf( 2033 java( 2034 """ 2035 package test.pkg; 2036 @SuppressWarnings("JavaDoc") 2037 public class Bar { 2038 /** @removed */ 2039 public Bar() { } 2040 public int field; 2041 public void test() { } 2042 /** @removed */ 2043 public int removedField; 2044 /** @removed */ 2045 public void removedMethod() { } 2046 /** @removed and @hide - should not be listed */ 2047 public int hiddenField; 2048 2049 /** @removed */ 2050 public class Inner { } 2051 2052 public class Inner2 { 2053 public class Inner3 { 2054 /** @removed */ 2055 public class Inner4 { } 2056 } 2057 } 2058 2059 public class Inner5 { 2060 public class Inner6 { 2061 public class Inner7 { 2062 /** @removed */ 2063 public int removed; 2064 } 2065 } 2066 } 2067 } 2068 """ 2069 ) 2070 ) 2071 ) 2072 } 2073 2074 @Test Regression test for bug 120847535null2075 fun `Regression test for bug 120847535`() { 2076 // Regression test for 2077 // 120847535: check-api doesn't fail on method that is in current.txt, but marked @hide @TestApi 2078 check( 2079 expectedIssues = """ 2080 TESTROOT/current-api.txt:6: error: Removed method test.view.ViewTreeObserver.registerFrameCommitCallback(Runnable) [RemovedMethod] 2081 """, 2082 sourceFiles = arrayOf( 2083 java( 2084 """ 2085 package test.view; 2086 import android.annotation.TestApi; 2087 public final class ViewTreeObserver { 2088 /** 2089 * @hide 2090 */ 2091 @TestApi 2092 public void registerFrameCommitCallback(Runnable callback) { 2093 } 2094 } 2095 """ 2096 ).indented(), 2097 java( 2098 """ 2099 package test.view; 2100 public final class View { 2101 private View() { } 2102 } 2103 """ 2104 ).indented(), 2105 testApiSource 2106 ), 2107 2108 api = """ 2109 package test.view { 2110 public final class View { 2111 } 2112 public final class ViewTreeObserver { 2113 ctor public ViewTreeObserver(); 2114 } 2115 } 2116 """, 2117 extraArguments = arrayOf( 2118 ARG_HIDE_PACKAGE, "android.annotation", 2119 ARG_HIDE_PACKAGE, "android.support.annotation" 2120 ), 2121 2122 checkCompatibilityApi = """ 2123 package test.view { 2124 public final class View { 2125 } 2126 public final class ViewTreeObserver { 2127 ctor public ViewTreeObserver(); 2128 method public void registerFrameCommitCallback(java.lang.Runnable); 2129 } 2130 } 2131 """ 2132 ) 2133 } 2134 2135 @Test Test release compatibility checkingnull2136 fun `Test release compatibility checking`() { 2137 // Different checks are enforced for current vs release API comparisons: 2138 // we don't flag AddedClasses etc. Removed classes *are* enforced. 2139 check( 2140 expectedIssues = """ 2141 src/test/pkg/Class1.java:3: error: Class test.pkg.Class1 added 'final' qualifier [AddedFinal] 2142 TESTROOT/released-api.txt:3: error: Removed constructor test.pkg.Class1() [RemovedMethod] 2143 src/test/pkg/MyClass.java:5: warning: Method test.pkg.MyClass.myMethod2 has changed 'abstract' qualifier [ChangedAbstract] 2144 src/test/pkg/MyClass.java:6: error: Method test.pkg.MyClass.myMethod3 has changed 'static' qualifier [ChangedStatic] 2145 TESTROOT/released-api.txt:14: error: Removed class test.pkg.MyOldClass [RemovedClass] 2146 TESTROOT/released-api.txt:17: error: Removed package test.pkg3 [RemovedPackage] 2147 """, 2148 checkCompatibilityApiReleased = """ 2149 package test.pkg { 2150 public class Class1 { 2151 ctor public Class1(); 2152 } 2153 public class Class2 { 2154 } 2155 public final class Class3 { 2156 } 2157 public abstract class MyClass { 2158 method public void myMethod2(); 2159 method public void myMethod3(); 2160 method deprecated public void myMethod4(); 2161 } 2162 public abstract class MyOldClass { 2163 } 2164 } 2165 package test.pkg3 { 2166 public abstract class MyOldClass { 2167 } 2168 } 2169 """, 2170 sourceFiles = arrayOf( 2171 java( 2172 """ 2173 package test.pkg; 2174 2175 public final class Class1 { 2176 private Class1() {} 2177 } 2178 """ 2179 ), 2180 java( 2181 """ 2182 package test.pkg; 2183 2184 public final class Class2 { 2185 private Class2() {} 2186 } 2187 """ 2188 ), 2189 java( 2190 """ 2191 package test.pkg; 2192 2193 public class Class3 { 2194 private Class3() {} 2195 } 2196 """ 2197 ), 2198 java( 2199 """ 2200 package test.pkg; 2201 2202 public abstract class MyNewClass { 2203 private MyNewClass() {} 2204 } 2205 """ 2206 ), 2207 java( 2208 """ 2209 package test.pkg; 2210 2211 public abstract class MyClass { 2212 private MyClass() {} 2213 public native abstract void myMethod2(); // Note that Errors.CHANGE_NATIVE is hidden by default 2214 public static void myMethod3() {} 2215 public void myMethod4() {} 2216 } 2217 """ 2218 ) 2219 ) 2220 ) 2221 } 2222 2223 @Test Test remove deprecated API is an errornull2224 fun `Test remove deprecated API is an error`() { 2225 // Regression test for b/145745855 2226 check( 2227 expectedIssues = """ 2228 TESTROOT/released-api.txt:6: error: Removed deprecated class test.pkg.DeprecatedClass [RemovedDeprecatedClass] 2229 TESTROOT/released-api.txt:3: error: Removed deprecated constructor test.pkg.SomeClass() [RemovedDeprecatedMethod] 2230 TESTROOT/released-api.txt:4: error: Removed deprecated method test.pkg.SomeClass.deprecatedMethod() [RemovedDeprecatedMethod] 2231 """, 2232 checkCompatibilityApiReleased = """ 2233 package test.pkg { 2234 public class SomeClass { 2235 ctor deprecated public SomeClass(); 2236 method deprecated public void deprecatedMethod(); 2237 } 2238 deprecated public class DeprecatedClass { 2239 ctor deprecated public DeprecatedClass(); 2240 method deprecated public void deprecatedMethod(); 2241 } 2242 } 2243 """, 2244 sourceFiles = arrayOf( 2245 java( 2246 """ 2247 package test.pkg; 2248 2249 public class SomeClass { 2250 private SomeClass() {} 2251 } 2252 """ 2253 ) 2254 ) 2255 ) 2256 } 2257 2258 @Test Test check release with base apinull2259 fun `Test check release with base api`() { 2260 check( 2261 expectedIssues = "", 2262 checkCompatibilityApiReleased = """ 2263 package test.pkg { 2264 public class SomeClass { 2265 method public static void publicMethodA(); 2266 method public static void publicMethodB(); 2267 } 2268 } 2269 """, 2270 sourceFiles = arrayOf( 2271 java( 2272 """ 2273 package test.pkg; 2274 2275 public class SomeClass { 2276 public static void publicMethodA(); 2277 } 2278 """ 2279 ) 2280 ), 2281 checkCompatibilityBaseApi = """ 2282 package test.pkg { 2283 public class SomeClass { 2284 method public static void publicMethodB(); 2285 } 2286 } 2287 """ 2288 ) 2289 } 2290 2291 @Test Test check a class moving from the released api to the base apinull2292 fun `Test check a class moving from the released api to the base api`() { 2293 check( 2294 compatibilityMode = false, 2295 checkCompatibilityApiReleased = """ 2296 package test.pkg { 2297 public class SomeClass1 { 2298 method public void method1(); 2299 } 2300 public class SomeClass2 { 2301 method public void oldMethod(); 2302 } 2303 } 2304 """, 2305 checkCompatibilityBaseApi = """ 2306 package test.pkg { 2307 public class SomeClass2 { 2308 method public void newMethod(); 2309 } 2310 } 2311 """, 2312 sourceFiles = arrayOf( 2313 java( 2314 """ 2315 package test.pkg; 2316 2317 public class SomeClass1 { 2318 public void method1(); 2319 } 2320 """ 2321 ) 2322 ), 2323 expectedIssues = """ 2324 TESTROOT/released-api.txt:6: error: Removed method test.pkg.SomeClass2.oldMethod() [RemovedMethod] 2325 """.trimIndent() 2326 ) 2327 } 2328 2329 @Test Implicit nullnessnull2330 fun `Implicit nullness`() { 2331 check( 2332 compatibilityMode = false, 2333 inputKotlinStyleNulls = true, 2334 checkCompatibilityApi = """ 2335 // Signature format: 2.0 2336 package androidx.annotation { 2337 @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 { 2338 method public abstract androidx.annotation.RestrictTo.Scope[] value(); 2339 } 2340 2341 public enum RestrictTo.Scope { 2342 enum_constant @Deprecated public static final androidx.annotation.RestrictTo.Scope GROUP_ID; 2343 enum_constant public static final androidx.annotation.RestrictTo.Scope LIBRARY; 2344 enum_constant public static final androidx.annotation.RestrictTo.Scope LIBRARY_GROUP; 2345 enum_constant public static final androidx.annotation.RestrictTo.Scope SUBCLASSES; 2346 enum_constant public static final androidx.annotation.RestrictTo.Scope TESTS; 2347 } 2348 } 2349 """, 2350 2351 sourceFiles = arrayOf( 2352 restrictToSource 2353 ) 2354 ) 2355 } 2356 2357 @Test Implicit nullness in compat formatnull2358 fun `Implicit nullness in compat format`() { 2359 // Make sure we put "static" in enum modifier lists when in v1/compat mode 2360 check( 2361 compatibilityMode = true, 2362 inputKotlinStyleNulls = true, 2363 checkCompatibilityApi = """ 2364 package androidx.annotation { 2365 public abstract class RestrictTo implements java.lang.annotation.Annotation { 2366 method public abstract androidx.annotation.RestrictTo.Scope[] value(); 2367 } 2368 2369 public static final class RestrictTo.Scope extends java.lang.Enum { 2370 enum_constant deprecated public static final androidx.annotation.RestrictTo.Scope GROUP_ID; 2371 enum_constant public static final androidx.annotation.RestrictTo.Scope LIBRARY; 2372 enum_constant public static final androidx.annotation.RestrictTo.Scope LIBRARY_GROUP; 2373 enum_constant public static final androidx.annotation.RestrictTo.Scope SUBCLASSES; 2374 enum_constant public static final androidx.annotation.RestrictTo.Scope TESTS; 2375 } 2376 } 2377 """, 2378 2379 sourceFiles = arrayOf( 2380 restrictToSource 2381 ) 2382 ) 2383 } 2384 2385 @Test Java String constantsnull2386 fun `Java String constants`() { 2387 check( 2388 compatibilityMode = false, 2389 inputKotlinStyleNulls = true, 2390 checkCompatibilityApi = """ 2391 package androidx.browser.browseractions { 2392 public class BrowserActionsIntent { 2393 field public static final String EXTRA_APP_ID = "androidx.browser.browseractions.APP_ID"; 2394 } 2395 } 2396 """, 2397 2398 sourceFiles = arrayOf( 2399 java( 2400 """ 2401 package androidx.browser.browseractions; 2402 public class BrowserActionsIntent { 2403 private BrowserActionsIntent() { } 2404 public static final String EXTRA_APP_ID = "androidx.browser.browseractions.APP_ID"; 2405 2406 } 2407 """ 2408 ).indented() 2409 ) 2410 ) 2411 } 2412 2413 @Test Classes with mapsnull2414 fun `Classes with maps`() { 2415 check( 2416 compatibilityMode = false, 2417 inputKotlinStyleNulls = true, 2418 checkCompatibilityApi = """ 2419 // Signature format: 2.0 2420 package androidx.collection { 2421 public class SimpleArrayMap<K, V> { 2422 } 2423 } 2424 """, 2425 2426 sourceFiles = arrayOf( 2427 java( 2428 """ 2429 package androidx.collection; 2430 2431 public class SimpleArrayMap<K, V> { 2432 private SimpleArrayMap() { } 2433 } 2434 """ 2435 ).indented() 2436 ) 2437 ) 2438 } 2439 2440 @Test Referencing type parameters in typesnull2441 fun `Referencing type parameters in types`() { 2442 check( 2443 compatibilityMode = false, 2444 inputKotlinStyleNulls = true, 2445 checkCompatibilityApi = """ 2446 // Signature format: 2.0 2447 package androidx.collection { 2448 public class MyMap<Key, Value> { 2449 ctor public MyMap(); 2450 field public Key! myField; 2451 method public Key! getReplacement(Key!); 2452 } 2453 } 2454 """, 2455 2456 sourceFiles = arrayOf( 2457 java( 2458 """ 2459 package androidx.collection; 2460 2461 public class MyMap<Key, Value> { 2462 public Key getReplacement(Key key) { return null; } 2463 public Key myField = null; 2464 } 2465 """ 2466 ).indented() 2467 ) 2468 ) 2469 } 2470 2471 @Test Comparing annotations with methods with v1 signature filesnull2472 fun `Comparing annotations with methods with v1 signature files`() { 2473 check( 2474 compatibilityMode = true, 2475 checkCompatibilityApi = """ 2476 package androidx.annotation { 2477 public abstract class RestrictTo implements java.lang.annotation.Annotation { 2478 } 2479 public static final class RestrictTo.Scope extends java.lang.Enum { 2480 method public static androidx.annotation.RestrictTo.Scope valueOf(java.lang.String); 2481 method public static final androidx.annotation.RestrictTo.Scope[] values(); 2482 enum_constant public static final deprecated androidx.annotation.RestrictTo.Scope GROUP_ID; 2483 enum_constant public static final androidx.annotation.RestrictTo.Scope LIBRARY; 2484 enum_constant public static final androidx.annotation.RestrictTo.Scope LIBRARY_GROUP; 2485 enum_constant public static final androidx.annotation.RestrictTo.Scope SUBCLASSES; 2486 enum_constant public static final androidx.annotation.RestrictTo.Scope TESTS; 2487 } 2488 } 2489 """, 2490 2491 sourceFiles = arrayOf( 2492 restrictToSource 2493 ) 2494 ) 2495 } 2496 2497 @Test Insignificant type formatting differencesnull2498 fun `Insignificant type formatting differences`() { 2499 check( 2500 checkCompatibilityApi = """ 2501 package test.pkg { 2502 public final class UsageStatsManager { 2503 method public java.util.Map<java.lang.String, java.lang.Integer> getAppStandbyBuckets(); 2504 method public void setAppStandbyBuckets(java.util.Map<java.lang.String, java.lang.Integer>); 2505 field public java.util.Map<java.lang.String, java.lang.Integer> map; 2506 } 2507 } 2508 """, 2509 signatureSource = """ 2510 package test.pkg { 2511 public final class UsageStatsManager { 2512 method public java.util.Map<java.lang.String,java.lang.Integer> getAppStandbyBuckets(); 2513 method public void setAppStandbyBuckets(java.util.Map<java.lang.String,java.lang.Integer>); 2514 field public java.util.Map<java.lang.String,java.lang.Integer> map; 2515 } 2516 } 2517 """ 2518 ) 2519 } 2520 2521 @Test Compare signatures with Kotlin nullability from signaturenull2522 fun `Compare signatures with Kotlin nullability from signature`() { 2523 check( 2524 expectedIssues = """ 2525 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] 2526 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] 2527 """.trimIndent(), 2528 format = FileFormat.V3, 2529 checkCompatibilityApi = """ 2530 // Signature format: 3.0 2531 package test.pkg { 2532 public final class Foo { 2533 ctor public Foo(); 2534 method public void method1(int p = 42, Integer? int2 = null, int p1 = 42, String str = "hello world", java.lang.String... args); 2535 method public void method2(int p, int int2 = (2 * int) * some.other.pkg.Constants.Misc.SIZE); 2536 method public void method3(String? str, int p, int int2 = double(int) + str.length); 2537 field public static final test.pkg.Foo.Companion! Companion; 2538 } 2539 } 2540 """, 2541 signatureSource = """ 2542 // Signature format: 3.0 2543 package test.pkg { 2544 public final class Foo { 2545 ctor public Foo(); 2546 method public void method1(int p = 42, Integer? int2 = null, int p1 = 42, String! str = "hello world", java.lang.String... args); 2547 method public void method2(int p, int int2 = (2 * int) * some.other.pkg.Constants.Misc.SIZE); 2548 method public void method3(String str, int p, int int2 = double(int) + str.length); 2549 field public static final test.pkg.Foo.Companion! Companion; 2550 } 2551 } 2552 """ 2553 ) 2554 } 2555 2556 @Test Compare signatures with Kotlin nullability from sourcenull2557 fun `Compare signatures with Kotlin nullability from source`() { 2558 check( 2559 expectedIssues = """ 2560 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] 2561 """.trimIndent(), 2562 format = FileFormat.V3, 2563 checkCompatibilityApi = """ 2564 // Signature format: 3.0 2565 package test.pkg { 2566 public final class TestKt { 2567 method public static void fun1(String? str1, String str2, java.util.List<java.lang.String!> list); 2568 } 2569 } 2570 """, 2571 sourceFiles = arrayOf( 2572 kotlin( 2573 """ 2574 package test.pkg 2575 import java.util.List 2576 2577 fun fun1(str1: String, str2: String?, list: List<String?>) { } 2578 2579 """.trimIndent() 2580 ) 2581 ) 2582 ) 2583 } 2584 2585 @Test Adding and removing reifiednull2586 fun `Adding and removing reified`() { 2587 check( 2588 compatibilityMode = false, 2589 inputKotlinStyleNulls = true, 2590 expectedIssues = """ 2591 src/test/pkg/test.kt:5: error: Method test.pkg.TestKt.add made type variable T reified: incompatible change [ChangedThrows] 2592 src/test/pkg/test.kt:8: error: Method test.pkg.TestKt.two made type variable S reified: incompatible change [ChangedThrows] 2593 """, 2594 checkCompatibilityApi = """ 2595 package test.pkg { 2596 public final class TestKt { 2597 method public static inline <T> void add(T! t); 2598 method public static inline <reified T> void remove(T! t); 2599 method public static inline <reified T> void unchanged(T! t); 2600 method public static inline <S, reified T> void two(S! s, T! t); 2601 } 2602 } 2603 """, 2604 2605 sourceFiles = arrayOf( 2606 kotlin( 2607 """ 2608 @file:Suppress("NOTHING_TO_INLINE", "RedundantVisibilityModifier", "unused") 2609 2610 package test.pkg 2611 2612 inline fun <reified T> add(t: T) { } 2613 inline fun <T> remove(t: T) { } 2614 inline fun <reified T> unchanged(t: T) { } 2615 inline fun <reified S, T> two(s: S, t: T) { } 2616 """ 2617 ).indented() 2618 ) 2619 ) 2620 } 2621 2622 @Test Empty prev api with @hide and --show-annotationnull2623 fun `Empty prev api with @hide and --show-annotation`() { 2624 check( 2625 compatibilityMode = false, 2626 checkCompatibilityApiReleased = """ 2627 """, 2628 sourceFiles = arrayOf( 2629 java( 2630 """ 2631 package android.media; 2632 2633 /** 2634 * @hide 2635 */ 2636 public class SubtitleController { 2637 public interface Listener { 2638 void onSubtitleTrackSelected() { } 2639 } 2640 } 2641 """ 2642 ), 2643 java( 2644 """ 2645 package android.media; 2646 import android.annotation.SystemApi; 2647 2648 /** 2649 * @hide 2650 */ 2651 @android.annotation.SystemApi 2652 public class MediaPlayer implements SubtitleController.Listener { 2653 } 2654 """ 2655 ), 2656 systemApiSource 2657 ), 2658 extraArguments = arrayOf( 2659 ARG_SHOW_ANNOTATION, "android.annotation.SystemApi", 2660 ARG_HIDE_PACKAGE, "android.annotation", 2661 ARG_HIDE_PACKAGE, "android.support.annotation" 2662 ), 2663 expectedIssues = "" 2664 2665 ) 2666 } 2667 2668 @Test Inherited systemApi method in an inner classnull2669 fun `Inherited systemApi method in an inner class`() { 2670 check( 2671 compatibilityMode = false, 2672 checkCompatibilityApiReleased = """ 2673 package android.telephony { 2674 public class MmTelFeature.Capabilities { 2675 method public boolean isCapable(int); 2676 } 2677 } 2678 """, 2679 sourceFiles = arrayOf( 2680 java( 2681 """ 2682 package android.telephony; 2683 2684 /** 2685 * @hide 2686 */ 2687 @android.annotation.SystemApi 2688 public class MmTelFeature { 2689 public static class Capabilities extends ParentCapabilities { 2690 @Override 2691 boolean isCapable(int argument) { return true; } 2692 } 2693 } 2694 """ 2695 ), 2696 java( 2697 """ 2698 package android.telephony; 2699 2700 /** 2701 * @hide 2702 */ 2703 @android.annotation.SystemApi 2704 public class Parent { 2705 public static class ParentCapabilities { 2706 public boolean isCapable(int argument) { return false; } 2707 } 2708 } 2709 """ 2710 ), 2711 systemApiSource 2712 ), 2713 extraArguments = arrayOf( 2714 ARG_SHOW_ANNOTATION, "android.annotation.SystemApi", 2715 ARG_HIDE_PACKAGE, "android.annotation", 2716 ARG_HIDE_PACKAGE, "android.support.annotation" 2717 ), 2718 expectedIssues = "" 2719 ) 2720 } 2721 2722 @Test Moving removed api back to public apinull2723 fun `Moving removed api back to public api`() { 2724 check( 2725 compatibilityMode = false, 2726 checkCompatibilityRemovedApiReleased = """ 2727 package android.content { 2728 public class ContextWrapper { 2729 method public void createContextForSplit(); 2730 } 2731 } 2732 """, 2733 sourceFiles = arrayOf( 2734 java( 2735 """ 2736 package android.content; 2737 2738 public class ContextWrapper extends Parent { 2739 /** @removed */ 2740 @Override 2741 public void getSharedPreferences() { } 2742 2743 /** @hide */ 2744 @Override 2745 public void createContextForSplit() { } 2746 } 2747 """ 2748 ), 2749 java( 2750 """ 2751 package android.content; 2752 2753 public abstract class Parent { 2754 /** @hide */ 2755 @Override 2756 public void getSharedPreferences() { } 2757 2758 public abstract void createContextForSplit() { } 2759 } 2760 """ 2761 ) 2762 ), 2763 expectedIssues = "" 2764 ) 2765 } 2766 2767 @Test Inherited nullability annotationsnull2768 fun `Inherited nullability annotations`() { 2769 check( 2770 compatibilityMode = false, 2771 checkCompatibilityApiReleased = """ 2772 package test.pkg { 2773 public final class SAXException extends test.pkg.Parent { 2774 } 2775 public final class Parent extends test.pkg.Grandparent { 2776 } 2777 public final class Grandparent { 2778 method @Nullable public String getMessage(); 2779 } 2780 } 2781 """, 2782 sourceFiles = arrayOf( 2783 java( 2784 """ 2785 package test.pkg; 2786 2787 public final class SAXException extends Parent { 2788 @Override public String getMessage() { 2789 return "sample"; 2790 } 2791 } 2792 """ 2793 ), 2794 java( 2795 """ 2796 package test.pkg; 2797 2798 public final class Parent extends Grandparent { 2799 } 2800 """ 2801 ), 2802 java( 2803 """ 2804 package test.pkg; 2805 2806 public final class Grandparent { 2807 public String getMessage() { 2808 return "sample"; 2809 } 2810 } 2811 """ 2812 ) 2813 ), 2814 mergeJavaStubAnnotations = """ 2815 package test.pkg; 2816 2817 public class Grandparent implements java.io.Serializable { 2818 @libcore.util.Nullable public test.pkg.String getMessage() { throw new RuntimeException("Stub!"); } 2819 } 2820 """, 2821 expectedIssues = """ 2822 """ 2823 ) 2824 } 2825 2826 @Test Inherited @removed fieldsnull2827 fun `Inherited @removed fields`() { 2828 check( 2829 compatibilityMode = false, 2830 checkCompatibilityRemovedApiReleased = """ 2831 package android.provider { 2832 2833 public static final class StreamItems implements android.provider.BaseColumns { 2834 field public static final String _COUNT = "_count"; 2835 field public static final String _ID = "_id"; 2836 } 2837 } 2838 """, 2839 sourceFiles = arrayOf( 2840 java( 2841 """ 2842 package android.provider; 2843 2844 /** 2845 * @removed 2846 */ 2847 public static final class StreamItems implements BaseColumns { 2848 } 2849 """ 2850 ), 2851 java( 2852 """ 2853 package android.provider; 2854 2855 public interface BaseColumns { 2856 public static final String _ID = "_id"; 2857 public static final String _COUNT = "_count"; 2858 } 2859 """ 2860 ) 2861 ), 2862 expectedIssues = """ 2863 """ 2864 ) 2865 } 2866 2867 @Test Inherited deprecated protected @removed methodnull2868 fun `Inherited deprecated protected @removed method`() { 2869 check( 2870 compatibilityMode = false, 2871 checkCompatibilityApiReleased = """ 2872 package android.icu.util { 2873 public class SpecificCalendar { 2874 method @Deprecated protected void validateField(); 2875 } 2876 } 2877 """, 2878 sourceFiles = arrayOf( 2879 java( 2880 """ 2881 package android.icu.util; 2882 import java.text.Format; 2883 2884 public class SpecificCalendar extends Calendar { 2885 /** 2886 * @deprecated for this test 2887 * @hide 2888 */ 2889 @Override 2890 @Deprecated 2891 protected void validateField() { 2892 } 2893 } 2894 """ 2895 ), 2896 java( 2897 """ 2898 package android.icu.util; 2899 2900 public class Calendar { 2901 protected void validateField() { 2902 } 2903 } 2904 """ 2905 ) 2906 ), 2907 expectedIssues = """ 2908 """ 2909 ) 2910 } 2911 2912 @Test Move class from SystemApi to public and then remove a methodnull2913 fun `Move class from SystemApi to public and then remove a method`() { 2914 check( 2915 compatibilityMode = false, 2916 checkCompatibilityApiReleased = """ 2917 package android.hardware.lights { 2918 public static final class LightsRequest.Builder { 2919 ctor public LightsRequest.Builder(); 2920 method public void clearLight(); 2921 method public void setLight(); 2922 } 2923 2924 public final class LightsManager { 2925 } 2926 } 2927 """, 2928 sourceFiles = arrayOf( 2929 java( 2930 """ 2931 package android.hardware.lights; 2932 2933 import android.annotation.SystemApi; 2934 2935 public class LightsRequest { 2936 public static class Builder { 2937 void clearLight() { } 2938 } 2939 } 2940 """ 2941 ), 2942 java( 2943 """ 2944 package android.hardware.lights; 2945 2946 import android.annotation.SystemApi; 2947 2948 /** 2949 * @hide 2950 */ 2951 @SystemApi 2952 public class LightsManager { 2953 } 2954 """ 2955 ), 2956 systemApiSource 2957 ), 2958 extraArguments = arrayOf( 2959 ARG_SHOW_ANNOTATION, "android.annotation.SystemApi", 2960 ARG_HIDE_PACKAGE, "android.annotation", 2961 ARG_HIDE_PACKAGE, "android.support.annotation" 2962 ), 2963 2964 expectedIssues = """ 2965 TESTROOT/released-api.txt:5: error: Removed method android.hardware.lights.LightsRequest.Builder.setLight() [RemovedMethod] 2966 """ 2967 ) 2968 } 2969 2970 @Test Moving a field from SystemApi to publicnull2971 fun `Moving a field from SystemApi to public`() { 2972 check( 2973 compatibilityMode = false, 2974 checkCompatibilityApiReleased = """ 2975 package android.content { 2976 public class Context { 2977 field public static final String BUGREPORT_SERVICE = "bugreport"; 2978 method public File getPreloadsFileCache(); 2979 } 2980 } 2981 """, 2982 sourceFiles = arrayOf( 2983 java( 2984 """ 2985 package android.content; 2986 2987 import android.annotation.SystemApi; 2988 2989 public class Context { 2990 public static final String BUGREPORT_SERVICE = "bugreport"; 2991 2992 /** 2993 * @hide 2994 */ 2995 @SystemApi 2996 public File getPreloadsFileCache() { return null; } 2997 } 2998 """ 2999 ), 3000 systemApiSource 3001 ), 3002 extraArguments = arrayOf( 3003 ARG_SHOW_ANNOTATION, "android.annotation.SystemApi", 3004 ARG_HIDE_PACKAGE, "android.annotation", 3005 ARG_HIDE_PACKAGE, "android.support.annotation" 3006 ), 3007 3008 expectedIssues = """ 3009 """ 3010 ) 3011 } 3012 3013 @Test Compare interfaces when Object is redefinednull3014 fun `Compare interfaces when Object is redefined`() { 3015 check( 3016 compatibilityMode = false, 3017 checkCompatibilityApiReleased = """ 3018 package java.lang { 3019 public class Object { 3020 method public final void wait(); 3021 } 3022 } 3023 package test.pkg { 3024 public interface SomeInterface { 3025 } 3026 } 3027 """, 3028 sourceFiles = arrayOf( 3029 java( 3030 """ 3031 package test.pkg; 3032 3033 public interface SomeInterface { 3034 } 3035 """ 3036 ) 3037 ), 3038 // it's not quite right to say that java.lang was removed, but it's better than also 3039 // saying that SomeInterface no longer implements wait() 3040 expectedIssues = """ 3041 TESTROOT/released-api.txt:1: error: Removed package java.lang [RemovedPackage] 3042 """ 3043 ) 3044 } 3045 3046 @Test Overriding method without redeclaring nullabilitynull3047 fun `Overriding method without redeclaring nullability`() { 3048 check( 3049 compatibilityMode = false, 3050 checkCompatibilityApiReleased = """ 3051 package test.pkg { 3052 public class Child extends test.pkg.Parent { 3053 } 3054 public class Parent { 3055 method public void sample(@Nullable String); 3056 } 3057 } 3058 """, 3059 sourceFiles = arrayOf( 3060 java( 3061 """ 3062 package test.pkg; 3063 3064 public class Child extends Parent { 3065 public void sample(String arg) { 3066 } 3067 } 3068 """ 3069 ), 3070 java( 3071 """ 3072 package test.pkg; 3073 3074 public class Parent { 3075 public void sample(@Nullable String arg) { 3076 } 3077 } 3078 """ 3079 ) 3080 ), 3081 // The correct behavior would be for this test to fail, because of the removal of 3082 // nullability annotations on the child class. However, when we generate signature files, 3083 // we omit methods having the same signature as super methods, so if we were to generate 3084 // a signature file for this source, we would generate the given signature file. So, 3085 // we temporarily allow (and expect) this to pass without errors 3086 // expectedIssues = "src/test/pkg/Child.java:4: error: Attempted to remove @Nullable annotation from parameter arg in test.pkg.Child.sample(String arg) [InvalidNullConversion]" 3087 expectedIssues = "" 3088 ) 3089 } 3090 3091 @Test Final class inherits a methodnull3092 fun `Final class inherits a method`() { 3093 check( 3094 compatibilityMode = false, 3095 checkCompatibilityApiReleased = """ 3096 package java.security { 3097 public abstract class BasicPermission extends java.security.Permission { 3098 method public boolean implies(java.security.Permission); 3099 } 3100 public abstract class Permission { 3101 method public abstract boolean implies(java.security.Permission); 3102 } 3103 } 3104 package javax.security.auth { 3105 public final class AuthPermission extends java.security.BasicPermission { 3106 } 3107 } 3108 """, 3109 sourceFiles = arrayOf( 3110 java( 3111 """ 3112 package javax.security.auth; 3113 3114 public final class AuthPermission extends java.security.BasicPermission { 3115 } 3116 """ 3117 ), 3118 java( 3119 """ 3120 package java.security; 3121 3122 public abstract class BasicPermission extends Permission { 3123 public boolean implies(Permission p) { 3124 return true; 3125 } 3126 } 3127 """ 3128 ), 3129 java( 3130 """ 3131 package java.security; 3132 public abstract class Permission { 3133 public abstract boolean implies(Permission permission); 3134 } 3135 } 3136 """ 3137 ) 3138 ), 3139 expectedIssues = "" 3140 ) 3141 } 3142 3143 @Test Implementing undefined interfacenull3144 fun `Implementing undefined interface`() { 3145 check( 3146 compatibilityMode = false, 3147 checkCompatibilityApiReleased = """ 3148 package org.apache.http.conn.scheme { 3149 @Deprecated public final class PlainSocketFactory implements org.apache.http.conn.scheme.SocketFactory { 3150 } 3151 } 3152 """, 3153 sourceFiles = arrayOf( 3154 java( 3155 """ 3156 package org.apache.http.conn.scheme; 3157 3158 /** @deprecated */ 3159 @Deprecated 3160 public final class PlainSocketFactory implements SocketFactory { 3161 } 3162 """ 3163 ) 3164 ), 3165 expectedIssues = "" 3166 ) 3167 } 3168 3169 @Test Inherited abstract methodnull3170 fun `Inherited abstract method`() { 3171 check( 3172 compatibilityMode = false, 3173 checkCompatibilityApiReleased = """ 3174 package test.pkg { 3175 public class MeasureFormat { 3176 method public test.pkg.MeasureFormat parse(); 3177 } 3178 } 3179 """, 3180 sourceFiles = arrayOf( 3181 java( 3182 """ 3183 package test.pkg; 3184 3185 public class MeasureFormat extends UFormat { 3186 private MeasureFormat() { } 3187 /** @hide */ 3188 public MeasureFormat parse(); 3189 } 3190 """ 3191 ), 3192 java( 3193 """ 3194 package test.pkg; 3195 import android.annotation.SystemApi; 3196 3197 public abstract class UFormat { 3198 public abstract UFormat parse() { 3199 } 3200 } 3201 """ 3202 ), 3203 systemApiSource 3204 ), 3205 expectedIssues = "" 3206 ) 3207 } 3208 3209 @Ignore("Not currently working: we're getting the wrong PSI results; I suspect caching across the two codebases") 3210 @Test Test All Android API levelsnull3211 fun `Test All Android API levels`() { 3212 // Checks API across Android SDK versions and makes sure the results are 3213 // intentional (to help shake out bugs in the API compatibility checker) 3214 3215 // Expected migration warnings (the map value) when migrating to the target key level from the previous level 3216 val expected = mapOf( 3217 5 to "warning: Method android.view.Surface.lockCanvas added thrown exception java.lang.IllegalArgumentException [ChangedThrows]", 3218 6 to """ 3219 warning: Method android.accounts.AbstractAccountAuthenticator.confirmCredentials added thrown exception android.accounts.NetworkErrorException [ChangedThrows] 3220 warning: Method android.accounts.AbstractAccountAuthenticator.updateCredentials added thrown exception android.accounts.NetworkErrorException [ChangedThrows] 3221 warning: Field android.view.WindowManager.LayoutParams.TYPE_STATUS_BAR_PANEL has changed value from 2008 to 2014 [ChangedValue] 3222 """, 3223 7 to """ 3224 error: Removed field android.view.ViewGroup.FLAG_USE_CHILD_DRAWING_ORDER [RemovedField] 3225 """, 3226 3227 // setOption getting removed here is wrong! Seems to be a PSI loading bug. 3228 8 to """ 3229 warning: Constructor android.net.SSLCertificateSocketFactory no longer throws exception java.security.KeyManagementException [ChangedThrows] 3230 warning: Constructor android.net.SSLCertificateSocketFactory no longer throws exception java.security.NoSuchAlgorithmException [ChangedThrows] 3231 error: Removed method java.net.DatagramSocketImpl.getOption(int) [RemovedMethod] 3232 error: Removed method java.net.DatagramSocketImpl.setOption(int,Object) [RemovedMethod] 3233 warning: Constructor java.nio.charset.Charset no longer throws exception java.nio.charset.IllegalCharsetNameException [ChangedThrows] 3234 warning: Method java.nio.charset.Charset.forName no longer throws exception java.nio.charset.IllegalCharsetNameException [ChangedThrows] 3235 warning: Method java.nio.charset.Charset.forName no longer throws exception java.nio.charset.UnsupportedCharsetException [ChangedThrows] 3236 warning: Method java.nio.charset.Charset.isSupported no longer throws exception java.nio.charset.IllegalCharsetNameException [ChangedThrows] 3237 warning: Method java.util.regex.Matcher.appendReplacement no longer throws exception java.lang.IllegalStateException [ChangedThrows] 3238 warning: Method java.util.regex.Matcher.start no longer throws exception java.lang.IllegalStateException [ChangedThrows] 3239 warning: Method java.util.regex.Pattern.compile no longer throws exception java.util.regex.PatternSyntaxException [ChangedThrows] 3240 warning: Class javax.xml.XMLConstants added final qualifier [AddedFinal] 3241 error: Removed constructor javax.xml.XMLConstants() [RemovedMethod] 3242 warning: Method javax.xml.parsers.DocumentBuilder.isXIncludeAware no longer throws exception java.lang.UnsupportedOperationException [ChangedThrows] 3243 warning: Method javax.xml.parsers.DocumentBuilderFactory.newInstance no longer throws exception javax.xml.parsers.FactoryConfigurationError [ChangedThrows] 3244 warning: Method javax.xml.parsers.SAXParser.isXIncludeAware no longer throws exception java.lang.UnsupportedOperationException [ChangedThrows] 3245 warning: Method javax.xml.parsers.SAXParserFactory.newInstance no longer throws exception javax.xml.parsers.FactoryConfigurationError [ChangedThrows] 3246 warning: Method org.w3c.dom.Element.getAttributeNS added thrown exception org.w3c.dom.DOMException [ChangedThrows] 3247 warning: Method org.w3c.dom.Element.getAttributeNodeNS added thrown exception org.w3c.dom.DOMException [ChangedThrows] 3248 warning: Method org.w3c.dom.Element.getElementsByTagNameNS added thrown exception org.w3c.dom.DOMException [ChangedThrows] 3249 warning: Method org.w3c.dom.Element.hasAttributeNS added thrown exception org.w3c.dom.DOMException [ChangedThrows] 3250 warning: Method org.w3c.dom.NamedNodeMap.getNamedItemNS added thrown exception org.w3c.dom.DOMException [ChangedThrows] 3251 """, 3252 3253 18 to """ 3254 warning: Class android.os.Looper added final qualifier but was previously uninstantiable and therefore could not be subclassed [AddedFinalUninstantiable] 3255 warning: Class android.os.MessageQueue added final qualifier but was previously uninstantiable and therefore could not be subclassed [AddedFinalUninstantiable] 3256 error: Removed field android.os.Process.BLUETOOTH_GID [RemovedField] 3257 error: Removed class android.renderscript.Program [RemovedClass] 3258 error: Removed class android.renderscript.ProgramStore [RemovedClass] 3259 """, 3260 19 to """ 3261 warning: Method android.app.Notification.Style.build has changed 'abstract' qualifier [ChangedAbstract] 3262 error: Removed method android.os.Debug.MemoryInfo.getOtherLabel(int) [RemovedMethod] 3263 error: Removed method android.os.Debug.MemoryInfo.getOtherPrivateDirty(int) [RemovedMethod] 3264 error: Removed method android.os.Debug.MemoryInfo.getOtherPss(int) [RemovedMethod] 3265 error: Removed method android.os.Debug.MemoryInfo.getOtherSharedDirty(int) [RemovedMethod] 3266 warning: Field android.view.animation.Transformation.TYPE_ALPHA has changed value from nothing/not constant to 1 [ChangedValue] 3267 warning: Field android.view.animation.Transformation.TYPE_ALPHA has added 'final' qualifier [AddedFinal] 3268 warning: Field android.view.animation.Transformation.TYPE_BOTH has changed value from nothing/not constant to 3 [ChangedValue] 3269 warning: Field android.view.animation.Transformation.TYPE_BOTH has added 'final' qualifier [AddedFinal] 3270 warning: Field android.view.animation.Transformation.TYPE_IDENTITY has changed value from nothing/not constant to 0 [ChangedValue] 3271 warning: Field android.view.animation.Transformation.TYPE_IDENTITY has added 'final' qualifier [AddedFinal] 3272 warning: Field android.view.animation.Transformation.TYPE_MATRIX has changed value from nothing/not constant to 2 [ChangedValue] 3273 warning: Field android.view.animation.Transformation.TYPE_MATRIX has added 'final' qualifier [AddedFinal] 3274 warning: Method java.nio.CharBuffer.subSequence has changed return type from CharSequence to java.nio.CharBuffer [ChangedType] 3275 """, // The last warning above is not right; seems to be a PSI jar loading bug. It returns the wrong return type! 3276 3277 20 to """ 3278 error: Removed method android.util.TypedValue.complexToDimensionNoisy(int,android.util.DisplayMetrics) [RemovedMethod] 3279 warning: Method org.json.JSONObject.keys has changed return type from java.util.Iterator to java.util.Iterator<java.lang.String> [ChangedType] 3280 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] 3281 """, 3282 26 to """ 3283 warning: Field android.app.ActivityManager.RunningAppProcessInfo.IMPORTANCE_PERCEPTIBLE has changed value from 130 to 230 [ChangedValue] 3284 warning: Field android.content.pm.PermissionInfo.PROTECTION_MASK_FLAGS has changed value from 4080 to 65520 [ChangedValue] 3285 """, 3286 27 to "" 3287 ) 3288 3289 val suppressLevels = mapOf( 3290 1 to "AddedPackage,AddedClass,AddedMethod,AddedInterface,AddedField,ChangedDeprecated", 3291 7 to "AddedPackage,AddedClass,AddedMethod,AddedInterface,AddedField,ChangedDeprecated", 3292 18 to "AddedPackage,AddedClass,AddedMethod,AddedInterface,AddedField,RemovedMethod,ChangedDeprecated,ChangedThrows,AddedFinal,ChangedType,RemovedDeprecatedClass", 3293 26 to "AddedPackage,AddedClass,AddedMethod,AddedInterface,AddedField,RemovedMethod,ChangedDeprecated,ChangedThrows,AddedFinal,RemovedClass,RemovedDeprecatedClass", 3294 27 to "AddedPackage,AddedClass,AddedMethod,AddedInterface,AddedField,RemovedMethod,ChangedDeprecated,ChangedThrows,AddedFinal" 3295 ) 3296 3297 val loadPrevAsSignature = false 3298 3299 for (apiLevel in 5..27) { 3300 if (!expected.containsKey(apiLevel)) { 3301 continue 3302 } 3303 println("Checking compatibility from API level ${apiLevel - 1} to $apiLevel...") 3304 val current = getAndroidJar(apiLevel) 3305 if (current == null) { 3306 println("Couldn't find $current: Check that pwd for test is correct. Skipping this test.") 3307 return 3308 } 3309 3310 val previous = getAndroidJar(apiLevel - 1) 3311 if (previous == null) { 3312 println("Couldn't find $previous: Check that pwd for test is correct. Skipping this test.") 3313 return 3314 } 3315 val previousApi = previous.path 3316 3317 // PSI based check 3318 3319 check( 3320 extraArguments = arrayOf( 3321 "--omit-locations", 3322 ARG_HIDE, 3323 suppressLevels[apiLevel] 3324 ?: "AddedPackage,AddedClass,AddedMethod,AddedInterface,AddedField,ChangedDeprecated,RemovedField,RemovedClass,RemovedDeprecatedClass" + 3325 (if ((apiLevel == 19 || apiLevel == 20) && loadPrevAsSignature) ",ChangedType" else "") 3326 3327 ), 3328 expectedIssues = expected[apiLevel]?.trimIndent() ?: "", 3329 checkCompatibilityApi = previousApi, 3330 apiJar = current 3331 ) 3332 3333 // Signature based check 3334 if (apiLevel >= 21) { 3335 // Check signature file checks. We have .txt files for API level 14 and up, but there are a 3336 // BUNCH of problems in older signature files that make the comparisons not work -- 3337 // missing type variables in class declarations, missing generics in method signatures, etc. 3338 val signatureFile = File("../../prebuilts/sdk/${apiLevel - 1}/public/api/android.txt") 3339 if (!(signatureFile.isFile)) { 3340 println("Couldn't find $signatureFile: Check that pwd for test is correct. Skipping this test.") 3341 return 3342 } 3343 val previousSignatureApi = signatureFile.readText(UTF_8) 3344 3345 check( 3346 extraArguments = arrayOf( 3347 "--omit-locations", 3348 ARG_HIDE, 3349 suppressLevels[apiLevel] 3350 ?: "AddedPackage,AddedClass,AddedMethod,AddedInterface,AddedField,ChangedDeprecated,RemovedField,RemovedClass,RemovedDeprecatedClass" 3351 ), 3352 expectedIssues = expected[apiLevel]?.trimIndent() ?: "", 3353 checkCompatibilityApi = previousSignatureApi, 3354 apiJar = current 3355 ) 3356 } 3357 } 3358 } 3359 3360 @Test Ignore hidden referencesnull3361 fun `Ignore hidden references`() { 3362 check( 3363 expectedIssues = """ 3364 """, 3365 compatibilityMode = false, 3366 checkCompatibilityApi = """ 3367 package test.pkg { 3368 public class MyClass { 3369 ctor public MyClass(); 3370 method public void method1(test.pkg.Hidden); 3371 } 3372 } 3373 """, 3374 sourceFiles = arrayOf( 3375 java( 3376 """ 3377 package test.pkg; 3378 3379 public class MyClass { 3380 public void method1(Hidden hidden) { } 3381 } 3382 """ 3383 ), 3384 java( 3385 """ 3386 package test.pkg; 3387 /** @hide */ 3388 public class Hidden { 3389 } 3390 """ 3391 ) 3392 ), 3393 extraArguments = arrayOf( 3394 ARG_HIDE, "ReferencesHidden", 3395 ARG_HIDE, "UnavailableSymbol", 3396 ARG_HIDE, "HiddenTypeParameter" 3397 ) 3398 ) 3399 } 3400 3401 @Test Fail on compatible changes that affect signature file contentsnull3402 fun `Fail on compatible changes that affect signature file contents`() { 3403 // Regression test for 122916999 3404 check( 3405 extraArguments = arrayOf(ARG_NO_NATIVE_DIFF), 3406 allowCompatibleDifferences = false, 3407 expectedFail = """ 3408 Aborting: Your changes have resulted in differences in the signature file 3409 for the public API. 3410 3411 The changes may be compatible, but the signature file needs to be updated. 3412 3413 Diffs: 3414 @@ -5 +5 3415 ctor public MyClass(); 3416 - method public void method2(); 3417 method public void method1(); 3418 @@ -7 +6 3419 method public void method1(); 3420 + method public void method2(); 3421 method public void method3(); 3422 """.trimIndent(), 3423 compatibilityMode = false, 3424 // Methods in order 3425 checkCompatibilityApi = """ 3426 package test.pkg { 3427 3428 public class MyClass { 3429 ctor public MyClass(); 3430 method public void method2(); 3431 method public void method1(); 3432 method public void method3(); 3433 method public void method4(); 3434 } 3435 3436 } 3437 """, 3438 sourceFiles = arrayOf( 3439 java( 3440 """ 3441 package test.pkg; 3442 3443 public class MyClass { 3444 public void method1() { } 3445 public void method2() { } 3446 public void method3() { } 3447 public native void method4(); 3448 } 3449 """ 3450 ) 3451 ) 3452 ) 3453 } 3454 3455 @Test Empty bundle filesnull3456 fun `Empty bundle files`() { 3457 // Regression test for 124333557 3458 // Makes sure we properly handle conflicting definitions of a java file in separate source roots 3459 check( 3460 expectedIssues = "", 3461 compatibilityMode = false, 3462 checkCompatibilityApi = """ 3463 // Signature format: 3.0 3464 package com.android.location.provider { 3465 public class LocationProviderBase1 { 3466 ctor public LocationProviderBase1(); 3467 method public void onGetStatus(android.os.Bundle!); 3468 } 3469 public class LocationProviderBase2 { 3470 ctor public LocationProviderBase2(); 3471 method public void onGetStatus(android.os.Bundle!); 3472 } 3473 } 3474 """, 3475 sourceFiles = arrayOf( 3476 java( 3477 "src2/com/android/location/provider/LocationProviderBase1.java", 3478 """ 3479 /** Something */ 3480 package com.android.location.provider; 3481 """ 3482 ), 3483 java( 3484 "src/com/android/location/provider/LocationProviderBase1.java", 3485 """ 3486 package com.android.location.provider; 3487 import android.os.Bundle; 3488 3489 public class LocationProviderBase1 { 3490 public void onGetStatus(Bundle bundle) { } 3491 } 3492 """ 3493 ), 3494 // Try both combinations (empty java file both first on the source path 3495 // and second on the source path) 3496 java( 3497 "src/com/android/location/provider/LocationProviderBase2.java", 3498 """ 3499 /** Something */ 3500 package com.android.location.provider; 3501 """ 3502 ), 3503 java( 3504 "src/com/android/location/provider/LocationProviderBase2.java", 3505 """ 3506 package com.android.location.provider; 3507 import android.os.Bundle; 3508 3509 public class LocationProviderBase2 { 3510 public void onGetStatus(Bundle bundle) { } 3511 } 3512 """ 3513 ) 3514 ) 3515 ) 3516 } 3517 3518 @Test Check parameterized return type nullabilitynull3519 fun `Check parameterized return type nullability`() { 3520 // Regression test for 130567941 3521 check( 3522 expectedIssues = "", 3523 compatibilityMode = false, 3524 checkCompatibilityApi = """ 3525 // Signature format: 3.0 3526 package androidx.coordinatorlayout.widget { 3527 public class CoordinatorLayout { 3528 ctor public CoordinatorLayout(); 3529 method public java.util.List<android.view.View!> getDependencies(); 3530 } 3531 } 3532 """, 3533 sourceFiles = arrayOf( 3534 java( 3535 """ 3536 package androidx.coordinatorlayout.widget; 3537 3538 import java.util.List; 3539 import androidx.annotation.NonNull; 3540 import android.view.View; 3541 3542 public class CoordinatorLayout { 3543 @NonNull 3544 public List<View> getDependencies() { 3545 throw Exception("Not implemented"); 3546 } 3547 } 3548 """ 3549 ), 3550 androidxNonNullSource 3551 ), 3552 extraArguments = arrayOf(ARG_HIDE_PACKAGE, "androidx.annotation") 3553 ) 3554 } 3555 3556 @Test Check return type changing packagenull3557 fun `Check return type changing package`() { 3558 // Regression test for 130567941 3559 check( 3560 expectedIssues = """ 3561 TESTROOT/load-api.txt:7: error: Method test.pkg.sample.SampleClass.convert has changed return type from Number to java.lang.Number [ChangedType] 3562 """, 3563 compatibilityMode = false, 3564 inputKotlinStyleNulls = true, 3565 outputKotlinStyleNulls = true, 3566 checkCompatibilityApi = """ 3567 // Signature format: 3.0 3568 package test.pkg.sample { 3569 public abstract class SampleClass { 3570 method public <Number> Number! convert(Number); 3571 method public <Number> Number! convert(Number); 3572 } 3573 } 3574 """, 3575 signatureSource = """ 3576 // Signature format: 3.0 3577 package test.pkg.sample { 3578 public abstract class SampleClass { 3579 // Here the generic type parameter applies to both the function argument and the function return type 3580 method public <Number> Number! convert(Number); 3581 // Here the generic type parameter applies to the function argument but not the function return type 3582 method public <Number> java.lang.Number! convert(Number); 3583 } 3584 } 3585 """ 3586 ) 3587 } 3588 3589 @Test Check generic type argument when showUnannotated is explicitly enablednull3590 fun `Check generic type argument when showUnannotated is explicitly enabled`() { 3591 // Regression test for 130567941 3592 check( 3593 expectedIssues = """ 3594 """, 3595 compatibilityMode = false, 3596 inputKotlinStyleNulls = true, 3597 outputKotlinStyleNulls = true, 3598 checkCompatibilityApi = """ 3599 // Signature format: 3.0 3600 package androidx.versionedparcelable { 3601 public abstract class VersionedParcel { 3602 method public <T> T![]! readArray(); 3603 } 3604 } 3605 """, 3606 sourceFiles = arrayOf( 3607 java( 3608 """ 3609 package androidx.versionedparcelable; 3610 3611 public abstract class VersionedParcel { 3612 private VersionedParcel() { } 3613 3614 public <T> T[] readArray() { return null; } 3615 } 3616 """ 3617 ) 3618 ), 3619 extraArguments = arrayOf(ARG_SHOW_UNANNOTATED, ARG_SHOW_ANNOTATION, "androidx.annotation.RestrictTo") 3620 ) 3621 } 3622 3623 @Test Check using parameterized arrays as type parametersnull3624 fun `Check using parameterized arrays as type parameters`() { 3625 check( 3626 format = FileFormat.V3, 3627 sourceFiles = arrayOf( 3628 java( 3629 """ 3630 package test.pkg; 3631 import java.util.ArrayList; 3632 import java.lang.Exception; 3633 3634 public class SampleArray<D extends ArrayList> extends ArrayList<D[]> { 3635 public D[] get(int index) { 3636 throw Exception("Not implemented"); 3637 } 3638 } 3639 """ 3640 ) 3641 ), 3642 3643 checkCompatibilityApi = """ 3644 // Signature format: 3.0 3645 package test.pkg { 3646 public class SampleArray<D extends java.util.ArrayList> extends java.util.ArrayList<D[]> { 3647 ctor public SampleArray(); 3648 method public D![]! get(int); 3649 } 3650 } 3651 """ 3652 ) 3653 } 3654 3655 @Test Check implicit containing classnull3656 fun `Check implicit containing class`() { 3657 // Regression test for 131633221 3658 check( 3659 expectedIssues = """ 3660 src/androidx/core/app/NotificationCompat.java:5: error: Added class androidx.core.app.NotificationCompat [AddedClass] 3661 """, 3662 compatibilityMode = false, 3663 inputKotlinStyleNulls = true, 3664 outputKotlinStyleNulls = true, 3665 checkCompatibilityApi = """ 3666 // Signature format: 3.0 3667 package androidx.core.app { 3668 public static class NotificationCompat.Builder { 3669 ctor public NotificationCompat.Builder(); 3670 } 3671 } 3672 """, 3673 sourceFiles = arrayOf( 3674 java( 3675 """ 3676 package androidx.core.app; 3677 3678 import android.content.Context; 3679 3680 public class NotificationCompat { 3681 private NotificationCompat() { 3682 } 3683 public static class Builder { 3684 } 3685 } 3686 """ 3687 ) 3688 ) 3689 ) 3690 } 3691 3692 @Test New default method on annotationnull3693 fun `New default method on annotation`() { 3694 // Regression test for 134754815 3695 check( 3696 expectedIssues = """ 3697 src/androidx/room/Relation.java:5: error: Added method androidx.room.Relation.IHaveNoDefault() [AddedAbstractMethod] 3698 """, 3699 compatibilityMode = false, 3700 inputKotlinStyleNulls = true, 3701 outputKotlinStyleNulls = true, 3702 checkCompatibilityApi = """ 3703 // Signature format: 3.0 3704 package androidx.room { 3705 public @interface Relation { 3706 } 3707 } 3708 """, 3709 sourceFiles = arrayOf( 3710 java( 3711 """ 3712 package androidx.room; 3713 3714 public @interface Relation { 3715 String IHaveADefault() default ""; 3716 String IHaveNoDefault(); 3717 } 3718 """ 3719 ) 3720 ) 3721 ) 3722 } 3723 3724 @Test Changing static qualifier on inner classes with no public constructorsnull3725 fun `Changing static qualifier on inner classes with no public constructors`() { 3726 check( 3727 expectedIssues = """ 3728 TESTROOT/load-api.txt:11: error: Class test.pkg.ParentClass.AnotherBadInnerClass changed 'static' qualifier [ChangedStatic] 3729 TESTROOT/load-api.txt:8: error: Class test.pkg.ParentClass.BadInnerClass changed 'static' qualifier [ChangedStatic] 3730 """, 3731 compatibilityMode = false, 3732 checkCompatibilityApi = """ 3733 package test.pkg { 3734 public class ParentClass { 3735 } 3736 public static class ParentClass.OkInnerClass { 3737 } 3738 public class ParentClass.AnotherOkInnerClass { 3739 } 3740 public static class ParentClass.BadInnerClass { 3741 ctor public BadInnerClass(); 3742 } 3743 public class ParentClass.AnotherBadInnerClass { 3744 ctor public AnotherBadInnerClass(); 3745 } 3746 } 3747 """, 3748 signatureSource = """ 3749 package test.pkg { 3750 public class ParentClass { 3751 } 3752 public class ParentClass.OkInnerClass { 3753 } 3754 public static class ParentClass.AnotherOkInnerClass { 3755 } 3756 public class ParentClass.BadInnerClass { 3757 ctor public BadInnerClass(); 3758 } 3759 public static class ParentClass.AnotherBadInnerClass { 3760 ctor public AnotherBadInnerClass(); 3761 } 3762 } 3763 """ 3764 ) 3765 } 3766 3767 @Test Remove fun modifier from interfacenull3768 fun `Remove fun modifier from interface`() { 3769 check( 3770 expectedIssues = """ 3771 src/test/pkg/FunctionalInterface.kt:3: error: Cannot remove 'fun' modifier from class test.pkg.FunctionalInterface: source incompatible change [FunRemoval] 3772 """, 3773 format = FileFormat.V4, 3774 checkCompatibilityApi = """ 3775 // Signature format: 4.0 3776 package test.pkg { 3777 public fun interface FunctionalInterface { 3778 method public boolean methodOne(int number); 3779 } 3780 } 3781 """, 3782 sourceFiles = arrayOf( 3783 kotlin( 3784 """ 3785 package test.pkg 3786 3787 interface FunctionalInterface { 3788 fun methodOne(number: Int): Boolean 3789 } 3790 """ 3791 ) 3792 ) 3793 ) 3794 } 3795 3796 @Test Remove fun modifier from interface signature filesnull3797 fun `Remove fun modifier from interface signature files`() { 3798 check( 3799 expectedIssues = """ 3800 TESTROOT/load-api.txt:3: error: Cannot remove 'fun' modifier from class test.pkg.FunctionalInterface: source incompatible change [FunRemoval] 3801 """, 3802 format = FileFormat.V4, 3803 checkCompatibilityApi = """ 3804 // Signature format: 4.0 3805 package test.pkg { 3806 public fun interface FunctionalInterface { 3807 method public boolean methodOne(int number); 3808 } 3809 } 3810 """, 3811 signatureSource = """ 3812 // Signature format: 4.0 3813 package test.pkg { 3814 public interface FunctionalInterface { 3815 method public boolean methodOne(int number); 3816 } 3817 } 3818 """.trimIndent() 3819 ) 3820 } 3821 3822 @Test Adding default value to annotation parameternull3823 fun `Adding default value to annotation parameter`() { 3824 check( 3825 expectedIssues = "", 3826 format = FileFormat.V4, 3827 checkCompatibilityApi = """ 3828 // Signature format: 4.0 3829 package androidx.annotation.experimental { 3830 public @interface UseExperimental { 3831 method public abstract Class<?> markerClass(); 3832 } 3833 } 3834 """, 3835 sourceFiles = arrayOf( 3836 java(""" 3837 package androidx.annotation.experimental; 3838 public @interface UseExperimental { 3839 Class<?> markerClass() default void.class; 3840 } 3841 """) 3842 ) 3843 ) 3844 } 3845 3846 // TODO: Check method signatures changing incompatibly (look especially out for adding new overloaded 3847 // methods and comparator getting confused!) 3848 // ..equals on the method items should actually be very useful! 3849 } 3850