From 1146bfbc9023689430c1c31855f06a28b547f92c Mon Sep 17 00:00:00 2001 From: "Vassiliy.Kudryashov" Date: Thu, 4 Aug 2022 21:02:27 +0300 Subject: [PATCH 1/4] Ability to create and run tests at once #156 --- .../generator/CodeGenerationController.kt | 34 ++----- .../generator/UtTestsDialogProcessor.kt | 19 ++-- .../plugin/models/GenerateTestsModel.kt | 1 + .../intellij/plugin/settings/Settings.kt | 7 +- .../plugin/ui/GenerateTestsDialogWindow.kt | 86 +++++++++++++---- .../intellij/plugin/util/AndroidApiHelper.kt | 22 ----- .../intellij/plugin/util/IntelliJApiHelper.kt | 46 +++++++++ .../plugin/util/PluginJdkPathProvider.kt | 4 +- .../plugin/util/RunConfigurationHelper.kt | 95 +++++++++++++++++++ 9 files changed, 240 insertions(+), 74 deletions(-) delete mode 100644 utbot-intellij/src/main/kotlin/org/utbot/intellij/plugin/util/AndroidApiHelper.kt create mode 100644 utbot-intellij/src/main/kotlin/org/utbot/intellij/plugin/util/IntelliJApiHelper.kt create mode 100644 utbot-intellij/src/main/kotlin/org/utbot/intellij/plugin/util/RunConfigurationHelper.kt diff --git a/utbot-intellij/src/main/kotlin/org/utbot/intellij/plugin/generator/CodeGenerationController.kt b/utbot-intellij/src/main/kotlin/org/utbot/intellij/plugin/generator/CodeGenerationController.kt index af33856772..9a98fe0de0 100644 --- a/utbot-intellij/src/main/kotlin/org/utbot/intellij/plugin/generator/CodeGenerationController.kt +++ b/utbot-intellij/src/main/kotlin/org/utbot/intellij/plugin/generator/CodeGenerationController.kt @@ -6,7 +6,6 @@ import com.intellij.ide.fileTemplates.FileTemplateManager import com.intellij.ide.fileTemplates.FileTemplateUtil import com.intellij.ide.fileTemplates.JavaTemplateUtil import com.intellij.openapi.application.ApplicationManager -import com.intellij.openapi.application.runReadAction import com.intellij.openapi.application.runWriteAction import com.intellij.openapi.command.WriteCommandAction.runWriteCommandAction import com.intellij.openapi.command.executeCommand @@ -29,14 +28,12 @@ import com.intellij.psi.search.GlobalSearchScopesCore import com.intellij.refactoring.util.classMembers.MemberInfo import com.intellij.testIntegration.TestIntegrationUtils import com.intellij.util.IncorrectOperationException -import com.intellij.util.concurrency.AppExecutorUtil import com.siyeh.ig.psiutils.ImportUtils import org.jetbrains.kotlin.asJava.classes.KtUltraLightClass import org.jetbrains.kotlin.idea.core.ShortenReferences import org.jetbrains.kotlin.idea.core.getPackage import org.jetbrains.kotlin.idea.core.util.toPsiDirectory import org.jetbrains.kotlin.idea.util.ImportInsertHelperImpl -import org.jetbrains.kotlin.idea.util.application.invokeLater import org.jetbrains.kotlin.name.FqName import org.jetbrains.kotlin.psi.KtClass import org.jetbrains.kotlin.psi.KtNamedFunction @@ -57,10 +54,7 @@ import org.utbot.framework.codegen.model.constructor.tree.TestsGenerationReport import org.utbot.framework.plugin.api.CodegenLanguage import org.utbot.framework.plugin.api.UtMethod import org.utbot.framework.plugin.api.UtMethodTestSet -import org.utbot.framework.plugin.api.util.UtContext -import org.utbot.framework.plugin.api.util.withUtContext import org.utbot.framework.util.Conflict -import org.utbot.intellij.plugin.generator.CodeGenerationController.Target.* import org.utbot.intellij.plugin.models.GenerateTestsModel import org.utbot.intellij.plugin.models.packageName import org.utbot.intellij.plugin.sarif.SarifReportIdea @@ -72,6 +66,7 @@ import org.utbot.intellij.plugin.ui.TestsReportNotifier import org.utbot.intellij.plugin.ui.WarningTestsReportNotifier import org.utbot.intellij.plugin.ui.utils.getOrCreateSarifReportsPath import org.utbot.intellij.plugin.ui.utils.showErrorDialogLater +import org.utbot.intellij.plugin.util.RunConfigurationHelper import org.utbot.intellij.plugin.util.signature import org.utbot.sarif.SarifReport import java.nio.file.Path @@ -79,9 +74,10 @@ import java.util.concurrent.CountDownLatch import java.util.concurrent.TimeUnit import kotlin.reflect.KClass import kotlin.reflect.full.functions +import org.utbot.intellij.plugin.util.IntelliJApiHelper.Target.* +import org.utbot.intellij.plugin.util.IntelliJApiHelper.run object CodeGenerationController { - private enum class Target { THREAD_POOL, READ_ACTION, WRITE_ACTION, EDT_LATER } fun generateTests(model: GenerateTestsModel, testSetsByClass: Map>) { val baseTestDirectory = model.testSourceRoot?.toPsiDirectory(model.project) @@ -90,6 +86,7 @@ object CodeGenerationController { val latch = CountDownLatch(testSetsByClass.size) val reports = mutableListOf() + val testFiles = mutableListOf() for (srcClass in testSetsByClass.keys) { val testSets = testSetsByClass[srcClass] ?: continue try { @@ -101,6 +98,7 @@ object CodeGenerationController { runWriteCommandAction(model.project, "Generate tests with UtBot", null, { try { generateCodeAndReport(testClass, file, testSets, model, latch, reports) + testFiles.add(file) } catch (e: IncorrectOperationException) { showCreatingClassError(model.project, createTestClassName(srcClass)) } @@ -129,24 +127,12 @@ object CodeGenerationController { } mergeSarifReports(model, sarifReportsPath) - } - } - } - } - - private fun run(target: Target, runnable: Runnable) { - UtContext.currentContext()?.let { - when (target) { - THREAD_POOL -> AppExecutorUtil.getAppExecutorService().submit { - withUtContext(it) { - runnable.run() + if (model.runGeneratedTestsWithCoverage) { + RunConfigurationHelper.runTestsWithCoverage(model, testFiles) } } - READ_ACTION -> runReadAction { withUtContext(it) { runnable.run() } } - WRITE_ACTION -> runWriteAction { withUtContext(it) { runnable.run() } } - EDT_LATER -> invokeLater { withUtContext(it) { runnable.run() } } } - } ?: error("No context in thread ${Thread.currentThread()}") + } } private fun waitForCountDown(latch: CountDownLatch, action: Runnable) { @@ -323,9 +309,9 @@ object CodeGenerationController { testsCodeWithTestReportFormatted, ) - reportsCountDown.countDown() - unblockDocument(testClassUpdated.project, editor.document) + + reportsCountDown.countDown() } } } diff --git a/utbot-intellij/src/main/kotlin/org/utbot/intellij/plugin/generator/UtTestsDialogProcessor.kt b/utbot-intellij/src/main/kotlin/org/utbot/intellij/plugin/generator/UtTestsDialogProcessor.kt index ddff6e931a..990ad5c9b4 100644 --- a/utbot-intellij/src/main/kotlin/org/utbot/intellij/plugin/generator/UtTestsDialogProcessor.kt +++ b/utbot-intellij/src/main/kotlin/org/utbot/intellij/plugin/generator/UtTestsDialogProcessor.kt @@ -19,7 +19,6 @@ import com.intellij.openapi.ui.Messages import com.intellij.openapi.util.Computable import com.intellij.openapi.util.text.StringUtil import com.intellij.psi.PsiClass -import com.intellij.psi.PsiModifier import com.intellij.psi.SyntheticElement import com.intellij.refactoring.util.classMembers.MemberInfo import com.intellij.testIntegration.TestIntegrationUtils @@ -42,7 +41,7 @@ import org.utbot.intellij.plugin.ui.GenerateTestsDialogWindow import org.utbot.intellij.plugin.ui.utils.jdkVersion import org.utbot.intellij.plugin.ui.utils.showErrorDialogLater import org.utbot.intellij.plugin.ui.utils.testModule -import org.utbot.intellij.plugin.util.AndroidApiHelper +import org.utbot.intellij.plugin.util.IntelliJApiHelper import org.utbot.intellij.plugin.util.PluginJdkPathProvider import org.utbot.intellij.plugin.util.signature import org.utbot.summary.summarize @@ -213,11 +212,15 @@ object UtTestsDialogProcessor { }.getOrDefault(listOf()) if (notEmptyCases.isEmpty()) { - showErrorDialogLater( - model.project, - errorMessage(className, secondsTimeout), - title = "Failed to generate unit tests for class $className" - ) + if (model.srcClasses.size > 1) { + logger.error { "Failed to generate any tests cases for class $className" } + } else { + showErrorDialogLater( + model.project, + errorMessage(className, secondsTimeout), + title = "Failed to generate unit tests for class $className" + ) + } } else { testSetsByClass[srcClass] = notEmptyCases } @@ -285,7 +288,7 @@ object UtTestsDialogProcessor { val buildDir = CompilerPaths.getModuleOutputPath(srcModule, false) ?: return null val pathsList = OrderEnumerator.orderEntries(srcModule).recursively().pathsList - val (classpath, classpathList) = if (AndroidApiHelper.isAndroidStudio()) { + val (classpath, classpathList) = if (IntelliJApiHelper.isAndroidStudio()) { // Add $JAVA_HOME/jre/lib/rt.jar to path. // This allows Soot to analyze real java instead of stub version in Android SDK on local machine. pathsList.add( diff --git a/utbot-intellij/src/main/kotlin/org/utbot/intellij/plugin/models/GenerateTestsModel.kt b/utbot-intellij/src/main/kotlin/org/utbot/intellij/plugin/models/GenerateTestsModel.kt index 2fb0b593fe..95c88e3575 100644 --- a/utbot-intellij/src/main/kotlin/org/utbot/intellij/plugin/models/GenerateTestsModel.kt +++ b/utbot-intellij/src/main/kotlin/org/utbot/intellij/plugin/models/GenerateTestsModel.kt @@ -48,6 +48,7 @@ data class GenerateTestsModel( val isMultiPackage: Boolean by lazy { srcClasses.map { it.packageName }.distinct().size != 1 } + var runGeneratedTestsWithCoverage : Boolean = false } val PsiClass.packageName: String get() = this.containingFile.containingDirectory.getPackage()?.qualifiedName ?: "" \ No newline at end of file diff --git a/utbot-intellij/src/main/kotlin/org/utbot/intellij/plugin/settings/Settings.kt b/utbot-intellij/src/main/kotlin/org/utbot/intellij/plugin/settings/Settings.kt index edde652c5c..7d48ea460f 100644 --- a/utbot-intellij/src/main/kotlin/org/utbot/intellij/plugin/settings/Settings.kt +++ b/utbot-intellij/src/main/kotlin/org/utbot/intellij/plugin/settings/Settings.kt @@ -53,6 +53,7 @@ class Settings(val project: Project) : PersistentStateComponent var parametrizedTestSource: ParametrizedTestSource = ParametrizedTestSource.defaultItem, var classesToMockAlways: Array = Mocker.defaultSuperClassesToMockAlwaysNames.toTypedArray(), var fuzzingValue: Double = 0.05, + var runGeneratedTestsWithCoverage : Boolean = false, ) { constructor(model: GenerateTestsModel) : this( codegenLanguage = model.codegenLanguage, @@ -65,7 +66,8 @@ class Settings(val project: Project) : PersistentStateComponent forceStaticMocking = model.forceStaticMocking, parametrizedTestSource = model.parametrizedTestSource, classesToMockAlways = model.chosenClassesToMockAlways.mapTo(mutableSetOf()) { it.name }.toTypedArray(), - fuzzingValue = model.fuzzingValue + fuzzingValue = model.fuzzingValue, + runGeneratedTestsWithCoverage = model.runGeneratedTestsWithCoverage ) override fun equals(other: Any?): Boolean { @@ -86,6 +88,7 @@ class Settings(val project: Project) : PersistentStateComponent if (parametrizedTestSource != other.parametrizedTestSource) return false if (!classesToMockAlways.contentEquals(other.classesToMockAlways)) return false if (fuzzingValue != other.fuzzingValue) return false + if (runGeneratedTestsWithCoverage != other.runGeneratedTestsWithCoverage) return false return true } @@ -102,6 +105,7 @@ class Settings(val project: Project) : PersistentStateComponent result = 31 * result + parametrizedTestSource.hashCode() result = 31 * result + classesToMockAlways.contentHashCode() result = 31 * result + fuzzingValue.hashCode() + result = 31 * result + if (runGeneratedTestsWithCoverage) 1 else 0 return result } @@ -138,6 +142,7 @@ class Settings(val project: Project) : PersistentStateComponent set(value) { state.fuzzingValue = value.coerceIn(0.0, 1.0) } + var runGeneratedTestsWithCoverage = state.runGeneratedTestsWithCoverage fun setClassesToMockAlways(classesToMockAlways: List) { state.classesToMockAlways = classesToMockAlways.distinct().toTypedArray() diff --git a/utbot-intellij/src/main/kotlin/org/utbot/intellij/plugin/ui/GenerateTestsDialogWindow.kt b/utbot-intellij/src/main/kotlin/org/utbot/intellij/plugin/ui/GenerateTestsDialogWindow.kt index c474e9b8ab..85c487cd19 100644 --- a/utbot-intellij/src/main/kotlin/org/utbot/intellij/plugin/ui/GenerateTestsDialogWindow.kt +++ b/utbot-intellij/src/main/kotlin/org/utbot/intellij/plugin/ui/GenerateTestsDialogWindow.kt @@ -27,9 +27,11 @@ import com.intellij.openapi.ui.ComboBox import com.intellij.openapi.ui.DialogPanel import com.intellij.openapi.ui.DialogWrapper import com.intellij.openapi.ui.Messages +import com.intellij.openapi.ui.OptionAction import com.intellij.openapi.ui.ValidationInfo import com.intellij.openapi.ui.popup.IconButton import com.intellij.openapi.util.Computable +import com.intellij.openapi.util.text.TextWithMnemonic import com.intellij.openapi.vfs.StandardFileSystems import com.intellij.openapi.vfs.VfsUtil import com.intellij.openapi.vfs.VfsUtilCore.urlToPath @@ -40,7 +42,6 @@ import com.intellij.psi.PsiClass import com.intellij.psi.PsiManager import com.intellij.psi.PsiMethod import com.intellij.psi.SyntheticElement -import com.intellij.psi.PsiModifier import com.intellij.refactoring.PackageWrapper import com.intellij.refactoring.ui.MemberSelectionTable import com.intellij.refactoring.ui.PackageNameReferenceEditorCombo @@ -75,11 +76,28 @@ import com.intellij.util.ui.JBUI.scale import com.intellij.util.ui.JBUI.size import com.intellij.util.ui.UIUtil import com.intellij.util.ui.components.BorderLayoutPanel +import java.awt.BorderLayout +import java.awt.Color +import java.awt.event.ActionEvent +import java.nio.file.Files +import java.nio.file.Path +import java.nio.file.Paths +import java.util.Objects +import java.util.concurrent.TimeUnit +import javax.swing.AbstractAction +import javax.swing.Action +import javax.swing.DefaultComboBoxModel +import javax.swing.JButton +import javax.swing.JComboBox +import javax.swing.JComponent +import javax.swing.JList +import javax.swing.JPanel +import kotlin.streams.toList import org.jetbrains.concurrency.Promise import org.jetbrains.concurrency.thenRun import org.jetbrains.kotlin.asJava.classes.KtUltraLightClass -import org.utbot.common.filterWhen import org.utbot.common.PathUtil.toPath +import org.utbot.common.filterWhen import org.utbot.framework.UtSettings import org.utbot.framework.codegen.ForceStaticMocking import org.utbot.framework.codegen.Junit4 @@ -116,20 +134,7 @@ import org.utbot.intellij.plugin.ui.utils.kotlinTargetPlatform import org.utbot.intellij.plugin.ui.utils.parseVersion import org.utbot.intellij.plugin.ui.utils.testResourceRootTypes import org.utbot.intellij.plugin.ui.utils.testRootType -import org.utbot.intellij.plugin.util.AndroidApiHelper -import java.awt.BorderLayout -import java.awt.Color -import java.nio.file.Files -import java.nio.file.Path -import java.nio.file.Paths -import java.util.* -import java.util.concurrent.TimeUnit -import javax.swing.DefaultComboBoxModel -import javax.swing.JComboBox -import javax.swing.JComponent -import javax.swing.JList -import javax.swing.JPanel -import kotlin.streams.toList +import org.utbot.intellij.plugin.util.IntelliJApiHelper import org.utbot.intellij.plugin.util.isAbstract private const val RECENTS_KEY = "org.utbot.recents" @@ -140,6 +145,9 @@ private const val WILL_BE_INSTALLED_LABEL = " (will be installed)" private const val WILL_BE_CONFIGURED_LABEL = " (will be configured)" private const val MINIMUM_TIMEOUT_VALUE_IN_SECONDS = 1 +private const val ACTION_GENERATE = "Generate Tests" +private const val ACTION_GENERATE_AND_RUN = "Generate && Run" //N + class GenerateTestsDialogWindow(val model: GenerateTestsModel) : DialogWrapper(model.project) { companion object { const val minSupportedSdkVersion = 8 @@ -213,6 +221,8 @@ class GenerateTestsDialogWindow(val model: GenerateTestsModel) : DialogWrapper(m twm.getToolWindow("Event Log")?.activate(null) } + model.runGeneratedTestsWithCoverage = model.project.service().runGeneratedTestsWithCoverage + init() } @@ -302,7 +312,7 @@ class GenerateTestsDialogWindow(val model: GenerateTestsModel) : DialogWrapper(m override fun createTitlePane(): JComponent? { val sdkVersion = findSdkVersion() //TODO:SAT-1571 investigate Android Studio specific sdk issues - if (sdkVersion?.feature in minSupportedSdkVersion..maxSupportedSdkVersion || AndroidApiHelper.isAndroidStudio()) return null + if (sdkVersion?.feature in minSupportedSdkVersion..maxSupportedSdkVersion || IntelliJApiHelper.isAndroidStudio()) return null isOKActionEnabled = false return SdkNotificationPanel(model, sdkVersion) } @@ -438,6 +448,45 @@ class GenerateTestsDialogWindow(val model: GenerateTestsModel) : DialogWrapper(m return null } + class OKOptionAction(val testsModel: GenerateTestsModel, val okAction : Action) : AbstractAction(testsModel.getActionText()), OptionAction { + init { + putValue(DEFAULT_ACTION, java.lang.Boolean.TRUE) + putValue(FOCUSED_ACTION, java.lang.Boolean.TRUE) + } + private val generateAction = object : AbstractAction(ACTION_GENERATE) { + override fun actionPerformed(e: ActionEvent?) { + testsModel.runGeneratedTestsWithCoverage = false + updateButtonText(e) + } + } + private val generateAndRunAction = object : AbstractAction(ACTION_GENERATE_AND_RUN) { + override fun actionPerformed(e: ActionEvent?) { + testsModel.runGeneratedTestsWithCoverage = true + updateButtonText(e) + } + } + + private fun updateButtonText(e: ActionEvent?) { + with(e?.source as JButton) { + text = TextWithMnemonic.parse(testsModel.getActionText()).dropMnemonic().text + testsModel.project.service().runGeneratedTestsWithCoverage = + testsModel.runGeneratedTestsWithCoverage + repaint() + } + } + + override fun actionPerformed(e: ActionEvent?) { + okAction.actionPerformed(e) + } + + override fun getOptions(): Array { + if (testsModel.runGeneratedTestsWithCoverage) return arrayOf(generateAndRunAction, generateAction) + return arrayOf(generateAction, generateAndRunAction) + } + } + + private val okOptionAction: OKOptionAction get() = OKOptionAction(model, super.getOKAction()) + override fun getOKAction() = okOptionAction override fun doOKAction() { model.testPackageName = @@ -964,6 +1013,9 @@ class GenerateTestsDialogWindow(val model: GenerateTestsModel) : DialogWrapper(m } } +fun GenerateTestsModel.getActionText() : String = + if (this.runGeneratedTestsWithCoverage) ACTION_GENERATE_AND_RUN else ACTION_GENERATE + private fun ComboBox.setHelpTooltipTextChanger(helpLabel: JBLabel) { addActionListener { event -> val comboBox = event.source as ComboBox<*> diff --git a/utbot-intellij/src/main/kotlin/org/utbot/intellij/plugin/util/AndroidApiHelper.kt b/utbot-intellij/src/main/kotlin/org/utbot/intellij/plugin/util/AndroidApiHelper.kt deleted file mode 100644 index 4f0aaa15f2..0000000000 --- a/utbot-intellij/src/main/kotlin/org/utbot/intellij/plugin/util/AndroidApiHelper.kt +++ /dev/null @@ -1,22 +0,0 @@ -package org.utbot.intellij.plugin.util - -import com.android.tools.idea.IdeInfo -import com.android.tools.idea.gradle.util.GradleProjectSettingsFinder -import com.intellij.ide.plugins.PluginManagerCore -import com.intellij.openapi.extensions.PluginId -import com.intellij.openapi.project.Project - -/** - * This object is required to encapsulate Android API usage and grant safe access to it. - */ -object AndroidApiHelper { - private val isAndroidPluginAvailable: Boolean = !PluginManagerCore.isDisabled(PluginId.getId("org.jetbrains.android")) - - fun isAndroidStudio(): Boolean = - isAndroidPluginAvailable && IdeInfo.getInstance().isAndroidStudio - - fun gradleSDK(project: Project): String? { - return if (isAndroidPluginAvailable) GradleProjectSettingsFinder.getInstance().findGradleProjectSettings(project)?.gradleJvm - else null - } -} diff --git a/utbot-intellij/src/main/kotlin/org/utbot/intellij/plugin/util/IntelliJApiHelper.kt b/utbot-intellij/src/main/kotlin/org/utbot/intellij/plugin/util/IntelliJApiHelper.kt new file mode 100644 index 0000000000..c4b88933a0 --- /dev/null +++ b/utbot-intellij/src/main/kotlin/org/utbot/intellij/plugin/util/IntelliJApiHelper.kt @@ -0,0 +1,46 @@ +package org.utbot.intellij.plugin.util + +import com.android.tools.idea.IdeInfo +import com.android.tools.idea.gradle.util.GradleProjectSettingsFinder +import com.intellij.ide.plugins.PluginManagerCore +import com.intellij.openapi.application.runReadAction +import com.intellij.openapi.application.runWriteAction +import com.intellij.openapi.extensions.PluginId +import com.intellij.openapi.project.Project +import com.intellij.util.concurrency.AppExecutorUtil +import org.jetbrains.kotlin.idea.util.application.invokeLater +import org.utbot.framework.plugin.api.util.UtContext +import org.utbot.framework.plugin.api.util.withUtContext + +/** + * This object is required to encapsulate Android API usage and grant safe access to it. + */ +object IntelliJApiHelper { + + enum class Target { THREAD_POOL, READ_ACTION, WRITE_ACTION, EDT_LATER } + + fun run(target: Target, runnable: Runnable) { + UtContext.currentContext()?.let { + when (target) { + Target.THREAD_POOL -> AppExecutorUtil.getAppExecutorService().submit { + withUtContext(it) { + runnable.run() + } + } + Target.READ_ACTION -> runReadAction { withUtContext(it) { runnable.run() } } + Target.WRITE_ACTION -> runWriteAction { withUtContext(it) { runnable.run() } } + Target.EDT_LATER -> invokeLater { withUtContext(it) { runnable.run() } } + } + } ?: error("No context in thread ${Thread.currentThread()}") + } + + private val isAndroidPluginAvailable: Boolean = !PluginManagerCore.isDisabled(PluginId.getId("org.jetbrains.android")) + + fun isAndroidStudio(): Boolean = + isAndroidPluginAvailable && IdeInfo.getInstance().isAndroidStudio + + fun androidGradleSDK(project: Project): String? { + return if (isAndroidPluginAvailable) GradleProjectSettingsFinder.getInstance().findGradleProjectSettings(project)?.gradleJvm + else null + } +} diff --git a/utbot-intellij/src/main/kotlin/org/utbot/intellij/plugin/util/PluginJdkPathProvider.kt b/utbot-intellij/src/main/kotlin/org/utbot/intellij/plugin/util/PluginJdkPathProvider.kt index 0adc27f575..ba78340018 100644 --- a/utbot-intellij/src/main/kotlin/org/utbot/intellij/plugin/util/PluginJdkPathProvider.kt +++ b/utbot-intellij/src/main/kotlin/org/utbot/intellij/plugin/util/PluginJdkPathProvider.kt @@ -16,9 +16,9 @@ class PluginJdkPathProvider( override val jdkPath: Path get() = - if (AndroidApiHelper.isAndroidStudio()) { + if (IntelliJApiHelper.isAndroidStudio()) { // Get Gradle JDK for Android - AndroidApiHelper.gradleSDK(project) + IntelliJApiHelper.androidGradleSDK(project) ?.let { sdkName -> ProjectJdkTable.getInstance().findJdk(sdkName)?.homePath?.toPath() } diff --git a/utbot-intellij/src/main/kotlin/org/utbot/intellij/plugin/util/RunConfigurationHelper.kt b/utbot-intellij/src/main/kotlin/org/utbot/intellij/plugin/util/RunConfigurationHelper.kt new file mode 100644 index 0000000000..a208e5d3ea --- /dev/null +++ b/utbot-intellij/src/main/kotlin/org/utbot/intellij/plugin/util/RunConfigurationHelper.kt @@ -0,0 +1,95 @@ +package org.utbot.intellij.plugin.util + +import com.intellij.coverage.CoverageExecutor +import com.intellij.execution.ExecutorRegistry +import com.intellij.execution.Location +import com.intellij.execution.PsiLocation +import com.intellij.execution.RunManagerEx +import com.intellij.execution.actions.ConfigurationContext +import com.intellij.execution.executors.DefaultRunExecutor +import com.intellij.execution.runners.ExecutionUtil +import com.intellij.openapi.actionSystem.DataContext +import com.intellij.openapi.actionSystem.LangDataKeys +import com.intellij.openapi.application.ApplicationManager +import com.intellij.openapi.util.Computable +import com.intellij.openapi.util.Key +import com.intellij.openapi.util.UserDataHolder +import com.intellij.openapi.util.UserDataHolderBase +import com.intellij.psi.PsiClass +import com.intellij.psi.PsiDocumentManager +import com.intellij.psi.PsiElement +import com.intellij.psi.PsiFile +import com.intellij.testFramework.MapDataContext +import org.jetbrains.plugins.groovy.lang.psi.util.childrenOfType +import org.utbot.intellij.plugin.models.GenerateTestsModel +import org.utbot.intellij.plugin.util.IntelliJApiHelper.run + +class RunConfigurationHelper { + private class MyMapDataContext : MapDataContext(), UserDataHolder { + val holder = UserDataHolderBase() + override fun getUserData(key: Key): T? { + return holder.getUserData(key) + } + + override fun putUserData(key: Key, value: T?) { + holder.putUserData(key, value) + } + } + + private class MyConfigurationContext(val context: DataContext, psiElement: PsiElement) : ConfigurationContext(psiElement) { + override fun getDataContext() = context + } + + companion object { + fun runTestsWithCoverage( + model: GenerateTestsModel, + testFiles: MutableList, + ) { + PsiDocumentManager.getInstance(model.project).commitAndRunReadAction() { + val testClasses = testFiles.map { file: PsiFile -> file.childrenOfType().firstOrNull() }.filterNotNull() + if (testClasses.isNotEmpty()) { + val locations = + testClasses.map { PsiLocation(model.project, model.testModule, it) }.toTypedArray() + val mapDataContext = MyMapDataContext().also { + it.put(LangDataKeys.PSI_ELEMENT_ARRAY, testClasses.toTypedArray()) + it.put(LangDataKeys.MODULE, model.testModule) + it.put(Location.DATA_KEYS, locations) + it.put(Location.DATA_KEY, locations[0]) + } + val myConfigurationContext = try { + MyConfigurationContext(mapDataContext, testClasses[0]) + } catch (e: Exception) { + e.printStackTrace() + return@commitAndRunReadAction + } + mapDataContext.putUserData( + ConfigurationContext.SHARED_CONTEXT, + myConfigurationContext + ) + run(IntelliJApiHelper.Target.THREAD_POOL) { + val configurations = ApplicationManager.getApplication().runReadAction(Computable { + myConfigurationContext.configurationsFromContext + }) + + val settings = if (configurations.isNullOrEmpty()) null else configurations[0].configurationSettings + if (settings != null) { + ExecutionUtil.runConfiguration( + settings, + ExecutorRegistry.getInstance().getExecutorById(CoverageExecutor.EXECUTOR_ID) + ?: DefaultRunExecutor.getRunExecutorInstance() + ) + with(RunManagerEx.getInstanceEx(model.project)) { + if (findSettings(settings.configuration) == null) { + settings.isTemporary = true + addConfiguration(settings) + } + selectedConfiguration = settings + } + } + } + } + } + } + + } +} \ No newline at end of file From 3ba5208ac90f853bf06c1933380f11c38d0533a2 Mon Sep 17 00:00:00 2001 From: "Vassiliy.Kudryashov" Date: Thu, 4 Aug 2022 21:14:10 +0300 Subject: [PATCH 2/4] Ability to create and run tests at once #156 --- .../org/utbot/intellij/plugin/ui/GenerateTestsDialogWindow.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/utbot-intellij/src/main/kotlin/org/utbot/intellij/plugin/ui/GenerateTestsDialogWindow.kt b/utbot-intellij/src/main/kotlin/org/utbot/intellij/plugin/ui/GenerateTestsDialogWindow.kt index 85c487cd19..165e90be1d 100644 --- a/utbot-intellij/src/main/kotlin/org/utbot/intellij/plugin/ui/GenerateTestsDialogWindow.kt +++ b/utbot-intellij/src/main/kotlin/org/utbot/intellij/plugin/ui/GenerateTestsDialogWindow.kt @@ -146,7 +146,7 @@ private const val WILL_BE_CONFIGURED_LABEL = " (will be configured)" private const val MINIMUM_TIMEOUT_VALUE_IN_SECONDS = 1 private const val ACTION_GENERATE = "Generate Tests" -private const val ACTION_GENERATE_AND_RUN = "Generate && Run" //N +private const val ACTION_GENERATE_AND_RUN = "Generate && Run" //Note that ampersand has to be escaped (doubled) class GenerateTestsDialogWindow(val model: GenerateTestsModel) : DialogWrapper(model.project) { companion object { From 71444c3a45635020aa00b27abb8d5c5c28afcc88 Mon Sep 17 00:00:00 2001 From: "Vassiliy.Kudryashov" Date: Fri, 5 Aug 2022 13:19:37 +0300 Subject: [PATCH 3/4] After-review fix for exception logging --- .../org/utbot/intellij/plugin/util/RunConfigurationHelper.kt | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/utbot-intellij/src/main/kotlin/org/utbot/intellij/plugin/util/RunConfigurationHelper.kt b/utbot-intellij/src/main/kotlin/org/utbot/intellij/plugin/util/RunConfigurationHelper.kt index a208e5d3ea..afb8792d32 100644 --- a/utbot-intellij/src/main/kotlin/org/utbot/intellij/plugin/util/RunConfigurationHelper.kt +++ b/utbot-intellij/src/main/kotlin/org/utbot/intellij/plugin/util/RunConfigurationHelper.kt @@ -20,6 +20,7 @@ import com.intellij.psi.PsiDocumentManager import com.intellij.psi.PsiElement import com.intellij.psi.PsiFile import com.intellij.testFramework.MapDataContext +import mu.KotlinLogging import org.jetbrains.plugins.groovy.lang.psi.util.childrenOfType import org.utbot.intellij.plugin.models.GenerateTestsModel import org.utbot.intellij.plugin.util.IntelliJApiHelper.run @@ -41,6 +42,8 @@ class RunConfigurationHelper { } companion object { + private val logger = KotlinLogging.logger {} + fun runTestsWithCoverage( model: GenerateTestsModel, testFiles: MutableList, @@ -59,7 +62,7 @@ class RunConfigurationHelper { val myConfigurationContext = try { MyConfigurationContext(mapDataContext, testClasses[0]) } catch (e: Exception) { - e.printStackTrace() + logger.error { e } return@commitAndRunReadAction } mapDataContext.putUserData( From 00318a49e31db71b88d6adad50bf670aaa599271 Mon Sep 17 00:00:00 2001 From: "Vassiliy.Kudryashov" Date: Fri, 5 Aug 2022 15:36:54 +0300 Subject: [PATCH 4/4] After-review fix: added the fallback in case 'Code Coverage for Java' plugin is not enabled --- .../intellij/plugin/util/RunConfigurationHelper.kt | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/utbot-intellij/src/main/kotlin/org/utbot/intellij/plugin/util/RunConfigurationHelper.kt b/utbot-intellij/src/main/kotlin/org/utbot/intellij/plugin/util/RunConfigurationHelper.kt index afb8792d32..7747667be0 100644 --- a/utbot-intellij/src/main/kotlin/org/utbot/intellij/plugin/util/RunConfigurationHelper.kt +++ b/utbot-intellij/src/main/kotlin/org/utbot/intellij/plugin/util/RunConfigurationHelper.kt @@ -8,6 +8,7 @@ import com.intellij.execution.RunManagerEx import com.intellij.execution.actions.ConfigurationContext import com.intellij.execution.executors.DefaultRunExecutor import com.intellij.execution.runners.ExecutionUtil +import com.intellij.execution.runners.ProgramRunner import com.intellij.openapi.actionSystem.DataContext import com.intellij.openapi.actionSystem.LangDataKeys import com.intellij.openapi.application.ApplicationManager @@ -76,16 +77,19 @@ class RunConfigurationHelper { val settings = if (configurations.isNullOrEmpty()) null else configurations[0].configurationSettings if (settings != null) { - ExecutionUtil.runConfiguration( - settings, - ExecutorRegistry.getInstance().getExecutorById(CoverageExecutor.EXECUTOR_ID) - ?: DefaultRunExecutor.getRunExecutorInstance() - ) + val executor = if (ProgramRunner.getRunner(CoverageExecutor.EXECUTOR_ID, settings.configuration) != null) { + ExecutorRegistry.getInstance().getExecutorById(CoverageExecutor.EXECUTOR_ID) ?: DefaultRunExecutor.getRunExecutorInstance() + } else { + //Fallback in case 'Code Coverage for Java' plugin is not enabled + DefaultRunExecutor.getRunExecutorInstance() + } + ExecutionUtil.runConfiguration(settings, executor) with(RunManagerEx.getInstanceEx(model.project)) { if (findSettings(settings.configuration) == null) { settings.isTemporary = true addConfiguration(settings) } + //TODO check shouldSetRunConfigurationFromContext in API 2021.3+ selectedConfiguration = settings } }