summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--packages/SystemUI/checks/src/com/android/internal/systemui/lint/RunTestShouldUseKosmosDetector.kt81
-rw-r--r--packages/SystemUI/checks/tests/com/android/internal/systemui/lint/RunTestShouldUseKosmosDetectorTest.kt214
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)
+ """
+ )
+ }
+}