1 /* 2 * Copyright 2019 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 @file:Suppress("KDocUnresolvedReference", "UnstableApiUsage") 18 19 package androidx.build.lint 20 21 import com.android.tools.lint.checks.infrastructure.LintDetectorTest 22 import com.android.tools.lint.checks.infrastructure.ProjectDescription 23 import com.android.tools.lint.checks.infrastructure.TestFile 24 import com.android.tools.lint.checks.infrastructure.TestMode 25 import org.junit.Test 26 import org.junit.runner.RunWith 27 import org.junit.runners.JUnit4 28 29 /** 30 * Test for [SampledAnnotationDetector] 31 * 32 * This tests the following module setup: 33 * 34 * Module 'foo', which lives in foo Module 'foo:samples', which lives in foo/samples, and depends on 35 * 'foo' 36 */ 37 @RunWith(JUnit4::class) 38 class SampledAnnotationDetectorTest : LintDetectorTest() { 39 getDetectornull40 override fun getDetector() = SampledAnnotationDetector() 41 42 override fun getIssues() = 43 mutableListOf( 44 SampledAnnotationDetector.OBSOLETE_SAMPLED_ANNOTATION, 45 SampledAnnotationDetector.UNRESOLVED_SAMPLE_LINK, 46 SampledAnnotationDetector.MULTIPLE_FUNCTIONS_FOUND, 47 SampledAnnotationDetector.INVALID_SAMPLES_LOCATION 48 ) 49 50 private val fooModuleName = "foo" 51 private val sampleModuleName = "samples" 52 53 private val barFilePath = "src/foo/Bar.kt" 54 private val sampleFilePath = "samples/src/foo/samples/test.kt" 55 56 private val sampledStub = 57 compiled( 58 "libs/sampled.jar", 59 kotlin( 60 """ 61 package androidx.annotation 62 63 @Target(AnnotationTarget.FUNCTION) 64 @Retention(AnnotationRetention.SOURCE) 65 annotation class Sampled 66 """ 67 ) 68 .to("androidx/annotation/Sampled.kt"), 69 0x9dd4162c, 70 """ 71 META-INF/main.kotlin_module: 72 H4sIAAAAAAAAAGNgYGBmYGBgBGJWKM3AJcrFnZafr1ecmFuQk1osxBaSWlzi 73 XaLEoMUAAClM+YMvAAAA 74 """, 75 """ 76 androidx/annotation/Sampled.class: 77 H4sIAAAAAAAAAIVSyU4CQRB9NYggbriDuGv0Jki8cXLBSKJiEL1wapmOGWlm 78 CNOg3rj5Tx4M8ehHGatdgMNE+1D9qvq96qrqfv94eQWwj01CSrh203Psx7Rw 79 XU8L7Xhu+krUG0raERAhfi/aIq2Ee5cu3t7Lqo4gRFjtRwd0Bz0YQZiweFbz 80 tHLcQUpZNO+kzhEmhFLeg7S/Az5hK4DdT9jTRU+uL47KheIFYTlAUZJaugYx 81 NdwWqiUJO39mHlQMXxWvS0d5wtpZYIOD3O1/KJeecqpPOVNmILHX0UbweV7J 82 OmcqPzWkqew8Xz4tHhOmfps5l1rYQgs+tOrtED8oGcNzpxqHHh3jZRjZe4Rk 83 txONWQkrZsVT0bdnK9HtZK0MHXY7hpA1Rf7xEfgKEGI/3m5NG8drNavyxFE8 84 4GSpxS3X5Y3jO7dK9mfrb3N6DLF4GGZZWP+ya9jgvYow+I8hWgFJjCCGUfbG 85 JMYxgclvGGc4ZeAXZxozmGXVXAWhAuYLWGCLhDHJAhaRYpaPJSxXYPlY8bH6 86 CeBn7eLsAgAA 87 """ 88 ) 89 90 private val emptySampleFile = 91 kotlin( 92 """ 93 package foo.samples 94 """ 95 ) 96 97 private val unannotatedSampleFile = 98 kotlin( 99 """ 100 package foo.samples 101 102 fun sampleBar() {} 103 """ 104 ) 105 106 private val multipleMatchingSampleFile = 107 kotlin( 108 """ 109 package foo.samples 110 111 import androidx.annotation.Sampled 112 113 @Sampled 114 fun sampleBar() {} 115 116 @Sampled 117 fun sampleBar(param: Boolean = false) {} 118 """ 119 ) 120 121 private val correctlyAnnotatedSampleFile = 122 kotlin( 123 """ 124 package foo.samples 125 126 import androidx.annotation.Sampled 127 128 @Sampled 129 fun sampleBar() {} 130 """ 131 ) 132 133 private fun checkKotlin( 134 fooFile: TestFile? = null, 135 sampleFile: TestFile, 136 expectedText: String? = null, 137 requiresPartialAnalysis: Boolean = true 138 ) { 139 val projectDescriptions = mutableListOf<ProjectDescription>() 140 val fooProject = 141 ProjectDescription().apply { 142 name = fooModuleName 143 type = ProjectDescription.Type.LIBRARY 144 fooFile?.let { addFile(fooFile) } 145 } 146 projectDescriptions += fooProject 147 148 val sampleProject = 149 ProjectDescription().apply { 150 name = sampleModuleName 151 type = ProjectDescription.Type.LIBRARY 152 under = fooProject 153 files = arrayOf(sampleFile, sampledStub) 154 dependsOn(fooProject) 155 } 156 projectDescriptions += sampleProject 157 158 lint().run { 159 projects(*projectDescriptions.toTypedArray()) 160 if (requiresPartialAnalysis) { 161 // The lint check will only work in partial analysis, so we expect non-partial 162 // test modes to have no errors. This has to be called before run(). 163 expectIdenticalTestModeOutput(false) 164 } 165 run().run { 166 if (expectedText == null) { 167 expectClean() 168 } else { 169 if (requiresPartialAnalysis) { 170 // expectClean doesn't accept a testMode param 171 expect("No warnings.", testMode = TestMode.DEFAULT) 172 expect(expectedText, testMode = TestMode.PARTIAL) 173 } else { 174 expect(expectedText) 175 } 176 } 177 } 178 } 179 } 180 181 @Test unresolvedSampleLink_Functionnull182 fun unresolvedSampleLink_Function() { 183 val fooFile = 184 kotlin( 185 """ 186 package foo 187 188 class Bar { 189 /** 190 * @sample foo.samples.sampleBar 191 */ 192 fun bar() {} 193 } 194 """ 195 ) 196 197 val sampleFile = emptySampleFile 198 199 val expected = 200 """$barFilePath:6: Error: Couldn't find a valid @Sampled function matching foo.samples.sampleBar [UnresolvedSampleLink] 201 * @sample foo.samples.sampleBar 202 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 203 1 errors, 0 warnings 204 """ 205 206 checkKotlin(fooFile = fooFile, sampleFile = sampleFile, expectedText = expected) 207 } 208 209 @Test unannotatedSampleFunction_Functionnull210 fun unannotatedSampleFunction_Function() { 211 val fooFile = 212 kotlin( 213 """ 214 package foo 215 216 class Bar { 217 /** 218 * @sample foo.samples.sampleBar 219 */ 220 fun bar() {} 221 } 222 """ 223 ) 224 225 val sampleFile = unannotatedSampleFile 226 227 val expected = 228 """$barFilePath:6: Error: Couldn't find a valid @Sampled function matching foo.samples.sampleBar [UnresolvedSampleLink] 229 * @sample foo.samples.sampleBar 230 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 231 1 errors, 0 warnings 232 """ 233 234 checkKotlin(fooFile = fooFile, sampleFile = sampleFile, expectedText = expected) 235 } 236 237 @Test multipleMatchingSampleFunctions_Functionnull238 fun multipleMatchingSampleFunctions_Function() { 239 val fooFile = 240 kotlin( 241 """ 242 package foo 243 244 class Bar { 245 /** 246 * @sample foo.samples.sampleBar 247 */ 248 fun bar() {} 249 } 250 """ 251 ) 252 253 val sampleFile = multipleMatchingSampleFile 254 255 val expected = 256 """$sampleFilePath:10: Error: Found multiple functions matching foo.samples.sampleBar [MultipleSampledFunctions] 257 fun sampleBar(param: Boolean = false) {} 258 ~~~~~~~~~ 259 1 errors, 0 warnings 260 """ 261 262 checkKotlin( 263 fooFile = fooFile, 264 sampleFile = sampleFile, 265 expectedText = expected, 266 // Since this particular is all done inside the same module, partial analysis isn't 267 // required so it will still work in global analysis 268 requiresPartialAnalysis = false 269 ) 270 } 271 272 @Test correctlyAnnotatedSampleFunction_Functionnull273 fun correctlyAnnotatedSampleFunction_Function() { 274 val fooFile = 275 kotlin( 276 """ 277 package foo 278 279 class Bar { 280 /** 281 * @sample foo.samples.sampleBar 282 */ 283 fun bar() {} 284 } 285 """ 286 ) 287 288 val sampleFile = correctlyAnnotatedSampleFile 289 290 checkKotlin(fooFile = fooFile, sampleFile = sampleFile, expectedText = null) 291 } 292 293 @Test unresolvedSampleLink_Classnull294 fun unresolvedSampleLink_Class() { 295 val fooFile = 296 kotlin( 297 """ 298 package foo 299 300 /** 301 * @sample foo.samples.sampleBar 302 */ 303 class Bar 304 """ 305 ) 306 307 val sampleFile = emptySampleFile 308 309 val expected = 310 """$barFilePath:5: Error: Couldn't find a valid @Sampled function matching foo.samples.sampleBar [UnresolvedSampleLink] 311 * @sample foo.samples.sampleBar 312 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 313 1 errors, 0 warnings 314 """ 315 316 checkKotlin(fooFile = fooFile, sampleFile = sampleFile, expectedText = expected) 317 } 318 319 @Test unannotatedSampleFunction_Classnull320 fun unannotatedSampleFunction_Class() { 321 val fooFile = 322 kotlin( 323 """ 324 package foo 325 326 /** 327 * @sample foo.samples.sampleBar 328 */ 329 class Bar 330 """ 331 ) 332 333 val sampleFile = unannotatedSampleFile 334 335 val expected = 336 """$barFilePath:5: Error: Couldn't find a valid @Sampled function matching foo.samples.sampleBar [UnresolvedSampleLink] 337 * @sample foo.samples.sampleBar 338 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 339 1 errors, 0 warnings 340 """ 341 342 checkKotlin(fooFile = fooFile, sampleFile = sampleFile, expectedText = expected) 343 } 344 345 @Test multipleMatchingSampleFunctions_Classnull346 fun multipleMatchingSampleFunctions_Class() { 347 val fooFile = 348 kotlin( 349 """ 350 package foo 351 352 /** 353 * @sample foo.samples.sampleBar 354 */ 355 class Bar 356 """ 357 ) 358 359 val sampleFile = multipleMatchingSampleFile 360 361 val expected = 362 """$sampleFilePath:10: Error: Found multiple functions matching foo.samples.sampleBar [MultipleSampledFunctions] 363 fun sampleBar(param: Boolean = false) {} 364 ~~~~~~~~~ 365 1 errors, 0 warnings 366 """ 367 368 checkKotlin( 369 fooFile = fooFile, 370 sampleFile = sampleFile, 371 expectedText = expected, 372 // Since this particular is all done inside the same module, partial analysis isn't 373 // required so it will still work in global analysis 374 requiresPartialAnalysis = false 375 ) 376 } 377 378 @Test correctlyAnnotatedSampleFunction_Classnull379 fun correctlyAnnotatedSampleFunction_Class() { 380 val fooFile = 381 kotlin( 382 """ 383 package foo 384 385 /** 386 * @sample foo.samples.sampleBar 387 */ 388 class Bar 389 """ 390 ) 391 392 val sampleFile = correctlyAnnotatedSampleFile 393 394 checkKotlin(fooFile = fooFile, sampleFile = sampleFile, expectedText = null) 395 } 396 397 @Test unresolvedSampleLink_Fieldnull398 fun unresolvedSampleLink_Field() { 399 val fooFile = 400 kotlin( 401 """ 402 package foo 403 404 class Bar { 405 /** 406 * @sample foo.samples.sampleBar 407 */ 408 const val bar = 0 409 } 410 """ 411 ) 412 413 val sampleFile = emptySampleFile 414 415 val expected = 416 """$barFilePath:6: Error: Couldn't find a valid @Sampled function matching foo.samples.sampleBar [UnresolvedSampleLink] 417 * @sample foo.samples.sampleBar 418 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 419 1 errors, 0 warnings 420 """ 421 422 checkKotlin(fooFile = fooFile, sampleFile = sampleFile, expectedText = expected) 423 } 424 425 @Test unannotatedSampleFunction_Fieldnull426 fun unannotatedSampleFunction_Field() { 427 val fooFile = 428 kotlin( 429 """ 430 package foo 431 432 class Bar { 433 /** 434 * @sample foo.samples.sampleBar 435 */ 436 const val bar = 0 437 } 438 """ 439 ) 440 441 val sampleFile = unannotatedSampleFile 442 443 val expected = 444 """$barFilePath:6: Error: Couldn't find a valid @Sampled function matching foo.samples.sampleBar [UnresolvedSampleLink] 445 * @sample foo.samples.sampleBar 446 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 447 1 errors, 0 warnings 448 """ 449 450 checkKotlin(fooFile = fooFile, sampleFile = sampleFile, expectedText = expected) 451 } 452 453 @Test multipleMatchingSampleFunctions_Fieldnull454 fun multipleMatchingSampleFunctions_Field() { 455 val fooFile = 456 kotlin( 457 """ 458 package foo 459 460 class Bar { 461 /** 462 * @sample foo.samples.sampleBar 463 */ 464 const val bar = 0 465 } 466 """ 467 ) 468 469 val sampleFile = multipleMatchingSampleFile 470 471 val expected = 472 """$sampleFilePath:10: Error: Found multiple functions matching foo.samples.sampleBar [MultipleSampledFunctions] 473 fun sampleBar(param: Boolean = false) {} 474 ~~~~~~~~~ 475 1 errors, 0 warnings 476 """ 477 478 checkKotlin( 479 fooFile = fooFile, 480 sampleFile = sampleFile, 481 expectedText = expected, 482 // Since this particular is all done inside the same module, partial analysis isn't 483 // required so it will still work in global analysis 484 requiresPartialAnalysis = false 485 ) 486 } 487 488 @Test correctlyAnnotatedSampleFunction_Fieldnull489 fun correctlyAnnotatedSampleFunction_Field() { 490 val fooFile = 491 kotlin( 492 """ 493 package foo 494 495 class Bar { 496 /** 497 * @sample foo.samples.sampleBar 498 */ 499 const val bar = 0 500 } 501 """ 502 ) 503 504 val sampleFile = correctlyAnnotatedSampleFile 505 506 checkKotlin(fooFile = fooFile, sampleFile = sampleFile, expectedText = null) 507 } 508 509 @Test unresolvedSampleLink_PropertyWithGetternull510 fun unresolvedSampleLink_PropertyWithGetter() { 511 val fooFile = 512 kotlin( 513 """ 514 package foo 515 516 class Bar { 517 /** 518 * @sample foo.samples.sampleBar 519 */ 520 val bar get() = 0 521 } 522 """ 523 ) 524 525 val sampleFile = emptySampleFile 526 527 val expected = 528 """$barFilePath:6: Error: Couldn't find a valid @Sampled function matching foo.samples.sampleBar [UnresolvedSampleLink] 529 * @sample foo.samples.sampleBar 530 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 531 1 errors, 0 warnings 532 """ 533 534 checkKotlin(fooFile = fooFile, sampleFile = sampleFile, expectedText = expected) 535 } 536 537 @Test unannotatedSampleFunction_PropertyWithGetternull538 fun unannotatedSampleFunction_PropertyWithGetter() { 539 val fooFile = 540 kotlin( 541 """ 542 package foo 543 544 class Bar { 545 /** 546 * @sample foo.samples.sampleBar 547 */ 548 val bar get() = 0 549 } 550 """ 551 ) 552 553 val sampleFile = unannotatedSampleFile 554 555 val expected = 556 """$barFilePath:6: Error: Couldn't find a valid @Sampled function matching foo.samples.sampleBar [UnresolvedSampleLink] 557 * @sample foo.samples.sampleBar 558 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 559 1 errors, 0 warnings 560 """ 561 562 checkKotlin(fooFile = fooFile, sampleFile = sampleFile, expectedText = expected) 563 } 564 565 @Test multipleMatchingSampleFunctions_PropertyWithGetternull566 fun multipleMatchingSampleFunctions_PropertyWithGetter() { 567 val fooFile = 568 kotlin( 569 """ 570 package foo 571 572 class Bar { 573 /** 574 * @sample foo.samples.sampleBar 575 */ 576 val bar get() = 0 577 } 578 """ 579 ) 580 581 val sampleFile = multipleMatchingSampleFile 582 583 val expected = 584 """$sampleFilePath:10: Error: Found multiple functions matching foo.samples.sampleBar [MultipleSampledFunctions] 585 fun sampleBar(param: Boolean = false) {} 586 ~~~~~~~~~ 587 1 errors, 0 warnings 588 """ 589 590 checkKotlin( 591 fooFile = fooFile, 592 sampleFile = sampleFile, 593 expectedText = expected, 594 // Since this particular is all done inside the same module, partial analysis isn't 595 // required so it will still work in global analysis 596 requiresPartialAnalysis = false 597 ) 598 } 599 600 @Test correctlyAnnotatedSampleFunction_PropertyWithGetternull601 fun correctlyAnnotatedSampleFunction_PropertyWithGetter() { 602 val fooFile = 603 kotlin( 604 """ 605 package foo 606 607 class Bar { 608 /** 609 * @sample foo.samples.sampleBar 610 */ 611 val bar get() = 0 612 } 613 """ 614 ) 615 616 val sampleFile = correctlyAnnotatedSampleFile 617 618 checkKotlin(fooFile = fooFile, sampleFile = sampleFile, expectedText = null) 619 } 620 621 @Test obsoleteSampledFunctionnull622 fun obsoleteSampledFunction() { 623 val sampleFile = correctlyAnnotatedSampleFile 624 625 val expected = 626 """$sampleFilePath:7: Error: foo.samples.sampleBar is annotated with @Sampled, but is not linked to from a @sample tag. [ObsoleteSampledAnnotation] 627 fun sampleBar() {} 628 ~~~~~~~~~ 629 1 errors, 0 warnings 630 631 """ 632 633 checkKotlin(sampleFile = sampleFile, expectedText = expected) 634 } 635 636 @Test invalidSampleLocationnull637 fun invalidSampleLocation() { 638 val sampleFile = 639 kotlin( 640 """ 641 package foo.wrong.location 642 643 import androidx.annotation.Sampled 644 645 @Sampled 646 fun sampleBar() {} 647 """ 648 ) 649 650 val expected = 651 """samples/src/foo/wrong/location/test.kt:7: Error: foo.wrong.location.sampleBar is annotated with @Sampled, but is not linked to from a @sample tag. [ObsoleteSampledAnnotation] 652 fun sampleBar() {} 653 ~~~~~~~~~ 654 1 errors, 0 warnings 655 656 """ 657 658 checkKotlin(sampleFile = sampleFile, expectedText = expected) 659 } 660 } 661