[terminal] IJPL-220311 Fix terminal completion tests

Run them with both Reworked and Experimental terminal engines.

GitOrigin-RevId: 36c627f7431ac7fe32528d9273d9d276012319b4
This commit is contained in:
Konstantin Hudyakov
2025-12-19 12:42:20 +00:00
committed by intellij-monorepo-bot
parent 2d9979a0f3
commit 23fe717328
7 changed files with 122 additions and 31 deletions
@@ -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 <T : Any> doExecuteGenerator(context: ShellRuntimeContext, generator: ShellRuntimeDataGenerator<T>): T? {
return try {
generator.generate(context)
@@ -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()
}
@@ -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<TerminalEngine> = 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<String>, 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<ShellFileInfo> {
return files.map { it.toShellFileInfo(separator) }
}
})
.build()
}
}
@@ -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<TerminalEngine> = TerminalTestUtil.enginesWithCompletionSupport()
}
private val commandName = "command"
/**
@@ -29,6 +40,7 @@ internal class ShellCommandSpecSuggestionsTest {
* Long story short: Use to mock `ls`.
*/
private var filePathSuggestions: List<String> = 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<ShellFileInfo> {
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
}
@@ -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<TerminalEngine> = TerminalTestUtil.enginesWithCompletionSupport()
}
private val commandName = "command"
private var filePathSuggestions: Map<String, List<String>> = 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<ShellFileInfo> {
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)
}
@@ -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<TerminalEngine> = 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<ShellCompletionSuggestion> {
val fixture = ShellCompletionTestFixture.builder(project)
.setIsReworkedTerminal(engine == TerminalEngine.REWORKED)
.mockCommandSpecs(spec)
.mockShellCommandResults { command ->
if (command.startsWith("command cat ") || command.startsWith("cat ")) {
@@ -64,4 +64,11 @@ object TerminalTestUtil {
this.set(prevValue)
}
}
/**
* Can be used to provide parameter values for a parameterized test.
*/
fun enginesWithCompletionSupport(): List<TerminalEngine> {
return listOf(TerminalEngine.REWORKED, TerminalEngine.NEW_TERMINAL)
}
}