Prefer Kotlin Junit test run configurations when possible (KT-33787)

This commit is contained in:
Yan Zhulanow
2019-10-24 20:21:18 +09:00
parent 987307cf92
commit ef8b3cb72a
12 changed files with 257 additions and 64 deletions
+2
View File
@@ -162,6 +162,8 @@ dependencies {
testCompile(intellijPluginDep("copyright"))
testCompile(intellijPluginDep("properties"))
testCompile(intellijPluginDep("java-i18n"))
testCompile(intellijPluginDep("junit"))
testCompileOnly(intellijDep())
testCompileOnly(commonDep("com.google.code.findbugs", "jsr305"))
testCompileOnly(intellijPluginDep("gradle"))
@@ -5,17 +5,13 @@
package org.jetbrains.kotlin.idea.codeInsight.gradle
import com.intellij.execution.PsiLocation
import com.intellij.execution.actions.ConfigurationContext
import com.intellij.execution.actions.ConfigurationFromContext
import com.intellij.openapi.vfs.VirtualFile
import com.intellij.psi.PsiManager
import org.junit.Test
import com.intellij.testFramework.runInEdtAndWait
import com.intellij.util.PlatformUtils
import org.jetbrains.kotlin.idea.run.KotlinGradleRunConfiguration
import org.jetbrains.kotlin.idea.run.KotlinJvmTestClassGradleConfigurationProducer
import org.jetbrains.kotlin.idea.run.KotlinJvmTestMethodGradleConfigurationProducer
import org.jetbrains.kotlin.idea.run.getConfiguration
import org.jetbrains.kotlin.idea.util.application.runReadAction
import org.jetbrains.plugins.gradle.execution.test.runner.TestClassGradleConfigurationProducer
import org.jetbrains.plugins.gradle.execution.test.runner.TestMethodGradleConfigurationProducer
@@ -36,34 +32,25 @@ class GradleTestRunConfigurationTest : GradleImportingTestCase() {
val javaFile = files.first { it.name == "MyTest.java" }
val kotlinFile = files.first { it.name == "MyKotlinTest.kt" }
val javaClassConfiguration = getConfiguration(javaFile, "MyTest")
val javaClassConfiguration = getConfiguration(javaFile, myProject, "MyTest")
javaClassConfiguration.isProducedBy(TestClassGradleConfigurationProducer::class.java)
assert(javaClassConfiguration.configuration !is KotlinGradleRunConfiguration)
val javaMethodConfiguration = getConfiguration(javaFile, "testA")
val javaMethodConfiguration = getConfiguration(javaFile, myProject, "testA")
javaMethodConfiguration.isProducedBy(TestMethodGradleConfigurationProducer::class.java)
assert(javaMethodConfiguration.configuration !is KotlinGradleRunConfiguration)
val kotlinClassConfiguration = getConfiguration(kotlinFile, "MyKotlinTest")
val kotlinClassConfiguration = getConfiguration(kotlinFile, myProject, "MyKotlinTest")
kotlinClassConfiguration.isProducedBy(KotlinJvmTestClassGradleConfigurationProducer::class.java)
assert(kotlinClassConfiguration.configuration is KotlinGradleRunConfiguration)
val kotlinFunctionConfiguration = getConfiguration(kotlinFile, "testA")
val kotlinFunctionConfiguration = getConfiguration(kotlinFile, myProject, "testA")
kotlinFunctionConfiguration.isProducedBy(KotlinJvmTestMethodGradleConfigurationProducer::class.java)
assert(kotlinFunctionConfiguration.configuration is KotlinGradleRunConfiguration)
}
}
}
private fun getConfiguration(file: VirtualFile, pattern: String): ConfigurationFromContext {
val psiFile = PsiManager.getInstance(myTestFixture.project).findFile(file) ?: error("PsiFile not found for $file")
val offset = psiFile.text.indexOf(pattern)
val psiElement = psiFile.findElementAt(offset)
val location = PsiLocation(psiElement)
val context = ConfigurationContext.createEmptyContextForLocation(location)
return context.configurationsFromContext.orEmpty().singleOrNull() ?: error("Configuration not found for pattern $pattern")
}
override fun testDataDirName(): String {
return "testRunConfigurations"
}
@@ -0,0 +1,41 @@
/*
* Copyright 2010-2019 JetBrains s.r.o. and Kotlin Programming Language contributors.
* Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file.
*/
package org.jetbrains.kotlin.idea.run
import com.intellij.execution.ExecutionBundle
import com.intellij.execution.configurations.ConfigurationFactory
import com.intellij.execution.configurations.ConfigurationType
import com.intellij.execution.configurations.ConfigurationTypeUtil
import com.intellij.execution.configurations.RunConfiguration
import com.intellij.execution.junit.JUnitConfiguration
import com.intellij.icons.AllIcons
import com.intellij.openapi.project.Project
import javax.swing.Icon
class KotlinJUnitConfigurationType : ConfigurationType {
val factory = object : ConfigurationFactory(this) {
override fun createTemplateConfiguration(project: Project): RunConfiguration {
return KotlinJUnitConfiguration("", project, this)
}
}
companion object {
val instance: KotlinJUnitConfigurationType
get() = ConfigurationTypeUtil.findConfigurationType(KotlinJUnitConfigurationType::class.java)
}
override fun getConfigurationFactories(): Array<ConfigurationFactory> = arrayOf(factory)
override fun getDisplayName() = ExecutionBundle.message("junit.configuration.display.name")
override fun getConfigurationTypeDescription() = ExecutionBundle.message("junit.configuration.description")
override fun getIcon(): Icon = AllIcons.RunConfigurations.Junit
override fun getId() = "KotlinJUnit"
override fun getTag() = "junit"
override fun isDumbAware() = false
}
class KotlinJUnitConfiguration(name: String, project: Project, configurationFactory: ConfigurationFactory) :
JUnitConfiguration(name, project, configurationFactory)
@@ -12,6 +12,7 @@ import com.intellij.execution.RunnerAndConfigurationSettings
import com.intellij.execution.actions.ConfigurationContext
import com.intellij.execution.actions.ConfigurationFromContext
import com.intellij.execution.actions.RunConfigurationProducer
import com.intellij.execution.configurations.ConfigurationFactory
import com.intellij.execution.configurations.ModuleBasedConfiguration
import com.intellij.execution.junit.*
import com.intellij.execution.testframework.AbstractPatternBasedConfigurationProducer
@@ -32,8 +33,13 @@ import org.jetbrains.kotlin.psi.KtNamedFunction
import org.jetbrains.kotlin.psi.psiUtil.getParentOfType
class KotlinJUnitRunConfigurationProducer : RunConfigurationProducer<JUnitConfiguration>(JUnitConfigurationType.getInstance()) {
override fun getConfigurationFactory(): ConfigurationFactory {
return KotlinJUnitConfigurationType.instance.factory
}
override fun shouldReplace(self: ConfigurationFromContext, other: ConfigurationFromContext): Boolean {
return other.isProducedBy(JUnitConfigurationProducer::class.java) || other.isProducedBy(AbstractPatternBasedConfigurationProducer::class.java)
return other.isProducedBy(JUnitConfigurationProducer::class.java)
|| other.isProducedBy(AbstractPatternBasedConfigurationProducer::class.java)
}
private fun isAvailableInMpp(context: ConfigurationContext): Boolean {
@@ -18,6 +18,7 @@ package org.jetbrains.kotlin.idea.run
import com.intellij.execution.actions.ConfigurationContext
import com.intellij.execution.actions.ConfigurationFromContext
import com.intellij.execution.configurations.ConfigurationFactory
import com.intellij.execution.junit.JUnitConfiguration
import com.intellij.execution.junit.PatternConfigurationProducer
import com.intellij.execution.junit.TestClassConfigurationProducer
@@ -28,6 +29,10 @@ import org.jetbrains.kotlin.asJava.toLightClass
import org.jetbrains.kotlin.psi.KtClassOrObject
class KotlinPatternConfigurationProducer : PatternConfigurationProducer() {
override fun getConfigurationFactory(): ConfigurationFactory {
return KotlinJUnitConfigurationType.instance.factory
}
override fun setupConfigurationFromContext(
configuration: JUnitConfiguration,
context: ConfigurationContext,
+1
View File
@@ -110,6 +110,7 @@
<library.presentationProvider implementation="org.jetbrains.kotlin.idea.framework.JavaRuntimePresentationProvider"/>
<configurationType implementation="org.jetbrains.kotlin.idea.run.KotlinRunConfigurationType"/>
<configurationType implementation="org.jetbrains.kotlin.idea.run.KotlinJUnitConfigurationType"/>
<configurationType implementation="org.jetbrains.kotlin.idea.run.script.standalone.KotlinStandaloneScriptRunConfigurationType"/>
<runConfigurationProducer implementation="org.jetbrains.kotlin.idea.run.KotlinRunConfigurationProducer"/>
<runConfigurationProducer implementation="org.jetbrains.kotlin.idea.run.script.standalone.KotlinStandaloneScriptRunConfigurationProducer"/>
+1
View File
@@ -115,6 +115,7 @@
<library.presentationProvider implementation="org.jetbrains.kotlin.idea.framework.JavaRuntimePresentationProvider"/>
<configurationType implementation="org.jetbrains.kotlin.idea.run.KotlinRunConfigurationType"/>
<configurationType implementation="org.jetbrains.kotlin.idea.run.KotlinJUnitConfigurationType"/>
<configurationType implementation="org.jetbrains.kotlin.idea.run.script.standalone.KotlinStandaloneScriptRunConfigurationType"/>
<runConfigurationProducer implementation="org.jetbrains.kotlin.idea.run.KotlinRunConfigurationProducer"/>
<runConfigurationProducer implementation="org.jetbrains.kotlin.idea.run.script.standalone.KotlinStandaloneScriptRunConfigurationProducer"/>
@@ -0,0 +1,11 @@
import org.junit.Test;
public class MyJavaTest {
@Test
public void testA() {
}
@Test
public void testB() {
}
}
@@ -0,0 +1,11 @@
import org.junit.Test
class MyKotlinTest {
@Test
fun testA() {
}
@Test
fun testB() {
}
}
@@ -0,0 +1,77 @@
/*
* Copyright 2010-2019 JetBrains s.r.o. and Kotlin Programming Language contributors.
* Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file.
*/
package org.jetbrains.kotlin.idea.run
import com.intellij.openapi.module.Module
import com.intellij.openapi.vfs.LocalFileSystem
import com.intellij.openapi.vfs.VirtualFile
import com.intellij.psi.PsiDocumentManager
import com.intellij.testFramework.PsiTestUtil
import org.jetbrains.kotlin.idea.util.application.runWriteAction
import org.jetbrains.kotlin.idea.test.KotlinCodeInsightTestCase
import org.jetbrains.kotlin.idea.test.PluginTestCaseBase.*
import java.io.File
abstract class AbstractRunConfigurationTest : @Suppress("DEPRECATION") KotlinCodeInsightTestCase() {
fun getTestProject() = myProject!!
override fun getModule() = myModule!!
protected fun configureModule(moduleDir: String, outputParentDir: VirtualFile, configModule: Module = module): CreateModuleResult {
val srcPath = "$moduleDir/src"
val srcDir: VirtualFile?
if (File(srcPath).exists()) {
srcDir = PsiTestUtil.createTestProjectStructure(project, configModule, srcPath, myFilesToDelete, false)
PsiTestUtil.addSourceRoot(module, srcDir, false)
} else {
srcDir = null
}
val testPath = "$moduleDir/test"
val testDir: VirtualFile?
if (File(testPath).exists()) {
testDir = if (srcDir == null) {
PsiTestUtil.createTestProjectStructure(project, configModule, testPath, myFilesToDelete, false)
} else {
val canonicalPath = File(testPath).canonicalPath.replace(File.separatorChar, '/')
LocalFileSystem.getInstance().refreshAndFindFileByPath(canonicalPath)
}
if (testDir != null) {
PsiTestUtil.addSourceRoot(module, testDir, true)
}
} else {
testDir = null
}
val (srcOutDir, testOutDir) = runWriteAction {
val outDir = outputParentDir.createChildDirectory(this, "out")
val srcOutDir = outDir.createChildDirectory(this, "production")
val testOutDir = outDir.createChildDirectory(this, "test")
PsiTestUtil.setCompilerOutputPath(configModule, srcOutDir.url, false)
PsiTestUtil.setCompilerOutputPath(configModule, testOutDir.url, true)
Pair(srcOutDir, testOutDir)
}
PsiDocumentManager.getInstance(getTestProject()).commitAllDocuments()
return CreateModuleResult(configModule, srcDir, testDir, srcOutDir, testOutDir)
}
protected class CreateModuleResult(
val module: Module,
val srcDir: VirtualFile?,
val testDir: VirtualFile?,
val srcOutputDir: VirtualFile,
val testOutputDir: VirtualFile
)
protected fun moduleDirPath(moduleName: String) = "${testDataPath}${getTestName(false)}/$moduleName"
override fun getTestProjectJdk() = mockJdk()
}
@@ -0,0 +1,93 @@
/*
* Copyright 2010-2019 JetBrains s.r.o. and Kotlin Programming Language contributors.
* Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file.
*/
package org.jetbrains.kotlin.idea.run
import com.intellij.execution.PsiLocation
import com.intellij.execution.actions.ConfigurationContext
import com.intellij.execution.actions.ConfigurationFromContext
import com.intellij.execution.junit.TestInClassConfigurationProducer
import com.intellij.openapi.application.PathManager
import com.intellij.openapi.project.Project
import com.intellij.openapi.roots.ModuleRootModificationUtil.updateModel
import com.intellij.openapi.roots.OrderRootType
import com.intellij.openapi.roots.ui.configuration.libraryEditor.NewLibraryEditor
import com.intellij.openapi.vfs.JarFileSystem
import com.intellij.openapi.vfs.LocalFileSystem
import com.intellij.openapi.vfs.VirtualFile
import com.intellij.psi.PsiManager
import com.intellij.util.PlatformUtils
import org.jetbrains.kotlin.idea.test.ConfigLibraryUtil
import org.jetbrains.kotlin.idea.test.PluginTestCaseBase.*
import org.jetbrains.kotlin.test.JUnit3WithIdeaConfigurationRunner
import org.junit.runner.RunWith
import java.io.File
@RunWith(JUnit3WithIdeaConfigurationRunner::class)
class KotlinJUnitRunConfigurationTest : AbstractRunConfigurationTest() {
fun testSimple() {
if (!PlatformUtils.isIntelliJ()) {
return
}
val createResult = configureModule(moduleDirPath("module"), getTestProject().baseDir!!)
val testDir = createResult.testDir!!
ConfigLibraryUtil.configureKotlinRuntimeAndSdk(module, addJdk(testRootDisposable, ::mockJdk))
try {
attachJUnitLibrary()
val javaFile = testDir.findChild("MyJavaTest.java")!!
val kotlinFile = testDir.findChild("MyKotlinTest.kt")!!
val javaClassConfiguration = getConfiguration(javaFile, project, "MyTest")
javaClassConfiguration.isProducedBy(TestInClassConfigurationProducer::class.java)
assert(javaClassConfiguration.configuration !is KotlinJUnitConfiguration)
val javaMethodConfiguration = getConfiguration(javaFile, project, "testA")
javaMethodConfiguration.isProducedBy(TestInClassConfigurationProducer::class.java)
assert(javaMethodConfiguration.configuration !is KotlinJUnitConfiguration)
val kotlinClassConfiguration = getConfiguration(kotlinFile, project, "MyKotlinTest")
kotlinClassConfiguration.isProducedBy(KotlinJUnitRunConfigurationProducer::class.java)
assert(kotlinClassConfiguration.configuration is KotlinJUnitConfiguration)
val kotlinFunctionConfiguration = getConfiguration(kotlinFile, project, "testA")
kotlinFunctionConfiguration.isProducedBy(KotlinJUnitRunConfigurationProducer::class.java)
assert(kotlinFunctionConfiguration.configuration is KotlinJUnitConfiguration)
} finally {
detachJUnitLibrary()
}
}
private fun attachJUnitLibrary() {
val platformPath = PathManager.getHomePath().replace(File.separatorChar, '/')
val junitLibraryFile = File("$platformPath/lib/junit-4.12.jar")
val junitLibraryVirtualFile = LocalFileSystem.getInstance().refreshAndFindFileByPath(junitLibraryFile.canonicalPath)!!
updateModel(module) { model ->
val editor = NewLibraryEditor()
editor.name = "JUnit"
editor.addRoot(JarFileSystem.getInstance().getJarRootForLocalFile(junitLibraryVirtualFile)!!, OrderRootType.CLASSES)
ConfigLibraryUtil.addLibrary(editor, model)
}
}
private fun detachJUnitLibrary() {
ConfigLibraryUtil.removeLibrary(module, "JUnit")
}
override fun getTestDataPath() = getTestDataPathBase() + "/runConfigurations/junit/"
}
fun getConfiguration(file: VirtualFile, project: Project, pattern: String): ConfigurationFromContext {
val psiFile = PsiManager.getInstance(project).findFile(file) ?: error("PsiFile not found for $file")
val offset = psiFile.text.indexOf(pattern)
val psiElement = psiFile.findElementAt(offset)
val location = PsiLocation(psiElement)
val context = ConfigurationContext.createEmptyContextForLocation(location)
return context.configurationsFromContext.orEmpty().singleOrNull() ?: error("Configuration not found for pattern $pattern")
}
@@ -11,10 +11,8 @@ import com.intellij.execution.actions.ConfigurationContext
import com.intellij.openapi.module.Module
import com.intellij.openapi.projectRoots.Sdk
import com.intellij.openapi.roots.ModuleRootModificationUtil
import com.intellij.openapi.vfs.VirtualFile
import com.intellij.psi.JavaPsiFacade
import com.intellij.psi.PsiComment
import com.intellij.psi.PsiDocumentManager
import com.intellij.psi.PsiManager
import com.intellij.refactoring.RefactoringFactory
import com.intellij.testFramework.MapDataContext
@@ -28,7 +26,6 @@ import org.jetbrains.kotlin.idea.search.allScope
import org.jetbrains.kotlin.idea.stubindex.KotlinFullClassNameIndex
import org.jetbrains.kotlin.idea.stubindex.KotlinTopLevelFunctionFqnNameIndex
import org.jetbrains.kotlin.idea.test.ConfigLibraryUtil
import org.jetbrains.kotlin.idea.test.KotlinCodeInsightTestCase
import org.jetbrains.kotlin.idea.test.PluginTestCaseBase.*
import org.jetbrains.kotlin.idea.test.configureLanguageAndApiVersion
import org.jetbrains.kotlin.idea.util.application.runWriteAction
@@ -43,10 +40,7 @@ import java.util.*
private const val RUN_PREFIX = "// RUN:"
@RunWith(JUnit3WithIdeaConfigurationRunner::class)
class RunConfigurationTest: KotlinCodeInsightTestCase() {
fun getTestProject() = myProject!!
override fun getModule() = myModule!!
class RunConfigurationTest : AbstractRunConfigurationTest() {
fun testMainInTest() {
val createResult = configureModule(moduleDirPath("module"), getTestProject().baseDir!!)
configureLanguageAndApiVersion(
@@ -108,7 +102,7 @@ class RunConfigurationTest: KotlinCodeInsightTestCase() {
}
}
createResult.srcDir.children.filter { it.extension == "kt" }.forEach {
createResult.srcDir?.children?.filter { it.extension == "kt" }?.forEach {
val psiFile = PsiManager.getInstance(createResult.module.project).findFile(it)
if (psiFile is KtFile) {
psiFile.acceptChildren(object : KtVisitorVoid() {
@@ -216,7 +210,7 @@ class RunConfigurationTest: KotlinCodeInsightTestCase() {
private fun doTest(configureRuntime: (Module, Sdk) -> Unit) {
val baseDir = getTestProject().baseDir!!
val createModuleResult = configureModule(moduleDirPath("module"), baseDir)
val srcDir = createModuleResult.srcDir
val srcDir = createModuleResult.srcDir!!
configureRuntime(createModuleResult.module, addJdk(testRootDisposable, ::mockJdk))
@@ -264,41 +258,5 @@ class RunConfigurationTest: KotlinCodeInsightTestCase() {
return createConfigurationFromElement(mainFunction, save) as KotlinRunConfiguration
}
private fun configureModule(moduleDir: String, outputParentDir: VirtualFile, configModule: Module = module): CreateModuleResult {
val srcPath = moduleDir + "/src"
val srcDir = PsiTestUtil.createTestProjectStructure(project, configModule, srcPath, myFilesToDelete, true)
val testPath = moduleDir + "/test"
if (File(testPath).exists()) {
val testDir = PsiTestUtil.createTestProjectStructure(project, configModule, testPath, myFilesToDelete, false)
PsiTestUtil.addSourceRoot(module, testDir, true)
}
val (srcOutDir, testOutDir) = runWriteAction {
val outDir = outputParentDir.createChildDirectory(this, "out")
val srcOutDir = outDir.createChildDirectory(this, "production")
val testOutDir = outDir.createChildDirectory(this, "test")
PsiTestUtil.setCompilerOutputPath(configModule, srcOutDir.url, false)
PsiTestUtil.setCompilerOutputPath(configModule, testOutDir.url, true)
Pair(srcOutDir, testOutDir)
}
PsiDocumentManager.getInstance(getTestProject()).commitAllDocuments()
return CreateModuleResult(configModule, srcDir, srcOutDir, testOutDir)
}
private fun moduleDirPath(moduleName: String) = "${testDataPath}${getTestName(false)}/$moduleName"
override fun getTestDataPath() = getTestDataPathBase() + "/run/"
override fun getTestProjectJdk() = mockJdk()
private class CreateModuleResult(
val module: Module,
val srcDir: VirtualFile,
val srcOutputDir: VirtualFile,
val testOutputDir: VirtualFile
)
}