blob: bcf1cee7bd54072df8dcf6502d85cf5243ba0472 [file] [log] [blame]
/*
* Copyright 2019 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
@file:Suppress("KDocUnresolvedReference", "UnstableApiUsage")
package androidx.build.lint
import com.android.tools.lint.checks.infrastructure.LintDetectorTest
import com.android.tools.lint.checks.infrastructure.ProjectDescription
import com.android.tools.lint.checks.infrastructure.TestFile
import com.android.tools.lint.checks.infrastructure.TestMode
import org.junit.Test
import org.junit.runner.RunWith
import org.junit.runners.JUnit4
/**
* Test for [SampledAnnotationDetector]
*
* This tests the following module setup:
*
* Module 'foo', which lives in foo Module 'foo:samples', which lives in foo/samples, and depends on
* 'foo'
*/
@RunWith(JUnit4::class)
class SampledAnnotationDetectorTest : LintDetectorTest() {
override fun getDetector() = SampledAnnotationDetector()
override fun getIssues() =
mutableListOf(
SampledAnnotationDetector.OBSOLETE_SAMPLED_ANNOTATION,
SampledAnnotationDetector.UNRESOLVED_SAMPLE_LINK,
SampledAnnotationDetector.MULTIPLE_FUNCTIONS_FOUND,
SampledAnnotationDetector.INVALID_SAMPLES_LOCATION
)
private val fooModuleName = "foo"
private val sampleModuleName = "samples"
private val barFilePath = "src/foo/Bar.kt"
private val sampleFilePath = "samples/src/foo/samples/test.kt"
private val sampledStub =
compiled(
"libs/sampled.jar",
kotlin(
"""
package androidx.annotation
@Target(AnnotationTarget.FUNCTION)
@Retention(AnnotationRetention.SOURCE)
annotation class Sampled
"""
)
.to("androidx/annotation/Sampled.kt"),
0x9dd4162c,
"""
META-INF/main.kotlin_module:
H4sIAAAAAAAAAGNgYGBmYGBgBGJWKM3AJcrFnZafr1ecmFuQk1osxBaSWlzi
XaLEoMUAAClM+YMvAAAA
""",
"""
androidx/annotation/Sampled.class:
H4sIAAAAAAAAAIVSyU4CQRB9NYggbriDuGv0Jki8cXLBSKJiEL1wapmOGWlm
CNOg3rj5Tx4M8ehHGatdgMNE+1D9qvq96qrqfv94eQWwj01CSrh203Psx7Rw
XU8L7Xhu+krUG0raERAhfi/aIq2Ee5cu3t7Lqo4gRFjtRwd0Bz0YQZiweFbz
tHLcQUpZNO+kzhEmhFLeg7S/Az5hK4DdT9jTRU+uL47KheIFYTlAUZJaugYx
NdwWqiUJO39mHlQMXxWvS0d5wtpZYIOD3O1/KJeecqpPOVNmILHX0UbweV7J
OmcqPzWkqew8Xz4tHhOmfps5l1rYQgs+tOrtED8oGcNzpxqHHh3jZRjZe4Rk
txONWQkrZsVT0bdnK9HtZK0MHXY7hpA1Rf7xEfgKEGI/3m5NG8drNavyxFE8
4GSpxS3X5Y3jO7dK9mfrb3N6DLF4GGZZWP+ya9jgvYow+I8hWgFJjCCGUfbG
JMYxgclvGGc4ZeAXZxozmGXVXAWhAuYLWGCLhDHJAhaRYpaPJSxXYPlY8bH6
CeBn7eLsAgAA
"""
)
private val emptySampleFile =
kotlin(
"""
package foo.samples
"""
)
private val unannotatedSampleFile =
kotlin(
"""
package foo.samples
fun sampleBar() {}
"""
)
private val multipleMatchingSampleFile =
kotlin(
"""
package foo.samples
import androidx.annotation.Sampled
@Sampled
fun sampleBar() {}
@Sampled
fun sampleBar(param: Boolean = false) {}
"""
)
private val correctlyAnnotatedSampleFile =
kotlin(
"""
package foo.samples
import androidx.annotation.Sampled
@Sampled
fun sampleBar() {}
"""
)
private fun checkKotlin(
fooFile: TestFile? = null,
sampleFile: TestFile,
expectedText: String? = null,
requiresPartialAnalysis: Boolean = true
) {
val projectDescriptions = mutableListOf<ProjectDescription>()
val fooProject =
ProjectDescription().apply {
name = fooModuleName
type = ProjectDescription.Type.LIBRARY
fooFile?.let { addFile(fooFile) }
}
projectDescriptions += fooProject
val sampleProject =
ProjectDescription().apply {
name = sampleModuleName
type = ProjectDescription.Type.LIBRARY
under = fooProject
files = arrayOf(sampleFile, sampledStub)
dependsOn(fooProject)
}
projectDescriptions += sampleProject
lint().run {
projects(*projectDescriptions.toTypedArray())
if (requiresPartialAnalysis) {
// The lint check will only work in partial analysis, so we expect non-partial
// test modes to have no errors. This has to be called before run().
expectIdenticalTestModeOutput(false)
}
run().run {
if (expectedText == null) {
expectClean()
} else {
if (requiresPartialAnalysis) {
// expectClean doesn't accept a testMode param
expect("No warnings.", testMode = TestMode.DEFAULT)
expect(expectedText, testMode = TestMode.PARTIAL)
} else {
expect(expectedText)
}
}
}
}
}
@Test
fun unresolvedSampleLink_Function() {
val fooFile =
kotlin(
"""
package foo
class Bar {
/**
* @sample foo.samples.sampleBar
*/
fun bar() {}
}
"""
)
val sampleFile = emptySampleFile
val expected =
"""$barFilePath:6: Error: Couldn't find a valid @Sampled function matching foo.samples.sampleBar [UnresolvedSampleLink]
* @sample foo.samples.sampleBar
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
1 errors, 0 warnings
"""
checkKotlin(fooFile = fooFile, sampleFile = sampleFile, expectedText = expected)
}
@Test
fun unannotatedSampleFunction_Function() {
val fooFile =
kotlin(
"""
package foo
class Bar {
/**
* @sample foo.samples.sampleBar
*/
fun bar() {}
}
"""
)
val sampleFile = unannotatedSampleFile
val expected =
"""$barFilePath:6: Error: Couldn't find a valid @Sampled function matching foo.samples.sampleBar [UnresolvedSampleLink]
* @sample foo.samples.sampleBar
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
1 errors, 0 warnings
"""
checkKotlin(fooFile = fooFile, sampleFile = sampleFile, expectedText = expected)
}
@Test
fun multipleMatchingSampleFunctions_Function() {
val fooFile =
kotlin(
"""
package foo
class Bar {
/**
* @sample foo.samples.sampleBar
*/
fun bar() {}
}
"""
)
val sampleFile = multipleMatchingSampleFile
val expected =
"""$sampleFilePath:10: Error: Found multiple functions matching foo.samples.sampleBar [MultipleSampledFunctions]
fun sampleBar(param: Boolean = false) {}
~~~~~~~~~
1 errors, 0 warnings
"""
checkKotlin(
fooFile = fooFile,
sampleFile = sampleFile,
expectedText = expected,
// Since this particular is all done inside the same module, partial analysis isn't
// required so it will still work in global analysis
requiresPartialAnalysis = false
)
}
@Test
fun correctlyAnnotatedSampleFunction_Function() {
val fooFile =
kotlin(
"""
package foo
class Bar {
/**
* @sample foo.samples.sampleBar
*/
fun bar() {}
}
"""
)
val sampleFile = correctlyAnnotatedSampleFile
checkKotlin(fooFile = fooFile, sampleFile = sampleFile, expectedText = null)
}
@Test
fun unresolvedSampleLink_Class() {
val fooFile =
kotlin(
"""
package foo
/**
* @sample foo.samples.sampleBar
*/
class Bar
"""
)
val sampleFile = emptySampleFile
val expected =
"""$barFilePath:5: Error: Couldn't find a valid @Sampled function matching foo.samples.sampleBar [UnresolvedSampleLink]
* @sample foo.samples.sampleBar
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
1 errors, 0 warnings
"""
checkKotlin(fooFile = fooFile, sampleFile = sampleFile, expectedText = expected)
}
@Test
fun unannotatedSampleFunction_Class() {
val fooFile =
kotlin(
"""
package foo
/**
* @sample foo.samples.sampleBar
*/
class Bar
"""
)
val sampleFile = unannotatedSampleFile
val expected =
"""$barFilePath:5: Error: Couldn't find a valid @Sampled function matching foo.samples.sampleBar [UnresolvedSampleLink]
* @sample foo.samples.sampleBar
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
1 errors, 0 warnings
"""
checkKotlin(fooFile = fooFile, sampleFile = sampleFile, expectedText = expected)
}
@Test
fun multipleMatchingSampleFunctions_Class() {
val fooFile =
kotlin(
"""
package foo
/**
* @sample foo.samples.sampleBar
*/
class Bar
"""
)
val sampleFile = multipleMatchingSampleFile
val expected =
"""$sampleFilePath:10: Error: Found multiple functions matching foo.samples.sampleBar [MultipleSampledFunctions]
fun sampleBar(param: Boolean = false) {}
~~~~~~~~~
1 errors, 0 warnings
"""
checkKotlin(
fooFile = fooFile,
sampleFile = sampleFile,
expectedText = expected,
// Since this particular is all done inside the same module, partial analysis isn't
// required so it will still work in global analysis
requiresPartialAnalysis = false
)
}
@Test
fun correctlyAnnotatedSampleFunction_Class() {
val fooFile =
kotlin(
"""
package foo
/**
* @sample foo.samples.sampleBar
*/
class Bar
"""
)
val sampleFile = correctlyAnnotatedSampleFile
checkKotlin(fooFile = fooFile, sampleFile = sampleFile, expectedText = null)
}
@Test
fun unresolvedSampleLink_Field() {
val fooFile =
kotlin(
"""
package foo
class Bar {
/**
* @sample foo.samples.sampleBar
*/
const val bar = 0
}
"""
)
val sampleFile = emptySampleFile
val expected =
"""$barFilePath:6: Error: Couldn't find a valid @Sampled function matching foo.samples.sampleBar [UnresolvedSampleLink]
* @sample foo.samples.sampleBar
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
1 errors, 0 warnings
"""
checkKotlin(fooFile = fooFile, sampleFile = sampleFile, expectedText = expected)
}
@Test
fun unannotatedSampleFunction_Field() {
val fooFile =
kotlin(
"""
package foo
class Bar {
/**
* @sample foo.samples.sampleBar
*/
const val bar = 0
}
"""
)
val sampleFile = unannotatedSampleFile
val expected =
"""$barFilePath:6: Error: Couldn't find a valid @Sampled function matching foo.samples.sampleBar [UnresolvedSampleLink]
* @sample foo.samples.sampleBar
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
1 errors, 0 warnings
"""
checkKotlin(fooFile = fooFile, sampleFile = sampleFile, expectedText = expected)
}
@Test
fun multipleMatchingSampleFunctions_Field() {
val fooFile =
kotlin(
"""
package foo
class Bar {
/**
* @sample foo.samples.sampleBar
*/
const val bar = 0
}
"""
)
val sampleFile = multipleMatchingSampleFile
val expected =
"""$sampleFilePath:10: Error: Found multiple functions matching foo.samples.sampleBar [MultipleSampledFunctions]
fun sampleBar(param: Boolean = false) {}
~~~~~~~~~
1 errors, 0 warnings
"""
checkKotlin(
fooFile = fooFile,
sampleFile = sampleFile,
expectedText = expected,
// Since this particular is all done inside the same module, partial analysis isn't
// required so it will still work in global analysis
requiresPartialAnalysis = false
)
}
@Test
fun correctlyAnnotatedSampleFunction_Field() {
val fooFile =
kotlin(
"""
package foo
class Bar {
/**
* @sample foo.samples.sampleBar
*/
const val bar = 0
}
"""
)
val sampleFile = correctlyAnnotatedSampleFile
checkKotlin(fooFile = fooFile, sampleFile = sampleFile, expectedText = null)
}
@Test
fun unresolvedSampleLink_PropertyWithGetter() {
val fooFile =
kotlin(
"""
package foo
class Bar {
/**
* @sample foo.samples.sampleBar
*/
val bar get() = 0
}
"""
)
val sampleFile = emptySampleFile
val expected =
"""$barFilePath:6: Error: Couldn't find a valid @Sampled function matching foo.samples.sampleBar [UnresolvedSampleLink]
* @sample foo.samples.sampleBar
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
1 errors, 0 warnings
"""
checkKotlin(fooFile = fooFile, sampleFile = sampleFile, expectedText = expected)
}
@Test
fun unannotatedSampleFunction_PropertyWithGetter() {
val fooFile =
kotlin(
"""
package foo
class Bar {
/**
* @sample foo.samples.sampleBar
*/
val bar get() = 0
}
"""
)
val sampleFile = unannotatedSampleFile
val expected =
"""$barFilePath:6: Error: Couldn't find a valid @Sampled function matching foo.samples.sampleBar [UnresolvedSampleLink]
* @sample foo.samples.sampleBar
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
1 errors, 0 warnings
"""
checkKotlin(fooFile = fooFile, sampleFile = sampleFile, expectedText = expected)
}
@Test
fun multipleMatchingSampleFunctions_PropertyWithGetter() {
val fooFile =
kotlin(
"""
package foo
class Bar {
/**
* @sample foo.samples.sampleBar
*/
val bar get() = 0
}
"""
)
val sampleFile = multipleMatchingSampleFile
val expected =
"""$sampleFilePath:10: Error: Found multiple functions matching foo.samples.sampleBar [MultipleSampledFunctions]
fun sampleBar(param: Boolean = false) {}
~~~~~~~~~
1 errors, 0 warnings
"""
checkKotlin(
fooFile = fooFile,
sampleFile = sampleFile,
expectedText = expected,
// Since this particular is all done inside the same module, partial analysis isn't
// required so it will still work in global analysis
requiresPartialAnalysis = false
)
}
@Test
fun correctlyAnnotatedSampleFunction_PropertyWithGetter() {
val fooFile =
kotlin(
"""
package foo
class Bar {
/**
* @sample foo.samples.sampleBar
*/
val bar get() = 0
}
"""
)
val sampleFile = correctlyAnnotatedSampleFile
checkKotlin(fooFile = fooFile, sampleFile = sampleFile, expectedText = null)
}
@Test
fun obsoleteSampledFunction() {
val sampleFile = correctlyAnnotatedSampleFile
val expected =
"""$sampleFilePath:7: Error: foo.samples.sampleBar is annotated with @Sampled, but is not linked to from a @sample tag. [ObsoleteSampledAnnotation]
fun sampleBar() {}
~~~~~~~~~
1 errors, 0 warnings
"""
checkKotlin(sampleFile = sampleFile, expectedText = expected)
}
@Test
fun invalidSampleLocation() {
val sampleFile =
kotlin(
"""
package foo.wrong.location
import androidx.annotation.Sampled
@Sampled
fun sampleBar() {}
"""
)
val expected =
"""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]
fun sampleBar() {}
~~~~~~~~~
1 errors, 0 warnings
"""
checkKotlin(sampleFile = sampleFile, expectedText = expected)
}
}