Skip to content

Commit 093508a

Browse files
Ability to create and run tests at once #156 (#670)
1 parent 1ca23e7 commit 093508a

File tree

9 files changed

+247
-72
lines changed

9 files changed

+247
-72
lines changed

utbot-intellij/src/main/kotlin/org/utbot/intellij/plugin/generator/CodeGenerationController.kt

Lines changed: 10 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,6 @@ import com.intellij.ide.fileTemplates.FileTemplateManager
66
import com.intellij.ide.fileTemplates.FileTemplateUtil
77
import com.intellij.ide.fileTemplates.JavaTemplateUtil
88
import com.intellij.openapi.application.ApplicationManager
9-
import com.intellij.openapi.application.runReadAction
109
import com.intellij.openapi.application.runWriteAction
1110
import com.intellij.openapi.command.WriteCommandAction.runWriteCommandAction
1211
import com.intellij.openapi.command.executeCommand
@@ -29,14 +28,12 @@ import com.intellij.psi.search.GlobalSearchScopesCore
2928
import com.intellij.refactoring.util.classMembers.MemberInfo
3029
import com.intellij.testIntegration.TestIntegrationUtils
3130
import com.intellij.util.IncorrectOperationException
32-
import com.intellij.util.concurrency.AppExecutorUtil
3331
import com.siyeh.ig.psiutils.ImportUtils
3432
import org.jetbrains.kotlin.asJava.classes.KtUltraLightClass
3533
import org.jetbrains.kotlin.idea.core.ShortenReferences
3634
import org.jetbrains.kotlin.idea.core.getPackage
3735
import org.jetbrains.kotlin.idea.core.util.toPsiDirectory
3836
import org.jetbrains.kotlin.idea.util.ImportInsertHelperImpl
39-
import org.jetbrains.kotlin.idea.util.application.invokeLater
4037
import org.jetbrains.kotlin.name.FqName
4138
import org.jetbrains.kotlin.psi.KtClass
4239
import org.jetbrains.kotlin.psi.KtNamedFunction
@@ -62,7 +59,6 @@ import org.utbot.framework.plugin.api.util.executableId
6259
import org.utbot.framework.plugin.api.util.id
6360
import org.utbot.framework.plugin.api.util.withUtContext
6461
import org.utbot.framework.util.Conflict
65-
import org.utbot.intellij.plugin.generator.CodeGenerationController.Target.*
6662
import org.utbot.intellij.plugin.models.GenerateTestsModel
6763
import org.utbot.intellij.plugin.models.packageName
6864
import org.utbot.intellij.plugin.sarif.SarifReportIdea
@@ -74,16 +70,18 @@ import org.utbot.intellij.plugin.ui.TestsReportNotifier
7470
import org.utbot.intellij.plugin.ui.WarningTestsReportNotifier
7571
import org.utbot.intellij.plugin.ui.utils.getOrCreateSarifReportsPath
7672
import org.utbot.intellij.plugin.ui.utils.showErrorDialogLater
73+
import org.utbot.intellij.plugin.util.RunConfigurationHelper
7774
import org.utbot.intellij.plugin.util.signature
7875
import org.utbot.sarif.SarifReport
7976
import java.nio.file.Path
8077
import java.util.concurrent.CountDownLatch
8178
import java.util.concurrent.TimeUnit
8279
import kotlin.reflect.KClass
8380
import kotlin.reflect.full.functions
81+
import org.utbot.intellij.plugin.util.IntelliJApiHelper.Target.*
82+
import org.utbot.intellij.plugin.util.IntelliJApiHelper.run
8483

8584
object CodeGenerationController {
86-
private enum class Target { THREAD_POOL, READ_ACTION, WRITE_ACTION, EDT_LATER }
8785

8886
fun generateTests(model: GenerateTestsModel, testSetsByClass: Map<PsiClass, List<UtMethodTestSet>>) {
8987
val baseTestDirectory = model.testSourceRoot?.toPsiDirectory(model.project)
@@ -92,6 +90,7 @@ object CodeGenerationController {
9290
val latch = CountDownLatch(testSetsByClass.size)
9391

9492
val reports = mutableListOf<TestsGenerationReport>()
93+
val testFiles = mutableListOf<PsiFile>()
9594
for (srcClass in testSetsByClass.keys) {
9695
val testSets = testSetsByClass[srcClass] ?: continue
9796
try {
@@ -103,6 +102,7 @@ object CodeGenerationController {
103102
runWriteCommandAction(model.project, "Generate tests with UtBot", null, {
104103
try {
105104
generateCodeAndReport(srcClass, testClass, file, testSets, model, latch, reports)
105+
testFiles.add(file)
106106
} catch (e: IncorrectOperationException) {
107107
showCreatingClassError(model.project, createTestClassName(srcClass))
108108
}
@@ -131,24 +131,12 @@ object CodeGenerationController {
131131
}
132132

133133
mergeSarifReports(model, sarifReportsPath)
134-
}
135-
}
136-
}
137-
}
138-
139-
private fun run(target: Target, runnable: Runnable) {
140-
UtContext.currentContext()?.let {
141-
when (target) {
142-
THREAD_POOL -> AppExecutorUtil.getAppExecutorService().submit {
143-
withUtContext(it) {
144-
runnable.run()
134+
if (model.runGeneratedTestsWithCoverage) {
135+
RunConfigurationHelper.runTestsWithCoverage(model, testFiles)
145136
}
146137
}
147-
READ_ACTION -> runReadAction { withUtContext(it) { runnable.run() } }
148-
WRITE_ACTION -> runWriteAction { withUtContext(it) { runnable.run() } }
149-
EDT_LATER -> invokeLater { withUtContext(it) { runnable.run() } }
150138
}
151-
} ?: error("No context in thread ${Thread.currentThread()}")
139+
}
152140
}
153141

154142
private fun waitForCountDown(latch: CountDownLatch, action: Runnable) {
@@ -324,9 +312,9 @@ object CodeGenerationController {
324312
testsCodeWithTestReportFormatted,
325313
)
326314

327-
reportsCountDown.countDown()
328-
329315
unblockDocument(testClassUpdated.project, editor.document)
316+
317+
reportsCountDown.countDown()
330318
}
331319
}
332320
}

utbot-intellij/src/main/kotlin/org/utbot/intellij/plugin/generator/UtTestsDialogProcessor.kt

Lines changed: 11 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,6 @@ import com.intellij.openapi.ui.Messages
1919
import com.intellij.openapi.util.Computable
2020
import com.intellij.openapi.util.text.StringUtil
2121
import com.intellij.psi.PsiClass
22-
import com.intellij.psi.PsiModifier
2322
import com.intellij.psi.SyntheticElement
2423
import com.intellij.refactoring.util.classMembers.MemberInfo
2524
import com.intellij.testIntegration.TestIntegrationUtils
@@ -42,7 +41,7 @@ import org.utbot.intellij.plugin.ui.GenerateTestsDialogWindow
4241
import org.utbot.intellij.plugin.ui.utils.jdkVersion
4342
import org.utbot.intellij.plugin.ui.utils.showErrorDialogLater
4443
import org.utbot.intellij.plugin.ui.utils.testModule
45-
import org.utbot.intellij.plugin.util.AndroidApiHelper
44+
import org.utbot.intellij.plugin.util.IntelliJApiHelper
4645
import org.utbot.intellij.plugin.util.PluginJdkPathProvider
4746
import org.utbot.intellij.plugin.util.signature
4847
import org.utbot.summary.summarize
@@ -213,11 +212,15 @@ object UtTestsDialogProcessor {
213212
}.getOrDefault(listOf())
214213

215214
if (notEmptyCases.isEmpty()) {
216-
showErrorDialogLater(
217-
model.project,
218-
errorMessage(className, secondsTimeout),
219-
title = "Failed to generate unit tests for class $className"
220-
)
215+
if (model.srcClasses.size > 1) {
216+
logger.error { "Failed to generate any tests cases for class $className" }
217+
} else {
218+
showErrorDialogLater(
219+
model.project,
220+
errorMessage(className, secondsTimeout),
221+
title = "Failed to generate unit tests for class $className"
222+
)
223+
}
221224
} else {
222225
testSetsByClass[srcClass] = notEmptyCases
223226
}
@@ -285,7 +288,7 @@ object UtTestsDialogProcessor {
285288
val buildDir = CompilerPaths.getModuleOutputPath(srcModule, false) ?: return null
286289
val pathsList = OrderEnumerator.orderEntries(srcModule).recursively().pathsList
287290

288-
val (classpath, classpathList) = if (AndroidApiHelper.isAndroidStudio()) {
291+
val (classpath, classpathList) = if (IntelliJApiHelper.isAndroidStudio()) {
289292
// Add $JAVA_HOME/jre/lib/rt.jar to path.
290293
// This allows Soot to analyze real java instead of stub version in Android SDK on local machine.
291294
pathsList.add(

utbot-intellij/src/main/kotlin/org/utbot/intellij/plugin/models/GenerateTestsModel.kt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,7 @@ data class GenerateTestsModel(
4848
val isMultiPackage: Boolean by lazy {
4949
srcClasses.map { it.packageName }.distinct().size != 1
5050
}
51+
var runGeneratedTestsWithCoverage : Boolean = false
5152
}
5253

5354
val PsiClass.packageName: String get() = this.containingFile.containingDirectory.getPackage()?.qualifiedName ?: ""

utbot-intellij/src/main/kotlin/org/utbot/intellij/plugin/settings/Settings.kt

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,7 @@ class Settings(val project: Project) : PersistentStateComponent<Settings.State>
5353
var parametrizedTestSource: ParametrizedTestSource = ParametrizedTestSource.defaultItem,
5454
var classesToMockAlways: Array<String> = Mocker.defaultSuperClassesToMockAlwaysNames.toTypedArray(),
5555
var fuzzingValue: Double = 0.05,
56+
var runGeneratedTestsWithCoverage : Boolean = false,
5657
) {
5758
constructor(model: GenerateTestsModel) : this(
5859
codegenLanguage = model.codegenLanguage,
@@ -65,7 +66,8 @@ class Settings(val project: Project) : PersistentStateComponent<Settings.State>
6566
forceStaticMocking = model.forceStaticMocking,
6667
parametrizedTestSource = model.parametrizedTestSource,
6768
classesToMockAlways = model.chosenClassesToMockAlways.mapTo(mutableSetOf()) { it.name }.toTypedArray(),
68-
fuzzingValue = model.fuzzingValue
69+
fuzzingValue = model.fuzzingValue,
70+
runGeneratedTestsWithCoverage = model.runGeneratedTestsWithCoverage
6971
)
7072

7173
override fun equals(other: Any?): Boolean {
@@ -86,6 +88,7 @@ class Settings(val project: Project) : PersistentStateComponent<Settings.State>
8688
if (parametrizedTestSource != other.parametrizedTestSource) return false
8789
if (!classesToMockAlways.contentEquals(other.classesToMockAlways)) return false
8890
if (fuzzingValue != other.fuzzingValue) return false
91+
if (runGeneratedTestsWithCoverage != other.runGeneratedTestsWithCoverage) return false
8992

9093
return true
9194
}
@@ -102,6 +105,7 @@ class Settings(val project: Project) : PersistentStateComponent<Settings.State>
102105
result = 31 * result + parametrizedTestSource.hashCode()
103106
result = 31 * result + classesToMockAlways.contentHashCode()
104107
result = 31 * result + fuzzingValue.hashCode()
108+
result = 31 * result + if (runGeneratedTestsWithCoverage) 1 else 0
105109

106110
return result
107111
}
@@ -138,6 +142,7 @@ class Settings(val project: Project) : PersistentStateComponent<Settings.State>
138142
set(value) {
139143
state.fuzzingValue = value.coerceIn(0.0, 1.0)
140144
}
145+
var runGeneratedTestsWithCoverage = state.runGeneratedTestsWithCoverage
141146

142147
fun setClassesToMockAlways(classesToMockAlways: List<String>) {
143148
state.classesToMockAlways = classesToMockAlways.distinct().toTypedArray()

utbot-intellij/src/main/kotlin/org/utbot/intellij/plugin/ui/GenerateTestsDialogWindow.kt

Lines changed: 69 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -27,9 +27,11 @@ import com.intellij.openapi.ui.ComboBox
2727
import com.intellij.openapi.ui.DialogPanel
2828
import com.intellij.openapi.ui.DialogWrapper
2929
import com.intellij.openapi.ui.Messages
30+
import com.intellij.openapi.ui.OptionAction
3031
import com.intellij.openapi.ui.ValidationInfo
3132
import com.intellij.openapi.ui.popup.IconButton
3233
import com.intellij.openapi.util.Computable
34+
import com.intellij.openapi.util.text.TextWithMnemonic
3335
import com.intellij.openapi.vfs.StandardFileSystems
3436
import com.intellij.openapi.vfs.VfsUtil
3537
import com.intellij.openapi.vfs.VfsUtilCore.urlToPath
@@ -40,7 +42,6 @@ import com.intellij.psi.PsiClass
4042
import com.intellij.psi.PsiManager
4143
import com.intellij.psi.PsiMethod
4244
import com.intellij.psi.SyntheticElement
43-
import com.intellij.psi.PsiModifier
4445
import com.intellij.refactoring.PackageWrapper
4546
import com.intellij.refactoring.ui.MemberSelectionTable
4647
import com.intellij.refactoring.ui.PackageNameReferenceEditorCombo
@@ -75,11 +76,28 @@ import com.intellij.util.ui.JBUI.scale
7576
import com.intellij.util.ui.JBUI.size
7677
import com.intellij.util.ui.UIUtil
7778
import com.intellij.util.ui.components.BorderLayoutPanel
79+
import java.awt.BorderLayout
80+
import java.awt.Color
81+
import java.awt.event.ActionEvent
82+
import java.nio.file.Files
83+
import java.nio.file.Path
84+
import java.nio.file.Paths
85+
import java.util.Objects
86+
import java.util.concurrent.TimeUnit
87+
import javax.swing.AbstractAction
88+
import javax.swing.Action
89+
import javax.swing.DefaultComboBoxModel
90+
import javax.swing.JButton
91+
import javax.swing.JComboBox
92+
import javax.swing.JComponent
93+
import javax.swing.JList
94+
import javax.swing.JPanel
95+
import kotlin.streams.toList
7896
import org.jetbrains.concurrency.Promise
7997
import org.jetbrains.concurrency.thenRun
8098
import org.jetbrains.kotlin.asJava.classes.KtUltraLightClass
81-
import org.utbot.common.filterWhen
8299
import org.utbot.common.PathUtil.toPath
100+
import org.utbot.common.filterWhen
83101
import org.utbot.framework.UtSettings
84102
import org.utbot.framework.codegen.ForceStaticMocking
85103
import org.utbot.framework.codegen.Junit4
@@ -116,20 +134,7 @@ import org.utbot.intellij.plugin.ui.utils.kotlinTargetPlatform
116134
import org.utbot.intellij.plugin.ui.utils.parseVersion
117135
import org.utbot.intellij.plugin.ui.utils.testResourceRootTypes
118136
import org.utbot.intellij.plugin.ui.utils.testRootType
119-
import org.utbot.intellij.plugin.util.AndroidApiHelper
120-
import java.awt.BorderLayout
121-
import java.awt.Color
122-
import java.nio.file.Files
123-
import java.nio.file.Path
124-
import java.nio.file.Paths
125-
import java.util.*
126-
import java.util.concurrent.TimeUnit
127-
import javax.swing.DefaultComboBoxModel
128-
import javax.swing.JComboBox
129-
import javax.swing.JComponent
130-
import javax.swing.JList
131-
import javax.swing.JPanel
132-
import kotlin.streams.toList
137+
import org.utbot.intellij.plugin.util.IntelliJApiHelper
133138
import org.utbot.intellij.plugin.util.isAbstract
134139

135140
private const val RECENTS_KEY = "org.utbot.recents"
@@ -140,6 +145,9 @@ private const val WILL_BE_INSTALLED_LABEL = " (will be installed)"
140145
private const val WILL_BE_CONFIGURED_LABEL = " (will be configured)"
141146
private const val MINIMUM_TIMEOUT_VALUE_IN_SECONDS = 1
142147

148+
private const val ACTION_GENERATE = "Generate Tests"
149+
private const val ACTION_GENERATE_AND_RUN = "Generate && Run" //Note that ampersand has to be escaped (doubled)
150+
143151
class GenerateTestsDialogWindow(val model: GenerateTestsModel) : DialogWrapper(model.project) {
144152
companion object {
145153
const val minSupportedSdkVersion = 8
@@ -213,6 +221,8 @@ class GenerateTestsDialogWindow(val model: GenerateTestsModel) : DialogWrapper(m
213221
twm.getToolWindow("Event Log")?.activate(null)
214222
}
215223

224+
model.runGeneratedTestsWithCoverage = model.project.service<Settings>().runGeneratedTestsWithCoverage
225+
216226
init()
217227
}
218228

@@ -302,7 +312,7 @@ class GenerateTestsDialogWindow(val model: GenerateTestsModel) : DialogWrapper(m
302312
override fun createTitlePane(): JComponent? {
303313
val sdkVersion = findSdkVersion()
304314
//TODO:SAT-1571 investigate Android Studio specific sdk issues
305-
if (sdkVersion?.feature in minSupportedSdkVersion..maxSupportedSdkVersion || AndroidApiHelper.isAndroidStudio()) return null
315+
if (sdkVersion?.feature in minSupportedSdkVersion..maxSupportedSdkVersion || IntelliJApiHelper.isAndroidStudio()) return null
306316
isOKActionEnabled = false
307317
return SdkNotificationPanel(model, sdkVersion)
308318
}
@@ -438,6 +448,45 @@ class GenerateTestsDialogWindow(val model: GenerateTestsModel) : DialogWrapper(m
438448
return null
439449
}
440450

451+
class OKOptionAction(val testsModel: GenerateTestsModel, val okAction : Action) : AbstractAction(testsModel.getActionText()), OptionAction {
452+
init {
453+
putValue(DEFAULT_ACTION, java.lang.Boolean.TRUE)
454+
putValue(FOCUSED_ACTION, java.lang.Boolean.TRUE)
455+
}
456+
private val generateAction = object : AbstractAction(ACTION_GENERATE) {
457+
override fun actionPerformed(e: ActionEvent?) {
458+
testsModel.runGeneratedTestsWithCoverage = false
459+
updateButtonText(e)
460+
}
461+
}
462+
private val generateAndRunAction = object : AbstractAction(ACTION_GENERATE_AND_RUN) {
463+
override fun actionPerformed(e: ActionEvent?) {
464+
testsModel.runGeneratedTestsWithCoverage = true
465+
updateButtonText(e)
466+
}
467+
}
468+
469+
private fun updateButtonText(e: ActionEvent?) {
470+
with(e?.source as JButton) {
471+
text = TextWithMnemonic.parse(testsModel.getActionText()).dropMnemonic().text
472+
testsModel.project.service<Settings>().runGeneratedTestsWithCoverage =
473+
testsModel.runGeneratedTestsWithCoverage
474+
repaint()
475+
}
476+
}
477+
478+
override fun actionPerformed(e: ActionEvent?) {
479+
okAction.actionPerformed(e)
480+
}
481+
482+
override fun getOptions(): Array<Action> {
483+
if (testsModel.runGeneratedTestsWithCoverage) return arrayOf(generateAndRunAction, generateAction)
484+
return arrayOf(generateAction, generateAndRunAction)
485+
}
486+
}
487+
488+
private val okOptionAction: OKOptionAction get() = OKOptionAction(model, super.getOKAction())
489+
override fun getOKAction() = okOptionAction
441490

442491
override fun doOKAction() {
443492
model.testPackageName =
@@ -964,6 +1013,9 @@ class GenerateTestsDialogWindow(val model: GenerateTestsModel) : DialogWrapper(m
9641013
}
9651014
}
9661015

1016+
fun GenerateTestsModel.getActionText() : String =
1017+
if (this.runGeneratedTestsWithCoverage) ACTION_GENERATE_AND_RUN else ACTION_GENERATE
1018+
9671019
private fun ComboBox<CodeGenerationSettingItem>.setHelpTooltipTextChanger(helpLabel: JBLabel) {
9681020
addActionListener { event ->
9691021
val comboBox = event.source as ComboBox<*>

utbot-intellij/src/main/kotlin/org/utbot/intellij/plugin/util/AndroidApiHelper.kt

Lines changed: 0 additions & 22 deletions
This file was deleted.

0 commit comments

Comments
 (0)