diff options
| author | 2025-01-21 11:21:51 -0800 | |
|---|---|---|
| committer | 2025-01-21 11:21:51 -0800 | |
| commit | c2ac4e9fe709686d6890d5fd50cde5ec85a36294 (patch) | |
| tree | 88e5cd75360f2805fe291a09ab6eb0d1c022cb6b | |
| parent | 0cbbf87260548dda62cd725963484d274cf3816a (diff) | |
| parent | 5eb875550fb5e9fa81d67ae901b62a3674f7f1a2 (diff) | |
Merge "Add linter to recommend Kosmos.runTest" into main
2 files changed, 295 insertions, 0 deletions
diff --git a/packages/SystemUI/checks/src/com/android/internal/systemui/lint/RunTestShouldUseKosmosDetector.kt b/packages/SystemUI/checks/src/com/android/internal/systemui/lint/RunTestShouldUseKosmosDetector.kt new file mode 100644 index 000000000000..4927fb9dc67d --- /dev/null +++ b/packages/SystemUI/checks/src/com/android/internal/systemui/lint/RunTestShouldUseKosmosDetector.kt @@ -0,0 +1,81 @@ +/* + * Copyright (C) 2023 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. + */ + +package com.android.internal.systemui.lint + +import com.android.tools.lint.detector.api.Category +import com.android.tools.lint.detector.api.Detector +import com.android.tools.lint.detector.api.Implementation +import com.android.tools.lint.detector.api.Issue +import com.android.tools.lint.detector.api.JavaContext +import com.android.tools.lint.detector.api.Scope +import com.android.tools.lint.detector.api.Severity +import com.android.tools.lint.detector.api.SourceCodeScanner +import com.android.tools.lint.detector.api.getReceiver +import com.intellij.psi.PsiMethod +import org.jetbrains.uast.UCallExpression +import org.jetbrains.uast.getContainingUFile + +/** + * Detects test function naming violations regarding use of the backtick-wrapped space-allowed + * feature of Kotlin functions. + */ +class RunTestShouldUseKosmosDetector : Detector(), SourceCodeScanner { + override fun getApplicableMethodNames() = listOf("runTest") + + override fun visitMethodCall(context: JavaContext, node: UCallExpression, method: PsiMethod) { + if (method.getReceiver()?.qualifiedName == "kotlinx.coroutines.test.TestScope") { + + val imports = + node.getContainingUFile()?.imports.orEmpty().mapNotNull { + it.importReference?.asSourceString() + } + if (imports.any { it == "com.android.systemui.kosmos.Kosmos" }) { + context.report( + issue = ISSUE, + scope = node, + location = context.getLocation(node.methodIdentifier), + message = + "Prefer Kosmos.runTest to TestScope.runTest in sysui tests that use Kosmos. go/kosmos-runtest", + ) + super.visitMethodCall(context, node, method) + } + } + } + + companion object { + @JvmStatic + val ISSUE = + Issue.create( + id = "RunTestShouldUseKosmos", + briefDescription = "When you can, use Kosmos.runTest instead of TestScope.runTest.", + explanation = + """ + Kosmos.runTest helps to ensure that the test uses the same coroutine + dispatchers that are used in Kosmos fixtures, preventing subtle bugs. + See go/kosmos-runtest + """, + category = Category.TESTING, + priority = 8, + severity = Severity.WARNING, + implementation = + Implementation( + RunTestShouldUseKosmosDetector::class.java, + Scope.JAVA_FILE_SCOPE, + ), + ) + } +} diff --git a/packages/SystemUI/checks/tests/com/android/internal/systemui/lint/RunTestShouldUseKosmosDetectorTest.kt b/packages/SystemUI/checks/tests/com/android/internal/systemui/lint/RunTestShouldUseKosmosDetectorTest.kt new file mode 100644 index 000000000000..41e90b863fab --- /dev/null +++ b/packages/SystemUI/checks/tests/com/android/internal/systemui/lint/RunTestShouldUseKosmosDetectorTest.kt @@ -0,0 +1,214 @@ +/* + * Copyright (C) 2023 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. + * + */ + +package com.android.internal.systemui.lint + +import com.android.tools.lint.checks.infrastructure.TestFile +import com.android.tools.lint.checks.infrastructure.TestFiles +import com.android.tools.lint.checks.infrastructure.TestLintResult +import com.android.tools.lint.detector.api.Detector +import com.android.tools.lint.detector.api.Issue +import org.junit.Test + +class RunTestShouldUseKosmosDetectorTest : SystemUILintDetectorTest() { + override fun getDetector(): Detector = RunTestShouldUseKosmosDetector() + + override fun getIssues(): List<Issue> = listOf(RunTestShouldUseKosmosDetector.ISSUE) + + @Test + fun wronglyTriesToUseScopeRunTest() { + val runOnSource = + runOnSource( + """ + package test.pkg.name + + import com.android.systemui.kosmos.Kosmos + import kotlinx.coroutines.test.runTest + import kotlinx.coroutines.test.TestScope + import org.junit.Test + + class MyTest { + val scope: TestScope + val kosmos: Kosmos + + @Test + fun badTest() = scope.runTest { + // test code + } + } + """ + ) + + runOnSource + .expectWarningCount(1) + .expect( + """ + src/test/pkg/name/MyTest.kt:13: Warning: Prefer Kosmos.runTest to TestScope.runTest in sysui tests that use Kosmos. go/kosmos-runtest [RunTestShouldUseKosmos] + fun badTest() = scope.runTest { + ~~~~~~~ + 0 errors, 1 warnings + """ + ) + } + + @Test + fun testScopeRunTestIsOKifKosmosNotUsed() { + runOnSource( + """ + package test.pkg.name + + import kotlinx.coroutines.test.runTest + import kotlinx.coroutines.test.TestScope + import org.junit.Test + + class MyTest { + val scope: TestScope + + @Test + fun okTest() = scope.runTest { + // test code + } + } + """ + ) + .expectWarningCount(0) + } + + @Test + fun otherTestScopeMethodsAreOK() { + runOnSource( + """ + package test.pkg.name + + import com.android.systemui.kosmos.Kosmos + import com.android.systemui.kosmos.runTest + import kotlinx.coroutines.test.TestScope + import org.junit.Test + + class MyTest { + val scope: TestScope + val kosmos: Kosmos + + @Test + fun okTest() = kosmos.runTest { + scope.cancel() + // test code + } + } + """ + ) + .expectWarningCount(0) + } + + @Test + fun correctlyUsesKosmosRunTest() { + runOnSource( + """ + package test.pkg.name + + import com.android.systemui.kosmos.Kosmos + import com.android.systemui.kosmos.runTest + import kotlinx.coroutines.test.TestScope + import org.junit.Test + + class MyTest { + val scope: TestScope + val kosmos: Kosmos + + @Test + fun okTest() = kosmos.runTest { + // test code + } + } + """ + ) + .expectWarningCount(0) + } + + private fun runOnSource(source: String): TestLintResult { + return lint() + .files( + TestFiles.kotlin(source).indented(), + testAnnotationStub, + runTestStub, + testScopeStub, + kosmosStub, + kosmosRunTestStub, + ) + .issues(RunTestShouldUseKosmosDetector.ISSUE) + .run() + } + + companion object { + private val testAnnotationStub: TestFile = + kotlin( + """ + package org.junit + + import java.lang.annotation.ElementType + import java.lang.annotation.Retention + import java.lang.annotation.RetentionPolicy + import java.lang.annotation.Target + + @Retention(RetentionPolicy.RUNTIME) + @Target({ElementType.METHOD}) + annotation class Test + """ + ) + + private val runTestStub: TestFile = + kotlin( + """ + package kotlinx.coroutines.test + + fun TestScope.runTest( + timeout: Duration = DEFAULT_TIMEOUT.getOrThrow(), + testBody: suspend TestScope.() -> Unit + ): Unit = {} + """ + ) + + private val testScopeStub: TestFile = + kotlin( + """ + package kotlinx.coroutines.test + + class TestScope + + public fun TestScope.cancel() {} + """ + ) + + private val kosmosStub: TestFile = + kotlin( + """ + package com.android.systemui.kosmos + + class Kosmos + """ + ) + + private val kosmosRunTestStub: TestFile = + kotlin( + """ + package com.android.systemui.kosmos + + fun Kosmos.runTest(testBody: suspend Kosmos.() -> Unit) + """ + ) + } +} |