From 2a49be74a88cafd36d3e4de8539dcdea189527ec Mon Sep 17 00:00:00 2001 From: hasanalpzengin Date: Fri, 18 Apr 2025 15:31:58 +0200 Subject: [PATCH 1/3] feat(pycharm): configuration page additions - environment field - variables - additional arguments - suite path Signed-off-by: hasanalpzengin --- .../execution/RobotCodeRunConfiguration.kt | 41 +++++++++---- .../RobotCodeRunConfigurationEditor.kt | 59 ++++++++++++++++--- .../execution/RobotCodeRunProfileState.kt | 54 ++++++++++++----- 3 files changed, 119 insertions(+), 35 deletions(-) diff --git a/intellij-client/src/main/kotlin/dev/robotcode/robotcode4ij/execution/RobotCodeRunConfiguration.kt b/intellij-client/src/main/kotlin/dev/robotcode/robotcode4ij/execution/RobotCodeRunConfiguration.kt index 208e86fe..54dc080a 100644 --- a/intellij-client/src/main/kotlin/dev/robotcode/robotcode4ij/execution/RobotCodeRunConfiguration.kt +++ b/intellij-client/src/main/kotlin/dev/robotcode/robotcode4ij/execution/RobotCodeRunConfiguration.kt @@ -1,9 +1,9 @@ package dev.robotcode.robotcode4ij.execution import com.intellij.execution.Executor +import com.intellij.execution.configuration.EnvironmentVariablesData import com.intellij.execution.configurations.ConfigurationFactory import com.intellij.execution.configurations.LocatableConfigurationBase -import com.intellij.execution.configurations.RunConfiguration import com.intellij.execution.configurations.RunProfileState import com.intellij.execution.runners.ExecutionEnvironment import com.intellij.execution.testframework.sm.runner.SMRunnerConsolePropertiesProvider @@ -17,32 +17,47 @@ class RobotCodeRunConfiguration(project: Project, factory: ConfigurationFactory) LocatableConfigurationBase (project, factory, "Robot Framework"), SMRunnerConsolePropertiesProvider { - override fun getState(executor: Executor, environment: ExecutionEnvironment): RunProfileState { - return RobotCodeRunProfileState(this, environment) - } + // Environment variables + var environmentVariables: EnvironmentVariablesData = EnvironmentVariablesData.DEFAULT - override fun getConfigurationEditor(): SettingsEditor { - // TODO: Implement configuration editor - return RobotCodeRunConfigurationEditor() - } + // Variables + var variables: String? = null + + // Test suite path + var testSuitePath: String? = null + + // Additional arguments + var additionalArguments: String? = null var includedTestItems: List = emptyList() - var paths: List = emptyList() + override fun getState(executor: Executor, environment: ExecutionEnvironment): RunProfileState { + return RobotCodeRunProfileState(this, environment) + } override fun createTestConsoleProperties(executor: Executor): SMTRunnerConsoleProperties { return RobotRunnerConsoleProperties(this, "Robot Framework", executor) } + override fun getConfigurationEditor(): SettingsEditor { + return RobotCodeRunConfigurationEditor(project) + } + override fun writeExternal(element: Element) { super.writeExternal(element) - // TODO: Implement serialization - // XmlSerializer.serializeInto(this, element) + // Save data to XML + environmentVariables.writeExternal(element) + element.setAttribute("variables", variables ?: "") + element.setAttribute("testSuitePath", testSuitePath ?: "") + element.setAttribute("additionalArguments", additionalArguments ?: "") } override fun readExternal(element: Element) { super.readExternal(element) - // TODO: Implement deserialization - // XmlSerializer.deserializeInto(this, element) + // Read data from XML + environmentVariables = EnvironmentVariablesData.readExternal(element) + variables = element.getAttributeValue("variables") + testSuitePath = element.getAttributeValue("testSuitePath") + additionalArguments = element.getAttributeValue("additionalArguments") } } diff --git a/intellij-client/src/main/kotlin/dev/robotcode/robotcode4ij/execution/RobotCodeRunConfigurationEditor.kt b/intellij-client/src/main/kotlin/dev/robotcode/robotcode4ij/execution/RobotCodeRunConfigurationEditor.kt index 5fe189ad..312c212b 100644 --- a/intellij-client/src/main/kotlin/dev/robotcode/robotcode4ij/execution/RobotCodeRunConfigurationEditor.kt +++ b/intellij-client/src/main/kotlin/dev/robotcode/robotcode4ij/execution/RobotCodeRunConfigurationEditor.kt @@ -1,17 +1,36 @@ package dev.robotcode.robotcode4ij.execution import com.intellij.execution.configuration.EnvironmentVariablesComponent +import com.intellij.openapi.fileChooser.FileChooserDescriptorFactory +import com.intellij.openapi.ui.TextFieldWithBrowseButton import com.intellij.openapi.options.SettingsEditor +import com.intellij.openapi.project.Project import com.intellij.ui.RawCommandLineEditor import com.intellij.ui.dsl.builder.AlignX import com.intellij.ui.dsl.builder.panel import com.intellij.util.ui.ComponentWithEmptyText import javax.swing.JComponent -class RobotCodeRunConfigurationEditor : SettingsEditor() { +class RobotCodeRunConfigurationEditor(private val project: Project) : SettingsEditor() { private val environmentVariablesField = EnvironmentVariablesComponent() + private val variablesField = + RawCommandLineEditor().apply { + if (textField is ComponentWithEmptyText) { + (textField as ComponentWithEmptyText).emptyText.text = "Define variables, e.g. VAR1=value1, VAR2=value2" + } + } + + private val testSuitePathField = TextFieldWithBrowseButton().apply { + addBrowseFolderListener( + project, + FileChooserDescriptorFactory.createSingleFileDescriptor() + .withTitle("Select Test Suite") + .withDescription("Select the path to the test suite") + ) + } + private val argumentsField = RawCommandLineEditor().apply { if (textField is ComponentWithEmptyText) { @@ -21,23 +40,47 @@ class RobotCodeRunConfigurationEditor : SettingsEditor() - val port = findFreePort(DEBUGGER_DEFAULT_PORT) if (port != DEBUGGER_DEFAULT_PORT) { included.add("--tcp") included.add(port.toString()) } + // Combine all parts into the final command line val commandLine = project.buildRobotCodeCommandLine( arrayOf( *defaultPaths, "debug", *connection.toTypedArray(), *(if (!debug) arrayOf("--no-debug") else arrayOf()), - *(included.toTypedArray()) - ), noColor = false // ,extraArgs = arrayOf("-v", "--log", "--log-level", "TRACE") + *included.toTypedArray(), + *variables, + *additionalArguments + ), + noColor = false + ) + // Apply environment variables + val environmentVariables = profile.environmentVariables.envs + for ((key, value) in environmentVariables) { + commandLine.environment[key] = value + } + + // Apply "passParentEnvironment" if set to false + commandLine.withParentEnvironmentType( + if (profile.environmentVariables.isPassParentEnvs) { + GeneralCommandLine.ParentEnvironmentType.CONSOLE + } else { + GeneralCommandLine.ParentEnvironmentType.NONE + } ) - val handler = KillableColoredProcessHandler(commandLine) // handler.setHasPty(true) + val handler = KillableColoredProcessHandler(commandLine) handler.putUserData(DEBUG_PORT, port) ProcessTerminatedListener.attach(handler) handler.addProcessListener(this) - // RunContentManager.getInstance(project).showRunContent(environment.executor, handler) - return handler } @@ -142,8 +168,8 @@ class RobotCodeRunProfileState(private val config: RobotCodeRunConfiguration, en consoleProperties.state = this } - var splitterPropertyName = SMTestRunnerConnectionUtil.getSplitterPropertyName(TESTFRAMEWORK_NAME) - var consoleView = RobotCodeRunnerConsoleView(consoleProperties, splitterPropertyName) + val splitterPropertyName = SMTestRunnerConnectionUtil.getSplitterPropertyName(TESTFRAMEWORK_NAME) + val consoleView = RobotCodeRunnerConsoleView(consoleProperties, splitterPropertyName) SMTestRunnerConnectionUtil.initConsoleView(consoleView, TESTFRAMEWORK_NAME) consoleView.attachToProcess(processHandler) consoleRef.set(consoleView) @@ -192,7 +218,7 @@ class RobotCodeRunProfileState(private val config: RobotCodeRunConfiguration, en @OptIn(ExperimentalUuidApi::class) override fun startNotified(event: ProcessEvent) { runBlocking(Dispatchers.IO) { - var port = event.processHandler.getUserData(DEBUG_PORT) ?: throw CantRunException("No debug port found.") + val port = event.processHandler.getUserData(DEBUG_PORT) ?: throw CantRunException("No debug port found.") socket = tryConnectToServerWithTimeout("127.0.0.1", port, 10000, retryIntervalMillis = 100) ?: throw CantRunException("Unable to establish connection to debug server.") @@ -232,7 +258,7 @@ class RobotCodeRunProfileState(private val config: RobotCodeRunConfiguration, en } afterConfigurationDone.fire(Unit) - debugServer.attach(emptyMap()) + debugServer.attach(emptyMap()) } } From 8657c448097e89f39f678fb2fd4cf104c9326885 Mon Sep 17 00:00:00 2001 From: hasanalpzengin Date: Fri, 18 Apr 2025 16:30:22 +0200 Subject: [PATCH 2/3] feat(pycharm): make testitems configuration visible and functional Signed-off-by: hasanalpzengin --- .../execution/RobotCodeRunConfiguration.kt | 4 +++- .../execution/RobotCodeRunConfigurationEditor.kt | 14 ++++++++++++++ .../execution/RobotCodeRunConfigurationProducer.kt | 6 +++--- .../execution/RobotCodeRunProfileState.kt | 9 ++++++--- 4 files changed, 26 insertions(+), 7 deletions(-) diff --git a/intellij-client/src/main/kotlin/dev/robotcode/robotcode4ij/execution/RobotCodeRunConfiguration.kt b/intellij-client/src/main/kotlin/dev/robotcode/robotcode4ij/execution/RobotCodeRunConfiguration.kt index 54dc080a..936033e1 100644 --- a/intellij-client/src/main/kotlin/dev/robotcode/robotcode4ij/execution/RobotCodeRunConfiguration.kt +++ b/intellij-client/src/main/kotlin/dev/robotcode/robotcode4ij/execution/RobotCodeRunConfiguration.kt @@ -29,7 +29,7 @@ class RobotCodeRunConfiguration(project: Project, factory: ConfigurationFactory) // Additional arguments var additionalArguments: String? = null - var includedTestItems: List = emptyList() + var includedTestItems: String? = null override fun getState(executor: Executor, environment: ExecutionEnvironment): RunProfileState { return RobotCodeRunProfileState(this, environment) @@ -47,6 +47,7 @@ class RobotCodeRunConfiguration(project: Project, factory: ConfigurationFactory) super.writeExternal(element) // Save data to XML environmentVariables.writeExternal(element) + element.setAttribute("testitems", includedTestItems ?: "") element.setAttribute("variables", variables ?: "") element.setAttribute("testSuitePath", testSuitePath ?: "") element.setAttribute("additionalArguments", additionalArguments ?: "") @@ -59,5 +60,6 @@ class RobotCodeRunConfiguration(project: Project, factory: ConfigurationFactory) variables = element.getAttributeValue("variables") testSuitePath = element.getAttributeValue("testSuitePath") additionalArguments = element.getAttributeValue("additionalArguments") + includedTestItems = element.getAttributeValue("testitems") } } diff --git a/intellij-client/src/main/kotlin/dev/robotcode/robotcode4ij/execution/RobotCodeRunConfigurationEditor.kt b/intellij-client/src/main/kotlin/dev/robotcode/robotcode4ij/execution/RobotCodeRunConfigurationEditor.kt index 312c212b..ae07a9f4 100644 --- a/intellij-client/src/main/kotlin/dev/robotcode/robotcode4ij/execution/RobotCodeRunConfigurationEditor.kt +++ b/intellij-client/src/main/kotlin/dev/robotcode/robotcode4ij/execution/RobotCodeRunConfigurationEditor.kt @@ -10,6 +10,7 @@ import com.intellij.ui.dsl.builder.AlignX import com.intellij.ui.dsl.builder.panel import com.intellij.util.ui.ComponentWithEmptyText import javax.swing.JComponent +import javax.swing.JTextField class RobotCodeRunConfigurationEditor(private val project: Project) : SettingsEditor() { @@ -22,6 +23,10 @@ class RobotCodeRunConfigurationEditor(private val project: Project) : SettingsEd } } + private val includedTestItemsField = JTextField().apply { + toolTipText = "Enter testitems separated by commas, e.g., MyProject.tests.testsuite1.testcase1,MyProject.tests.testsuite1.testcase2" + } + private val testSuitePathField = TextFieldWithBrowseButton().apply { addBrowseFolderListener( project, @@ -51,6 +56,9 @@ class RobotCodeRunConfigurationEditor(private val project: Project) : SettingsEd // Reset the additional arguments field argumentsField.text = s.additionalArguments ?: "" + + // Reset the included test cases field + includedTestItemsField.text = s.includedTestItems } override fun applyEditorTo(s: RobotCodeRunConfiguration) { @@ -65,6 +73,9 @@ class RobotCodeRunConfigurationEditor(private val project: Project) : SettingsEd // Apply the additional arguments field s.additionalArguments = argumentsField.text.ifBlank { null } + + // Apply the included test items field + s.includedTestItems = includedTestItemsField.text } override fun createEditor(): JComponent { @@ -72,6 +83,9 @@ class RobotCodeRunConfigurationEditor(private val project: Project) : SettingsEd row("Test Suite Path:") { cell(testSuitePathField).align(AlignX.FILL) } + row("Included TestItems:") { + cell(includedTestItemsField).align(AlignX.FILL) + } row("Environment Variables:") { cell(environmentVariablesField.component).align(AlignX.FILL) } diff --git a/intellij-client/src/main/kotlin/dev/robotcode/robotcode4ij/execution/RobotCodeRunConfigurationProducer.kt b/intellij-client/src/main/kotlin/dev/robotcode/robotcode4ij/execution/RobotCodeRunConfigurationProducer.kt index 6b00e560..8714aa5a 100644 --- a/intellij-client/src/main/kotlin/dev/robotcode/robotcode4ij/execution/RobotCodeRunConfigurationProducer.kt +++ b/intellij-client/src/main/kotlin/dev/robotcode/robotcode4ij/execution/RobotCodeRunConfigurationProducer.kt @@ -40,7 +40,7 @@ class RobotCodeRunConfigurationProducer : LazyRunConfigurationProducer() - for (test in profile.includedTestItems) { - included.add("--by-longname") - included.add(test.longname) + if (!profile.includedTestItems.isNullOrEmpty()) { + val includedTestItems = profile.includedTestItems!!.split(",").map { it.trim() }.filter { it.isNotEmpty() } + for (testitem in includedTestItems) { + included.add("--by-longname") + included.add(testitem) + } } val connection = mutableListOf() From 12db07725271eb023cf1a79b47849f65a2068ab5 Mon Sep 17 00:00:00 2001 From: hasanalpzengin Date: Fri, 18 Apr 2025 17:36:53 +0200 Subject: [PATCH 3/3] chore(pycharm): seperate defaultPaths from testsuites Signed-off-by: hasanalpzengin --- .../RobotCodeRunConfigurationEditor.kt | 2 +- .../execution/RobotCodeRunProfileState.kt | 20 +++++++++++++++---- 2 files changed, 17 insertions(+), 5 deletions(-) diff --git a/intellij-client/src/main/kotlin/dev/robotcode/robotcode4ij/execution/RobotCodeRunConfigurationEditor.kt b/intellij-client/src/main/kotlin/dev/robotcode/robotcode4ij/execution/RobotCodeRunConfigurationEditor.kt index ae07a9f4..088c31a0 100644 --- a/intellij-client/src/main/kotlin/dev/robotcode/robotcode4ij/execution/RobotCodeRunConfigurationEditor.kt +++ b/intellij-client/src/main/kotlin/dev/robotcode/robotcode4ij/execution/RobotCodeRunConfigurationEditor.kt @@ -83,7 +83,7 @@ class RobotCodeRunConfigurationEditor(private val project: Project) : SettingsEd row("Test Suite Path:") { cell(testSuitePathField).align(AlignX.FILL) } - row("Included TestItems:") { + row("Included Test Items:") { cell(includedTestItemsField).align(AlignX.FILL) } row("Environment Variables:") { diff --git a/intellij-client/src/main/kotlin/dev/robotcode/robotcode4ij/execution/RobotCodeRunProfileState.kt b/intellij-client/src/main/kotlin/dev/robotcode/robotcode4ij/execution/RobotCodeRunProfileState.kt index 027ce441..523c7827 100644 --- a/intellij-client/src/main/kotlin/dev/robotcode/robotcode4ij/execution/RobotCodeRunProfileState.kt +++ b/intellij-client/src/main/kotlin/dev/robotcode/robotcode4ij/execution/RobotCodeRunProfileState.kt @@ -26,6 +26,7 @@ import com.jetbrains.rd.util.reactive.adviseEternal import dev.robotcode.robotcode4ij.buildRobotCodeCommandLine import dev.robotcode.robotcode4ij.debugging.RobotCodeDebugProgramRunner import dev.robotcode.robotcode4ij.debugging.RobotCodeDebugProtocolClient +import dev.robotcode.robotcode4ij.testing.testManger import dev.robotcode.robotcode4ij.utils.NetUtils.findFreePort import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.TimeoutCancellationException @@ -41,6 +42,7 @@ import org.eclipse.lsp4j.debug.services.IDebugProtocolServer import org.eclipse.lsp4j.jsonrpc.Launcher import java.net.Socket import java.net.SocketTimeoutException +import kotlin.io.path.Path import kotlin.uuid.ExperimentalUuidApi import kotlin.uuid.Uuid @@ -80,7 +82,9 @@ class RobotCodeRunProfileState(private val config: RobotCodeRunConfiguration, en // Determine default paths val testSuitePath = profile.testSuitePath - val defaultPaths = arrayOf("--default-path", if (!testSuitePath.isNullOrEmpty()) testSuitePath else ".") + + // TODO: Add support for configurable paths + val defaultPaths = arrayOf("--default-path", ".") // Prepare variables as command line arguments val variables = profile.variables?.split(",")?.mapNotNull { @@ -94,11 +98,19 @@ class RobotCodeRunProfileState(private val config: RobotCodeRunConfiguration, en val debug = environment.runner is RobotCodeDebugProgramRunner val included = mutableListOf() + + if (testSuitePath != null){ + val testitem = profile.project.testManger.findTestItem(Path(testSuitePath).toUri().toString()) + if (testitem != null) { + included.add("--by-longname") + included.add(testitem.longname) + } + } if (!profile.includedTestItems.isNullOrEmpty()) { - val includedTestItems = profile.includedTestItems!!.split(",").map { it.trim() }.filter { it.isNotEmpty() } - for (testitem in includedTestItems) { + val testItemLongNames = profile.includedTestItems!!.split(",").map { it.trim() }.filter { it.isNotEmpty() } + for (testItemLongName in testItemLongNames){ included.add("--by-longname") - included.add(testitem) + included.add(testItemLongName) } }