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