[kotlin] Implement 'genericKotlinTestUrls' & fix GenericTestIconProvider's test location URLs

GitOrigin-RevId: 5071955afa9a40f085a0342cd02764fe049e9ff4
This commit is contained in:
Sebastian Sellmair
2024-09-27 09:20:30 +02:00
committed by intellij-monorepo-bot
parent 32050b0c36
commit f412fe5046
14 changed files with 226 additions and 56 deletions

View File

@@ -388,6 +388,7 @@ org.jetbrains.kotlin.idea.*.k2.*
org.jetbrains.kotlin.idea.*.k2
org.jetbrains.fir.uast.test.*
org.jetbrains.kotlin.j2k.k2.*
org.jetbrains.kotlin.idea.testIntegration.*
[KOTLIN_COMPOSE_K2_TESTS]
org.jetbrains.kotlin.idea.compose.k2.*

View File

@@ -4,6 +4,7 @@ package org.jetbrains.kotlin.idea.base.codeInsight.tooling
import org.jetbrains.kotlin.idea.base.codeInsight.KotlinTestAvailabilityChecker
import org.jetbrains.kotlin.idea.base.codeInsight.isFrameworkAvailable
import org.jetbrains.kotlin.idea.highlighter.KotlinTestRunLineMarkerContributor
import org.jetbrains.kotlin.idea.testIntegration.genericKotlinTestUrls
import org.jetbrains.kotlin.lexer.KtTokens
import org.jetbrains.kotlin.psi.KtClassOrObject
import org.jetbrains.kotlin.psi.KtNamedDeclaration
@@ -24,8 +25,6 @@ abstract class AbstractGenericTestIconProvider {
}
fun getGenericTestIcon(declaration: KtNamedDeclaration, initialLocations: List<String>): Icon? {
val locations = initialLocations.toMutableList()
if (!isFrameworkAvailable<KotlinTestAvailabilityChecker>(declaration)) {
return null
}
@@ -41,19 +40,7 @@ abstract class AbstractGenericTestIconProvider {
return null
}
locations += testContainer.parentsWithSelf.filterIsInstance<KtNamedDeclaration>()
.mapNotNull { it.name }
.toList().asReversed()
val testName = (declaration as? KtNamedFunction)?.name
if (testName != null) {
locations += "$testName"
}
val prefix = if (testName != null) "test://" else "suite://"
val url = prefix + locations.joinWithEscape('.')
return KotlinTestRunLineMarkerContributor.getTestStateIcon(listOf("java:$url", url), declaration)
return KotlinTestRunLineMarkerContributor.getTestStateIcon(declaration.genericKotlinTestUrls(), declaration)
}
private fun Collection<String>.joinWithEscape(delimiterChar: Char): String {

View File

@@ -12,6 +12,7 @@ import org.jetbrains.kotlin.idea.base.platforms.KotlinNativeLibraryKind
import org.jetbrains.kotlin.idea.base.util.module
import org.jetbrains.kotlin.idea.highlighter.KotlinTestRunLineMarkerContributor
import org.jetbrains.kotlin.idea.projectModel.KotlinPlatform
import org.jetbrains.kotlin.idea.testIntegration.genericKotlinTestUrls
import org.jetbrains.kotlin.platform.impl.NativeIdePlatformKind
import org.jetbrains.kotlin.psi.KtClassOrObject
import org.jetbrains.kotlin.psi.KtFunction
@@ -49,26 +50,4 @@ abstract class AbstractNativeIdePlatformKindTooling : IdePlatformKindTooling() {
return hasRunConfigurations
}
protected fun getTestIcon(declaration: KtNamedDeclaration, moduleName: String): Icon? {
val targetName = moduleName.substringAfterLast(".").removeSuffix("Test>")
val urls = when (declaration) {
is KtClassOrObject -> {
val lightClass = declaration.toLightClass() ?: return null
listOf("java:suite://${lightClass.qualifiedName}")
}
is KtNamedFunction -> {
val lightMethod = declaration.toLightMethods().firstOrNull() ?: return null
val lightClass = lightMethod.containingClass as? KtLightClass ?: return null
val baseName = "java:test://${lightClass.qualifiedName}/${lightMethod.name}"
listOf("$baseName[${targetName}X64]", "$baseName[$targetName]", baseName)
}
else -> return null
}
return KotlinTestRunLineMarkerContributor.getTestStateIcon(urls, declaration)
}
}

View File

@@ -0,0 +1,36 @@
// Copyright 2000-2024 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
package org.jetbrains.kotlin.idea.testIntegration
import com.intellij.execution.TestStateStorage
import com.intellij.openapi.util.IntellijInternalApi
import org.jetbrains.kotlin.psi.KtClassOrObject
import org.jetbrains.kotlin.psi.KtDeclaration
import org.jetbrains.kotlin.psi.KtNamedFunction
import org.jetbrains.kotlin.psi.psiUtil.containingClass
/**
* Returns urls which can be used to query the [TestStateStorage].
* This is a generic implementation; More specialized versions might exist.
*
* Will return
* ```
* package foo.bar
*
* class MyTest { // <- 'java:suite://foo.bar.MyTest'
* @Test
* fun doTest() // <- 'java:suite://foo.bar.MyTest/doTest
* }
* ```
*/
@IntellijInternalApi
fun KtDeclaration.genericKotlinTestUrls(): List<String> {
return when (this) {
is KtClassOrObject -> listOf("java:suite://${this.fqName?.asString()}")
is KtNamedFunction -> {
val containingClass = this.containingClass()
listOf("java:test://${containingClass?.fqName?.asString()}/${this.name}")
}
else -> emptyList()
}
}

View File

@@ -0,0 +1,48 @@
// Copyright 2000-2024 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
package org.jetbrains.kotlin.idea.testIntegration
import com.intellij.openapi.application.ApplicationManager
import com.intellij.openapi.project.Project
import org.jetbrains.kotlin.idea.test.KotlinPluginUnitTest
import org.jetbrains.kotlin.psi.KtClass
import org.jetbrains.kotlin.psi.KtPsiFactory
import org.jetbrains.kotlin.psi.psiUtil.findFunctionByName
import kotlin.test.assertEquals
class GenericKotlinTestUrlsTest {
@KotlinPluginUnitTest
fun testSimpleClass(project: Project) = ApplicationManager.getApplication().runReadAction {
val ktClass = KtPsiFactory(project).createClass("class A")
assertEquals(listOf("java:suite://A"), ktClass.genericKotlinTestUrls())
}
@KotlinPluginUnitTest
fun testClassInPackage(project: Project) = ApplicationManager.getApplication().runReadAction {
val ktClass = KtPsiFactory(project).createClass("package a.b.c; class A")
assertEquals(listOf("java:suite://a.b.c.A"), ktClass.genericKotlinTestUrls())
}
@KotlinPluginUnitTest
fun testNestedClass(project: Project) = ApplicationManager.getApplication().runReadAction {
val ktClass = KtPsiFactory(project).createClass("class A { class B }")
val bKtClass = ktClass.declarations.find { it.name == "B" } as? KtClass ?: error("Cannot find B")
assertEquals(listOf("java:suite://A.B"), bKtClass.genericKotlinTestUrls())
}
@KotlinPluginUnitTest
fun testFun(project: Project) = ApplicationManager.getApplication().runReadAction {
val ktClass = KtPsiFactory(project).createClass(
"""
package a.b.c
class Foo {
fun foo()
}
""".trimIndent()
)
val foo = ktClass.findFunctionByName("foo") ?: error("Cannot find foo()")
assertEquals(listOf("java:test://a.b.c.Foo/foo"), foo.genericKotlinTestUrls())
}
}

View File

@@ -8,6 +8,8 @@ import org.jetbrains.kotlin.idea.base.codeInsight.tooling.AbstractGenericTestIco
import org.jetbrains.kotlin.idea.base.codeInsight.tooling.AbstractNativeIdePlatformKindTooling
import org.jetbrains.kotlin.idea.base.projectStructure.languageVersionSettings
import org.jetbrains.kotlin.idea.caches.resolve.resolveToDescriptorIfAny
import org.jetbrains.kotlin.idea.highlighter.KotlinTestRunLineMarkerContributor
import org.jetbrains.kotlin.idea.testIntegration.genericKotlinTestUrls
import org.jetbrains.kotlin.psi.KtElement
import org.jetbrains.kotlin.psi.KtFunction
import org.jetbrains.kotlin.psi.KtNamedDeclaration
@@ -36,8 +38,7 @@ class Fe10NativeIdePlatformKindTooling : AbstractNativeIdePlatformKindTooling()
val descriptor = declaration.resolveToDescriptorIfAny() ?: return null
if (!Fe10GenericTestIconProvider.isKotlinTestDeclaration(descriptor)) return null
val moduleName = descriptor.module.stableName?.asString() ?: ""
return getTestIcon(declaration, moduleName)
return KotlinTestRunLineMarkerContributor.getTestStateIcon(declaration.genericKotlinTestUrls(), declaration)
}
}

View File

@@ -7,6 +7,7 @@ import org.jetbrains.kotlin.idea.base.codeInsight.tooling.AbstractNativeIdePlatf
import org.jetbrains.kotlin.idea.base.facet.implementingModules
import org.jetbrains.kotlin.idea.base.util.module
import org.jetbrains.kotlin.idea.highlighter.KotlinTestRunLineMarkerContributor
import org.jetbrains.kotlin.idea.testIntegration.genericKotlinTestUrls
import org.jetbrains.kotlin.psi.KtClassOrObject
import org.jetbrains.kotlin.psi.KtFunction
import org.jetbrains.kotlin.psi.KtNamedDeclaration
@@ -35,16 +36,6 @@ internal class FirNativeIdePlatformKindTooling : AbstractNativeIdePlatformKindTo
return null
}
val urls = when (declaration) {
is KtClassOrObject -> listOf("java:suite://${declaration.fqName?.asString()}")
is KtNamedFunction -> {
val containingClass = declaration.containingClass()
listOf("java:test://${containingClass?.fqName?.asString()}/${declaration.name}")
}
else -> return null
}
return KotlinTestRunLineMarkerContributor.getTestStateIcon(urls, declaration)
return KotlinTestRunLineMarkerContributor.getTestStateIcon(declaration.genericKotlinTestUrls(), declaration)
}
}

View File

@@ -1,27 +1,22 @@
// Copyright 2000-2023 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
package org.jetbrains.kotlin.gradle.idea.importing.multiplatformTests
import com.intellij.codeInsight.daemon.LineMarkerInfo
import com.intellij.codeInsight.daemon.impl.DaemonCodeAnalyzerImpl
import com.intellij.execution.lineMarker.RunLineMarkerContributor
import com.intellij.icons.AllIcons
import com.intellij.lang.annotation.HighlightSeverity
import com.intellij.openapi.vfs.findFile
import com.intellij.psi.PsiDocumentManager
import com.intellij.psi.PsiManager
import com.intellij.testFramework.runInEdtAndWait
import com.intellij.testIntegration.TestRunLineMarkerProvider
import com.intellij.util.containers.ContainerUtil.filterIsInstance
import org.jetbrains.kotlin.gradle.multiplatformTests.AbstractKotlinMppGradleImportingTest
import org.jetbrains.kotlin.gradle.multiplatformTests.TestConfigurationDslScope
import org.jetbrains.kotlin.gradle.multiplatformTests.testFeatures.CustomChecksDsl
import org.jetbrains.kotlin.gradle.multiplatformTests.testFeatures.CustomGradleProperties
import org.jetbrains.kotlin.gradle.multiplatformTests.testFeatures.CustomGradlePropertiesTestFeature
import org.jetbrains.kotlin.gradle.multiplatformTests.testFeatures.checkers.highlighting.HighlightingChecker
import org.jetbrains.kotlin.gradle.multiplatformTests.testFeatures.checkers.hooks.TestHooks
import org.jetbrains.kotlin.gradle.multiplatformTests.testFeatures.checkers.runConfigurations.ExecuteRunConfigurationsChecker
import org.jetbrains.kotlin.gradle.multiplatformTests.testFeatures.checkers.runConfigurations.RunConfigurationsChecker
import org.jetbrains.kotlin.idea.base.plugin.KotlinPluginMode
import org.jetbrains.kotlin.idea.base.plugin.KotlinPluginModeProvider
import org.jetbrains.kotlin.test.TestMetadata
import org.jetbrains.kotlin.tooling.core.compareTo
import org.jetbrains.plugins.gradle.tooling.annotation.PluginTargetVersions
@@ -66,6 +61,7 @@ class KotlinMppRunConfigurationsTest : AbstractKotlinMppGradleImportingTest(), C
setRegistryPropertyForTest("gradle.testLauncherAPI.enabled", "false")
doTest {
onlyModules("project.*")
executeRunConfiguration("CommonTest.test in commonTest - success")
executeRunConfiguration("CommonTest.test in commonTest - failure")
executeRunConfiguration("IosTest.test in iosTest - success")
@@ -117,4 +113,48 @@ class KotlinMppRunConfigurationsTest : AbstractKotlinMppGradleImportingTest(), C
}
}
}
@PluginTargetVersions(pluginVersion = "2.0.20-dev-0+")
@Test
fun testCommonTest() {
doTest {
onlyModules(".*commonTest")
executeRunConfiguration("foo.bar.CommonTest")
/**
* Let's test the line-markers after the test executed
*
* ```kotlin
* class CommonTest { // <- RED!
* @Test
* fun failure() // <- RED!
*
* @Test
* fun success() // <- GREEN!
* }
* ```
*/
runAfterTestExecution {
val nativeTestFile = projectRoot.findFile("src/commonTest/kotlin/CommonTest.kt") ?: error("Missing 'CommonTest.kt'")
runInEdtAndWait {
codeInsightTestFixture.openFileInEditor(nativeTestFile)
codeInsightTestFixture.doHighlighting()
val psi = PsiManager.getInstance(myProject).findFile(nativeTestFile) ?: error("Missing 'CommonTest.kt' PsiFile")
val document = PsiDocumentManager.getInstance(myProject).getDocument(psi) ?: error("Missing 'CommonTest.kt' Document")
val lineMarkers = DaemonCodeAnalyzerImpl.getLineMarkers(document, project)
fun assertStateAtText(text: String, icon: Icon) {
val lineMarker = lineMarkers.find { marker -> marker.element?.text == text }
?: error("Missing line marker for '$text'")
kotlin.test.assertEquals(icon, lineMarker.icon, "Wrong line-marker at '$text'")
}
assertStateAtText("CommonTest", AllIcons.RunConfigurations.TestState.Red2)
assertStateAtText("success", AllIcons.RunConfigurations.TestState.Green2)
assertStateAtText("failure", AllIcons.RunConfigurations.TestState.Red2)
}
}
}
}
}

View File

@@ -0,0 +1,19 @@
plugins {
kotlin("multiplatform")
}
repositories {
{ { kts_kotlin_plugin_repositories } }
}
kotlin {
jvm()
macosX64()
macosArm64()
linuxX64()
linuxArm64()
sourceSets.commonTest.dependencies {
implementation(kotlin("test"))
}
}

View File

@@ -0,0 +1,36 @@
MODULES
project.commonTest
fqName: foo.bar.CommonTest
name: CommonTest
type: Gradle
tasks: [:cleanJvmTest, :jvmTest, --tests, "foo.bar.CommonTest"]
scriptParameters:
isDebugAllEnabled: false
isRunAsTest: true
fqName: foo.bar.CommonTest.failure
name: CommonTest.failure
type: Gradle
tasks: [:cleanJvmTest, :jvmTest, --tests, "foo.bar.CommonTest.failure"]
scriptParameters:
isDebugAllEnabled: false
isRunAsTest: true
fqName: foo.bar.CommonTest.success
name: CommonTest.success
type: Gradle
tasks: [:cleanJvmTest, :jvmTest, --tests, "foo.bar.CommonTest.success"]
scriptParameters:
isDebugAllEnabled: false
isRunAsTest: true
Test configuration:
- showing only modules matching .*commonTest
- Showing runConfiguration detail: name
- Showing runConfiguration detail: type
- Showing runConfiguration detail: tasks
- Showing runConfiguration detail: scriptParameters
- Showing runConfiguration detail: isDebugAllEnabled
- Showing runConfiguration detail: isRunAsTest

View File

@@ -0,0 +1,8 @@
09:47:03: Executing ':cleanJvmTest :jvmTest --tests "foo.bar.CommonTest"'…
09:47:06: Execution finished ':cleanJvmTest :jvmTest --tests "foo.bar.CommonTest"'.
com.intellij.openapi.externalSystem.model.ExternalSystemException: There were failing tests. See the report at: file:///private/var/folders/1j/4f6kfm9d3fq98slcxvk2dr7c0000gn/T/unitTest_commonTest_2mbEIjqWRHVprTFR6f21p0INRfd/tmp/testDir1727336800275/project/build/reports/tests/jvmTest/index.html
org.gradle.api.internal.exceptions.MarkedVerificationException: There were failing tests. See the report at: file:///private/var/folders/1j/4f6kfm9d3fq98slcxvk2dr7c0000gn/T/unitTest_commonTest_2mbEIjqWRHVprTFR6f21p0INRfd/tmp/testDir1727336800275/project/build/reports/tests/jvmTest/index.html
<exitCode: 1>

View File

@@ -0,0 +1,10 @@
pluginManagement {
repositories {
{{kts_kotlin_plugin_repositories}}
}
plugins {
kotlin("multiplatform") version "{{kgp_version}}"
}
}

View File

@@ -0,0 +1,13 @@
package foo.bar
class CommonTest {
@kotlin.test.Test
fun success() {
}
@kotlin.test.Test
fun failure() {
kotlin.test.assertEquals(1, 2)
}
}

View File

@@ -107,6 +107,7 @@ project.nativeTest
Test configuration:
- showing only modules matching project.*
- Showing runConfiguration detail: name
- Showing runConfiguration detail: type
- Showing runConfiguration detail: tasks