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

Commit c6d1d7f3 authored by David Saff's avatar David Saff Committed by Android (Google) Code Review
Browse files

Merge "Add linter to prefer testKosmos over Kosmos()" into main

parents 48012db1 117e7070
Loading
Loading
Loading
Loading
+79 −0
Original line number 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 Diff line number Diff line
@@ -29,16 +29,12 @@ 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.
 */
/** Detects use of `TestScope.runTest` when we should use `Kosmos.runTest` by go/kosmos-runtest */
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()
+104 −0
Original line number 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
                """
            )
    }
}