1 /* 2 * Copyright (C) 2018 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.doc 18 19 import com.android.tools.lint.checks.infrastructure.TestFiles 20 import com.android.tools.metalava.ARG_CURRENT_CODENAME 21 import com.android.tools.metalava.ARG_CURRENT_VERSION 22 import com.android.tools.metalava.DriverTest 23 import com.android.tools.metalava.columnSource 24 import com.android.tools.metalava.lint.DefaultLintErrorMessage 25 import com.android.tools.metalava.model.provider.Capability 26 import com.android.tools.metalava.model.psi.trimDocIndent 27 import com.android.tools.metalava.model.testing.RequiresCapabilities 28 import com.android.tools.metalava.nonNullSource 29 import com.android.tools.metalava.nullableSource 30 import com.android.tools.metalava.requiresApiSource 31 import com.android.tools.metalava.requiresPermissionSource 32 import com.android.tools.metalava.systemApiSource 33 import com.android.tools.metalava.testing.java 34 import com.android.tools.metalava.testing.kotlin 35 import com.android.tools.metalava.uiThreadSource 36 import com.android.tools.metalava.workerThreadSource 37 import org.junit.Assert 38 import org.junit.Test 39 40 /** Tests for the [DocAnalyzer] which enhances the docs */ 41 class DocAnalyzerTest : DriverTest() { 42 // TODO: Test @StringDef 43 44 @Test Basic documentation generation testnull45 fun `Basic documentation generation test`() { 46 check( 47 sourceFiles = 48 arrayOf( 49 java( 50 """ 51 package test.pkg; 52 import android.annotation.Nullable; 53 import android.annotation.NonNull; 54 public class Foo { 55 /** These are the docs for method1. */ 56 @Nullable public Double method1(@NonNull Double factor1, @NonNull Double factor2) { } 57 /** These are the docs for method2. It can sometimes return null. */ 58 @Nullable public Double method2(@NonNull Double factor1, @NonNull Double factor2) { } 59 @Nullable public Double method3(@NonNull Double factor1, @NonNull Double factor2) { } 60 /** 61 * @param factor2 Don't pass null here please. 62 */ 63 @Nullable public Double method4(@NonNull Double factor1, @NonNull Double factor2) { } 64 } 65 """ 66 ), 67 nonNullSource, 68 nullableSource 69 ), 70 checkCompilation = false, // needs androidx.annotations in classpath 71 docStubs = true, 72 stubFiles = 73 arrayOf( 74 java( 75 """ 76 package test.pkg; 77 @SuppressWarnings({"unchecked", "deprecation", "all"}) 78 public class Foo { 79 public Foo() { throw new RuntimeException("Stub!"); } 80 /** 81 * These are the docs for method1. 82 * @param factor1 This value must never be {@code null}. 83 * @param factor2 This value must never be {@code null}. 84 * @return This value may be {@code null}. 85 */ 86 @androidx.annotation.Nullable 87 public java.lang.Double method1(@androidx.annotation.NonNull java.lang.Double factor1, @androidx.annotation.NonNull java.lang.Double factor2) { throw new RuntimeException("Stub!"); } 88 /** 89 * These are the docs for method2. It can sometimes return null. 90 * @param factor1 This value must never be {@code null}. 91 * @param factor2 This value must never be {@code null}. 92 */ 93 @androidx.annotation.Nullable 94 public java.lang.Double method2(@androidx.annotation.NonNull java.lang.Double factor1, @androidx.annotation.NonNull java.lang.Double factor2) { throw new RuntimeException("Stub!"); } 95 /** 96 * @param factor1 This value must never be {@code null}. 97 * @param factor2 This value must never be {@code null}. 98 * @return This value may be {@code null}. 99 */ 100 @androidx.annotation.Nullable 101 public java.lang.Double method3(@androidx.annotation.NonNull java.lang.Double factor1, @androidx.annotation.NonNull java.lang.Double factor2) { throw new RuntimeException("Stub!"); } 102 /** 103 * @param factor2 Don't pass null here please. 104 * @param factor1 This value must never be {@code null}. 105 * @return This value may be {@code null}. 106 */ 107 @androidx.annotation.Nullable 108 public java.lang.Double method4(@androidx.annotation.NonNull java.lang.Double factor1, @androidx.annotation.NonNull java.lang.Double factor2) { throw new RuntimeException("Stub!"); } 109 } 110 """ 111 ) 112 ) 113 ) 114 } 115 116 @Test Check construct ApiLookup works correctlynull117 fun `Check construct ApiLookup works correctly`() { 118 check( 119 sourceFiles = 120 arrayOf( 121 java( 122 """ 123 package test.pkg; 124 125 public class Foo { 126 public Foo() {} 127 public Foo(int i) { this.i = i; } 128 129 private int i; 130 } 131 """ 132 ), 133 ), 134 checkCompilation = true, 135 docStubs = true, 136 applyApiLevelsXml = 137 """ 138 <?xml version="1.0" encoding="utf-8"?> 139 <api version="2"> 140 <class name="test/pkg/Foo" since="17"> 141 <method name="<init>()V" since="18"/> 142 <method name="<init>(I)V" since="19"/> 143 </class> 144 </api> 145 """, 146 stubFiles = 147 arrayOf( 148 java( 149 """ 150 package test.pkg; 151 /** @apiSince 17 */ 152 @SuppressWarnings({"unchecked", "deprecation", "all"}) 153 public class Foo { 154 /** @apiSince 18 */ 155 public Foo() { throw new RuntimeException("Stub!"); } 156 /** @apiSince 19 */ 157 public Foo(int i) { throw new RuntimeException("Stub!"); } 158 } 159 """ 160 ), 161 ), 162 ) 163 } 164 165 @Test Fix first sentence handlingnull166 fun `Fix first sentence handling`() { 167 check( 168 sourceFiles = 169 arrayOf( 170 java( 171 """ 172 package android.annotation; 173 174 import static java.lang.annotation.ElementType.*; 175 import static java.lang.annotation.RetentionPolicy.CLASS; 176 import java.lang.annotation.*; 177 178 /** 179 * Denotes that an integer parameter, field or method return value is expected 180 * to be a String resource reference (e.g. {@code android.R.string.ok}). 181 */ 182 @Documented 183 @Retention(CLASS) 184 @Target({METHOD, PARAMETER, FIELD, LOCAL_VARIABLE}) 185 public @interface StringRes { 186 } 187 """ 188 ) 189 ), 190 checkCompilation = true, 191 docStubs = true, 192 stubFiles = 193 arrayOf( 194 java( 195 """ 196 package android.annotation; 197 /** 198 * Denotes that an integer parameter, field or method return value is expected 199 * to be a String resource reference (e.g. {@code android.R.string.ok}). 200 */ 201 @SuppressWarnings({"unchecked", "deprecation", "all"}) 202 @java.lang.annotation.Documented 203 @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.CLASS) 204 @java.lang.annotation.Target({java.lang.annotation.ElementType.METHOD, java.lang.annotation.ElementType.PARAMETER, java.lang.annotation.ElementType.FIELD, java.lang.annotation.ElementType.LOCAL_VARIABLE}) 205 public @interface StringRes { 206 } 207 """ 208 ) 209 ), 210 ) 211 } 212 213 @Test Document Permissionsnull214 fun `Document Permissions`() { 215 check( 216 docStubs = true, 217 sourceFiles = 218 arrayOf( 219 java( 220 """ 221 package test.pkg; 222 223 import android.Manifest; 224 import android.annotation.RequiresPermission; 225 226 public class PermissionTest { 227 @RequiresPermission(Manifest.permission.ACCESS_COARSE_LOCATION) 228 public void test1() { 229 } 230 231 @RequiresPermission(allOf = Manifest.permission.ACCESS_COARSE_LOCATION) 232 public void test2() { 233 } 234 235 @RequiresPermission(anyOf = {Manifest.permission.ACCESS_COARSE_LOCATION, Manifest.permission.ACCESS_FINE_LOCATION}) 236 public void test3() { 237 } 238 239 @RequiresPermission(allOf = {Manifest.permission.ACCESS_COARSE_LOCATION, Manifest.permission.ACCOUNT_MANAGER}) 240 public void test4() { 241 } 242 243 @RequiresPermission(value=Manifest.permission.WATCH_APPOPS, conditional=true) // b/73559440 244 public void test5() { 245 } 246 247 @RequiresPermission(anyOf = {Manifest.permission.ACCESS_COARSE_LOCATION, "carrier privileges"}) 248 public void test6() { 249 } 250 251 // Typo in marker 252 @RequiresPermission(anyOf = {Manifest.permission.ACCESS_COARSE_LOCATION, "carier priviliges"}) // NOTYPO 253 public void test6() { 254 } 255 } 256 """ 257 ), 258 java( 259 """ 260 package android; 261 262 public abstract class Manifest { 263 public static final class permission { 264 public static final String ACCESS_COARSE_LOCATION = "android.permission.ACCESS_COARSE_LOCATION"; 265 public static final String ACCESS_FINE_LOCATION = "android.permission.ACCESS_FINE_LOCATION"; 266 public static final String ACCOUNT_MANAGER = "android.permission.ACCOUNT_MANAGER"; 267 public static final String WATCH_APPOPS = "android.permission.WATCH_APPOPS"; 268 } 269 } 270 """ 271 ), 272 requiresPermissionSource 273 ), 274 checkCompilation = false, // needs androidx.annotations in classpath 275 expectedFail = DefaultLintErrorMessage, 276 expectedIssues = 277 "src/test/pkg/PermissionTest.java:33: error: Unrecognized permission `carier priviliges`; did you mean `carrier privileges`? [MissingPermission]", // NOTYPO 278 stubFiles = 279 arrayOf( 280 // common_typos_disable 281 java( 282 """ 283 package test.pkg; 284 import android.Manifest; 285 @SuppressWarnings({"unchecked", "deprecation", "all"}) 286 public class PermissionTest { 287 public PermissionTest() { throw new RuntimeException("Stub!"); } 288 /** 289 * Requires {@link android.Manifest.permission#ACCESS_COARSE_LOCATION} 290 */ 291 public void test1() { throw new RuntimeException("Stub!"); } 292 /** 293 * Requires {@link android.Manifest.permission#ACCESS_COARSE_LOCATION} 294 */ 295 public void test2() { throw new RuntimeException("Stub!"); } 296 /** 297 * Requires {@link android.Manifest.permission#ACCESS_COARSE_LOCATION} or {@link android.Manifest.permission#ACCESS_FINE_LOCATION} 298 */ 299 public void test3() { throw new RuntimeException("Stub!"); } 300 /** 301 * Requires {@link android.Manifest.permission#ACCESS_COARSE_LOCATION} and {@link android.Manifest.permission#ACCOUNT_MANAGER} 302 */ 303 public void test4() { throw new RuntimeException("Stub!"); } 304 public void test5() { throw new RuntimeException("Stub!"); } 305 /** 306 * Requires {@link android.Manifest.permission#ACCESS_COARSE_LOCATION} or {@link android.telephony.TelephonyManager#hasCarrierPrivileges carrier privileges} 307 */ 308 public void test6() { throw new RuntimeException("Stub!"); } 309 /** 310 * Requires {@link android.Manifest.permission#ACCESS_COARSE_LOCATION} or "carier priviliges" 311 */ 312 public void test6() { throw new RuntimeException("Stub!"); } 313 } 314 """ 315 ) 316 // common_typos_enable 317 ) 318 ) 319 } 320 321 @Test Conditional Permissionnull322 fun `Conditional Permission`() { 323 check( 324 sourceFiles = 325 arrayOf( 326 java( 327 """ 328 package test.pkg; 329 330 import android.Manifest; 331 import android.annotation.RequiresPermission; 332 333 // Scenario described in b/73559440 334 public class PermissionTest { 335 @RequiresPermission(value=Manifest.permission.WATCH_APPOPS, conditional=true) 336 public void test1() { 337 } 338 } 339 """ 340 ), 341 java( 342 """ 343 package android; 344 345 public abstract class Manifest { 346 public static final class permission { 347 public static final String WATCH_APPOPS = "android.permission.WATCH_APPOPS"; 348 } 349 } 350 """ 351 ), 352 requiresPermissionSource 353 ), 354 checkCompilation = false, // needs androidx.annotations in classpath 355 stubFiles = 356 arrayOf( 357 java( 358 """ 359 package test.pkg; 360 @SuppressWarnings({"unchecked", "deprecation", "all"}) 361 public class PermissionTest { 362 public PermissionTest() { throw new RuntimeException("Stub!"); } 363 public void test1() { throw new RuntimeException("Stub!"); } 364 } 365 """ 366 ) 367 ) 368 ) 369 } 370 371 @Test Merging in documentation snippets from annotation memberDoc and classDocnull372 fun `Merging in documentation snippets from annotation memberDoc and classDoc`() { 373 check( 374 sourceFiles = 375 arrayOf( 376 java( 377 """ 378 package test.pkg; 379 import androidx.annotation.UiThread; 380 import androidx.annotation.WorkerThread; 381 @UiThread 382 public class RangeTest { 383 @WorkerThread 384 public int test1() { } 385 } 386 """ 387 ), 388 uiThreadSource, 389 workerThreadSource 390 ), 391 checkCompilation = true, 392 docStubs = true, 393 stubFiles = 394 arrayOf( 395 java( 396 """ 397 package test.pkg; 398 /** 399 * Methods in this class must be called on the thread that originally created 400 * this UI element, unless otherwise noted. This is typically the 401 * main thread of your app. * 402 */ 403 @SuppressWarnings({"unchecked", "deprecation", "all"}) 404 public class RangeTest { 405 public RangeTest() { throw new RuntimeException("Stub!"); } 406 /** 407 * This method may take several seconds to complete, so it should 408 * only be called from a worker thread. 409 */ 410 public int test1() { throw new RuntimeException("Stub!"); } 411 } 412 """ 413 ) 414 ) 415 ) 416 } 417 418 @Test Warn about multiple threading annotationsnull419 fun `Warn about multiple threading annotations`() { 420 check( 421 sourceFiles = 422 arrayOf( 423 java( 424 """ 425 package test.pkg; 426 import androidx.annotation.UiThread; 427 import androidx.annotation.WorkerThread; 428 public class RangeTest { 429 @UiThread @WorkerThread 430 public int test1() { } 431 } 432 """ 433 ), 434 uiThreadSource, 435 workerThreadSource 436 ), 437 checkCompilation = true, 438 expectedFail = DefaultLintErrorMessage, 439 expectedIssues = 440 "src/test/pkg/RangeTest.java:6: error: Found more than one threading annotation on method test.pkg.RangeTest.test1(); the auto-doc feature does not handle this correctly [MultipleThreadAnnotations]", 441 docStubs = true, 442 stubFiles = 443 arrayOf( 444 java( 445 """ 446 package test.pkg; 447 @SuppressWarnings({"unchecked", "deprecation", "all"}) 448 public class RangeTest { 449 public RangeTest() { throw new RuntimeException("Stub!"); } 450 /** 451 * This method must be called on the thread that originally created 452 * this UI element. This is typically the main thread of your app. 453 * <br> 454 * This method may take several seconds to complete, so it should 455 * only be called from a worker thread. 456 */ 457 public int test1() { throw new RuntimeException("Stub!"); } 458 } 459 """ 460 ) 461 ) 462 ) 463 } 464 465 @Test Merge Multiple sectionsnull466 fun `Merge Multiple sections`() { 467 check( 468 expectedIssues = 469 "src/android/widget/Toolbar2.java:18: error: Documentation should not specify @apiSince manually; it's computed and injected at build time by metalava [ForbiddenTag]", 470 expectedFail = DefaultLintErrorMessage, 471 sourceFiles = 472 arrayOf( 473 java( 474 """ 475 package android.widget; 476 import androidx.annotation.UiThread; 477 478 public class Toolbar2 { 479 /** 480 * Existing documentation for {@linkplain #getCurrentContentInsetEnd()} here. 481 * @return blah blah blah 482 */ 483 @UiThread 484 public int getCurrentContentInsetEnd() { 485 return 0; 486 } 487 488 /** 489 * @apiSince 15 490 */ 491 @UiThread 492 public int getCurrentContentInsetRight() { 493 return 0; 494 } 495 } 496 """ 497 ), 498 uiThreadSource 499 ), 500 checkCompilation = true, 501 docStubs = true, 502 applyApiLevelsXml = 503 """ 504 <?xml version="1.0" encoding="utf-8"?> 505 <api version="2"> 506 <class name="android/widget/Toolbar2" since="21"> 507 <method name="<init>(Landroid/content/Context;)V"/> 508 <method name="collapseActionView()V"/> 509 <method name="getContentInsetStartWithNavigation()I" since="24"/> 510 <method name="getCurrentContentInsetEnd()I" since="24"/> 511 <method name="getCurrentContentInsetLeft()I" since="24"/> 512 <method name="getCurrentContentInsetRight()I" since="24"/> 513 <method name="getCurrentContentInsetStart()I" since="24"/> 514 </class> 515 </api> 516 """, 517 stubFiles = 518 arrayOf( 519 java( 520 """ 521 package android.widget; 522 /** @apiSince 21 */ 523 @SuppressWarnings({"unchecked", "deprecation", "all"}) 524 public class Toolbar2 { 525 public Toolbar2() { throw new RuntimeException("Stub!"); } 526 /** 527 * Existing documentation for {@linkplain #getCurrentContentInsetEnd()} here. 528 * <br> 529 * This method must be called on the thread that originally created 530 * this UI element. This is typically the main thread of your app. 531 * @return blah blah blah 532 * @apiSince 24 533 */ 534 public int getCurrentContentInsetEnd() { throw new RuntimeException("Stub!"); } 535 /** 536 * <br> 537 * This method must be called on the thread that originally created 538 * this UI element. This is typically the main thread of your app. 539 * @apiSince 15 540 */ 541 public int getCurrentContentInsetRight() { throw new RuntimeException("Stub!"); } 542 } 543 """ 544 ) 545 ) 546 ) 547 } 548 549 @Test Create method documentation from nothingnull550 fun `Create method documentation from nothing`() { 551 check( 552 sourceFiles = 553 arrayOf( 554 java( 555 """ 556 package test.pkg; 557 import android.annotation.RequiresPermission; 558 @SuppressWarnings("WeakerAccess") 559 public class RangeTest { 560 public static final String ACCESS_COARSE_LOCATION = "android.permission.ACCESS_COARSE_LOCATION"; 561 @RequiresPermission(ACCESS_COARSE_LOCATION) 562 public void test1() { 563 } 564 } 565 """ 566 ), 567 requiresPermissionSource 568 ), 569 checkCompilation = true, 570 docStubs = true, 571 stubFiles = 572 arrayOf( 573 java( 574 """ 575 package test.pkg; 576 @SuppressWarnings({"unchecked", "deprecation", "all"}) 577 public class RangeTest { 578 public RangeTest() { throw new RuntimeException("Stub!"); } 579 /** 580 * Requires {@link test.pkg.RangeTest#ACCESS_COARSE_LOCATION} 581 */ 582 public void test1() { throw new RuntimeException("Stub!"); } 583 public static final java.lang.String ACCESS_COARSE_LOCATION = "android.permission.ACCESS_COARSE_LOCATION"; 584 } 585 """ 586 ) 587 ) 588 ) 589 } 590 591 @Test Warn about missing fieldnull592 fun `Warn about missing field`() { 593 check( 594 sourceFiles = 595 arrayOf( 596 java( 597 """ 598 package test.pkg; 599 import android.annotation.RequiresPermission; 600 public class RangeTest { 601 @RequiresPermission("MyPermission") 602 public void test1() { 603 } 604 } 605 """ 606 ), 607 requiresPermissionSource 608 ), 609 checkCompilation = true, 610 docStubs = true, 611 expectedFail = DefaultLintErrorMessage, 612 expectedIssues = 613 "src/test/pkg/RangeTest.java:5: error: Cannot find permission field for \"MyPermission\" required by method test.pkg.RangeTest.test1() (may be hidden or removed) [MissingPermission]", 614 stubFiles = 615 arrayOf( 616 java( 617 """ 618 package test.pkg; 619 @SuppressWarnings({"unchecked", "deprecation", "all"}) 620 public class RangeTest { 621 public RangeTest() { throw new RuntimeException("Stub!"); } 622 /** 623 * Requires "MyPermission" 624 */ 625 public void test1() { throw new RuntimeException("Stub!"); } 626 } 627 """ 628 ) 629 ) 630 ) 631 } 632 633 @Test Add to existing single-line method documentationnull634 fun `Add to existing single-line method documentation`() { 635 check( 636 sourceFiles = 637 arrayOf( 638 java( 639 """ 640 package test.pkg; 641 import android.annotation.RequiresPermission; 642 @SuppressWarnings("WeakerAccess") 643 public class RangeTest { 644 public static final String ACCESS_COARSE_LOCATION = "android.permission.ACCESS_COARSE_LOCATION"; 645 /** This is the existing documentation. */ 646 @RequiresPermission(ACCESS_COARSE_LOCATION) 647 public int test1() { } 648 } 649 """ 650 ), 651 requiresPermissionSource 652 ), 653 checkCompilation = true, 654 docStubs = true, 655 stubFiles = 656 arrayOf( 657 java( 658 """ 659 package test.pkg; 660 @SuppressWarnings({"unchecked", "deprecation", "all"}) 661 public class RangeTest { 662 public RangeTest() { throw new RuntimeException("Stub!"); } 663 /** 664 * This is the existing documentation. 665 * <br> 666 * Requires {@link test.pkg.RangeTest#ACCESS_COARSE_LOCATION} 667 */ 668 public int test1() { throw new RuntimeException("Stub!"); } 669 public static final java.lang.String ACCESS_COARSE_LOCATION = "android.permission.ACCESS_COARSE_LOCATION"; 670 } 671 """ 672 ) 673 ) 674 ) 675 } 676 677 @Test Add to existing multi-line method documentationnull678 fun `Add to existing multi-line method documentation`() { 679 check( 680 sourceFiles = 681 arrayOf( 682 java( 683 """ 684 package test.pkg; 685 import android.annotation.RequiresPermission; 686 @SuppressWarnings("WeakerAccess") 687 public class RangeTest { 688 public static final String ACCESS_COARSE_LOCATION = "android.permission.ACCESS_COARSE_LOCATION"; 689 /** 690 * This is the existing documentation. 691 * Multiple lines of it. 692 */ 693 @RequiresPermission(ACCESS_COARSE_LOCATION) 694 public int test1() { } 695 } 696 """ 697 ), 698 requiresPermissionSource 699 ), 700 checkCompilation = true, 701 docStubs = true, 702 stubFiles = 703 arrayOf( 704 java( 705 """ 706 package test.pkg; 707 @SuppressWarnings({"unchecked", "deprecation", "all"}) 708 public class RangeTest { 709 public RangeTest() { throw new RuntimeException("Stub!"); } 710 /** 711 * This is the existing documentation. 712 * Multiple lines of it. 713 * <br> 714 * Requires {@link test.pkg.RangeTest#ACCESS_COARSE_LOCATION} 715 */ 716 public int test1() { throw new RuntimeException("Stub!"); } 717 public static final java.lang.String ACCESS_COARSE_LOCATION = "android.permission.ACCESS_COARSE_LOCATION"; 718 } 719 """ 720 ) 721 ) 722 ) 723 } 724 725 @Test Add to method when there are existing parameter docs and appear before thesenull726 fun `Add to method when there are existing parameter docs and appear before these`() { 727 check( 728 sourceFiles = 729 arrayOf( 730 java( 731 """ 732 package test.pkg; 733 import android.annotation.RequiresPermission; 734 @SuppressWarnings("WeakerAccess") 735 public class RangeTest { 736 public static final String ACCESS_COARSE_LOCATION = "android.permission.ACCESS_COARSE_LOCATION"; 737 /** 738 * This is the existing documentation. 739 * @param parameter1 docs for parameter1 740 * @param parameter2 docs for parameter2 741 * @param parameter3 docs for parameter2 742 * @return return value documented here 743 */ 744 @RequiresPermission(ACCESS_COARSE_LOCATION) 745 public int test1(int parameter1, int parameter2, int parameter3) { } 746 } 747 """ 748 ), 749 requiresPermissionSource 750 ), 751 docStubs = true, 752 checkCompilation = true, 753 stubFiles = 754 arrayOf( 755 java( 756 """ 757 package test.pkg; 758 @SuppressWarnings({"unchecked", "deprecation", "all"}) 759 public class RangeTest { 760 public RangeTest() { throw new RuntimeException("Stub!"); } 761 /** 762 * This is the existing documentation. 763 * <br> 764 * Requires {@link test.pkg.RangeTest#ACCESS_COARSE_LOCATION} 765 * @param parameter1 docs for parameter1 766 * @param parameter2 docs for parameter2 767 * @param parameter3 docs for parameter2 768 * @return return value documented here 769 */ 770 public int test1(int parameter1, int parameter2, int parameter3) { throw new RuntimeException("Stub!"); } 771 public static final java.lang.String ACCESS_COARSE_LOCATION = "android.permission.ACCESS_COARSE_LOCATION"; 772 } 773 """ 774 ) 775 ) 776 ) 777 } 778 779 @Test test documentation trim utilitynull780 fun `test documentation trim utility`() { 781 Assert.assertEquals( 782 "/**\n * This is a comment\n * This is a second comment\n */", 783 trimDocIndent( 784 """/** 785 * This is a comment 786 * This is a second comment 787 */ 788 """ 789 .trimIndent() 790 ) 791 ) 792 } 793 794 @Test Merge deprecation levelsnull795 fun `Merge deprecation levels`() { 796 check( 797 sourceFiles = 798 arrayOf( 799 java( 800 """ 801 package android.hardware; 802 /** 803 * The Camera class is used to set image capture settings, start/stop preview. 804 * 805 * @deprecated We recommend using the new {@link android.hardware.camera2} API for new 806 * applications.* 807 */ 808 @Deprecated 809 public class Camera { 810 /** @deprecated Use something else. */ 811 public static final String ACTION_NEW_VIDEO = "android.hardware.action.NEW_VIDEO"; 812 } 813 """ 814 ) 815 ), 816 applyApiLevelsXml = 817 """ 818 <?xml version="1.0" encoding="utf-8"?> 819 <api version="2"> 820 <class name="android/hardware/Camera" since="1" deprecated="21"> 821 <method name="<init>()V"/> 822 <method name="addCallbackBuffer([B)V" since="8"/> 823 <method name="getLogo()Landroid/graphics/drawable/Drawable;"/> 824 <field name="ACTION_NEW_VIDEO" since="14" deprecated="19"/> 825 </class> 826 </api> 827 """, 828 checkCompilation = true, 829 docStubs = true, 830 stubFiles = 831 arrayOf( 832 java( 833 """ 834 package android.hardware; 835 /** 836 * The Camera class is used to set image capture settings, start/stop preview. 837 * 838 * @deprecated We recommend using the new {@link android.hardware.camera2} API for new 839 * applications.* 840 * @apiSince 1 841 * @deprecatedSince 21 842 */ 843 @SuppressWarnings({"unchecked", "deprecation", "all"}) 844 @Deprecated 845 public class Camera { 846 @Deprecated 847 public Camera() { throw new RuntimeException("Stub!"); } 848 /** 849 * @deprecated Use something else. 850 * @apiSince 14 851 * @deprecatedSince 19 852 */ 853 @Deprecated public static final java.lang.String ACTION_NEW_VIDEO = "android.hardware.action.NEW_VIDEO"; 854 } 855 """ 856 ) 857 ) 858 ) 859 } 860 861 @Test Api levels around current and previewnull862 fun `Api levels around current and preview`() { 863 check( 864 extraArguments = 865 arrayOf( 866 ARG_CURRENT_CODENAME, 867 "Z", 868 ARG_CURRENT_VERSION, 869 "35" // not real api level of Z 870 ), 871 includeSystemApiAnnotations = true, 872 sourceFiles = 873 arrayOf( 874 java( 875 """ 876 package android.pkg; 877 import android.annotation.SystemApi; 878 public class Test { 879 public static final String UNIT_TEST_1 = "unit.test.1"; 880 /** 881 * @hide 882 */ 883 @SystemApi 884 public static final String UNIT_TEST_2 = "unit.test.2"; 885 } 886 """ 887 ), 888 systemApiSource 889 ), 890 applyApiLevelsXml = 891 """ 892 <?xml version="1.0" encoding="utf-8"?> 893 <api version="2"> 894 <class name="android/pkg/Test" since="1"> 895 <field name="UNIT_TEST_1" since="35"/> 896 <field name="UNIT_TEST_2" since="36"/> 897 </class> 898 </api> 899 """, 900 checkCompilation = true, 901 docStubs = true, 902 stubFiles = 903 arrayOf( 904 java( 905 """ 906 package android.pkg; 907 /** @apiSince 1 */ 908 @SuppressWarnings({"unchecked", "deprecation", "all"}) 909 public class Test { 910 public Test() { throw new RuntimeException("Stub!"); } 911 /** @apiSince 35 */ 912 public static final java.lang.String UNIT_TEST_1 = "unit.test.1"; 913 /** 914 * @hide 915 */ 916 public static final java.lang.String UNIT_TEST_2 = "unit.test.2"; 917 } 918 """ 919 ) 920 ) 921 ) 922 } 923 924 @Test Api levels current codename but no current versionnull925 fun `Api levels current codename but no current version`() { 926 check( 927 extraArguments = 928 arrayOf( 929 ARG_CURRENT_CODENAME, 930 "Z", 931 ), 932 includeSystemApiAnnotations = true, 933 sourceFiles = 934 arrayOf( 935 java( 936 """ 937 package android.pkg; 938 public class Test { 939 public static final String UNIT_TEST_1 = "unit.test.1"; 940 public static final String UNIT_TEST_2 = "unit.test.2"; 941 } 942 """ 943 ), 944 ), 945 applyApiLevelsXml = 946 """ 947 <?xml version="1.0" encoding="utf-8"?> 948 <api version="2"> 949 <class name="android/pkg/Test" since="1"> 950 <field name="UNIT_TEST_1" since="24" deprecated="30"/> 951 <field name="UNIT_TEST_2" since="36"/> 952 </class> 953 </api> 954 """, 955 checkCompilation = true, 956 docStubs = true, 957 stubFiles = 958 arrayOf( 959 java( 960 """ 961 package android.pkg; 962 /** @apiSince 1 */ 963 @SuppressWarnings({"unchecked", "deprecation", "all"}) 964 public class Test { 965 public Test() { throw new RuntimeException("Stub!"); } 966 /** 967 * @apiSince 24 968 * @deprecatedSince 30 969 */ 970 public static final java.lang.String UNIT_TEST_1 = "unit.test.1"; 971 /** @apiSince 36 */ 972 public static final java.lang.String UNIT_TEST_2 = "unit.test.2"; 973 } 974 """ 975 ) 976 ) 977 ) 978 } 979 980 @Test No api levels on SystemApi only elementsnull981 fun `No api levels on SystemApi only elements`() { 982 // @SystemApi, @TestApi etc cannot get api versions since we don't have 983 // accurate android.jar files (or even reliable api.txt/api.xml files) for them. 984 check( 985 extraArguments = 986 arrayOf( 987 ARG_CURRENT_CODENAME, 988 "Z", 989 ARG_CURRENT_VERSION, 990 "35" // not real api level of Z 991 ), 992 sourceFiles = 993 arrayOf( 994 java( 995 """ 996 package android.pkg; 997 public class Test { 998 public Test(int i) { } 999 public static final String UNIT_TEST_1 = "unit.test.1"; 1000 public static final String UNIT_TEST_2 = "unit.test.2"; 1001 } 1002 """ 1003 ) 1004 ), 1005 applyApiLevelsXml = 1006 """ 1007 <?xml version="1.0" encoding="utf-8"?> 1008 <api version="2"> 1009 <class name="android/pkg/Test" since="1"> 1010 <method name="<init>(I)V"/> 1011 <field name="UNIT_TEST_1" since="35"/> 1012 <field name="UNIT_TEST_2" since="36"/> 1013 </class> 1014 </api> 1015 """, 1016 checkCompilation = true, 1017 docStubs = true, 1018 stubFiles = 1019 arrayOf( 1020 java( 1021 """ 1022 package android.pkg; 1023 /** @apiSince 1 */ 1024 @SuppressWarnings({"unchecked", "deprecation", "all"}) 1025 public class Test { 1026 /** @apiSince 1 */ 1027 public Test(int i) { throw new RuntimeException("Stub!"); } 1028 /** @apiSince 35 */ 1029 public static final java.lang.String UNIT_TEST_1 = "unit.test.1"; 1030 /** @apiSince Z */ 1031 public static final java.lang.String UNIT_TEST_2 = "unit.test.2"; 1032 } 1033 """ 1034 ) 1035 ) 1036 ) 1037 } 1038 1039 @Test Generate API level javadocsnull1040 fun `Generate API level javadocs`() { 1041 // TODO: Check package-info.java conflict 1042 // TODO: Test merging 1043 // TODO: Test non-merging 1044 check( 1045 extraArguments = 1046 arrayOf( 1047 ARG_CURRENT_CODENAME, 1048 "Z", 1049 ARG_CURRENT_VERSION, 1050 "35" // not real api level of Z 1051 ), 1052 sourceFiles = 1053 arrayOf( 1054 java( 1055 """ 1056 package android.pkg1; 1057 public class Test1 { 1058 } 1059 """ 1060 ), 1061 java( 1062 """ 1063 package android.pkg1; 1064 public class Test2 { 1065 } 1066 """ 1067 ), 1068 TestFiles.source( 1069 "src/android/pkg2/package.html", 1070 """ 1071 <body bgcolor="white"> 1072 Some existing doc here. 1073 @deprecated 1074 <!-- comment --> 1075 </body> 1076 """ 1077 ) 1078 .indented(), 1079 java( 1080 """ 1081 package android.pkg2; 1082 public class Test1 { 1083 } 1084 """ 1085 ), 1086 java( 1087 """ 1088 package android.pkg2; 1089 public class Test2 { 1090 } 1091 """ 1092 ), 1093 java( 1094 """ 1095 package android.pkg3; 1096 public class Test1 { 1097 } 1098 """ 1099 ) 1100 ), 1101 applyApiLevelsXml = 1102 """ 1103 <?xml version="1.0" encoding="utf-8"?> 1104 <api version="2"> 1105 <class name="android/pkg1/Test1" since="15"/> 1106 <class name="android/pkg3/Test1" since="20"/> 1107 </api> 1108 """, 1109 checkCompilation = true, 1110 docStubs = true, 1111 stubFiles = 1112 arrayOf( 1113 java( 1114 """ 1115 package android.pkg1; 1116 /** @apiSince 15 */ 1117 @SuppressWarnings({"unchecked", "deprecation", "all"}) 1118 public class Test1 { 1119 public Test1() { throw new RuntimeException("Stub!"); } 1120 } 1121 """ 1122 ), 1123 java( 1124 """ 1125 /** @apiSince 15 */ 1126 package android.pkg1; 1127 """ 1128 ), 1129 java( 1130 """ 1131 /** 1132 * Some existing doc here. 1133 * @deprecated 1134 * <!-- comment --> 1135 */ 1136 package android.pkg2; 1137 """ 1138 ), 1139 java( 1140 """ 1141 /** @apiSince 20 */ 1142 package android.pkg3; 1143 """ 1144 ) 1145 ), 1146 ) 1147 } 1148 1149 object SdkExtSinceConstants { 1150 val sourceFiles = 1151 arrayOf( 1152 java( 1153 """ 1154 package android.pkg; 1155 public class Test { 1156 public static final String UNIT_TEST_1 = "unit.test.1"; 1157 public static final String UNIT_TEST_2 = "unit.test.2"; 1158 public static final String UNIT_TEST_3 = "unit.test.3"; 1159 public Test() {} 1160 public void foo() {} 1161 public class Inner { 1162 public Inner() {} 1163 public static final boolean UNIT_TEST_4 = true; 1164 } 1165 } 1166 """ 1167 ) 1168 ) 1169 1170 const val apiVersionsXml = 1171 """ 1172 <?xml version="1.0" encoding="utf-8"?> 1173 <api version="3"> 1174 <sdk id="30" shortname="R-ext" name="R Extensions" reference="android/os/Build${'$'}VERSION_CODES${'$'}R" /> 1175 <sdk id="31" shortname="S-ext" name="S Extensions" reference="android/os/Build${'$'}VERSION_CODES${'$'}S" /> 1176 <sdk id="33" shortname="T-ext" name="T Extensions" reference="android/os/Build${'$'}VERSION_CODES${'$'}T" /> 1177 <sdk id="1000000" shortname="standalone-ext" name="Standalone Extensions" reference="some/other/CONST" /> 1178 <class name="android/pkg/Test" since="1" sdks="0:1,30:2,31:2,33:2"> 1179 <method name="foo()V"/> 1180 <method name="<init>()V"/> 1181 <field name="UNIT_TEST_1"/> 1182 <field name="UNIT_TEST_2" since="2" sdks="1000000:3,31:3,33:3,0:2"/> 1183 <!-- 1184 ! TODO(b/283062196) - This relies on an api-versions.xml structure that is 1185 ! not yet created. If the resolution of this bug is to not support this 1186 ! structure then this test will need updating. 1187 !--> 1188 <field name="UNIT_TEST_3" since="31" sdks="1000000:4,0:31"/> 1189 </class> 1190 <class name="android/pkg/Test${'$'}Inner" since="1" sdks="0:1,30:2,31:2,33:2"> 1191 <method name="<init>()V"/> 1192 <field name="UNIT_TEST_4"/> 1193 </class> 1194 </api> 1195 """ 1196 } 1197 1198 @Test @sdkExtSince (finalized, no codename)null1199 fun `@sdkExtSince (finalized, no codename)`() { 1200 check( 1201 extraArguments = 1202 arrayOf( 1203 ARG_CURRENT_VERSION, 1204 "30", 1205 ), 1206 sourceFiles = SdkExtSinceConstants.sourceFiles, 1207 applyApiLevelsXml = SdkExtSinceConstants.apiVersionsXml, 1208 checkCompilation = true, 1209 docStubs = true, 1210 stubFiles = 1211 arrayOf( 1212 java( 1213 """ 1214 package android.pkg; 1215 /** 1216 * @apiSince 1 1217 * @sdkExtSince R Extensions 2 1218 */ 1219 @SuppressWarnings({"unchecked", "deprecation", "all"}) 1220 public class Test { 1221 /** 1222 * @apiSince 1 1223 * @sdkExtSince R Extensions 2 1224 */ 1225 public Test() { throw new RuntimeException("Stub!"); } 1226 /** 1227 * @apiSince 1 1228 * @sdkExtSince R Extensions 2 1229 */ 1230 public void foo() { throw new RuntimeException("Stub!"); } 1231 /** 1232 * @apiSince 1 1233 * @sdkExtSince R Extensions 2 1234 */ 1235 public static final java.lang.String UNIT_TEST_1 = "unit.test.1"; 1236 /** 1237 * @apiSince 2 1238 * @sdkExtSince Standalone Extensions 3 1239 */ 1240 public static final java.lang.String UNIT_TEST_2 = "unit.test.2"; 1241 /** @sdkExtSince Standalone Extensions 4 */ 1242 public static final java.lang.String UNIT_TEST_3 = "unit.test.3"; 1243 /** 1244 * @apiSince 1 1245 * @sdkExtSince R Extensions 2 1246 */ 1247 @SuppressWarnings({"unchecked", "deprecation", "all"}) 1248 public class Inner { 1249 /** 1250 * @apiSince 1 1251 * @sdkExtSince R Extensions 2 1252 */ 1253 public Inner() { throw new RuntimeException("Stub!"); } 1254 /** 1255 * @apiSince 1 1256 * @sdkExtSince R Extensions 2 1257 */ 1258 public static final boolean UNIT_TEST_4 = true; 1259 } 1260 } 1261 """ 1262 ) 1263 ) 1264 ) 1265 } 1266 1267 @Test @sdkExtSince (not finalized)null1268 fun `@sdkExtSince (not finalized)`() { 1269 check( 1270 sourceFiles = SdkExtSinceConstants.sourceFiles, 1271 applyApiLevelsXml = SdkExtSinceConstants.apiVersionsXml, 1272 checkCompilation = true, 1273 docStubs = true, 1274 stubFiles = 1275 arrayOf( 1276 java( 1277 """ 1278 package android.pkg; 1279 /** 1280 * @apiSince 1 1281 * @sdkExtSince R Extensions 2 1282 */ 1283 @SuppressWarnings({"unchecked", "deprecation", "all"}) 1284 public class Test { 1285 /** 1286 * @apiSince 1 1287 * @sdkExtSince R Extensions 2 1288 */ 1289 public Test() { throw new RuntimeException("Stub!"); } 1290 /** 1291 * @apiSince 1 1292 * @sdkExtSince R Extensions 2 1293 */ 1294 public void foo() { throw new RuntimeException("Stub!"); } 1295 /** 1296 * @apiSince 1 1297 * @sdkExtSince R Extensions 2 1298 */ 1299 public static final java.lang.String UNIT_TEST_1 = "unit.test.1"; 1300 /** 1301 * @apiSince 2 1302 * @sdkExtSince Standalone Extensions 3 1303 */ 1304 public static final java.lang.String UNIT_TEST_2 = "unit.test.2"; 1305 /** 1306 * @apiSince 31 1307 * @sdkExtSince Standalone Extensions 4 1308 */ 1309 public static final java.lang.String UNIT_TEST_3 = "unit.test.3"; 1310 /** 1311 * @apiSince 1 1312 * @sdkExtSince R Extensions 2 1313 */ 1314 @SuppressWarnings({"unchecked", "deprecation", "all"}) 1315 public class Inner { 1316 /** 1317 * @apiSince 1 1318 * @sdkExtSince R Extensions 2 1319 */ 1320 public Inner() { throw new RuntimeException("Stub!"); } 1321 /** 1322 * @apiSince 1 1323 * @sdkExtSince R Extensions 2 1324 */ 1325 public static final boolean UNIT_TEST_4 = true; 1326 } 1327 } 1328 """ 1329 ) 1330 ) 1331 ) 1332 } 1333 1334 @Test Generate overview html docsnull1335 fun `Generate overview html docs`() { 1336 // If a codebase provides overview.html files in the a public package, 1337 // make sure that we include this in the exported stubs folder as well! 1338 check( 1339 sourceFiles = 1340 arrayOf( 1341 TestFiles.source("src/overview.html", "<html>My overview docs</html>"), 1342 TestFiles.source( 1343 "src/foo/test/visible/package.html", 1344 """ 1345 <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 3.2 Final//EN"> 1346 <!-- not a body tag: <body> --> 1347 <html> 1348 <body bgcolor="white"> 1349 My package docs<br> 1350 <!-- comment --> 1351 Sample code: /** code here */ 1352 Another line.<br> 1353 </BODY> 1354 </html> 1355 """ 1356 ) 1357 .indented(), 1358 java( 1359 // Note that we're *deliberately* placing the source file in the wrong 1360 // source root here. This is to simulate the scenario where the source 1361 // root (--source-path) points to a parent of the source folder instead 1362 // of the source folder instead. In this case, we need to try a bit harder 1363 // to compute the right package name; metalava has some code for that. 1364 // This is a regression test for b/144264106. 1365 "src/foo/test/visible/MyClass.java", 1366 """ 1367 package test.visible; 1368 public class MyClass { 1369 public void test() { } 1370 } 1371 """ 1372 ), 1373 // Also test hiding classes via javadoc 1374 TestFiles.source( 1375 "src/foo/test/hidden1/package.html", 1376 """ 1377 <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 3.2 Final//EN"> 1378 <html> 1379 <body> 1380 @hide 1381 This is a hidden package 1382 </body> 1383 </html> 1384 """ 1385 ) 1386 .indented(), 1387 java( 1388 "src/foo/test/hidden1/Hidden.java", 1389 """ 1390 package test.hidden1; 1391 public class Hidden { 1392 public void test() { } 1393 } 1394 """ 1395 ), 1396 // Also test hiding classes via package-info.java 1397 java( 1398 """ 1399 /** 1400 * My package docs<br> 1401 * @hide 1402 */ 1403 package test.hidden2; 1404 """ 1405 ) 1406 .indented(), 1407 java( 1408 """ 1409 package test.hidden2; 1410 public class Hidden { 1411 public void test() { } 1412 } 1413 """ 1414 ) 1415 ), 1416 docStubs = true, 1417 // Make sure we expose exactly what we intend (so @hide via javadocs and 1418 // via package-info.java works) 1419 api = 1420 """ 1421 package test.visible { 1422 public class MyClass { 1423 ctor public MyClass(); 1424 method public void test(); 1425 } 1426 } 1427 """, 1428 // Make sure the stubs are generated correctly; in particular, that we've 1429 // pulled docs from overview.html into javadoc on package-info.java instead 1430 // (removing all the content surrounding <body>, etc) 1431 stubFiles = 1432 arrayOf( 1433 TestFiles.source("overview.html", "<html>My overview docs</html>"), 1434 java( 1435 """ 1436 /** 1437 * My package docs<br> 1438 * <!-- comment --> 1439 * Sample code: /** code here */ 1440 * Another line.<br> 1441 */ 1442 package test.visible; 1443 """ 1444 ), 1445 java( 1446 """ 1447 package test.visible; 1448 @SuppressWarnings({"unchecked", "deprecation", "all"}) 1449 public class MyClass { 1450 public MyClass() { throw new RuntimeException("Stub!"); } 1451 public void test() { throw new RuntimeException("Stub!"); } 1452 } 1453 """ 1454 ) 1455 ) 1456 ) 1457 } 1458 1459 @Test Check RequiresApi handlingnull1460 fun `Check RequiresApi handling`() { 1461 check( 1462 sourceFiles = 1463 arrayOf( 1464 java( 1465 """ 1466 package test.pkg; 1467 import androidx.annotation.RequiresApi; 1468 @RequiresApi(value = 21) 1469 public class MyClass1 { 1470 } 1471 """ 1472 ), 1473 requiresApiSource 1474 ), 1475 docStubs = true, 1476 checkCompilation = false, // duplicate class: androidx.annotation.RequiresApi 1477 stubFiles = 1478 arrayOf( 1479 java( 1480 """ 1481 package test.pkg; 1482 /** @apiSince 21 */ 1483 @SuppressWarnings({"unchecked", "deprecation", "all"}) 1484 public class MyClass1 { 1485 public MyClass1() { throw new RuntimeException("Stub!"); } 1486 } 1487 """ 1488 ) 1489 ) 1490 ) 1491 } 1492 1493 @RequiresCapabilities(Capability.KOTLIN) 1494 @Test Include Kotlin deprecation textnull1495 fun `Include Kotlin deprecation text`() { 1496 check( 1497 sourceFiles = 1498 arrayOf( 1499 kotlin( 1500 """ 1501 package test.pkg 1502 1503 @Suppress("DeprecatedCallableAddReplaceWith","EqualsOrHashCode") 1504 @Deprecated("Use Jetpack preference library", level = DeprecationLevel.ERROR) 1505 class Foo { 1506 fun foo() 1507 1508 @Deprecated("Blah blah blah 1", level = DeprecationLevel.ERROR) 1509 override fun toString(): String = "Hello World" 1510 1511 /** 1512 * My description 1513 * @deprecated Existing deprecation message. 1514 */ 1515 @Deprecated("Blah blah blah 2", level = DeprecationLevel.ERROR) 1516 override fun hashCode(): Int = 0 1517 } 1518 1519 """ 1520 ) 1521 ), 1522 checkCompilation = true, 1523 docStubs = true, 1524 stubFiles = 1525 arrayOf( 1526 java( 1527 """ 1528 package test.pkg; 1529 /** 1530 * @deprecated Use Jetpack preference library 1531 */ 1532 @SuppressWarnings({"unchecked", "deprecation", "all"}) 1533 @Deprecated 1534 public final class Foo { 1535 @Deprecated 1536 public Foo() { throw new RuntimeException("Stub!"); } 1537 @Deprecated 1538 public void foo() { throw new RuntimeException("Stub!"); } 1539 /** 1540 * My description 1541 * @deprecated Existing deprecation message. 1542 * Blah blah blah 2 1543 */ 1544 @Deprecated 1545 public int hashCode() { throw new RuntimeException("Stub!"); } 1546 /** 1547 * {@inheritDoc} 1548 * @deprecated Blah blah blah 1 1549 */ 1550 @Deprecated 1551 @androidx.annotation.NonNull 1552 public java.lang.String toString() { throw new RuntimeException("Stub!"); } 1553 } 1554 """ 1555 ) 1556 ) 1557 ) 1558 } 1559 1560 @Test Annotation annotating selfnull1561 fun `Annotation annotating self`() { 1562 check( 1563 sourceFiles = 1564 arrayOf( 1565 java( 1566 """ 1567 package test.pkg; 1568 import java.lang.annotation.Retention; 1569 import java.lang.annotation.RetentionPolicy; 1570 /** 1571 * Documentation here 1572 */ 1573 @SuppressWarnings("WeakerAccess") 1574 @MyAnnotation 1575 @Retention(RetentionPolicy.SOURCE) 1576 public @interface MyAnnotation { 1577 } 1578 """ 1579 ), 1580 java( 1581 """ 1582 package test.pkg; 1583 1584 /** 1585 * Other documentation here 1586 */ 1587 @SuppressWarnings("WeakerAccess") 1588 @MyAnnotation 1589 public class OtherClass { 1590 } 1591 """ 1592 ) 1593 ), 1594 checkCompilation = true, 1595 stubFiles = 1596 arrayOf( 1597 java( 1598 """ 1599 package test.pkg; 1600 /** 1601 * Documentation here 1602 */ 1603 @SuppressWarnings({"unchecked", "deprecation", "all"}) 1604 @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.SOURCE) 1605 public @interface MyAnnotation { 1606 } 1607 """ 1608 ), 1609 java( 1610 """ 1611 package test.pkg; 1612 /** 1613 * Other documentation here 1614 */ 1615 @SuppressWarnings({"unchecked", "deprecation", "all"}) 1616 public class OtherClass { 1617 public OtherClass() { throw new RuntimeException("Stub!"); } 1618 } 1619 """ 1620 ) 1621 ) 1622 ) 1623 } 1624 1625 @Test Annotation annotating itself indirectlynull1626 fun `Annotation annotating itself indirectly`() { 1627 check( 1628 sourceFiles = 1629 arrayOf( 1630 java( 1631 """ 1632 package test.pkg; 1633 1634 /** 1635 * Documentation 1 here 1636 */ 1637 @SuppressWarnings("WeakerAccess") 1638 @MyAnnotation2 1639 public @interface MyAnnotation1 { 1640 } 1641 """ 1642 ), 1643 java( 1644 """ 1645 package test.pkg; 1646 1647 /** 1648 * Documentation 2 here 1649 */ 1650 @SuppressWarnings("WeakerAccess") 1651 @MyAnnotation1 1652 public @interface MyAnnotation2 { 1653 } 1654 """ 1655 ) 1656 ), 1657 checkCompilation = true, 1658 stubFiles = 1659 arrayOf( 1660 java( 1661 """ 1662 package test.pkg; 1663 /** 1664 * Documentation 1 here 1665 */ 1666 @SuppressWarnings({"unchecked", "deprecation", "all"}) 1667 @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.CLASS) 1668 @test.pkg.MyAnnotation2 1669 public @interface MyAnnotation1 { 1670 } 1671 """ 1672 ), 1673 java( 1674 """ 1675 package test.pkg; 1676 /** 1677 * Documentation 2 here 1678 */ 1679 @SuppressWarnings({"unchecked", "deprecation", "all"}) 1680 @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.CLASS) 1681 @test.pkg.MyAnnotation1 1682 public @interface MyAnnotation2 { 1683 } 1684 """ 1685 ) 1686 ) 1687 ) 1688 } 1689 1690 @Test Test Column annotationnull1691 fun `Test Column annotation`() { 1692 // Bug: 120429729 1693 check( 1694 sourceFiles = 1695 arrayOf( 1696 java( 1697 """ 1698 package test.pkg; 1699 import android.provider.Column; 1700 import android.database.Cursor; 1701 @SuppressWarnings("WeakerAccess") 1702 public class ColumnTest { 1703 @Column(Cursor.FIELD_TYPE_STRING) 1704 public static final String DATA = "_data"; 1705 @Column(value = Cursor.FIELD_TYPE_BLOB, readOnly = true) 1706 public static final String HASH = "_hash"; 1707 @Column(value = Cursor.FIELD_TYPE_STRING, readOnly = true) 1708 public static final String TITLE = "title"; 1709 @Column(value = Cursor.NONEXISTENT, readOnly = true) 1710 public static final String BOGUS = "bogus"; 1711 } 1712 """ 1713 ), 1714 java( 1715 """ 1716 package android.database; 1717 public interface Cursor { 1718 int FIELD_TYPE_NULL = 0; 1719 int FIELD_TYPE_INTEGER = 1; 1720 int FIELD_TYPE_FLOAT = 2; 1721 int FIELD_TYPE_STRING = 3; 1722 int FIELD_TYPE_BLOB = 4; 1723 } 1724 """ 1725 ), 1726 columnSource 1727 ), 1728 checkCompilation = false, // stubs contain Cursor.NONEXISTENT so it does not compile 1729 expectedIssues = 1730 """ 1731 src/test/pkg/ColumnTest.java:13: warning: Cannot find feature field for Cursor.NONEXISTENT required by field ColumnTest.BOGUS (may be hidden or removed) [MissingColumn] 1732 """, 1733 docStubs = true, 1734 stubFiles = 1735 arrayOf( 1736 java( 1737 """ 1738 package test.pkg; 1739 import android.database.Cursor; 1740 @SuppressWarnings({"unchecked", "deprecation", "all"}) 1741 public class ColumnTest { 1742 public ColumnTest() { throw new RuntimeException("Stub!"); } 1743 /** 1744 * This constant represents a column name that can be used with a {@link android.content.ContentProvider} through a {@link android.content.ContentValues} or {@link android.database.Cursor} object. The values stored in this column are {@link Cursor.NONEXISTENT}, and are read-only and cannot be mutated. 1745 */ 1746 @android.provider.Column(value=Cursor.NONEXISTENT, readOnly=true) public static final java.lang.String BOGUS = "bogus"; 1747 /** 1748 * This constant represents a column name that can be used with a {@link android.content.ContentProvider} through a {@link android.content.ContentValues} or {@link android.database.Cursor} object. The values stored in this column are {@link android.database.Cursor#FIELD_TYPE_STRING Cursor#FIELD_TYPE_STRING} . 1749 */ 1750 @android.provider.Column(android.database.Cursor.FIELD_TYPE_STRING) public static final java.lang.String DATA = "_data"; 1751 /** 1752 * This constant represents a column name that can be used with a {@link android.content.ContentProvider} through a {@link android.content.ContentValues} or {@link android.database.Cursor} object. The values stored in this column are {@link android.database.Cursor#FIELD_TYPE_BLOB Cursor#FIELD_TYPE_BLOB} , and are read-only and cannot be mutated. 1753 */ 1754 @android.provider.Column(value=android.database.Cursor.FIELD_TYPE_BLOB, readOnly=true) public static final java.lang.String HASH = "_hash"; 1755 /** 1756 * This constant represents a column name that can be used with a {@link android.content.ContentProvider} through a {@link android.content.ContentValues} or {@link android.database.Cursor} object. The values stored in this column are {@link android.database.Cursor#FIELD_TYPE_STRING Cursor#FIELD_TYPE_STRING} , and are read-only and cannot be mutated. 1757 */ 1758 @android.provider.Column(value=android.database.Cursor.FIELD_TYPE_STRING, readOnly=true) public static final java.lang.String TITLE = "title"; 1759 } 1760 """ 1761 ) 1762 ) 1763 ) 1764 } 1765 1766 @Test memberDoc crashnull1767 fun `memberDoc crash`() { 1768 check( 1769 sourceFiles = 1770 arrayOf( 1771 java( 1772 """ 1773 package test.pkg; 1774 import java.lang.annotation.ElementType; 1775 import java.lang.annotation.Retention; 1776 import java.lang.annotation.RetentionPolicy; 1777 import java.lang.annotation.Target; 1778 /** 1779 * More text here 1780 * @memberDoc Important {@link another.pkg.Bar#BAR} 1781 * and here 1782 */ 1783 @Target({ ElementType.FIELD }) 1784 @Retention(RetentionPolicy.SOURCE) 1785 public @interface Foo { } 1786 """ 1787 ), 1788 java( 1789 """ 1790 package another.pkg; 1791 public class Bar { 1792 public String BAR = "BAAAAR"; 1793 } 1794 """ 1795 ), 1796 java( 1797 """ 1798 package yetonemore.pkg; 1799 public class Fun { 1800 @test.pkg.Foo 1801 public Fun() {} 1802 1803 /** 1804 * Separate comment 1805 */ 1806 @test.pkg.Foo 1807 public static final String FUN = "FUN"; 1808 } 1809 """ 1810 ) 1811 ), 1812 docStubs = true, 1813 stubFiles = 1814 arrayOf( 1815 java( 1816 """ 1817 package yetonemore.pkg; 1818 @SuppressWarnings({"unchecked", "deprecation", "all"}) 1819 public class Fun { 1820 /** 1821 * Important {@link another.pkg.Bar#BAR} 1822 * and here 1823 */ 1824 public Fun() { throw new RuntimeException("Stub!"); } 1825 /** 1826 * Separate comment 1827 * <br> 1828 * Important {@link another.pkg.Bar#BAR} 1829 * and here 1830 */ 1831 public static final java.lang.String FUN = "FUN"; 1832 } 1833 """ 1834 ) 1835 ) 1836 ) 1837 } 1838 } 1839