Skip to content

Commit ad929a6

Browse files
committed
Unit Test Tooling: Implement "Create Test" action
#KT-6472 In Progress
1 parent c697eef commit ad929a6

File tree

14 files changed

+354
-83
lines changed

14 files changed

+354
-83
lines changed

idea/idea-analysis/src/org/jetbrains/kotlin/idea/util/ApplicationUtils.kt

Lines changed: 13 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -17,8 +17,9 @@
1717
package org.jetbrains.kotlin.idea.util.application
1818

1919
import com.intellij.openapi.application.ApplicationManager
20-
import com.intellij.openapi.project.Project
2120
import com.intellij.openapi.command.CommandProcessor
21+
import com.intellij.openapi.project.DumbService
22+
import com.intellij.openapi.project.Project
2223

2324
public fun runReadAction<T>(action: () -> T): T {
2425
return ApplicationManager.getApplication().runReadAction<T>(action)
@@ -32,13 +33,20 @@ public fun Project.executeWriteCommand(name: String, command: () -> Unit) {
3233
CommandProcessor.getInstance().executeCommand(this, { runWriteAction(command) }, name, null)
3334
}
3435

35-
public fun Project.executeCommand(name: String, groupId: Any? = null, command: () -> Unit) {
36-
CommandProcessor.getInstance().executeCommand(this, command, name, groupId)
36+
public fun <T> Project.executeWriteCommand(name: String, groupId: Any? = null, command: () -> T): T {
37+
return executeCommand<T>(name, groupId) { runWriteAction(command) }
3738
}
3839

39-
public fun <T> Project.executeWriteCommand(name: String, groupId: Any? = null, command: () -> T): T {
40+
public fun <T> Project.executeCommand(name: String, groupId: Any? = null, command: () -> T): T {
4041
var result: T = null as T
41-
CommandProcessor.getInstance().executeCommand(this, { result = runWriteAction(command) }, name, groupId)
42+
CommandProcessor.getInstance().executeCommand(this, { result = command() }, name, groupId)
4243
@Suppress("USELESS_CAST")
4344
return result as T
4445
}
46+
47+
public fun <T> Project.runWithAlternativeResolveEnabled(action: () -> T): T {
48+
var result: T = null as T
49+
DumbService.getInstance(this).withAlternativeResolveEnabled { result = action() }
50+
@Suppress("USELESS_CAST")
51+
return result as T
52+
}
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
class Foo {
2+
fun foo() {
3+
4+
}
5+
}
6+
7+
class FooTest : TestCase {
8+
fun testFoo() {
9+
10+
}
11+
}
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
<spot>class Foo</spot> {
2+
fun foo() {
3+
4+
}
5+
}
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
<html>
2+
<body>
3+
This intention generates a test case for the selected class. The generated class contains skeleton test functions for the chosen public functions.
4+
</body>
5+
</html>

idea/src/META-INF/plugin.xml

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1062,6 +1062,11 @@
10621062
<category>Kotlin</category>
10631063
</intentionAction>
10641064

1065+
<intentionAction>
1066+
<className>org.jetbrains.kotlin.idea.testIntegration.KotlinCreateTestIntention</className>
1067+
<category>Kotlin</category>
1068+
</intentionAction>
1069+
10651070
<localInspection implementationClass="org.jetbrains.kotlin.idea.intentions.DeprecatedCallableAddReplaceWithInspection"
10661071
displayName="Add 'replaceWith' argument to 'deprecated' annotation"
10671072
groupName="Kotlin"

idea/src/org/jetbrains/kotlin/idea/actions/JavaToKotlinAction.kt

Lines changed: 77 additions & 68 deletions
Original file line numberDiff line numberDiff line change
@@ -32,69 +32,106 @@ import com.intellij.openapi.vfs.VirtualFile
3232
import com.intellij.openapi.vfs.VirtualFileVisitor
3333
import com.intellij.psi.PsiJavaFile
3434
import com.intellij.psi.PsiManager
35+
import org.jetbrains.kotlin.idea.core.refactoring.toPsiFile
3536
import org.jetbrains.kotlin.idea.j2k.IdeaJavaToKotlinServices
3637
import org.jetbrains.kotlin.idea.j2k.J2kPostProcessor
3738
import org.jetbrains.kotlin.idea.util.application.executeWriteCommand
3839
import org.jetbrains.kotlin.idea.util.application.runReadAction
3940
import org.jetbrains.kotlin.j2k.ConverterSettings
4041
import org.jetbrains.kotlin.j2k.JavaToKotlinConverter
42+
import org.jetbrains.kotlin.psi.JetFile
4143
import java.io.File
4244
import java.io.IOException
4345
import java.util.ArrayList
4446

4547
public class JavaToKotlinAction : AnAction() {
46-
override fun actionPerformed(e: AnActionEvent) {
47-
ApplicationManager.getApplication().saveAll()
48-
49-
val javaFiles = selectedJavaFiles(e).toList()
50-
val project = CommonDataKeys.PROJECT.getData(e.getDataContext())!!
51-
52-
var converterResult: JavaToKotlinConverter.FilesResult? = null
53-
fun convert() {
54-
val converter = JavaToKotlinConverter(project, ConverterSettings.defaultSettings, IdeaJavaToKotlinServices)
55-
converterResult = converter.filesToKotlin(javaFiles, J2kPostProcessor(formatCode = true), ProgressManager.getInstance().getProgressIndicator())
48+
companion object {
49+
private fun uniqueKotlinFileName(javaFile: VirtualFile): String {
50+
val ioFile = File(javaFile.getPath().replace('/', File.separatorChar))
51+
52+
var i = 0
53+
while (true) {
54+
val fileName = javaFile.getNameWithoutExtension() + (if (i > 0) i else "") + ".kt"
55+
if (!ioFile.resolveSibling(fileName).exists()) return fileName
56+
i++
57+
}
5658
}
5759

58-
val title = "Convert Java to Kotlin"
59-
if (!ProgressManager.getInstance().runProcessWithProgressSynchronously(
60-
{
61-
runReadAction(::convert)
62-
},
63-
title,
64-
true,
65-
project)) return
66-
67-
68-
var externalCodeUpdate: (() -> Unit)? = null
69-
70-
if (converterResult!!.externalCodeProcessing != null) {
71-
val question = "Some code in the rest of your project may require corrections after performing this conversion. Do you want to find such code and correct it too?"
72-
if (Messages.showOkCancelDialog(project, question, title, Messages.getQuestionIcon()) == Messages.OK) {
73-
ProgressManager.getInstance().runProcessWithProgressSynchronously(
74-
{
75-
runReadAction {
76-
externalCodeUpdate = converterResult!!.externalCodeProcessing!!.prepareWriteOperation(ProgressManager.getInstance().getProgressIndicator())
77-
}
78-
},
79-
title,
80-
true,
81-
project)
60+
private fun saveResults(javaFiles: List<PsiJavaFile>, convertedTexts: List<String>): List<VirtualFile> {
61+
val result = ArrayList<VirtualFile>()
62+
for ((psiFile, text) in javaFiles.zip(convertedTexts)) {
63+
val virtualFile = psiFile.getVirtualFile()
64+
val fileName = uniqueKotlinFileName(virtualFile)
65+
try {
66+
virtualFile.rename(this, fileName)
67+
virtualFile.setBinaryContent(CharsetToolkit.getUtf8Bytes(text))
68+
result.add(virtualFile)
69+
}
70+
catch (e: IOException) {
71+
MessagesEx.error(psiFile.getProject(), e.getMessage()).showLater()
72+
}
8273
}
74+
return result
8375
}
8476

85-
project.executeWriteCommand("Convert files from Java to Kotlin") {
86-
CommandProcessor.getInstance().markCurrentCommandAsGlobal(project)
77+
fun convertFiles(javaFiles: List<PsiJavaFile>, project: Project, enableExternalCodeProcessing: Boolean = true): List<JetFile> {
78+
ApplicationManager.getApplication().saveAll()
8779

88-
val newFiles = saveResults(javaFiles, converterResult!!.results)
80+
var converterResult: JavaToKotlinConverter.FilesResult? = null
81+
fun convert() {
82+
val converter = JavaToKotlinConverter(project, ConverterSettings.defaultSettings, IdeaJavaToKotlinServices)
83+
converterResult = converter.filesToKotlin(javaFiles, J2kPostProcessor(formatCode = true), ProgressManager.getInstance().getProgressIndicator())
84+
}
85+
86+
val title = "Convert Java to Kotlin"
87+
if (!ProgressManager.getInstance().runProcessWithProgressSynchronously(
88+
{
89+
runReadAction(::convert)
90+
},
91+
title,
92+
true,
93+
project)) return emptyList()
94+
95+
96+
var externalCodeUpdate: (() -> Unit)? = null
97+
98+
if (enableExternalCodeProcessing && converterResult!!.externalCodeProcessing != null) {
99+
val question = "Some code in the rest of your project may require corrections after performing this conversion. Do you want to find such code and correct it too?"
100+
if (Messages.showOkCancelDialog(project, question, title, Messages.getQuestionIcon()) == Messages.OK) {
101+
ProgressManager.getInstance().runProcessWithProgressSynchronously(
102+
{
103+
runReadAction {
104+
externalCodeUpdate = converterResult!!.externalCodeProcessing!!.prepareWriteOperation(ProgressManager.getInstance().getProgressIndicator())
105+
}
106+
},
107+
title,
108+
true,
109+
project)
110+
}
111+
}
89112

90-
externalCodeUpdate?.invoke()
113+
return project.executeWriteCommand("Convert files from Java to Kotlin", null) {
114+
CommandProcessor.getInstance().markCurrentCommandAsGlobal(project)
115+
116+
val newFiles = saveResults(javaFiles, converterResult!!.results)
117+
118+
externalCodeUpdate?.invoke()
119+
120+
newFiles.singleOrNull()?.let {
121+
FileEditorManager.getInstance(project).openFile(it, true)
122+
}
91123

92-
newFiles.singleOrNull()?.let {
93-
FileEditorManager.getInstance(project).openFile(it, true)
124+
newFiles.map { it.toPsiFile(project) as JetFile }
94125
}
95126
}
96127
}
97128

129+
override fun actionPerformed(e: AnActionEvent) {
130+
val javaFiles = selectedJavaFiles(e).toList()
131+
val project = CommonDataKeys.PROJECT.getData(e.getDataContext())!!
132+
convertFiles(javaFiles, project)
133+
}
134+
98135
override fun update(e: AnActionEvent) {
99136
val enabled = selectedJavaFiles(e).any()
100137
e.getPresentation().setEnabled(enabled)
@@ -126,32 +163,4 @@ public class JavaToKotlinAction : AnAction() {
126163
}
127164
return result
128165
}
129-
130-
private fun saveResults(javaFiles: List<PsiJavaFile>, convertedTexts: List<String>): List<VirtualFile> {
131-
val result = ArrayList<VirtualFile>()
132-
for ((psiFile, text) in javaFiles.zip(convertedTexts)) {
133-
val virtualFile = psiFile.getVirtualFile()
134-
val fileName = uniqueKotlinFileName(virtualFile)
135-
try {
136-
virtualFile.rename(this, fileName)
137-
virtualFile.setBinaryContent(CharsetToolkit.getUtf8Bytes(text))
138-
result.add(virtualFile)
139-
}
140-
catch (e: IOException) {
141-
MessagesEx.error(psiFile.getProject(), e.getMessage()).showLater()
142-
}
143-
}
144-
return result
145-
}
146-
147-
private fun uniqueKotlinFileName(javaFile: VirtualFile): String {
148-
val ioFile = File(javaFile.getPath().replace('/', File.separatorChar))
149-
150-
var i = 0
151-
while (true) {
152-
val fileName = javaFile.getNameWithoutExtension() + (if (i > 0) i else "") + ".kt"
153-
if (!ioFile.resolveSibling(fileName).exists()) return fileName
154-
i++
155-
}
156-
}
157166
}

idea/src/org/jetbrains/kotlin/idea/actions/generate/KotlinGenerateTestSupportActionBase.kt

Lines changed: 1 addition & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -21,14 +21,12 @@ import com.intellij.codeInsight.generation.actions.GenerateActionPopupTemplateIn
2121
import com.intellij.codeInsight.hint.HintManager
2222
import com.intellij.ide.fileTemplates.FileTemplateManager
2323
import com.intellij.ide.fileTemplates.impl.AllFileTemplatesConfigurable
24-
import com.intellij.lang.java.JavaLanguage
2524
import com.intellij.openapi.actionSystem.AnAction
2625
import com.intellij.openapi.actionSystem.AnActionEvent
2726
import com.intellij.openapi.actionSystem.CommonDataKeys
2827
import com.intellij.openapi.actionSystem.DataContext
2928
import com.intellij.openapi.application.ApplicationManager
3029
import com.intellij.openapi.editor.Editor
31-
import com.intellij.openapi.extensions.Extensions
3230
import com.intellij.openapi.project.Project
3331
import com.intellij.openapi.ui.InputValidator
3432
import com.intellij.openapi.ui.Messages
@@ -42,7 +40,6 @@ import com.intellij.testIntegration.TestFramework
4240
import com.intellij.testIntegration.TestIntegrationUtils.MethodKind
4341
import com.intellij.ui.components.JBList
4442
import com.intellij.util.IncorrectOperationException
45-
import com.intellij.util.SmartList
4643
import org.jetbrains.kotlin.asJava.toLightClass
4744
import org.jetbrains.kotlin.descriptors.FunctionDescriptor
4845
import org.jetbrains.kotlin.idea.caches.resolve.resolveToDescriptor
@@ -52,6 +49,7 @@ import org.jetbrains.kotlin.idea.core.overrideImplement.generateUnsupportedOrSup
5249
import org.jetbrains.kotlin.idea.core.refactoring.j2k
5350
import org.jetbrains.kotlin.idea.quickfix.createFromUsage.callableBuilder.setupEditorSelection
5451
import org.jetbrains.kotlin.idea.quickfix.generateMember
52+
import org.jetbrains.kotlin.idea.testIntegration.findSuitableFrameworks
5553
import org.jetbrains.kotlin.idea.util.application.executeWriteCommand
5654
import org.jetbrains.kotlin.lexer.JetTokens
5755
import org.jetbrains.kotlin.psi.JetClassOrObject
@@ -69,13 +67,6 @@ abstract class KotlinGenerateTestSupportActionBase(
6967
return elementAtCaret.parentsWithSelf.filterIsInstance<JetClassOrObject>().firstOrNull { !it.isLocal() }
7068
}
7169

72-
private fun findSuitableFrameworks(klass: JetClassOrObject): List<TestFramework> {
73-
val lightClass = klass.toLightClass() ?: return emptyList()
74-
val frameworks = Extensions.getExtensions(TestFramework.EXTENSION_NAME).filter { it.language == JavaLanguage.INSTANCE }
75-
return frameworks.firstOrNull { it.isTestClass(lightClass) }?.let { listOf(it) }
76-
?: frameworks.filterTo(SmartList<TestFramework>()) { it.isPotentialTestClass(lightClass) }
77-
}
78-
7970
private fun chooseAndPerform(editor: Editor, frameworks: List<TestFramework>, consumer: (TestFramework) -> Unit) {
8071
frameworks.ifEmpty { return }
8172
frameworks.singleOrNull()?.let { return consumer(it) }
Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
/*
2+
* Copyright 2010-2015 JetBrains s.r.o.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package org.jetbrains.kotlin.idea.testIntegration
18+
19+
import com.intellij.openapi.module.Module
20+
import com.intellij.openapi.project.Project
21+
import com.intellij.psi.PsiClass
22+
import com.intellij.psi.PsiPackage
23+
import com.intellij.testIntegration.createTest.CreateTestDialog
24+
25+
class KotlinCreateTestDialog(project: Project,
26+
title: String,
27+
targetClass: PsiClass?,
28+
targetPackage: PsiPackage?,
29+
targetModule: Module) : CreateTestDialog(project, title, targetClass, targetPackage, targetModule) {
30+
public var explicitClassName: String? = null
31+
32+
override fun getClassName() = explicitClassName ?: super.getClassName()
33+
}

0 commit comments

Comments
 (0)