From 20330bec18165ddf3c4d04bb8c0137f4e0d15802 Mon Sep 17 00:00:00 2001 From: rockofox Date: Sun, 14 Apr 2024 19:07:27 +0200 Subject: [PATCH] Testing/HSpec support --- build.gradle.kts | 2 +- .../runconfiguration/CabalRunConfiguration.kt | 32 +++++--- .../CabalTestConsoleProperties.kt | 24 ++++++ .../runconfiguration/CabalTestLocator.kt | 16 ++++ ...tTestOutputToGeneralTestEventsConverter.kt | 76 +++++++++++++++++++ 5 files changed, 140 insertions(+), 10 deletions(-) create mode 100644 src/main/kotlin/boo/fox/haskelllsp/runconfiguration/CabalTestConsoleProperties.kt create mode 100644 src/main/kotlin/boo/fox/haskelllsp/runconfiguration/CabalTestLocator.kt create mode 100644 src/main/kotlin/boo/fox/haskelllsp/runconfiguration/CabalTestTestOutputToGeneralTestEventsConverter.kt diff --git a/build.gradle.kts b/build.gradle.kts index 85a8d3f..842cd68 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -5,7 +5,7 @@ plugins { } group = "boo.fox" -version = "1.0-SNAPSHOT" +version = "1.1.0" repositories { mavenCentral() diff --git a/src/main/kotlin/boo/fox/haskelllsp/runconfiguration/CabalRunConfiguration.kt b/src/main/kotlin/boo/fox/haskelllsp/runconfiguration/CabalRunConfiguration.kt index 5d38ab0..55c5e3e 100644 --- a/src/main/kotlin/boo/fox/haskelllsp/runconfiguration/CabalRunConfiguration.kt +++ b/src/main/kotlin/boo/fox/haskelllsp/runconfiguration/CabalRunConfiguration.kt @@ -1,20 +1,22 @@ package boo.fox.haskelllsp.runconfiguration +import com.intellij.execution.DefaultExecutionResult import com.intellij.execution.ExecutionException +import com.intellij.execution.ExecutionResult import com.intellij.execution.Executor import com.intellij.execution.configurations.* import com.intellij.execution.process.ProcessHandler import com.intellij.execution.process.ProcessHandlerFactory import com.intellij.execution.process.ProcessTerminatedListener import com.intellij.execution.runners.ExecutionEnvironment +import com.intellij.execution.runners.ProgramRunner +import com.intellij.execution.testframework.sm.SMTestRunnerConnectionUtil import com.intellij.openapi.options.SettingsEditor import com.intellij.openapi.project.Project import java.io.File class CabalRunConfiguration( - project: Project?, - factory: ConfigurationFactory?, - name: String? + project: Project?, factory: ConfigurationFactory?, name: String? ) : RunConfigurationBase(project!!, factory, name) { override fun getOptions(): CabalRunConfigurationOptions { return super.getOptions() as CabalRunConfigurationOptions @@ -31,20 +33,32 @@ class CabalRunConfiguration( } override fun getState( - executor: Executor, - environment: ExecutionEnvironment + executor: Executor, environment: ExecutionEnvironment ): RunProfileState { return object : CommandLineState(environment) { @Throws(ExecutionException::class) override fun startProcess(): ProcessHandler { - val commandLine = - GeneralCommandLine("cabal", options.command!!) + val commandLine = GeneralCommandLine("cabal", options.command!!, "--test-show-details=always") commandLine.workDirectory = File(project.basePath!!) - val processHandler = ProcessHandlerFactory.getInstance() - .createColoredProcessHandler(commandLine) + val processHandler = ProcessHandlerFactory.getInstance().createColoredProcessHandler(commandLine) ProcessTerminatedListener.attach(processHandler) return processHandler } + + override fun execute(executor: Executor, runner: ProgramRunner<*>): ExecutionResult { + val processHandler = startProcess() + + val consoleView = if (options.command == "test") { + val testConsoleProperties = CabalTestConsoleProperties(this@CabalRunConfiguration, executor) + SMTestRunnerConnectionUtil.createAndAttachConsole("Cabal", processHandler, testConsoleProperties) + } else { + createConsole(executor).also { console -> + console!!.attachToProcess(processHandler) + } + } + + return DefaultExecutionResult(consoleView, processHandler) + } } } } diff --git a/src/main/kotlin/boo/fox/haskelllsp/runconfiguration/CabalTestConsoleProperties.kt b/src/main/kotlin/boo/fox/haskelllsp/runconfiguration/CabalTestConsoleProperties.kt new file mode 100644 index 0000000..612805f --- /dev/null +++ b/src/main/kotlin/boo/fox/haskelllsp/runconfiguration/CabalTestConsoleProperties.kt @@ -0,0 +1,24 @@ +package boo.fox.haskelllsp.runconfiguration + +import com.intellij.execution.Executor +import com.intellij.execution.configurations.RunConfiguration +import com.intellij.execution.testframework.TestConsoleProperties +import com.intellij.execution.testframework.sm.SMCustomMessagesParsing +import com.intellij.execution.testframework.sm.runner.OutputToGeneralTestEventsConverter +import com.intellij.execution.testframework.sm.runner.SMTRunnerConsoleProperties +import com.intellij.execution.testframework.sm.runner.SMTestLocator + +class CabalTestConsoleProperties(config: RunConfiguration, executor: Executor) : SMTRunnerConsoleProperties( + config, + "Cabal", + executor +), SMCustomMessagesParsing { + override fun getTestFrameworkName(): String = "Cabal" + override fun getTestLocator(): SMTestLocator = CabalTestLocator() + override fun createTestEventsConverter( + testFrameworkName: String, + consoleProperties: TestConsoleProperties + ): OutputToGeneralTestEventsConverter { + return CabalTestOutputToGeneralTestEventsConverter(testFrameworkName, consoleProperties) + } +} \ No newline at end of file diff --git a/src/main/kotlin/boo/fox/haskelllsp/runconfiguration/CabalTestLocator.kt b/src/main/kotlin/boo/fox/haskelllsp/runconfiguration/CabalTestLocator.kt new file mode 100644 index 0000000..66be462 --- /dev/null +++ b/src/main/kotlin/boo/fox/haskelllsp/runconfiguration/CabalTestLocator.kt @@ -0,0 +1,16 @@ +package boo.fox.haskelllsp.runconfiguration + +import com.intellij.execution.Location +import com.intellij.execution.testframework.sm.runner.SMTestLocator +import com.intellij.openapi.project.Project +import com.intellij.psi.PsiElement +import com.intellij.psi.search.GlobalSearchScope + +class CabalTestLocator: SMTestLocator{ + override fun getLocation( + protocol: String, + path: String, + project: Project, + scope: GlobalSearchScope + ): MutableList> = mutableListOf() +} \ No newline at end of file diff --git a/src/main/kotlin/boo/fox/haskelllsp/runconfiguration/CabalTestTestOutputToGeneralTestEventsConverter.kt b/src/main/kotlin/boo/fox/haskelllsp/runconfiguration/CabalTestTestOutputToGeneralTestEventsConverter.kt new file mode 100644 index 0000000..b677fbf --- /dev/null +++ b/src/main/kotlin/boo/fox/haskelllsp/runconfiguration/CabalTestTestOutputToGeneralTestEventsConverter.kt @@ -0,0 +1,76 @@ +package boo.fox.haskelllsp.runconfiguration + +import com.intellij.execution.process.ProcessOutputType +import com.intellij.execution.testframework.TestConsoleProperties +import com.intellij.execution.testframework.sm.runner.OutputToGeneralTestEventsConverter +import com.intellij.openapi.diagnostic.Logger +import com.intellij.openapi.util.Key +import jetbrains.buildServer.messages.serviceMessages.* + +class CabalTestOutputToGeneralTestEventsConverter(testFrameworkName: String, consoleProperties: TestConsoleProperties) : + OutputToGeneralTestEventsConverter(testFrameworkName, consoleProperties) { + private val log: Logger = Logger.getInstance(CabalTestOutputToGeneralTestEventsConverter::class.java) + private var previousTestSuite: String? = null + private var testSuiteRunning = false + private var expectingNewline = false + private var unfinishedTestSuites = mutableListOf() + + override fun processServiceMessages(text: String, outputType: Key<*>, visitor: ServiceMessageVisitor): Boolean { + if(outputType == ProcessOutputType.STDERR) { + return super.processServiceMessages(text, outputType, visitor) + } + Regex("""^\s+$""").find(text)?.let { matches -> + if(expectingNewline) { + expectingNewline = false + testSuiteRunning = true + } else { + testSuiteRunning = false + } + if(unfinishedTestSuites.isNotEmpty()) { + log.info("Unfinished test suites: $unfinishedTestSuites") + unfinishedTestSuites.forEach { + visitor.visitTestSuiteFinished(TestSuiteFinished(it)) + } + return super.processServiceMessages(text, outputType, visitor) + } else { + log.info("No unfinished test suites") + } + return super.processServiceMessages(text, outputType, visitor) + } + Regex("""Test suite (.+): RUNNING\.\.\.""").find(text)?.let { matches -> + testSuiteRunning = true + expectingNewline = true + visitor.visitTestSuiteStarted(TestSuiteStarted(matches.groupValues[1])) + return super.processServiceMessages(text, outputType, visitor) + } + Regex("""Test suite (.+): PASS""").find(text)?.let { matches -> + testSuiteRunning = false + expectingNewline = false + visitor.visitTestSuiteFinished(TestSuiteFinished(matches.groupValues[1])) + + return super.processServiceMessages(text, outputType, visitor) + } + Regex("""( )(.+) \[([✘✔])]""").find(text)?.let { matches -> + visitor.visitTestStarted(TestStarted(matches.groupValues[2], true, null)) + if (matches.groupValues[3] == "✘") { + visitor.visitTestFailed(TestFailed(matches.groupValues[2], "Test failed")) + } + visitor.visitTestFinished(TestFinished(matches.groupValues[2], 1)) + return super.processServiceMessages(text, outputType, visitor) + } + if (testSuiteRunning) { + Regex("""( +)?([\w-]+)""").find(text)?.let { matches -> + if (previousTestSuite != null) { + visitor.visitTestSuiteFinished(TestSuiteFinished(previousTestSuite!!)) + unfinishedTestSuites.remove(previousTestSuite!!) + } + visitor.visitTestSuiteStarted(TestSuiteStarted(matches.groupValues[2])) + unfinishedTestSuites.add(matches.groupValues[2]) + previousTestSuite = matches.groupValues[2] + } + return super.processServiceMessages(text, outputType, visitor) + } + + return super.processServiceMessages(text, outputType, visitor) + } +} \ No newline at end of file