Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 117e7070 authored by David Saff's avatar David Saff
Browse files

Add linter to prefer testKosmos over Kosmos()

Bug: 342622417
Test: DoNotDirectlyConstructKosmosTest
Flag: TEST_ONLY

Change-Id: I96596782669fc4634da1a8c789d3f15f5b6f1ddb
parent af4d7546
Loading
Loading
Loading
Loading
+79 −0
Original line number Original line Diff line number Diff line
/*
 * 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.intellij.psi.PsiMethod
import org.jetbrains.uast.UCallExpression
import org.jetbrains.uast.getContainingUClass

/**
 * Detects direct construction of `Kosmos()` in subclasses of SysuiTestCase, which can and should
 * use `testKosmos`. See go/thetiger
 */
class DoNotDirectlyConstructKosmosDetector : Detector(), SourceCodeScanner {
    override fun getApplicableConstructorTypes() = listOf("com.android.systemui.kosmos.Kosmos")

    override fun visitConstructor(
        context: JavaContext,
        node: UCallExpression,
        constructor: PsiMethod,
    ) {
        val superClassNames =
            node.getContainingUClass()?.superTypes.orEmpty().map { it.resolve()?.qualifiedName }
        if (superClassNames.contains("com.android.systemui.SysuiTestCase")) {
            context.report(
                issue = ISSUE,
                scope = node,
                location = context.getLocation(node.methodIdentifier),
                message = "Prefer testKosmos to direct Kosmos() in sysui tests.  go/testkosmos",
            )
        }
        super.visitConstructor(context, node, constructor)
    }

    companion object {
        @JvmStatic
        val ISSUE =
            Issue.create(
                id = "DoNotDirectlyConstructKosmos",
                briefDescription =
                    "Prefer testKosmos to direct Kosmos() in sysui tests.  go/testkosmos",
                explanation =
                    """
                    SysuiTestCase.testKosmos allows us to pre-populate a Kosmos instance with
                    team-standard fixture values, and makes it easier to make centralized changes
                    when necessary.  See go/testkosmos
                """,
                category = Category.TESTING,
                priority = 8,
                severity = Severity.WARNING,
                implementation =
                    Implementation(
                        DoNotDirectlyConstructKosmosDetector::class.java,
                        Scope.JAVA_FILE_SCOPE,
                    ),
            )
    }
}
+1 −5
Original line number Original line Diff line number Diff line
@@ -29,16 +29,12 @@ import com.intellij.psi.PsiMethod
import org.jetbrains.uast.UCallExpression
import org.jetbrains.uast.UCallExpression
import org.jetbrains.uast.getContainingUFile
import org.jetbrains.uast.getContainingUFile


/**
/** Detects use of `TestScope.runTest` when we should use `Kosmos.runTest` by go/kosmos-runtest */
 * Detects test function naming violations regarding use of the backtick-wrapped space-allowed
 * feature of Kotlin functions.
 */
class RunTestShouldUseKosmosDetector : Detector(), SourceCodeScanner {
class RunTestShouldUseKosmosDetector : Detector(), SourceCodeScanner {
    override fun getApplicableMethodNames() = listOf("runTest")
    override fun getApplicableMethodNames() = listOf("runTest")


    override fun visitMethodCall(context: JavaContext, node: UCallExpression, method: PsiMethod) {
    override fun visitMethodCall(context: JavaContext, node: UCallExpression, method: PsiMethod) {
        if (method.getReceiver()?.qualifiedName == "kotlinx.coroutines.test.TestScope") {
        if (method.getReceiver()?.qualifiedName == "kotlinx.coroutines.test.TestScope") {

            val imports =
            val imports =
                node.getContainingUFile()?.imports.orEmpty().mapNotNull {
                node.getContainingUFile()?.imports.orEmpty().mapNotNull {
                    it.importReference?.asSourceString()
                    it.importReference?.asSourceString()
+104 −0
Original line number Original line Diff line number Diff line
/*
 * 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 DoNotDirectlyConstructKosmosTest : SystemUILintDetectorTest() {
    override fun getDetector(): Detector = DoNotDirectlyConstructKosmosDetector()

    override fun getIssues(): List<Issue> = listOf(DoNotDirectlyConstructKosmosDetector.ISSUE)

    @Test
    fun wronglyTriesToDirectlyConstructKosmos() {
        val runOnSource =
            runOnSource(
                """
                      package test.pkg.name

                      import com.android.systemui.kosmos.Kosmos
                      import com.android.systemui.SysuiTestCase

                      class MyTest: SysuiTestCase {
                          val kosmos = Kosmos()
                      }
                """
            )

        runOnSource
            .expectWarningCount(1)
            .expect(
                """
                src/test/pkg/name/MyTest.kt:7: Warning: Prefer testKosmos to direct Kosmos() in sysui tests.  go/testkosmos [DoNotDirectlyConstructKosmos]
                    val kosmos = Kosmos()
                                 ~~~~~~
                0 errors, 1 warnings
                """
            )
    }

    @Test
    fun okToConstructKosmosIfNotInSysuiTestCase() {
        val runOnSource =
            runOnSource(
                """
                      package test.pkg.name

                      import com.android.systemui.kosmos.Kosmos

                      class MyTest {
                          val kosmos = Kosmos()
                      }
                """
            )

        runOnSource.expectWarningCount(0)
    }

    private fun runOnSource(source: String): TestLintResult {
        return lint()
            .files(TestFiles.kotlin(source).indented(), kosmosStub, sysuiTestCaseStub)
            .issues(DoNotDirectlyConstructKosmosDetector.ISSUE)
            .run()
    }

    companion object {
        private val kosmosStub: TestFile =
            kotlin(
                """
                package com.android.systemui.kosmos

                class Kosmos
            """
            )

        private val sysuiTestCaseStub: TestFile =
            kotlin(
                """
                package com.android.systemui

                class SysuiTestCase
                """
            )
    }
}