Skip to content

Commit

Permalink
Testing/HSpec support
Browse files Browse the repository at this point in the history
  • Loading branch information
rockofox committed Apr 14, 2024
1 parent 378d4d7 commit 20330be
Show file tree
Hide file tree
Showing 5 changed files with 140 additions and 10 deletions.
2 changes: 1 addition & 1 deletion build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ plugins {
}

group = "boo.fox"
version = "1.0-SNAPSHOT"
version = "1.1.0"

repositories {
mavenCentral()
Expand Down
Original file line number Diff line number Diff line change
@@ -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<CabalRunConfigurationOptions?>(project!!, factory, name) {
override fun getOptions(): CabalRunConfigurationOptions {
return super.getOptions() as CabalRunConfigurationOptions
Expand All @@ -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)
}
}
}
}
Original file line number Diff line number Diff line change
@@ -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)
}
}
Original file line number Diff line number Diff line change
@@ -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<Location<PsiElement>> = mutableListOf()
}
Original file line number Diff line number Diff line change
@@ -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<String>()

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)
}
}

0 comments on commit 20330be

Please sign in to comment.