From 23fe7173286f6b1d8d5b0c8b3d28059486f87ec4 Mon Sep 17 00:00:00 2001 From: Konstantin Hudyakov Date: Mon, 15 Dec 2025 14:31:23 +0200 Subject: [PATCH] [terminal] IJPL-220311 Fix terminal completion tests Run them with both Reworked and Experimental terminal engines. GitOrigin-RevId: 36c627f7431ac7fe32528d9273d9d276012319b4 --- .../completion/TerminalCompletionUtil.kt | 7 +++ .../spec/ShellRuntimeContextExtensions.kt | 6 +-- .../block/completion/ShellCdCommandTest.kt | 33 ++++++++++-- .../ShellCommandSpecSuggestionsTest.kt | 33 +++++++++--- .../completion/ShellCommandTreeBuilderTest.kt | 53 ++++++++++++++----- .../completion/ShellMakefileCompletionTest.kt | 14 +++-- .../tests/reworked/util/TerminalTestUtil.kt | 7 +++ 7 files changed, 122 insertions(+), 31 deletions(-) diff --git a/plugins/terminal/src/org/jetbrains/plugins/terminal/block/completion/TerminalCompletionUtil.kt b/plugins/terminal/src/org/jetbrains/plugins/terminal/block/completion/TerminalCompletionUtil.kt index 8848b5e69368..095bc87d12b7 100644 --- a/plugins/terminal/src/org/jetbrains/plugins/terminal/block/completion/TerminalCompletionUtil.kt +++ b/plugins/terminal/src/org/jetbrains/plugins/terminal/block/completion/TerminalCompletionUtil.kt @@ -77,6 +77,13 @@ object TerminalCompletionUtil { return ShellName(this.toString().lowercase()) } + fun String.toShellFileInfo(fileSeparator: Char): ShellFileInfo { + return if (endsWith(fileSeparator)) { + ShellFileInfo.create(removeSuffix(fileSeparator.toString()), ShellFileInfo.Type.DIRECTORY) + } + else ShellFileInfo.create(this, ShellFileInfo.Type.FILE) + } + suspend fun doExecuteGenerator(context: ShellRuntimeContext, generator: ShellRuntimeDataGenerator): T? { return try { generator.generate(context) diff --git a/plugins/terminal/src/org/jetbrains/plugins/terminal/block/completion/spec/ShellRuntimeContextExtensions.kt b/plugins/terminal/src/org/jetbrains/plugins/terminal/block/completion/spec/ShellRuntimeContextExtensions.kt index 941e4d138e84..a073078c3bb4 100644 --- a/plugins/terminal/src/org/jetbrains/plugins/terminal/block/completion/spec/ShellRuntimeContextExtensions.kt +++ b/plugins/terminal/src/org/jetbrains/plugins/terminal/block/completion/spec/ShellRuntimeContextExtensions.kt @@ -11,6 +11,7 @@ import com.intellij.terminal.completion.spec.ShellFileInfo import com.intellij.terminal.completion.spec.ShellRuntimeContext import org.jetbrains.annotations.ApiStatus import org.jetbrains.plugins.terminal.block.completion.TerminalCompletionUtil.throwUnsupportedInExpTerminalException +import org.jetbrains.plugins.terminal.block.completion.TerminalCompletionUtil.toShellFileInfo import org.jetbrains.plugins.terminal.block.session.ShellIntegrationFunctions.GET_DIRECTORY_FILES import java.io.File import java.nio.file.InvalidPathException @@ -104,9 +105,6 @@ private suspend fun ShellRuntimeContext.getChildFilesExp(path: String, onlyDirec .filter { !onlyDirectories || it.endsWith(separator) } // do not suggest './' and '../' directories if the user already typed some path .filter { path.isEmpty() || (it != ".$separator" && it != "..$separator") } - .map { - val type = if (it.endsWith(separator)) ShellFileInfo.Type.DIRECTORY else ShellFileInfo.Type.FILE - ShellFileInfo.create(it.removeSuffix(separator.toString()), type) - } + .map { it.toShellFileInfo(separator) } .toList() } diff --git a/plugins/terminal/tests/src/com/intellij/terminal/tests/block/completion/ShellCdCommandTest.kt b/plugins/terminal/tests/src/com/intellij/terminal/tests/block/completion/ShellCdCommandTest.kt index 4d1e78aef676..e6c3364ee0c5 100644 --- a/plugins/terminal/tests/src/com/intellij/terminal/tests/block/completion/ShellCdCommandTest.kt +++ b/plugins/terminal/tests/src/com/intellij/terminal/tests/block/completion/ShellCdCommandTest.kt @@ -2,17 +2,34 @@ package com.intellij.terminal.tests.block.completion import com.intellij.terminal.completion.spec.ShellCommandResult -import com.intellij.testFramework.fixtures.BasePlatformTestCase +import com.intellij.terminal.completion.spec.ShellFileInfo +import com.intellij.terminal.tests.reworked.util.TerminalTestUtil +import com.intellij.testFramework.ProjectRule +import com.intellij.testFramework.UsefulTestCase.assertSameElements import kotlinx.coroutines.runBlocking +import org.jetbrains.plugins.terminal.TerminalEngine +import org.jetbrains.plugins.terminal.block.completion.TerminalCompletionUtil.toShellFileInfo +import org.jetbrains.plugins.terminal.block.completion.spec.ShellFileSystemSupport import org.jetbrains.plugins.terminal.block.session.ShellIntegrationFunctions.GET_DIRECTORY_FILES import org.jetbrains.plugins.terminal.testFramework.completion.ShellCompletionTestFixture +import org.junit.Rule import org.junit.Test import org.junit.runner.RunWith -import org.junit.runners.JUnit4 +import org.junit.runners.Parameterized import java.io.File -@RunWith(JUnit4::class) -internal class ShellCdCommandTest : BasePlatformTestCase() { +@RunWith(Parameterized::class) +internal class ShellCdCommandTest(private val engine: TerminalEngine) { + @JvmField + @Rule + val projectRule: ProjectRule = ProjectRule() + + companion object { + @JvmStatic + @Parameterized.Parameters(name = "{0}") + fun engine(): List = TerminalTestUtil.enginesWithCompletionSupport() + } + private val separator = File.separatorChar private val expectedDirectories = listOf("directory$separator", "settings$separator", ".hiddenDir$separator") private val allFiles = expectedDirectories + listOf("file.txt", ".hidden") @@ -36,13 +53,19 @@ internal class ShellCdCommandTest : BasePlatformTestCase() { * @param files files to return on [expectedPath] child files request. */ private fun createFixture(files: List, expectedPath: String): ShellCompletionTestFixture { - return ShellCompletionTestFixture.builder(project) + return ShellCompletionTestFixture.builder(projectRule.project) + .setIsReworkedTerminal(engine == TerminalEngine.REWORKED) .mockShellCommandResults { command -> if (command == "${GET_DIRECTORY_FILES.functionName} $expectedPath") { ShellCommandResult.create(files.joinToString("\n"), exitCode = 0) } else error("Unknown command: $command") } + .mockFileSystemSupport(object : ShellFileSystemSupport { + override suspend fun listDirectoryFiles(path: String): List { + return files.map { it.toShellFileInfo(separator) } + } + }) .build() } } diff --git a/plugins/terminal/tests/src/com/intellij/terminal/tests/block/completion/ShellCommandSpecSuggestionsTest.kt b/plugins/terminal/tests/src/com/intellij/terminal/tests/block/completion/ShellCommandSpecSuggestionsTest.kt index 6e636b9a8117..edb9e85c5848 100644 --- a/plugins/terminal/tests/src/com/intellij/terminal/tests/block/completion/ShellCommandSpecSuggestionsTest.kt +++ b/plugins/terminal/tests/src/com/intellij/terminal/tests/block/completion/ShellCommandSpecSuggestionsTest.kt @@ -5,10 +5,15 @@ import com.intellij.terminal.completion.ShellCommandSpecCompletion import com.intellij.terminal.completion.spec.ShellCommandExecutor import com.intellij.terminal.completion.spec.ShellCommandParserOptions import com.intellij.terminal.completion.spec.ShellCommandResult +import com.intellij.terminal.completion.spec.ShellFileInfo +import com.intellij.terminal.tests.reworked.util.TerminalTestUtil import com.intellij.testFramework.UsefulTestCase.assertSameElements import kotlinx.coroutines.runBlocking +import org.jetbrains.plugins.terminal.TerminalEngine +import org.jetbrains.plugins.terminal.block.completion.TerminalCompletionUtil.toShellFileInfo import org.jetbrains.plugins.terminal.block.completion.spec.ShellCommandSpec import org.jetbrains.plugins.terminal.block.completion.spec.ShellDataGenerators.fileSuggestionsGenerator +import org.jetbrains.plugins.terminal.block.completion.spec.ShellFileSystemSupport import org.jetbrains.plugins.terminal.testFramework.completion.impl.TestCommandSpecsManager import org.jetbrains.plugins.terminal.testFramework.completion.impl.TestGeneratorsExecutor import org.jetbrains.plugins.terminal.testFramework.completion.impl.TestRuntimeContextProvider @@ -16,11 +21,17 @@ import org.junit.Before import org.junit.Test import org.junit.jupiter.api.fail import org.junit.runner.RunWith -import org.junit.runners.JUnit4 +import org.junit.runners.Parameterized import java.io.File -@RunWith(JUnit4::class) -internal class ShellCommandSpecSuggestionsTest { +@RunWith(Parameterized::class) +internal class ShellCommandSpecSuggestionsTest(private val engine: TerminalEngine) { + companion object { + @JvmStatic + @Parameterized.Parameters(name = "{0}") + fun engine(): List = TerminalTestUtil.enginesWithCompletionSupport() + } + private val commandName = "command" /** @@ -29,6 +40,7 @@ internal class ShellCommandSpecSuggestionsTest { * Long story short: Use to mock `ls`. */ private var filePathSuggestions: List = emptyList() + private val separator = File.separatorChar @Before fun setUp() { @@ -340,21 +352,18 @@ internal class ShellCommandSpecSuggestionsTest { @Test fun `suggest hardcoded suggestions with files`() { - val separator = File.separatorChar mockFilePathsSuggestions("file.txt", "dir$separator", "folder$separator") assertSameElements(getSuggestions(listOf("cdWithSuggestions")), listOf("dir$separator", "folder$separator", "-", "~", "--bcde")) } @Test fun `suggest filenames for path in single quotes`() { - val separator = File.separatorChar mockFilePathsSuggestions("file.txt", "dir$separator", "folder$separator") assertSameElements(getSuggestions(listOf("cd"), "'someDir$separator"), listOf("dir$separator", "folder$separator")) } @Test fun `suggest filenames for path in double quotes`() { - val separator = File.separatorChar mockFilePathsSuggestions("file.txt", "dir$separator", "folder$separator") assertSameElements(getSuggestions(listOf("cd"), "\"someDir$separator"), listOf("dir$separator", "folder$separator")) } @@ -409,10 +418,20 @@ internal class ShellCommandSpecSuggestionsTest { return ShellCommandResult.create(output, exitCode = 0) } } + val fileSystemSupport = object : ShellFileSystemSupport { + override suspend fun listDirectoryFiles(path: String): List { + return mockFiles.map { it.toShellFileInfo(separator) } + } + } + val runtimeContextProvider = TestRuntimeContextProvider( + isReworkedTerminal = engine == TerminalEngine.REWORKED, + generatorCommandsRunner = generatorCommandsRunner, + fileSystemSupport = fileSystemSupport + ) val completion = ShellCommandSpecCompletion( TestCommandSpecsManager(spec), TestGeneratorsExecutor(), - TestRuntimeContextProvider(generatorCommandsRunner = generatorCommandsRunner) + runtimeContextProvider, ) return completion } diff --git a/plugins/terminal/tests/src/com/intellij/terminal/tests/block/completion/ShellCommandTreeBuilderTest.kt b/plugins/terminal/tests/src/com/intellij/terminal/tests/block/completion/ShellCommandTreeBuilderTest.kt index 0b95b19a81ca..86e464ffd421 100644 --- a/plugins/terminal/tests/src/com/intellij/terminal/tests/block/completion/ShellCommandTreeBuilderTest.kt +++ b/plugins/terminal/tests/src/com/intellij/terminal/tests/block/completion/ShellCommandTreeBuilderTest.kt @@ -6,26 +6,38 @@ import com.intellij.terminal.completion.ShellCommandTreeBuilderFixture import com.intellij.terminal.completion.spec.ShellCommandExecutor import com.intellij.terminal.completion.spec.ShellCommandParserOptions import com.intellij.terminal.completion.spec.ShellCommandResult +import com.intellij.terminal.completion.spec.ShellFileInfo +import com.intellij.terminal.tests.reworked.util.TerminalTestUtil import com.intellij.testFramework.common.timeoutRunBlocking import kotlinx.coroutines.runBlocking +import org.jetbrains.plugins.terminal.TerminalEngine +import org.jetbrains.plugins.terminal.block.completion.TerminalCompletionUtil.toShellFileInfo import org.jetbrains.plugins.terminal.block.completion.spec.ShellAliasSuggestion import org.jetbrains.plugins.terminal.block.completion.spec.ShellCommandSpec import org.jetbrains.plugins.terminal.block.completion.spec.ShellDataGenerators.fileSuggestionsGenerator +import org.jetbrains.plugins.terminal.block.completion.spec.ShellFileSystemSupport import org.jetbrains.plugins.terminal.block.session.ShellIntegrationFunctions.GET_DIRECTORY_FILES import org.jetbrains.plugins.terminal.testFramework.completion.impl.TestCommandSpecsManager import org.jetbrains.plugins.terminal.testFramework.completion.impl.TestGeneratorsExecutor import org.jetbrains.plugins.terminal.testFramework.completion.impl.TestRuntimeContextProvider import org.junit.Test import org.junit.runner.RunWith -import org.junit.runners.JUnit4 +import org.junit.runners.Parameterized import java.io.File import kotlin.time.Duration import kotlin.time.Duration.Companion.milliseconds -@RunWith(JUnit4::class) -internal class ShellCommandTreeBuilderTest { +@RunWith(Parameterized::class) +internal class ShellCommandTreeBuilderTest(private val engine: TerminalEngine) { + companion object { + @JvmStatic + @Parameterized.Parameters(name = "{0}") + fun engine(): List = TerminalTestUtil.enginesWithCompletionSupport() + } + private val commandName = "command" private var filePathSuggestions: Map> = emptyMap() + private val separator = File.separatorChar private val spec = ShellCommandSpec(commandName) { option("-a", "--asd") @@ -260,7 +272,7 @@ internal class ShellCommandTreeBuilderTest { @Test fun `option with file argument`() { - mockFilePathsSuggestions("." to listOf("file.txt", "folder${File.separatorChar}", "file")) + mockFilePathsSuggestions("." to listOf("file.txt", "folder${separator}", "file")) doTest("withFiles", "-o", "file.txt") { assertSubcommandOf("withFiles", commandName) assertOptionOf("-o", "withFiles") @@ -270,8 +282,8 @@ internal class ShellCommandTreeBuilderTest { @Test fun `option with file argument prefixed with directory name`() { - val dir = "someDir${File.separatorChar}" - mockFilePathsSuggestions(dir to listOf("file.txt", "folder${File.separatorChar}", "file")) + val dir = "someDir${separator}" + mockFilePathsSuggestions(dir to listOf("file.txt", "folder${separator}", "file")) doTest("withFiles", "-o", "${dir}file") { assertSubcommandOf("withFiles", commandName) assertOptionOf("-o", "withFiles") @@ -281,8 +293,8 @@ internal class ShellCommandTreeBuilderTest { @Test fun `subcommand with directory argument`() { - val dirSuggestion = "dir${File.separatorChar}" - mockFilePathsSuggestions(dirSuggestion to listOf("file.txt", "folder${File.separatorChar}", "file")) + val dirSuggestion = "dir${separator}" + mockFilePathsSuggestions(dirSuggestion to listOf("file.txt", "folder${separator}", "file")) doTest("withFiles", dirSuggestion) { assertSubcommandOf("withFiles", commandName) assertArgumentOfSubcommand(dirSuggestion, "withFiles") @@ -291,8 +303,8 @@ internal class ShellCommandTreeBuilderTest { @Test fun `subcommand with directory without ending slash`() { - val dir = "someDir${File.separatorChar}" - mockFilePathsSuggestions(dir to listOf("file.txt", "folder${File.separatorChar}", "file")) + val dir = "someDir${separator}" + mockFilePathsSuggestions(dir to listOf("file.txt", "folder${separator}", "file")) doTest("withFiles", "${dir}folder") { assertSubcommandOf("withFiles", commandName) assertArgumentOfSubcommand("${dir}folder", "withFiles") @@ -301,7 +313,6 @@ internal class ShellCommandTreeBuilderTest { @Test fun `subcommand with two directory arguments`() { - val separator = File.separatorChar val someDir = "someDir$separator" val otherDir = "otherDir$separator" val nestedDir = "nestedDir$separator" @@ -380,10 +391,28 @@ internal class ShellCommandTreeBuilderTest { else ShellCommandResult.create("", exitCode = 1) } } + + val fileSystemSupport = object : ShellFileSystemSupport { + override suspend fun listDirectoryFiles(path: String): List { + val key = filePathSuggestions.keys.find { path.endsWith(it) || "$path${separator}".endsWith(it) } + return if (key != null) { + val suggestions = filePathSuggestions[key]!! + suggestions.map { it.toShellFileInfo(separator) } + } + else emptyList() + } + } + + val runtimeContextProvider = TestRuntimeContextProvider( + isReworkedTerminal = engine == TerminalEngine.REWORKED, + generatorCommandsRunner = generatorCommandsRunner, + fileSystemSupport = fileSystemSupport, + ) + val fixture = ShellCommandTreeBuilderFixture( TestCommandSpecsManager(spec), TestGeneratorsExecutor(), - TestRuntimeContextProvider(generatorCommandsRunner = generatorCommandsRunner) + runtimeContextProvider, ) fixture.buildCommandTreeAndTest(spec, arguments.toList(), assertions) } diff --git a/plugins/terminal/tests/src/com/intellij/terminal/tests/block/completion/ShellMakefileCompletionTest.kt b/plugins/terminal/tests/src/com/intellij/terminal/tests/block/completion/ShellMakefileCompletionTest.kt index 3bd1aa0d811f..874129d81f43 100644 --- a/plugins/terminal/tests/src/com/intellij/terminal/tests/block/completion/ShellMakefileCompletionTest.kt +++ b/plugins/terminal/tests/src/com/intellij/terminal/tests/block/completion/ShellMakefileCompletionTest.kt @@ -6,11 +6,12 @@ import com.intellij.terminal.completion.spec.ShellCompletionSuggestion import com.intellij.testFramework.fixtures.BasePlatformTestCase import kotlinx.coroutines.runBlocking import org.intellij.lang.annotations.Language +import org.jetbrains.plugins.terminal.TerminalEngine import org.jetbrains.plugins.terminal.block.completion.spec.specs.make.ShellMakeCommandSpec import org.jetbrains.plugins.terminal.testFramework.completion.ShellCompletionTestFixture import org.junit.Test import org.junit.runner.RunWith -import org.junit.runners.JUnit4 +import org.junit.runners.Parameterized @Language("makefile") private const val MAKEFILE: String = """ @@ -37,8 +38,14 @@ private const val INVALID_MAKEFILE: String = """ This is an incorrect Makefile """ -@RunWith(JUnit4::class) -class ShellMakefileCompletionTest : BasePlatformTestCase() { +@RunWith(Parameterized::class) +class ShellMakefileCompletionTest(private val engine: TerminalEngine) : BasePlatformTestCase() { + companion object { + @JvmStatic + @Parameterized.Parameters(name = "{0}") + fun engine(): List = listOf(TerminalEngine.REWORKED, TerminalEngine.NEW_TERMINAL) + } + private val commandName = "make" private val spec = ShellMakeCommandSpec.create() @@ -62,6 +69,7 @@ class ShellMakefileCompletionTest : BasePlatformTestCase() { private fun getMakefileSuggestions(makefile: String): List { val fixture = ShellCompletionTestFixture.builder(project) + .setIsReworkedTerminal(engine == TerminalEngine.REWORKED) .mockCommandSpecs(spec) .mockShellCommandResults { command -> if (command.startsWith("command cat ") || command.startsWith("cat ")) { diff --git a/plugins/terminal/tests/src/com/intellij/terminal/tests/reworked/util/TerminalTestUtil.kt b/plugins/terminal/tests/src/com/intellij/terminal/tests/reworked/util/TerminalTestUtil.kt index 49aebf0c36fb..7680898d557b 100644 --- a/plugins/terminal/tests/src/com/intellij/terminal/tests/reworked/util/TerminalTestUtil.kt +++ b/plugins/terminal/tests/src/com/intellij/terminal/tests/reworked/util/TerminalTestUtil.kt @@ -64,4 +64,11 @@ object TerminalTestUtil { this.set(prevValue) } } + + /** + * Can be used to provide parameter values for a parameterized test. + */ + fun enginesWithCompletionSupport(): List { + return listOf(TerminalEngine.REWORKED, TerminalEngine.NEW_TERMINAL) + } } \ No newline at end of file