Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

wip for new IDE test runner #276

Draft
wants to merge 1 commit into
base: master
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -223,6 +223,7 @@ allprojects {
task {
systemProperty("org.move.debug.enabled", true)
systemProperty("org.move.types.highlight.unknown.as.error", false)
systemProperty("org.move.aptos.test.tool.window", true) // 30 ms
// systemProperty("org.move.external.linter.max.duration", 30) // 30 ms
// systemProperty("org.move.aptos.bundled.force.unsupported", true)
// systemProperty("idea.log.debug.categories", "org.move.cli")
Expand Down
22 changes: 16 additions & 6 deletions src/main/kotlin/org/move/cli/runConfigurations/AptosCommandLine.kt
Original file line number Diff line number Diff line change
@@ -1,19 +1,29 @@
package org.move.cli.runConfigurations

import com.intellij.execution.configuration.EnvironmentVariablesData
import com.intellij.execution.configurations.GeneralCommandLine
import com.intellij.execution.configurations.PtyCommandLine
import com.intellij.util.execution.ParametersListUtil
import org.move.cli.tools.MvCommandLine
import java.nio.file.Path

class AptosCommandLine(
val subCommand: String?,
subCommand: String?,
arguments: List<String> = emptyList(),
workingDirectory: Path? = null,
environmentVariables: EnvironmentVariablesData = EnvironmentVariablesData.DEFAULT
): MvCommandLine(
subCommand?.split(" ").orEmpty() + arguments,
subCommand,
arguments,
workingDirectory,
environmentVariables
)
) {
fun copy(
subCommand: String? = this.subCommand,
arguments: List<String> = this.arguments,
): AptosCommandLine {
return AptosCommandLine(
subCommand,
arguments,
this.workingDirectory,
this.environmentVariables
)
}
}
20 changes: 20 additions & 0 deletions src/main/kotlin/org/move/cli/runConfigurations/AptosRunState.kt
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,8 @@ import com.intellij.execution.runners.ExecutionEnvironment
import com.intellij.openapi.util.Disposer
import org.move.cli.MoveFileHyperlinkFilter
import org.move.cli.runConfigurations.CommandConfigurationBase.CleanConfiguration
import org.move.cli.runConfigurations.buildtool.AptosPatch
import org.move.cli.runConfigurations.buildtool.aptosPatches

abstract class AptosRunStateBase(
environment: ExecutionEnvironment,
Expand All @@ -18,7 +20,25 @@ abstract class AptosRunStateBase(
val project = environment.project
val commandLine: AptosCommandLine = cleanConfiguration.cmd

protected val commandLinePatches: MutableList<AptosPatch> = mutableListOf()

init {
commandLinePatches.addAll(environment.aptosPatches)
}

fun prepareCommandLine(vararg additionalPatches: AptosPatch): AptosCommandLine {
var commandLine = commandLine
for (patch in commandLinePatches) {
commandLine = patch(commandLine)
}
for (patch in additionalPatches) {
commandLine = patch(commandLine)
}
return commandLine
}

override fun startProcess(): ProcessHandler {
val commandLine = prepareCommandLine()
// emulateTerminal=true allows for the colored output
val generalCommandLine =
commandLine.toGeneralCommandLine(cleanConfiguration.aptosPath, emulateTerminal = true)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -90,12 +90,13 @@ abstract class CommandConfigurationBase(
Ok(aptosPath, commandLine)
}

protected fun showTestToolWindow(commandLine: AptosCommandLine): Boolean =
when {
protected fun showTestToolWindow(commandLine: AptosCommandLine): Boolean {
return when {
!AdvancedSettings.getBoolean(TEST_TOOL_WINDOW_SETTING_KEY) -> false
commandLine.subCommand != "move test" -> false
else -> true
}
}

sealed class CleanConfiguration {
class Ok(val aptosPath: Path, val cmd: AptosCommandLine): CleanConfiguration()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,16 @@ import com.intellij.execution.ExecutionManager
import com.intellij.execution.process.ProcessHandler
import com.intellij.execution.runners.ExecutionEnvironment
import com.intellij.openapi.progress.EmptyProgressIndicator
import com.intellij.openapi.util.Key
import org.move.cli.runConfigurations.AptosCommandLine

typealias AptosPatch = (AptosCommandLine) -> AptosCommandLine

var ExecutionEnvironment.aptosPatches: List<AptosPatch>
get() = putUserDataIfAbsent(APTOS_PATCHES, emptyList())
set(value) = putUserData(APTOS_PATCHES, value)

private val APTOS_PATCHES: Key<List<AptosPatch>> = Key.create("APTOS.PATCHES")

private val ExecutionEnvironment.executionListener: ExecutionListener
get() = project.messageBus.syncPublisher(ExecutionManager.EXECUTION_TOPIC)
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,189 @@
package org.move.cli.runConfigurations.test

import com.google.gson.Gson
import com.google.gson.JsonObject
import com.intellij.execution.testframework.TestConsoleProperties
import com.intellij.execution.testframework.sm.ServiceMessageBuilder
import com.intellij.execution.testframework.sm.runner.OutputToGeneralTestEventsConverter
import com.intellij.openapi.util.Key
import jetbrains.buildServer.messages.serviceMessages.ServiceMessageVisitor
import org.move.stdext.GsonUtils
import org.move.stdext.partitionLast

private typealias TestNodeId = String

class AptosJsonTestEventsConverter(
testFrameworkName: String,
consoleProperties: TestConsoleProperties
): OutputToGeneralTestEventsConverter(testFrameworkName, consoleProperties) {

override fun processServiceMessages(text: String, outputType: Key<*>, visitor: ServiceMessageVisitor): Boolean {
val jsonObject = GsonUtils.tryParseJsonObject(text)

return when {
jsonObject == null -> false
handleTestEvent(jsonObject, outputType, visitor) -> true
handleModuleEvent(jsonObject, outputType, visitor) -> true
else -> true // don't print unknown json messages
}
}

private fun handleTestEvent(
jsonObject: JsonObject,
outputType: Key<*>,
visitor: ServiceMessageVisitor
): Boolean {
val serviceMessages = AptosTestEvent.fromJson(jsonObject)
?.let { createServiceMessagesFor(it) } ?: return false
for (message in serviceMessages) {
val message = message.toString()
super.processServiceMessages(message, outputType, visitor)
}
return true
}

private fun handleModuleEvent(
jsonObject: JsonObject,
outputType: Key<*>,
visitor: ServiceMessageVisitor
): Boolean {
val moduleEvent = AptosModuleEvent.fromJson(jsonObject) ?: return false
val messages = createServiceMessagesFor(moduleEvent) ?: return false
for (message in messages) {
super.processServiceMessages(message.toString(), outputType, visitor)
}
return true
}

private fun createServiceMessagesFor(moduleEvent: AptosModuleEvent): List<ServiceMessageBuilder>? {
val messages = mutableListOf<ServiceMessageBuilder>()
when (moduleEvent.event) {
"started" -> {
processor.onTestsReporterAttached()
messages.add(createModuleStartedMessage(moduleEvent.module_name))
}
"finished" -> {
messages.add(createModuleFinishedMessage(moduleEvent.module_name))
}
else -> return null
}
return messages
}

private fun createServiceMessagesFor(testEvent: AptosTestEvent): List<ServiceMessageBuilder>? {
val messages = mutableListOf<ServiceMessageBuilder>()
when (testEvent.event) {
"start" -> messages.add(createTestStartedMessage(testEvent.fn_name))
"pass" -> {
val duration = parseTestDuration(testEvent)
messages.add(createTestFinishedMessage(testEvent.fn_name, duration))
}
"fail" -> {
val duration = parseTestDuration(testEvent)
val failedMessage = testEvent.failure.orEmpty().trim()
messages.add(createTestFailedMessage(testEvent.fn_name, failedMessage))
messages.add(createTestFinishedMessage(testEvent.fn_name, duration))
}
else -> return null
}
return messages
}

private fun parseTestDuration(testEvent: AptosTestEvent): String {
val execTimeText = "${testEvent.exec_time}s"
return kotlin.time.Duration.parse(execTimeText).inWholeMilliseconds.toString()
}

companion object {
private const val ROOT_SUITE: String = "0"
private const val NAME_SEPARATOR: String = "::"

private val TestNodeId.name: String
get() {
val (parent, name) = this.partitionLast(NAME_SEPARATOR)
return when {
parent.contains(NAME_SEPARATOR) -> name
else -> this
}
}

private val TestNodeId.parent: TestNodeId
get() {
val (parent, _) = this.partitionLast(NAME_SEPARATOR)
return when {
parent.contains(NAME_SEPARATOR) -> parent
else -> ROOT_SUITE
}
}

private fun createModuleStartedMessage(module: TestNodeId): ServiceMessageBuilder {
return ServiceMessageBuilder.testSuiteStarted(module.name)
.addAttribute("nodeId", module)
.addAttribute("parentNodeId", ROOT_SUITE)
.addAttribute("locationHint", AptosTestLocator.getTestUrl(module))
}

private fun createModuleFinishedMessage(moduleId: TestNodeId): ServiceMessageBuilder =
ServiceMessageBuilder.testSuiteFinished(moduleId.name)
.addAttribute("nodeId", moduleId)

private fun createTestStartedMessage(test: TestNodeId): ServiceMessageBuilder {
return ServiceMessageBuilder.testStarted(test.name)
.addAttribute("nodeId", test)
.addAttribute("parentNodeId", test.parent)
.addAttribute("locationHint", AptosTestLocator.getTestUrl(test))
}

private fun createTestFailedMessage(test: TestNodeId, failedMessage: String): ServiceMessageBuilder {
val builder = ServiceMessageBuilder.testFailed(test.name)
.addAttribute("nodeId", test)
// TODO: pass backtrace here
.addAttribute("message", failedMessage)
return builder
}

private fun createTestFinishedMessage(test: TestNodeId, duration: String): ServiceMessageBuilder =
ServiceMessageBuilder.testFinished(test.name)
.addAttribute("nodeId", test)
.addAttribute("duration", duration)

// private fun createTestStdOutMessage(test: TestNodeId, stdout: String): ServiceMessageBuilder =
// ServiceMessageBuilder.testStdOut(test.name)
// .addAttribute("nodeId", test)
// .addAttribute("out", stdout)
}
}

@Suppress("PropertyName")
private data class AptosModuleEvent(
val type: String,
val event: String,
val module_name: String,
) {
companion object {
fun fromJson(json: JsonObject): AptosModuleEvent? {
if (json.getAsJsonPrimitive("type")?.asString != "module") {
return null
}
return Gson().fromJson(json, AptosModuleEvent::class.java)
}
}
}

@Suppress("PropertyName")
private data class AptosTestEvent(
val type: String,
val event: String,
val fn_name: String,
val failure: String?,
val exec_time: Float,
) {
companion object {
fun fromJson(json: JsonObject): AptosTestEvent? {
if (json.getAsJsonPrimitive("type")?.asString != "test") {
return null
}
return Gson().fromJson(json, AptosTestEvent::class.java)
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ class AptosTestConsoleProperties(
testFrameworkName: String,
consoleProperties: TestConsoleProperties
): OutputToGeneralTestEventsConverter =
AptosTestEventsConverter(testFrameworkName, consoleProperties)
AptosJsonTestEventsConverter(testFrameworkName, consoleProperties)

companion object {
const val TEST_FRAMEWORK_NAME: String = "Aptos Test"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import com.intellij.openapi.project.Project
import com.intellij.psi.PsiElement
import com.intellij.psi.search.GlobalSearchScope
import org.move.lang.core.psi.MvFunction
import org.move.lang.core.psi.MvModule
import org.move.lang.index.MvNamedElementIndex
import org.move.lang.moveProject

Expand All @@ -28,9 +29,11 @@ object AptosTestLocator : SMTestLocator {
return buildList {
val name = qualifiedName.substringAfterLast(NAME_SEPARATOR)
for (element in MvNamedElementIndex.getElementsByName(project, name, scope)) {
if (element is MvFunction) {
if (element.qualName?.cmdText() == qualifiedName) {
add(PsiLocation.fromPsiElement(element))
when (element) {
is MvFunction, is MvModule -> {
if (element.qualName?.cmdText() == qualifiedName) {
add(PsiLocation.fromPsiElement(element))
}
}
}
}
Expand Down
Loading