diff --git a/clion-plugin/src/main/kotlin/org/utbot/cpp/clion/plugin/UTBotStartupActivity.kt b/clion-plugin/src/main/kotlin/org/utbot/cpp/clion/plugin/UTBotStartupActivity.kt index 3abf0bad5..1abfaf519 100644 --- a/clion-plugin/src/main/kotlin/org/utbot/cpp/clion/plugin/UTBotStartupActivity.kt +++ b/clion-plugin/src/main/kotlin/org/utbot/cpp/clion/plugin/UTBotStartupActivity.kt @@ -1,12 +1,11 @@ package org.utbot.cpp.clion.plugin import com.intellij.ide.util.RunOnceUtil +import com.intellij.openapi.application.ApplicationManager import com.intellij.openapi.components.service import com.intellij.openapi.project.Project import com.intellij.openapi.startup.StartupActivity -import org.utbot.cpp.clion.plugin.client.Client import org.utbot.cpp.clion.plugin.client.ClientManager -import org.utbot.cpp.clion.plugin.settings.pluginSettings import org.utbot.cpp.clion.plugin.settings.settings import org.utbot.cpp.clion.plugin.ui.wizard.UTBotWizard import org.utbot.cpp.clion.plugin.utils.invokeOnEdt @@ -27,10 +26,11 @@ class UTBotStartupActivity : StartupActivity { private fun showWizardOnFirstOpen(project: Project) { - if (pluginSettings.isFirstLaunch && !Client.IS_TEST_MODE) { - pluginSettings.isFirstLaunch = false + if (!ApplicationManager.getApplication().isUnitTestMode) { invokeOnEdt { - UTBotWizard(project).showAndGet() + RunOnceUtil.runOnceForProject(project, "Show UTBot quick-start wizard to configure project") { + UTBotWizard(project).showAndGet() + } } } } diff --git a/clion-plugin/src/main/kotlin/org/utbot/cpp/clion/plugin/client/Client.kt b/clion-plugin/src/main/kotlin/org/utbot/cpp/clion/plugin/client/Client.kt index a32f92d9c..012380919 100644 --- a/clion-plugin/src/main/kotlin/org/utbot/cpp/clion/plugin/client/Client.kt +++ b/clion-plugin/src/main/kotlin/org/utbot/cpp/clion/plugin/client/Client.kt @@ -201,7 +201,6 @@ class Client( } companion object { - var IS_TEST_MODE = false const val HEARTBEAT_INTERVAL: Long = 500L const val SERVER_TIMEOUT: Long = 300000L const val DELAY_TIME: Long = 500L diff --git a/clion-plugin/src/main/kotlin/org/utbot/cpp/clion/plugin/settings/SettingsProvider.kt b/clion-plugin/src/main/kotlin/org/utbot/cpp/clion/plugin/settings/SettingsProvider.kt index 61f7f91ee..4412337e1 100644 --- a/clion-plugin/src/main/kotlin/org/utbot/cpp/clion/plugin/settings/SettingsProvider.kt +++ b/clion-plugin/src/main/kotlin/org/utbot/cpp/clion/plugin/settings/SettingsProvider.kt @@ -7,7 +7,4 @@ val Project.settings: UTBotAllProjectSettings get() = this.service() val projectIndependentSettings: UTBotProjectIndependentSettings.State - get() = service().state - -val pluginSettings: UTBotPluginSpecificSettings - get() = service() \ No newline at end of file + get() = service().state \ No newline at end of file diff --git a/clion-plugin/src/main/kotlin/org/utbot/cpp/clion/plugin/settings/UTBotAllProjectSettings.kt b/clion-plugin/src/main/kotlin/org/utbot/cpp/clion/plugin/settings/UTBotAllProjectSettings.kt index 06d09b0b7..bad3b121f 100644 --- a/clion-plugin/src/main/kotlin/org/utbot/cpp/clion/plugin/settings/UTBotAllProjectSettings.kt +++ b/clion-plugin/src/main/kotlin/org/utbot/cpp/clion/plugin/settings/UTBotAllProjectSettings.kt @@ -9,6 +9,7 @@ import org.utbot.cpp.clion.plugin.ui.targetsToolWindow.UTBotTarget import org.utbot.cpp.clion.plugin.utils.convertToRemotePathIfNeeded import org.utbot.cpp.clion.plugin.utils.isWindows import org.utbot.cpp.clion.plugin.utils.notifyWarning +import org.utbot.cpp.clion.plugin.utils.path import java.io.File import java.nio.file.Path import java.nio.file.Paths @@ -18,29 +19,23 @@ class UTBotAllProjectSettings(val project: Project) { val storedSettings: UTBotProjectStoredSettings.State get() = project.service().state - // todo: maybe remove this property and access directly - var projectPath: String - get() { - return storedSettings.projectPath - } - set(value) { - storedSettings.projectPath = value - } - val buildDirPath: Path - get() = Paths.get(projectPath).resolve(storedSettings.buildDirRelativePath) + get() = Paths.get(project.path).resolve(storedSettings.buildDirRelativePath) + + val testsDirPath: Path + get() = Paths.get(project.path).resolve(storedSettings.testsDirRelativePath) val convertedSourcePaths: List get() = storedSettings.sourceDirs.map { it.convertToRemotePathIfNeeded(project) } val convertedTestDirPath: String - get() = storedSettings.testDirPath.convertToRemotePathIfNeeded(project) + get() = testsDirPath.toString().convertToRemotePathIfNeeded(project) val convertedTargetPath: String get() = if (storedSettings.targetPath == UTBotTarget.autoTarget.path) storedSettings.targetPath else storedSettings.targetPath.convertToRemotePathIfNeeded(project) - val convertedProjectPath: String get() = projectPath.convertToRemotePathIfNeeded(project) + val convertedProjectPath: String get() = project.path.convertToRemotePathIfNeeded(project) /** * If this property returns true, plugin must convert path sent and returned from server. @@ -52,7 +47,7 @@ class UTBotAllProjectSettings(val project: Project) { get() { val isLocalHost = projectIndependentSettings.serverName == "localhost" || projectIndependentSettings.serverName == "127.0.0.01" - return !(storedSettings.remotePath == projectPath && isLocalHost) || isWindows + return !(storedSettings.remotePath == UTBotProjectStoredSettings.REMOTE_PATH_VALUE_FOR_LOCAL_SCENARIO && isLocalHost) || isWindows } fun fireUTBotSettingsChanged() { @@ -66,13 +61,6 @@ class UTBotAllProjectSettings(val project: Project) { storedSettings.buildDirRelativePath = UTBotProjectStoredSettings.DEFAULT_RELATIVE_PATH_TO_BUILD_DIR storedSettings.targetPath = UTBotTarget.autoTarget.path - try { - storedSettings.testDirPath = - Paths.get(projectPath, UTBotProjectStoredSettings.DEFAULT_RELATIVE_PATH_TO_TEST_DIR).toString() - } catch (e: IllegalStateException) { - notifyWarning("Guessing settings failed: could not guess project path! Please specify it in settings!") - } - val cmakeRunConfiguration = CMakeAppRunConfiguration.getSelectedConfigurationAndTarget(project)?.first val buildConfigurationSources = cmakeRunConfiguration?.cMakeTarget?.buildConfigurations?.map { it.sources } //TODO: why do we use firstOrNull here? diff --git a/clion-plugin/src/main/kotlin/org/utbot/cpp/clion/plugin/settings/UTBotConfigurable.kt b/clion-plugin/src/main/kotlin/org/utbot/cpp/clion/plugin/settings/UTBotConfigurable.kt index 11535adc7..9910f7815 100644 --- a/clion-plugin/src/main/kotlin/org/utbot/cpp/clion/plugin/settings/UTBotConfigurable.kt +++ b/clion-plugin/src/main/kotlin/org/utbot/cpp/clion/plugin/settings/UTBotConfigurable.kt @@ -2,11 +2,13 @@ package org.utbot.cpp.clion.plugin.settings +import com.intellij.openapi.components.service import com.intellij.openapi.diagnostic.Logger import com.intellij.openapi.fileChooser.FileChooserDescriptorFactory import com.intellij.openapi.options.BoundConfigurable import com.intellij.openapi.project.Project import com.intellij.openapi.ui.DialogPanel +import com.intellij.ui.components.JBTextField import com.intellij.ui.dsl.builder.BottomGap import com.intellij.ui.dsl.builder.COLUMNS_LARGE import com.intellij.ui.dsl.builder.LabelPosition @@ -24,10 +26,13 @@ import org.utbot.cpp.clion.plugin.UTBot import org.utbot.cpp.clion.plugin.listeners.UTBotSettingsChangedListener import org.utbot.cpp.clion.plugin.ui.ObservableValue import org.utbot.cpp.clion.plugin.ui.sourceFoldersView.UTBotProjectViewPaneForSettings +import org.utbot.cpp.clion.plugin.ui.targetsToolWindow.UTBotTarget import org.utbot.cpp.clion.plugin.utils.commandLineEditor import org.utbot.cpp.clion.plugin.utils.isWindows +import org.utbot.cpp.clion.plugin.utils.path import org.utbot.cpp.clion.plugin.utils.toWslFormat import java.awt.Dimension +import java.nio.file.Paths class UTBotConfigurable(private val myProject: Project) : BoundConfigurable( "Project Settings to Generate Tests" @@ -35,8 +40,9 @@ class UTBotConfigurable(private val myProject: Project) : BoundConfigurable( private val logger = Logger.getInstance("ProjectConfigurable") private val panel by lazy { createMainPanel() } - private val settings: UTBotProjectStoredSettings.State - get() = myProject.settings.storedSettings + private val settings: UTBotProjectStoredSettings = myProject.service() + private lateinit var portTextfield: JBTextField + private lateinit var serverNameTextField: JBTextField private val isLocalOrWsl = ObservableValue(settings.isLocalOrWslScenario) @@ -62,16 +68,17 @@ class UTBotConfigurable(private val myProject: Project) : BoundConfigurable( private fun createMainPanel(): DialogPanel { logger.trace("createPanel was called") return panel { - group("Connection Settings") { this.createConnectionSettings() } - group("Paths") { this.createPathsSettings() } - group("CMake") { this.createCMakeSettings() } - group("Generator Settings") { this.createGeneratorSettings() } + group("Connection Settings") { createConnectionSettings() } + group("Paths") { createPathsSettings() } + group("CMake") { createCMakeSettings() } + group("Generator Settings") { createGeneratorSettings() } } } private fun Panel.createConnectionSettings() { row(UTBot.message("settings.project.port")) { intTextField().bindIntText(projectIndependentSettings::port).applyToComponent { + portTextfield = this maximumSize = TEXT_FIELD_MAX_SIZE } }.rowComment(UTBot.message("deployment.utbotPort.description")) @@ -94,6 +101,7 @@ class UTBotConfigurable(private val myProject: Project) : BoundConfigurable( row(UTBot.message("settings.project.serverName")) { textField().bindText(projectIndependentSettings::serverName).applyToComponent { + serverNameTextField = this isLocalOrWsl.addOnChangeListener { newValue -> if (newValue) this.text = "localhost" @@ -106,38 +114,31 @@ class UTBotConfigurable(private val myProject: Project) : BoundConfigurable( .applyToComponent { isLocalOrWsl.addOnChangeListener { newValue -> if (newValue) - this.text = if (isWindows) myProject.settings.projectPath.toWslFormat() else "" + this.text = if (isWindows) myProject.path.toWslFormat() else "" } }.enabledIf(enabledIfNotLocalOrWslScenario) }.rowComment(UTBot.message("deployment.remotePath.description")) } private fun Panel.createPathsSettings() { - row(UTBot.message("settings.project.projectPath")) { - textFieldWithBrowseButton( - UTBot.message("settings.project.projectPath.title"), - myProject, - FileChooserDescriptorFactory.createSingleFileDescriptor() - ).bindText( - getter = { myProject.settings.projectPath }, - setter = { value -> myProject.settings.projectPath = value }) - .columns(COLUMNS_LARGE) - }.rowComment(UTBot.message("settings.project.projectPath.info")) createPathChooser( settings::buildDirRelativePath, UTBot.message("settings.project.buildDir"), UTBot.message("settings.project.buildDir.browse.title") ).rowComment(UTBot.message("paths.buildDirectory.description")) - createPathChooser( - settings::targetPath, - UTBot.message("settings.project.target"), - UTBot.message("settings.project.target.browse.title") - ).rowComment(UTBot.message("paths.target.description")) - createPathChooser( - settings::testDirPath, - UTBot.message("settings.project.testsDir"), - UTBot.message("settings.project.testsDir.browse.title") - ).rowComment(UTBot.message("paths.testsDirectory.description")) + + row(UTBot.message("settings.project.target")) { + textField().bindText( + getter = { + settings.uiTargetPath + }, + setter = {} + ).columns(COLUMNS_LARGE).enabled(false) + }.rowComment(UTBot.message("paths.target.description")) + + row(UTBot.message("settings.project.testsDir")) { + textField().bindText(settings::testDirRelativePath).columns(COLUMNS_LARGE) + }.rowComment(UTBot.message("paths.testsDir.description")) row { val pane = UTBotProjectViewPaneForSettings(myProject) @@ -221,7 +222,7 @@ class UTBotConfigurable(private val myProject: Project) : BoundConfigurable( spinner( UTBotProjectStoredSettings.TIMEOUT_PER_TEST_MIN_VALUE.. UTBotProjectStoredSettings.TIMEOUT_PER_TEST_MAX_VALUE - ).bindIntValue(settings::timeoutPerFunction).applyToComponent { + ).bindIntValue(settings::timeoutPerTest).applyToComponent { maximumSize = TEXT_FIELD_MAX_SIZE } }.rowComment(UTBot.message("advanced.timeoutPerTest.description")) @@ -232,8 +233,12 @@ class UTBotConfigurable(private val myProject: Project) : BoundConfigurable( } override fun apply() { + val wereConnectionSettingsModified = + portTextfield.text != projectIndependentSettings.port.toString() || serverNameTextField.text != projectIndependentSettings.serverName panel.apply() myProject.settings.fireUTBotSettingsChanged() + if (wereConnectionSettingsModified) + projectIndependentSettings.fireConnectionSettingsChanged() } override fun reset() { diff --git a/clion-plugin/src/main/kotlin/org/utbot/cpp/clion/plugin/settings/UTBotPluginSpecificSettings.kt b/clion-plugin/src/main/kotlin/org/utbot/cpp/clion/plugin/settings/UTBotPluginSpecificSettings.kt deleted file mode 100644 index 39ef35b9d..000000000 --- a/clion-plugin/src/main/kotlin/org/utbot/cpp/clion/plugin/settings/UTBotPluginSpecificSettings.kt +++ /dev/null @@ -1,15 +0,0 @@ -package org.utbot.cpp.clion.plugin.settings - -import com.intellij.openapi.components.PersistentStateComponent -import com.intellij.openapi.components.State -import com.intellij.openapi.components.Storage -import com.intellij.util.xmlb.XmlSerializerUtil - -@State(name = "UTBotPluginSpecificSettings", storages = [Storage("UTBotPluginSpecificSettings.xml")]) -data class UTBotPluginSpecificSettings(var isFirstLaunch: Boolean = true) : PersistentStateComponent { - - override fun getState(): UTBotPluginSpecificSettings = this - override fun loadState(state: UTBotPluginSpecificSettings) { - XmlSerializerUtil.copyBean(state, this); - } -} \ No newline at end of file diff --git a/clion-plugin/src/main/kotlin/org/utbot/cpp/clion/plugin/settings/UTBotProjectStoredSettings.kt b/clion-plugin/src/main/kotlin/org/utbot/cpp/clion/plugin/settings/UTBotProjectStoredSettings.kt index 26aa62288..08b68ec3b 100644 --- a/clion-plugin/src/main/kotlin/org/utbot/cpp/clion/plugin/settings/UTBotProjectStoredSettings.kt +++ b/clion-plugin/src/main/kotlin/org/utbot/cpp/clion/plugin/settings/UTBotProjectStoredSettings.kt @@ -4,9 +4,11 @@ import com.intellij.openapi.components.PersistentStateComponent import com.intellij.openapi.components.Service import com.intellij.openapi.components.State import com.intellij.openapi.components.Storage +import com.intellij.openapi.components.service import com.intellij.openapi.project.Project -import com.intellij.openapi.project.guessProjectDir import org.utbot.cpp.clion.plugin.ui.targetsToolWindow.UTBotTarget +import org.utbot.cpp.clion.plugin.ui.targetsToolWindow.UTBotTargetsController +import org.utbot.cpp.clion.plugin.utils.path import java.nio.file.Paths /** @@ -22,9 +24,8 @@ class UTBotProjectStoredSettings(val project: Project) : PersistentStateComponen // serialized by the ide data class State( - var projectPath: String = "", var buildDirRelativePath: String = DEFAULT_RELATIVE_PATH_TO_BUILD_DIR, - var testDirPath: String = "", + var testsDirRelativePath: String = DEFAULT_TESTS_DIR_RELATIVE_PATH, var targetPath: String = UTBotTarget.autoTarget.path, var remotePath: String = REMOTE_PATH_VALUE_FOR_LOCAL_SCENARIO, var sourceDirs: Set = setOf(), @@ -39,7 +40,7 @@ class UTBotProjectStoredSettings(val project: Project) : PersistentStateComponen ) { fun fromSettingsModel(model: UTBotSettingsModel) { buildDirRelativePath = model.projectSettings.buildDirRelativePath - testDirPath = model.projectSettings.testDirPath + testsDirRelativePath = model.projectSettings.testsDirRelativePath targetPath = model.projectSettings.targetPath remotePath = model.projectSettings.remotePath sourceDirs = model.projectSettings.sourceDirs @@ -53,6 +54,92 @@ class UTBotProjectStoredSettings(val project: Project) : PersistentStateComponen } } + var cmakeOptions: String + get() = myState.cmakeOptions + set(value) { + myState.cmakeOptions = value + } + + var generateForStaticFunctions: Boolean + get() = myState.generateForStaticFunctions + set(value) { + myState.generateForStaticFunctions = value + } + + var useStubs: Boolean + get() = myState.useStubs + set(value) { + myState.useStubs = value + } + + var useDeterministicSearcher: Boolean + get() = myState.useDeterministicSearcher + set(value) { + myState.useDeterministicSearcher = value + } + + var isLocalOrWslScenario: Boolean + get() = myState.isLocalOrWslScenario + set(value) { + myState.isLocalOrWslScenario = value + } + + var verbose: Boolean + get() = myState.verbose + set(value) { + myState.verbose = value + } + + var timeoutPerFunction: Int + get() = myState.timeoutPerFunction + set(value) { + myState.timeoutPerFunction = value + } + + var timeoutPerTest: Int + get() = myState.timeoutPerTest + set(value) { + myState.timeoutPerTest = value + } + + var testDirRelativePath: String + get() = myState.testsDirRelativePath + set(value) { + myState.testsDirRelativePath = value + } + + var remotePath: String + get() = myState.remotePath + set(value) { + myState.remotePath = value + } + + var targetPath: String + get() { + if (isTargetUpToDate()) + return myState.targetPath + return UTBotTarget.autoTarget.path + } + set(value) { + myState.targetPath = value + } + + val uiTargetPath: String + get() = if (targetPath == UTBotTarget.autoTarget.path) + UTBotTarget.autoTarget.path + else + Paths.get(project.path).relativize(Paths.get(targetPath)).toString() + + var buildDirRelativePath: String + get() = myState.buildDirRelativePath + set(value) { + myState.buildDirRelativePath = value + } + + private fun isTargetUpToDate(): Boolean { + return project.service().isTargetUpToDate(myState.targetPath) + } + override fun getState() = myState override fun loadState(state: State) { myState = state @@ -61,22 +148,20 @@ class UTBotProjectStoredSettings(val project: Project) : PersistentStateComponen // called when during component initialization if there is no persisted state. // See java docs for PersistingStateComponent override fun noStateLoaded() { - myState.projectPath = - project.guessProjectDir()?.path ?: error("Could not guess project path! Should be specified in settings") - myState.testDirPath = Paths.get(myState.projectPath).resolve(DEFAULT_RELATIVE_PATH_TO_TEST_DIR).toString() myState.remotePath = REMOTE_PATH_VALUE_FOR_LOCAL_SCENARIO } companion object { val DEFAULT_CMAKE_OPTIONS = listOf("-DCMAKE_EXPORT_COMPILE_COMMANDS=ON", "-DCMAKE_EXPORT_LINK_COMMANDS=ON") + // local means no conversion of paths is needed. This is the case for when server runs locally on Linux + const val DEFAULT_TESTS_DIR_RELATIVE_PATH = "tests" const val REMOTE_PATH_VALUE_FOR_LOCAL_SCENARIO = "" - const val DEFAULT_RELATIVE_PATH_TO_TEST_DIR = "utbot-tests" const val DEFAULT_RELATIVE_PATH_TO_BUILD_DIR = "utbot-build" const val TIMEOUT_PER_TEST_MAX_VALUE = 1000 const val TIMEOUT_PER_TEST_MIN_VALUE = 0 const val TIMEOUT_PER_FUNCTION_MAX_VALUE = 1000 const val TIMEOUT_PER_FUNCTION_MIN_VALUE = 0 } -} \ No newline at end of file +} diff --git a/clion-plugin/src/main/kotlin/org/utbot/cpp/clion/plugin/ui/statusBar/StatusBarConnectionStatus.kt b/clion-plugin/src/main/kotlin/org/utbot/cpp/clion/plugin/ui/statusBar/StatusBarConnectionStatus.kt index abfcaa202..c904bac0d 100644 --- a/clion-plugin/src/main/kotlin/org/utbot/cpp/clion/plugin/ui/statusBar/StatusBarConnectionStatus.kt +++ b/clion-plugin/src/main/kotlin/org/utbot/cpp/clion/plugin/ui/statusBar/StatusBarConnectionStatus.kt @@ -67,8 +67,8 @@ class UTBotStatusBarWidget : StatusBarWidget, StatusBarWidget.TextPresentation { val component = event.component val popup = StatusBarActionsPopup.getPopup(DataManager.getInstance().getDataContext(component)) - //TODO: this is a kind of peacedeath val dimension = popup.content.preferredSize + // the point for popup was set experimentally val popupLocation = Point(0, -dimension.height) popup.show(RelativePoint(component, popupLocation)) diff --git a/clion-plugin/src/main/kotlin/org/utbot/cpp/clion/plugin/ui/statusBar/UTBotStatusBarVerboseWidget.kt b/clion-plugin/src/main/kotlin/org/utbot/cpp/clion/plugin/ui/statusBar/UTBotStatusBarVerboseWidget.kt index 4137b31c7..a4aabd198 100644 --- a/clion-plugin/src/main/kotlin/org/utbot/cpp/clion/plugin/ui/statusBar/UTBotStatusBarVerboseWidget.kt +++ b/clion-plugin/src/main/kotlin/org/utbot/cpp/clion/plugin/ui/statusBar/UTBotStatusBarVerboseWidget.kt @@ -26,11 +26,10 @@ class UTBotStatusBarVerboseWidget : StatusBarWidget, StatusBarWidget.TextPresent override fun getTooltipText() = VerboseModeWidgetFactory.STATUS_BAR_DISPLAY_NAME override fun getClickConsumer() = Consumer { _ -> - statusBar?.updateWidget(ID()) - val project = statusBar?.project ?: return@Consumer val settings = project.settings.storedSettings settings.verbose = !settings.verbose + statusBar?.updateWidget(ID()) } override fun getText(): String { diff --git a/clion-plugin/src/main/kotlin/org/utbot/cpp/clion/plugin/ui/targetsToolWindow/UTBotTargetsController.kt b/clion-plugin/src/main/kotlin/org/utbot/cpp/clion/plugin/ui/targetsToolWindow/UTBotTargetsController.kt index 0b4c504cc..5a22dea3a 100644 --- a/clion-plugin/src/main/kotlin/org/utbot/cpp/clion/plugin/ui/targetsToolWindow/UTBotTargetsController.kt +++ b/clion-plugin/src/main/kotlin/org/utbot/cpp/clion/plugin/ui/targetsToolWindow/UTBotTargetsController.kt @@ -7,13 +7,11 @@ import org.utbot.cpp.clion.plugin.client.requests.ProjectTargetsRequest import org.utbot.cpp.clion.plugin.grpc.getProjectTargetsGrpcRequest import org.utbot.cpp.clion.plugin.listeners.ConnectionStatus import org.utbot.cpp.clion.plugin.listeners.UTBotEventsListener -import org.utbot.cpp.clion.plugin.listeners.UTBotSettingsChangedListener import org.utbot.cpp.clion.plugin.settings.UTBotAllProjectSettings import org.utbot.cpp.clion.plugin.settings.settings import org.utbot.cpp.clion.plugin.utils.getCurrentClient import org.utbot.cpp.clion.plugin.utils.invokeOnEdt import org.utbot.cpp.clion.plugin.utils.logger -import org.utbot.cpp.clion.plugin.utils.relativize import testsgen.Testgen @Service @@ -21,23 +19,28 @@ class UTBotTargetsController(val project: Project) { private val settings: UTBotAllProjectSettings get() = project.settings - private val listModel = CollectionListModel(mutableListOf()) private val logger get() = project.logger - val targetsToolWindow: UTBotTargetsToolWindow by lazy { UTBotTargetsToolWindow(listModel, this) } + + private var areTargetsUpToDate = false + private val targetsUiModel = CollectionListModel(mutableListOf()) + val targetsToolWindow: UTBotTargetsToolWindow by lazy { UTBotTargetsToolWindow(targetsUiModel, this) } val targets: List - get() = listModel.toList() + get() = targetsUiModel.toList() init { requestTargetsFromServer() connectToEvents() } + fun isTargetUpToDate(path: String): Boolean = areTargetsUpToDate && targets.find { it.path == path } != null + fun requestTargetsFromServer() { val currentClient = project.getCurrentClient() + areTargetsUpToDate = false invokeOnEdt { - listModel.removeAll() + targetsUiModel.removeAll() targetsToolWindow.setBusy(true) } ProjectTargetsRequest( @@ -47,12 +50,23 @@ class UTBotTargetsController(val project: Project) { invokeOnEdt { targetsToolWindow.setBusy(false) - listModel.apply { - listModel.replaceAll( + targetsUiModel.apply { + targetsUiModel.replaceAll( targetsResponse.targetsList.map { projectTarget -> UTBotTarget(projectTarget, project) }) } + areTargetsUpToDate = true + + // set selected target in ui + val storedTargetPath = project.settings.storedSettings.targetPath + var targetToSelect = UTBotTarget.autoTarget + if (isTargetUpToDate(storedTargetPath)) { + targets.find { it.path == storedTargetPath }?.let { + targetToSelect = it + } + } + targetsToolWindow.setSelectedTarget(targetToSelect) } }, onError = { @@ -72,38 +86,19 @@ class UTBotTargetsController(val project: Project) { } } - private fun addTargetPathIfNotPresent(possiblyNewTargetPath: String) { - listModel.apply { - toList().find { utbotTarget -> utbotTarget.path == possiblyNewTargetPath } ?: add( - UTBotTarget( - possiblyNewTargetPath, - "custom target", - relativize(settings.projectPath, possiblyNewTargetPath) - ) - ) - } - } fun selectionChanged(selectedTarget: UTBotTarget) { // when user selects target update model settings.storedSettings.targetPath = selectedTarget.path } - fun setTargetByName(targetName: String) { + fun setTargetPathByName(targetName: String) { val target = targets.find { it.name == targetName } ?: error("No such target!") settings.storedSettings.targetPath = target.path } private fun connectToEvents() { project.messageBus.connect().also { connection -> - // if user specifies some custom target path in settings, it will be added if not already present - connection.subscribe( - UTBotSettingsChangedListener.TOPIC, - UTBotSettingsChangedListener { - // todo: remove custom target - // val possiblyNewTargetPath = settings.storedSettings.targetPath - // addTargetPathIfNotPresent(possiblyNewTargetPath) - }) // when we reconnected to server, the targets should be updated, so we request them from server connection.subscribe( UTBotEventsListener.CONNECTION_CHANGED_TOPIC, diff --git a/clion-plugin/src/main/kotlin/org/utbot/cpp/clion/plugin/ui/targetsToolWindow/UTBotTargetsToolWindow.kt b/clion-plugin/src/main/kotlin/org/utbot/cpp/clion/plugin/ui/targetsToolWindow/UTBotTargetsToolWindow.kt index 7c251afe8..55e759bae 100644 --- a/clion-plugin/src/main/kotlin/org/utbot/cpp/clion/plugin/ui/targetsToolWindow/UTBotTargetsToolWindow.kt +++ b/clion-plugin/src/main/kotlin/org/utbot/cpp/clion/plugin/ui/targetsToolWindow/UTBotTargetsToolWindow.kt @@ -16,6 +16,7 @@ import javax.swing.JList import javax.swing.ListSelectionModel import org.utbot.cpp.clion.plugin.UTBot import org.utbot.cpp.clion.plugin.actions.RefreshTargetsAction +import org.utbot.cpp.clion.plugin.utils.invokeOnEdt import org.utbot.cpp.clion.plugin.utils.logger class UTBotTargetsToolWindow( @@ -47,6 +48,10 @@ class UTBotTargetsToolWindow( uiList.setPaintBusy(busy) } + fun setSelectedTarget(utBotTarget: UTBotTarget) { + uiList.setSelectedValue(utBotTarget, true) + } + private fun createActionToolBar(isHorizontal: Boolean = false): ActionToolbar { return ActionManager.getInstance().createActionToolbar(ActionPlaces.TOOLBAR, createActionGroup(), isHorizontal) } diff --git a/clion-plugin/src/main/kotlin/org/utbot/cpp/clion/plugin/ui/testsResults/UTBotTestRunLineMarkerProvider.kt b/clion-plugin/src/main/kotlin/org/utbot/cpp/clion/plugin/ui/testsResults/UTBotTestRunLineMarkerProvider.kt index 4b4543570..cf9326f47 100644 --- a/clion-plugin/src/main/kotlin/org/utbot/cpp/clion/plugin/ui/testsResults/UTBotTestRunLineMarkerProvider.kt +++ b/clion-plugin/src/main/kotlin/org/utbot/cpp/clion/plugin/ui/testsResults/UTBotTestRunLineMarkerProvider.kt @@ -16,15 +16,19 @@ import testsgen.Testgen class UTBotTestRunLineMarkerProvider : LineMarkerProvider { val log = Logger.getInstance(this::class.java) - override fun getLineMarkerInfo(element: PsiElement): LineMarkerInfo<*>? { - return UTBotRunWithCoverageLineMarkerInfo.createFromPsiElementOrNull(element) - } + override fun getLineMarkerInfo(element: PsiElement): LineMarkerInfo<*>? = + UTBotRunWithCoverageLineMarkerInfo.createFromPsiElementOrNull(element) + + private class UTBotRunWithCoverageLineMarkerInfo + private constructor(callElement: PsiElement, message: String, icon: Icon) : LineMarkerInfo( + callElement, + callElement.textRange, + icon, + { message }, + null, + GutterIconRenderer.Alignment.LEFT, + { message }) { - private class UTBotRunWithCoverageLineMarkerInfo private constructor( - callElement: PsiElement, - message: String, - icon: Icon, - ) : LineMarkerInfo(callElement, callElement.textRange, icon, { message }, null, GutterIconRenderer.Alignment.LEFT, { message }) { override fun createGutterRenderer(): GutterIconRenderer { return object : LineMarkerGutterIconRenderer(this) { override fun isNavigateAction(): Boolean = true @@ -32,25 +36,31 @@ class UTBotTestRunLineMarkerProvider : LineMarkerProvider { } } - companion object { fun createFromPsiElementOrNull(element: PsiElement): UTBotRunWithCoverageLineMarkerInfo? { - if (element.firstChild != null || (!isSingleTest(element) && !isAllTests(element)) || element.containingFile.name.endsWith(".h")) { + val elementRequiresIcon = element.canPlaceSingleTestIcon() || element.canPlaceAllTestsIcon() + if (element.firstChild != null + || !elementRequiresIcon + || element.containingFile.name.endsWith(".h") + ) { return null } - val message = if (isSingleTest(element)) "UTBot: Run with coverage" else "Run all tests with coverage" + val message = + if (element.canPlaceSingleTestIcon()) "UTBot: Run with coverage" else "Run all tests with coverage" return UTBotRunWithCoverageLineMarkerInfo(element, message, getStatusIcon(element)) } private fun getStatusIcon(element: PsiElement): Icon { // return icon for Running All Tests - if (!isAllTests(element)) { + if (element.canPlaceAllTestsIcon()) { return AllIcons.RunConfigurations.TestState.Run_run } + //TODO: it is a little strange to create smth just for a testName val testName: String = TestNameAndTestSuite.create(element).name - val testResult: Testgen.TestResultObject? = element.project.service().getTestResultByTestName(testName) + val testResult: Testgen.TestResultObject? = + element.project.service().getTestResultByTestName(testName) // if there's no info about TestResult with the specified name, return icon for running single test if (testResult == null || testName.isEmpty()) { @@ -61,7 +71,9 @@ class UTBotTestRunLineMarkerProvider : LineMarkerProvider { return when (testResult.status) { Testgen.TestStatus.TEST_FAILED -> AllIcons.RunConfigurations.TestState.Red2 Testgen.TestStatus.TEST_PASSED -> AllIcons.RunConfigurations.TestState.Green2 - else -> AllIcons.RunConfigurations.TestError + Testgen.TestStatus.TEST_DEATH, + Testgen.TestStatus.TEST_INTERRUPTED, + Testgen.TestStatus.UNRECOGNIZED -> AllIcons.RunConfigurations.TestError } } } @@ -70,7 +82,8 @@ class UTBotTestRunLineMarkerProvider : LineMarkerProvider { companion object { private const val RUN_SINGLE_TEST_TEXT_MARK = "TEST" private const val RUN_ALL_TESTS_TEXT_MARK = "UTBot" - private fun isSingleTest(element: PsiElement) = element.text == RUN_SINGLE_TEST_TEXT_MARK - private fun isAllTests(element: PsiElement) = element.text == RUN_ALL_TESTS_TEXT_MARK + + private fun PsiElement.canPlaceSingleTestIcon() = this.text == RUN_SINGLE_TEST_TEXT_MARK + private fun PsiElement.canPlaceAllTestsIcon() = this.text == RUN_ALL_TESTS_TEXT_MARK } } diff --git a/clion-plugin/src/main/kotlin/org/utbot/cpp/clion/plugin/ui/wizard/UTBotWizard.kt b/clion-plugin/src/main/kotlin/org/utbot/cpp/clion/plugin/ui/wizard/UTBotWizard.kt index 10e9a71e5..74a8f20df 100644 --- a/clion-plugin/src/main/kotlin/org/utbot/cpp/clion/plugin/ui/wizard/UTBotWizard.kt +++ b/clion-plugin/src/main/kotlin/org/utbot/cpp/clion/plugin/ui/wizard/UTBotWizard.kt @@ -3,6 +3,7 @@ package org.utbot.cpp.clion.plugin.ui.wizard import com.intellij.ide.BrowserUtil import com.intellij.ide.wizard.AbstractWizard import com.intellij.openapi.project.Project +import javax.swing.JButton import org.utbot.cpp.clion.plugin.UTBot import org.utbot.cpp.clion.plugin.settings.UTBotSettingsModel import org.utbot.cpp.clion.plugin.settings.projectIndependentSettings @@ -24,6 +25,7 @@ class UTBotWizard(private val project: Project) : AbstractWizardLearn more deployment.remotePath.description=Remote path configuration specifies the path to the project on a remote host. Empty value specifies local scenario (for Linux). Learn more paths.buildDirectory.description=Relative path to build directory with compile_commands.json and/or coverage.json. Learn more -paths.target.description=Path to target which is passed to UTBot +paths.target.description=Path to target which is passed to UTBot. You can select targets in UTBot targets tool window paths.cmakeOptions.description=Options passed to CMake command. Learn more -paths.testsDirectory.description=Relative path to directory in which tests will be generated. Learn more +paths.testsDir.description=Relative path to directory in which tests will be generated. Learn more paths.sourceDirectories.description=Mark/unmark directory as source by double-clicking or using actions from context menu. You can also unmark or mark directories in UTBot project view pane. Learn more testsGeneration.verboseFormatting.description=If set to true, tests will be formatted in more detailed form. Learn more testsGeneration.verboseFormatting.title=Use verbose mode diff --git a/clion-plugin/src/test/kotlin/org/utbot/cpp/clion/plugin/BaseGenerationTestCase.kt b/clion-plugin/src/test/kotlin/org/utbot/cpp/clion/plugin/BaseGenerationTestCase.kt index 8f78d765b..f6cc28943 100644 --- a/clion-plugin/src/test/kotlin/org/utbot/cpp/clion/plugin/BaseGenerationTestCase.kt +++ b/clion-plugin/src/test/kotlin/org/utbot/cpp/clion/plugin/BaseGenerationTestCase.kt @@ -46,10 +46,6 @@ abstract class BaseGenerationTestCase { override fun deleteOnTearDown() = false } - init { - Client.IS_TEST_MODE = true - } - val projectPath: Path = Paths.get(File(".").canonicalPath).resolve("../integration-tests/c-example-mini").normalize() val testsDirectoryPath: Path = projectPath.resolve("cl-plugin-test-tests") @@ -64,7 +60,7 @@ abstract class BaseGenerationTestCase { init { project.settings.storedSettings.buildDirRelativePath = buildDirName - project.settings.storedSettings.testDirPath = testsDirectoryPath.toString() + project.settings.storedSettings.testsDirRelativePath = projectPath.relativize(testsDirectoryPath).toString() project.logger.logWriters.let { it.clear() it.add(SystemWriter()) @@ -95,7 +91,7 @@ abstract class BaseGenerationTestCase { targetsController.requestTargetsFromServer() waitForRequestsToFinish() PlatformTestUtil.dispatchAllInvocationEventsInIdeEventQueue() - targetsController.setTargetByName(targetName) + targetsController.setTargetPathByName(targetName) } /**