From 89678d73b3522be4880c360fc42c43c5d3da37df Mon Sep 17 00:00:00 2001 From: Maksim Kurnikov Date: Sun, 5 Jan 2025 13:46:27 +0100 Subject: [PATCH] wip --- build.gradle.kts | 1 + .../cli/runConfigurations/AptosCommandLine.kt | 22 +- .../cli/runConfigurations/AptosRunState.kt | 20 ++ .../CommandConfigurationBase.kt | 5 +- .../cli/runConfigurations/buildtool/Utils.kt | 9 + .../test/AptosJsonTestEventsConverter.kt | 189 ++++++++++++++++++ .../test/AptosTestConsoleProperties.kt | 2 +- .../test/AptosTestLocator.kt | 9 +- .../test/AptosTestRunState.kt | 72 +++++++ .../org/move/cli/settings/VersionLabel.kt | 2 +- src/main/kotlin/org/move/cli/tools/Movefmt.kt | 2 +- .../org/move/cli/tools/MvCommandLine.kt | 16 +- src/main/kotlin/org/move/stdext/GsonUtils.kt | 24 +++ src/main/kotlin/org/move/stdext/Utils.kt | 8 + .../AptosTestRunStatePatchArgsTest.kt | 48 +++++ 15 files changed, 409 insertions(+), 20 deletions(-) create mode 100644 src/main/kotlin/org/move/cli/runConfigurations/test/AptosJsonTestEventsConverter.kt create mode 100644 src/main/kotlin/org/move/stdext/GsonUtils.kt create mode 100644 src/test/kotlin/org/move/cli/runConfigurations/AptosTestRunStatePatchArgsTest.kt diff --git a/build.gradle.kts b/build.gradle.kts index ed8e0a004..efeebac81 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -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") diff --git a/src/main/kotlin/org/move/cli/runConfigurations/AptosCommandLine.kt b/src/main/kotlin/org/move/cli/runConfigurations/AptosCommandLine.kt index 586582ae0..980506acc 100644 --- a/src/main/kotlin/org/move/cli/runConfigurations/AptosCommandLine.kt +++ b/src/main/kotlin/org/move/cli/runConfigurations/AptosCommandLine.kt @@ -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 = 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 = this.arguments, + ): AptosCommandLine { + return AptosCommandLine( + subCommand, + arguments, + this.workingDirectory, + this.environmentVariables + ) + } +} diff --git a/src/main/kotlin/org/move/cli/runConfigurations/AptosRunState.kt b/src/main/kotlin/org/move/cli/runConfigurations/AptosRunState.kt index 48b1fbed8..eb2814257 100644 --- a/src/main/kotlin/org/move/cli/runConfigurations/AptosRunState.kt +++ b/src/main/kotlin/org/move/cli/runConfigurations/AptosRunState.kt @@ -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, @@ -18,7 +20,25 @@ abstract class AptosRunStateBase( val project = environment.project val commandLine: AptosCommandLine = cleanConfiguration.cmd + protected val commandLinePatches: MutableList = 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) diff --git a/src/main/kotlin/org/move/cli/runConfigurations/CommandConfigurationBase.kt b/src/main/kotlin/org/move/cli/runConfigurations/CommandConfigurationBase.kt index 87105737e..7fa2f977b 100644 --- a/src/main/kotlin/org/move/cli/runConfigurations/CommandConfigurationBase.kt +++ b/src/main/kotlin/org/move/cli/runConfigurations/CommandConfigurationBase.kt @@ -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() diff --git a/src/main/kotlin/org/move/cli/runConfigurations/buildtool/Utils.kt b/src/main/kotlin/org/move/cli/runConfigurations/buildtool/Utils.kt index efc8ad52d..3c4392841 100644 --- a/src/main/kotlin/org/move/cli/runConfigurations/buildtool/Utils.kt +++ b/src/main/kotlin/org/move/cli/runConfigurations/buildtool/Utils.kt @@ -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 + get() = putUserDataIfAbsent(APTOS_PATCHES, emptyList()) + set(value) = putUserData(APTOS_PATCHES, value) + +private val APTOS_PATCHES: Key> = Key.create("APTOS.PATCHES") private val ExecutionEnvironment.executionListener: ExecutionListener get() = project.messageBus.syncPublisher(ExecutionManager.EXECUTION_TOPIC) diff --git a/src/main/kotlin/org/move/cli/runConfigurations/test/AptosJsonTestEventsConverter.kt b/src/main/kotlin/org/move/cli/runConfigurations/test/AptosJsonTestEventsConverter.kt new file mode 100644 index 000000000..cff00d99f --- /dev/null +++ b/src/main/kotlin/org/move/cli/runConfigurations/test/AptosJsonTestEventsConverter.kt @@ -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? { + val messages = mutableListOf() + 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? { + val messages = mutableListOf() + 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) + } + } +} diff --git a/src/main/kotlin/org/move/cli/runConfigurations/test/AptosTestConsoleProperties.kt b/src/main/kotlin/org/move/cli/runConfigurations/test/AptosTestConsoleProperties.kt index dc6868a8c..da8a26291 100644 --- a/src/main/kotlin/org/move/cli/runConfigurations/test/AptosTestConsoleProperties.kt +++ b/src/main/kotlin/org/move/cli/runConfigurations/test/AptosTestConsoleProperties.kt @@ -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" diff --git a/src/main/kotlin/org/move/cli/runConfigurations/test/AptosTestLocator.kt b/src/main/kotlin/org/move/cli/runConfigurations/test/AptosTestLocator.kt index f65857a1b..3027fe916 100644 --- a/src/main/kotlin/org/move/cli/runConfigurations/test/AptosTestLocator.kt +++ b/src/main/kotlin/org/move/cli/runConfigurations/test/AptosTestLocator.kt @@ -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 @@ -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)) + } } } } diff --git a/src/main/kotlin/org/move/cli/runConfigurations/test/AptosTestRunState.kt b/src/main/kotlin/org/move/cli/runConfigurations/test/AptosTestRunState.kt index 87c2644a4..7fcdc6717 100644 --- a/src/main/kotlin/org/move/cli/runConfigurations/test/AptosTestRunState.kt +++ b/src/main/kotlin/org/move/cli/runConfigurations/test/AptosTestRunState.kt @@ -1,19 +1,62 @@ package org.move.cli.runConfigurations.test +import com.google.common.annotations.VisibleForTesting import com.intellij.execution.runners.ExecutionEnvironment +import org.move.cli.runConfigurations.AptosCommandLine import org.move.cli.runConfigurations.AptosRunStateBase import org.move.cli.runConfigurations.CommandConfigurationBase import org.move.cli.runConfigurations.aptos.AptosTestConsoleBuilder import org.move.cli.runConfigurations.aptos.cmd.AptosCommandConfiguration +import org.move.cli.runConfigurations.buildtool.AptosPatch class AptosTestRunState( environment: ExecutionEnvironment, cleanConfiguration: CommandConfigurationBase.CleanConfiguration.Ok ): AptosRunStateBase(environment, cleanConfiguration) { + private val aptosTestPatch: AptosPatch = { commandLine -> +// val rustcVersion = cargoProject?.rustcInfo?.version +// // Stable Rust test framework does not support `-Z unstable-options --format json` since 1.70.0-beta +// // (https://github.com/rust-lang/rust/pull/109044) +// val requiresRustcBootstrap = !(rustcVersion != null +// && (rustcVersion.channel == RustChannel.NIGHTLY +// || rustcVersion.channel == RustChannel.DEV +// || rustcVersion.semver < RUSTC_1_70_BETA)) +// val environmentVariables = if (requiresRustcBootstrap) { +// if (!PropertiesComponent.getInstance().getBoolean(DO_NOT_SHOW_KEY, false)) { +// showRustcBootstrapWarning(project) +// } +// val oldVariables = commandLine.environmentVariables +// EnvironmentVariablesData.create( +// oldVariables.envs + mapOf(RUSTC_BOOTSTRAP to "1"), +// oldVariables.isPassParentEnvs +// ) +// } else { +// commandLine.environmentVariables +// } +// +// // TODO: always pass `withSudo` when `com.intellij.execution.process.ElevationService` supports error stream redirection +// // https://github.com/intellij-rust/intellij-rust/issues/7320 +// if (commandLine.withSudo) { +// val message = if (SystemInfo.isWindows) { +// RsBundle.message("notification.run.tests.as.root.windows") +// } else { +// RsBundle.message("notification.run.tests.as.root.unix") +// } +// project.showBalloon(message, NotificationType.WARNING) +// } + + commandLine.copy( + arguments = patchArgs(commandLine), +// emulateTerminal = false, +// withSudo = false + ) + } + init { consoleBuilder = AptosTestConsoleBuilder(environment.runProfile as AptosCommandConfiguration, environment.executor) + commandLinePatches.add(aptosTestPatch) createFilters().forEach { consoleBuilder.addFilter(it) } } @@ -23,4 +66,33 @@ class AptosTestRunState( // console?.attachToProcess(processHandler) // return DefaultExecutionResult(console, processHandler).apply { setRestartActions(ToggleAutoTestAction()) } // } + + companion object { + @VisibleForTesting + fun patchArgs(commandLine: AptosCommandLine): List { +// val (pre, post) = commandLine.splitOnDoubleDash() +// .let { (pre, post) -> pre.toMutableList() to post.toMutableList() } + + val args = commandLine.arguments.toMutableList() +// val noFailFastOption = "--no-fail-fast" +// if (noFailFastOption !in pre) { +// pre.add(noFailFastOption) +// } + +// val unstableOption = "-Z" +// if (unstableOption !in post) { +// post.add(unstableOption) +// post.add("unstable-options") +// } + + if ("--format-json" !in args) { + args.add("--format-json") + } + +// addFormatJsonOption(post, "--format", "json") +// post.add("--show-output") + + return args + } + } } diff --git a/src/main/kotlin/org/move/cli/settings/VersionLabel.kt b/src/main/kotlin/org/move/cli/settings/VersionLabel.kt index 4b3834d3c..7a79bee2d 100644 --- a/src/main/kotlin/org/move/cli/settings/VersionLabel.kt +++ b/src/main/kotlin/org/move/cli/settings/VersionLabel.kt @@ -50,7 +50,7 @@ class VersionLabel( } val commandLineArgs = MvCommandLine( - listOf("--version"), + arguments = listOf("--version"), workingDirectory = null, environmentVariables = envs ) diff --git a/src/main/kotlin/org/move/cli/tools/Movefmt.kt b/src/main/kotlin/org/move/cli/tools/Movefmt.kt index 058abbad7..f78e75c48 100644 --- a/src/main/kotlin/org/move/cli/tools/Movefmt.kt +++ b/src/main/kotlin/org/move/cli/tools/Movefmt.kt @@ -31,7 +31,7 @@ class Movefmt(val cliLocation: Path, val parentDisposable: Disposable): Disposab runner: CapturingProcessHandler.() -> ProcessOutput = { runProcessWithGlobalProgress() } ): RsProcessResult { val commandLine = MvCommandLine( - buildList { + arguments = buildList { add("-q") addAllIfNotNull("--emit", "stdout") if (additionalArguments.isNotEmpty()) { diff --git a/src/main/kotlin/org/move/cli/tools/MvCommandLine.kt b/src/main/kotlin/org/move/cli/tools/MvCommandLine.kt index 58ee2ff60..1bf379e7a 100644 --- a/src/main/kotlin/org/move/cli/tools/MvCommandLine.kt +++ b/src/main/kotlin/org/move/cli/tools/MvCommandLine.kt @@ -7,24 +7,28 @@ import com.intellij.util.execution.ParametersListUtil import java.nio.file.Path open class MvCommandLine( + val subCommand: String? = null, val arguments: List = emptyList(), val workingDirectory: Path? = null, val environmentVariables: EnvironmentVariablesData = EnvironmentVariablesData.DEFAULT ) { - val commandLineString: String get() = ParametersListUtil.join(arguments) + private val subCommandWithArguments get() = subCommand?.split(" ").orEmpty() + arguments + + val commandLineString: String get() = ParametersListUtil.join(subCommandWithArguments) fun toGeneralCommandLine(cliExePath: Path, emulateTerminal: Boolean = false): GeneralCommandLine { - var commandLine = GeneralCommandLine() + val commandLine = if (emulateTerminal) { + PtyCommandLine() + } else { + GeneralCommandLine() + } .withExePath(cliExePath.toString()) - .withParameters(this.arguments) + .withParameters(this.subCommandWithArguments) .withWorkingDirectory(this.workingDirectory) .withCharset(Charsets.UTF_8) // disables default coloring for stderr .withRedirectErrorStream(true) this.environmentVariables.configureCommandLine(commandLine, true) - if (emulateTerminal) { - commandLine = PtyCommandLine(commandLine) - } return commandLine } } \ No newline at end of file diff --git a/src/main/kotlin/org/move/stdext/GsonUtils.kt b/src/main/kotlin/org/move/stdext/GsonUtils.kt new file mode 100644 index 000000000..e65f345b7 --- /dev/null +++ b/src/main/kotlin/org/move/stdext/GsonUtils.kt @@ -0,0 +1,24 @@ +package org.move.stdext + +import com.google.gson.JsonIOException +import com.google.gson.JsonObject +import com.google.gson.JsonParser +import com.google.gson.JsonSyntaxException +import com.google.gson.stream.JsonReader +import java.io.StringReader + +object GsonUtils { + fun tryParseJsonObject(content: String?, lenient: Boolean = true): JsonObject? = + try { + parseJsonObject(content, lenient) + } catch (ignored: Exception) { + null + } + + @Throws(JsonIOException::class, JsonSyntaxException::class, IllegalStateException::class) + fun parseJsonObject(content: String?, lenient: Boolean = true): JsonObject { + val jsonReader = JsonReader(StringReader(content ?: "")) + jsonReader.isLenient = lenient + return jsonReader.use { JsonParser.parseReader(it).asJsonObject } + } +} diff --git a/src/main/kotlin/org/move/stdext/Utils.kt b/src/main/kotlin/org/move/stdext/Utils.kt index 233dd4a6b..206011c88 100644 --- a/src/main/kotlin/org/move/stdext/Utils.kt +++ b/src/main/kotlin/org/move/stdext/Utils.kt @@ -87,3 +87,11 @@ fun Long.isPowerOfTwo(): Boolean { return this > 0 && (this.and(this - 1)) == 0L } +fun String.partitionLast(delimiter: String): Pair { + val head = substringBeforeLast(delimiter) + val tail = substringAfterLast(delimiter) + return when { + head == tail -> head to "" + else -> head to tail + } +} diff --git a/src/test/kotlin/org/move/cli/runConfigurations/AptosTestRunStatePatchArgsTest.kt b/src/test/kotlin/org/move/cli/runConfigurations/AptosTestRunStatePatchArgsTest.kt new file mode 100644 index 000000000..fb3e89f0b --- /dev/null +++ b/src/test/kotlin/org/move/cli/runConfigurations/AptosTestRunStatePatchArgsTest.kt @@ -0,0 +1,48 @@ +package org.move.cli.runConfigurations + +import com.intellij.util.execution.ParametersListUtil +import org.junit.Assert +import org.junit.Test +import org.junit.runner.RunWith +import org.junit.runners.Parameterized +import org.move.cli.runConfigurations.test.AptosTestRunState +import org.move.utils.tests.MvTestBase +import java.nio.file.Path +import java.nio.file.Paths + +@RunWith(Parameterized::class) +class AptosTestRunStatePatchArgsTest( + private val input: String, + private val expected: String +): MvTestBase() { + private val wd: Path = Paths.get("/my-crate") + + @Test + fun test() = Assert.assertEquals( + ParametersListUtil.parse(expected), + AptosTestRunState.patchArgs( + AptosCommandLine("move test", ParametersListUtil.parse(input), wd) + ) + ) + + companion object { + @Parameterized.Parameters(name = "{index}: {0}") + @JvmStatic + fun data(): Collection> = listOf( + arrayOf("", "--format-json"), + arrayOf("foo", "foo --format-json"), + arrayOf("--filter test_name --format-json", "--filter test_name --format-json"), + +// arrayOf("", "--no-fail-fast -- --format=json -Z unstable-options --show-output"), +// arrayOf("foo", "foo --no-fail-fast -- --format=json -Z unstable-options --show-output"), +// arrayOf("foo bar", "foo bar --no-fail-fast -- --format=json -Z unstable-options --show-output"), +// arrayOf("--", "--no-fail-fast -- --format=json -Z unstable-options --show-output"), +// +// arrayOf("-- -Z unstable-options", "--no-fail-fast -- --format=json -Z unstable-options --show-output"), +// arrayOf("-- --format=json", "--no-fail-fast -- --format=json -Z unstable-options --show-output"), +// arrayOf("-- --format json", "--no-fail-fast -- --format json -Z unstable-options --show-output"), +// arrayOf("-- --format pretty", "--no-fail-fast -- --format json -Z unstable-options --show-output"), +// arrayOf("-- --format=pretty", "--no-fail-fast -- --format=json -Z unstable-options --show-output") + ) + } +} \ No newline at end of file