Line Marking: Respect @Test/@Ignore annotations in test line markers
#KT-16814 In Progress
This commit is contained in:
@@ -33,3 +33,33 @@ fun String.collapseSpaces(): String {
|
||||
}
|
||||
return builder.toString()
|
||||
}
|
||||
|
||||
// -------------------- copied from EscapeUtils.java --------------------
|
||||
|
||||
private val ESCAPE_CHAR = '\\'
|
||||
|
||||
private fun calcExpectedJoinedSize(list: Collection<String>) = list.size - 1 + list.sumBy { it.length }
|
||||
|
||||
fun Collection<String>.joinWithEscape(delimiterChar: Char): String {
|
||||
if (isEmpty()) return ""
|
||||
|
||||
val expectedSize = calcExpectedJoinedSize(this)
|
||||
val out = StringBuilder(expectedSize)
|
||||
var first = true
|
||||
for (s in this) {
|
||||
if (!first) {
|
||||
out.append(delimiterChar)
|
||||
}
|
||||
first = false
|
||||
for (i in 0 until s.length) {
|
||||
val ch = s[i]
|
||||
if (ch == delimiterChar || ch == ESCAPE_CHAR) {
|
||||
out.append(ESCAPE_CHAR)
|
||||
}
|
||||
out.append(ch)
|
||||
}
|
||||
}
|
||||
return out.toString()
|
||||
}
|
||||
|
||||
// ----------------------------------------------------------------------
|
||||
+79
-13
@@ -23,19 +23,37 @@ import com.intellij.execution.lineMarker.RunLineMarkerContributor
|
||||
import com.intellij.execution.testframework.TestIconMapper
|
||||
import com.intellij.execution.testframework.sm.runner.states.TestStateInfo
|
||||
import com.intellij.icons.AllIcons
|
||||
import com.intellij.openapi.module.ModuleUtilCore
|
||||
import com.intellij.openapi.project.Project
|
||||
import com.intellij.openapi.util.io.FileUtil
|
||||
import com.intellij.psi.PsiElement
|
||||
import org.jetbrains.kotlin.asJava.classes.KtLightClass
|
||||
import org.jetbrains.kotlin.asJava.toLightClass
|
||||
import org.jetbrains.kotlin.asJava.toLightMethods
|
||||
import org.jetbrains.kotlin.descriptors.ClassDescriptor
|
||||
import org.jetbrains.kotlin.descriptors.ClassDescriptorWithResolutionScopes
|
||||
import org.jetbrains.kotlin.descriptors.DeclarationDescriptor
|
||||
import org.jetbrains.kotlin.idea.caches.resolve.resolveToDescriptorIfAny
|
||||
import org.jetbrains.kotlin.idea.js.getJsOutputFilePath
|
||||
import org.jetbrains.kotlin.idea.project.TargetPlatformDetector
|
||||
import org.jetbrains.kotlin.idea.util.string.joinWithEscape
|
||||
import org.jetbrains.kotlin.js.resolve.JsPlatform
|
||||
import org.jetbrains.kotlin.name.FqName
|
||||
import org.jetbrains.kotlin.psi.KtClassOrObject
|
||||
import org.jetbrains.kotlin.psi.KtNamedDeclaration
|
||||
import org.jetbrains.kotlin.psi.KtNamedFunction
|
||||
import org.jetbrains.kotlin.psi.psiUtil.containingClassOrObject
|
||||
import org.jetbrains.kotlin.psi.psiUtil.getStrictParentOfType
|
||||
import org.jetbrains.kotlin.psi.psiUtil.parentsWithSelf
|
||||
import org.jetbrains.kotlin.resolve.jvm.platform.JvmPlatform
|
||||
import javax.swing.Icon
|
||||
|
||||
class KotlinTestRunLineMarkerContributor : RunLineMarkerContributor() {
|
||||
companion object {
|
||||
private val TEST_FQ_NAME = FqName("kotlin.test.Test")
|
||||
private val IGNORE_FQ_NAME = FqName("kotlin.test.Ignore")
|
||||
}
|
||||
|
||||
private fun getTestStateIcon(url: String, project: Project): Icon? {
|
||||
val defaultIcon = AllIcons.RunConfigurations.TestState.Run
|
||||
val state = TestStateStorage.getInstance(project).getState(url) ?: return defaultIcon
|
||||
@@ -49,17 +67,7 @@ class KotlinTestRunLineMarkerContributor : RunLineMarkerContributor() {
|
||||
}
|
||||
}
|
||||
|
||||
override fun getInfo(element: PsiElement): RunLineMarkerContributor.Info? {
|
||||
val declaration = element.getStrictParentOfType<KtNamedDeclaration>() ?: return null
|
||||
if (declaration.nameIdentifier != element) return null
|
||||
|
||||
if (declaration !is KtClassOrObject && declaration !is KtNamedFunction) return null
|
||||
|
||||
// To prevent IDEA failing on red code
|
||||
if (declaration.resolveToDescriptorIfAny() == null) return null
|
||||
|
||||
val project = element.project
|
||||
|
||||
private fun getJavaTestIcon(declaration: KtNamedDeclaration): Icon? {
|
||||
val (url, framework) = when (declaration) {
|
||||
is KtClassOrObject -> {
|
||||
val lightClass = declaration.toLightClass() ?: return null
|
||||
@@ -81,8 +89,66 @@ class KotlinTestRunLineMarkerContributor : RunLineMarkerContributor() {
|
||||
|
||||
else -> return null
|
||||
}
|
||||
return getTestStateIcon(url, declaration.project) ?: framework.icon
|
||||
}
|
||||
|
||||
val icon = getTestStateIcon(url, project) ?: framework.icon
|
||||
return RunLineMarkerContributor.Info(icon, { "Run Test" }, ExecutorAction.getActions(1))
|
||||
private fun DeclarationDescriptor.isIgnored(): Boolean =
|
||||
annotations.any { it.fqName == IGNORE_FQ_NAME } || ((containingDeclaration as? ClassDescriptor)?.isIgnored() ?: false)
|
||||
|
||||
private fun DeclarationDescriptor.isTest(): Boolean {
|
||||
if (isIgnored()) return false
|
||||
|
||||
if (annotations.any { it.fqName == TEST_FQ_NAME }) return true
|
||||
if (this is ClassDescriptorWithResolutionScopes) {
|
||||
return declaredCallableMembers.any { it.isTest() }
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
private fun getJavaScriptTestIcon(declaration: KtNamedDeclaration, descriptor: DeclarationDescriptor): Icon? {
|
||||
if (!descriptor.isTest()) return null
|
||||
|
||||
val module = ModuleUtilCore.findModuleForPsiElement(declaration) ?: return null
|
||||
val testFilePath = getJsOutputFilePath(module, true, false) ?: return null
|
||||
|
||||
val locations = ArrayList<String>()
|
||||
|
||||
locations += FileUtil.toSystemDependentName(testFilePath)
|
||||
|
||||
val klass = when (declaration) {
|
||||
is KtClassOrObject -> declaration
|
||||
is KtNamedFunction -> declaration.containingClassOrObject ?: return null
|
||||
else -> return null
|
||||
}
|
||||
locations += klass.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 getTestStateIcon(url, declaration.project)
|
||||
}
|
||||
|
||||
override fun getInfo(element: PsiElement): RunLineMarkerContributor.Info? {
|
||||
val declaration = element.getStrictParentOfType<KtNamedDeclaration>() ?: return null
|
||||
if (declaration.nameIdentifier != element) return null
|
||||
|
||||
if (declaration !is KtClassOrObject && declaration !is KtNamedFunction) return null
|
||||
|
||||
// To prevent IDEA failing on red code
|
||||
val descriptor = declaration.resolveToDescriptorIfAny() ?: return null
|
||||
|
||||
val platform = TargetPlatformDetector.getPlatform(declaration.containingKtFile)
|
||||
val icon = when (platform) {
|
||||
is JvmPlatform -> getJavaTestIcon(declaration)
|
||||
is JsPlatform -> getJavaScriptTestIcon(declaration, descriptor)
|
||||
else -> return null
|
||||
}
|
||||
return RunLineMarkerContributor.Info(icon, { "Run Test" }, ExecutorAction.getActions())
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,32 @@
|
||||
/*
|
||||
* Copyright 2010-2017 JetBrains s.r.o.
|
||||
*
|
||||
* 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 org.jetbrains.kotlin.idea.js
|
||||
|
||||
import com.intellij.openapi.module.Module
|
||||
import com.intellij.openapi.roots.CompilerModuleExtension
|
||||
import org.jetbrains.jps.util.JpsPathUtil
|
||||
import org.jetbrains.kotlin.utils.KotlinJavascriptMetadataUtils
|
||||
|
||||
fun getJsOutputFilePath(module: Module, isTests: Boolean, isMeta: Boolean): String? {
|
||||
val compilerExtension = CompilerModuleExtension.getInstance(module)
|
||||
val outputDir = (if (isTests) compilerExtension?.compilerOutputUrlForTests else compilerExtension?.compilerOutputUrl)
|
||||
?: return null
|
||||
val extension = if (isMeta) KotlinJavascriptMetadataUtils.META_JS_SUFFIX else KotlinJavascriptMetadataUtils.JS_EXT
|
||||
return JpsPathUtil.urlToPath("$outputDir/${module.name}${suffix(isTests)}$extension")
|
||||
}
|
||||
|
||||
private fun suffix(isTests: Boolean) = if (isTests) "_test" else ""
|
||||
@@ -0,0 +1,6 @@
|
||||
// !CHECK_HIGHLIGHTING
|
||||
|
||||
package kotlin.test
|
||||
|
||||
annotation class Test
|
||||
annotation class Ignore
|
||||
@@ -0,0 +1,18 @@
|
||||
import kotlin.test.*
|
||||
|
||||
class <lineMarker descr="Run Test">SimpleTest</lineMarker> {
|
||||
@Test fun <lineMarker descr="Run Test">testFoo</lineMarker>() {
|
||||
// Will run
|
||||
}
|
||||
|
||||
@Ignore fun testFooWrong() {
|
||||
// Will not run
|
||||
}
|
||||
}
|
||||
|
||||
@Ignore
|
||||
class TestTest {
|
||||
@Test fun emptyTest() {
|
||||
// Will not run
|
||||
}
|
||||
}
|
||||
@@ -16,6 +16,8 @@
|
||||
|
||||
package org.jetbrains.kotlin.idea.caches.resolve
|
||||
|
||||
import com.intellij.openapi.roots.CompilerModuleExtension
|
||||
import com.intellij.openapi.roots.ModuleRootModificationUtil
|
||||
import org.jetbrains.kotlin.config.JvmTarget
|
||||
import org.jetbrains.kotlin.config.TargetPlatformKind
|
||||
import org.jetbrains.kotlin.idea.test.PluginTestCaseBase
|
||||
@@ -49,4 +51,16 @@ class MultiModuleLineMarkerTest : AbstractMultiModuleHighlightingTest() {
|
||||
fun testSuspendImplInPlatformModules() {
|
||||
doMultiPlatformTest(TargetPlatformKind.Jvm[JvmTarget.JVM_1_6], TargetPlatformKind.JavaScript)
|
||||
}
|
||||
|
||||
fun testKotlinTestAnnotations() {
|
||||
doMultiPlatformTest(TargetPlatformKind.JavaScript,
|
||||
configureModule = { module, _ ->
|
||||
ModuleRootModificationUtil.updateModel(module) {
|
||||
with(it.getModuleExtension(CompilerModuleExtension::class.java)!!) {
|
||||
inheritCompilerOutputPath(false)
|
||||
setCompilerOutputPathForTests("js_out")
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user