Python: separate api and impl in ExecService

GitOrigin-RevId: b390c2c626fe14fc576da34de5b6ecd360bf6fd5
This commit is contained in:
Ilya.Kazakevich
2025-05-09 04:08:10 +02:00
committed by intellij-monorepo-bot
parent ad39be4f1a
commit 924a192984
4 changed files with 35 additions and 21 deletions

View File

@@ -0,0 +1,21 @@
// Copyright 2000-2025 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
package com.intellij.python.community.execService.impl
import com.intellij.platform.eel.EelProcess
import com.intellij.platform.eel.provider.utils.EelProcessExecutionResult
import com.intellij.platform.eel.provider.utils.awaitProcessResult
import com.intellij.python.community.execService.CustomErrorMessage
import com.intellij.python.community.execService.ProcessInteractiveHandler
import com.intellij.python.community.execService.ProcessSemiInteractiveFun
import com.jetbrains.python.Result
import com.jetbrains.python.mapError
internal class ProcessSemiInteractiveHandlerImpl<T>(private val code: ProcessSemiInteractiveFun<T>) : ProcessInteractiveHandler<T> {
override suspend fun getResultFromProcess(process: EelProcess): Result<T, Pair<EelProcessExecutionResult, CustomErrorMessage?>> {
val result = code(process.stdin, process.exitCode)
val processOutput = process.awaitProcessResult()
return result.mapError { customErrorMessage ->
Pair(processOutput, customErrorMessage)
}
}
}

View File

@@ -4,9 +4,8 @@ package com.intellij.python.community.execService
import com.intellij.platform.eel.EelProcess
import com.intellij.platform.eel.channels.EelSendChannel
import com.intellij.platform.eel.provider.utils.EelProcessExecutionResult
import com.intellij.platform.eel.provider.utils.awaitProcessResult
import com.intellij.python.community.execService.impl.ProcessSemiInteractiveHandlerImpl
import com.jetbrains.python.Result
import com.jetbrains.python.mapError
import kotlinx.coroutines.Deferred
import org.jetbrains.annotations.Nls
import java.io.IOException
@@ -19,7 +18,7 @@ typealias CustomErrorMessage = @Nls String
/**
* In most cases you need [ProcessSemiInteractiveHandler]
* In most cases you need [processSemiInteractiveHandler]
*/
fun interface ProcessInteractiveHandler<T> {
/**
@@ -31,21 +30,13 @@ fun interface ProcessInteractiveHandler<T> {
}
/**
* [ProcessInteractiveHandler], but you do not have to collect output by yourself. You only have access to stdout and exit code.
* So, you can only *write* something to process.
*/
class ProcessSemiInteractiveHandler<T>(private val code: ProcessSemiInteractiveFun<T>) : ProcessInteractiveHandler<T> {
override suspend fun getResultFromProcess(process: EelProcess): Result<T, Pair<EelProcessExecutionResult, CustomErrorMessage?>> {
val result = code(process.stdin, process.exitCode)
val processOutput = process.awaitProcessResult()
return result.mapError { customErrorMessage ->
Pair(processOutput, customErrorMessage)
}
}
}
/**
* Process stdout -> result
*/
typealias ProcessSemiInteractiveFun<T> = suspend (EelSendChannel<IOException>, Deferred<Int>) -> Result<T, CustomErrorMessage?>
/**
* [ProcessInteractiveHandler], but you do not have to collect output by yourself. You only have access to stdout and exit code.
* So, you can only *write* something to process.
*/
fun <T> processSemiInteractiveHandler(code: ProcessSemiInteractiveFun<T>): ProcessInteractiveHandler<T> = ProcessSemiInteractiveHandlerImpl(code)

View File

@@ -13,18 +13,21 @@ import com.intellij.platform.testFramework.junit5.eel.params.api.EelSource
import com.intellij.platform.testFramework.junit5.eel.params.api.TestApplicationWithEel
import com.intellij.python.community.execService.ExecService
import com.intellij.python.community.execService.ProcessInteractiveHandler
import com.intellij.python.community.execService.ProcessSemiInteractiveHandler
import com.intellij.python.community.execService.WhatToExec
import com.intellij.python.community.execService.processSemiInteractiveHandler
import com.intellij.testFramework.common.timeoutRunBlocking
import com.jetbrains.python.Result
import com.jetbrains.python.getOrThrow
import kotlinx.coroutines.async
import kotlinx.coroutines.delay
import kotlinx.coroutines.launch
import org.hamcrest.CoreMatchers
import org.hamcrest.MatcherAssert.assertThat
import org.junit.jupiter.api.Assertions.*
import org.junit.jupiter.api.condition.OS
import org.junit.jupiter.params.ParameterizedTest
import org.junitpioneer.jupiter.cartesian.CartesianTest
import kotlin.time.Duration.Companion.minutes
/**
* How to use [ExecService].
@@ -110,14 +113,13 @@ class ExecServiceShowCaseTest {
}
@CartesianTest
fun testSemiInteractive(
@EelSource eelHolder: EelHolder,
@CartesianTest.Values(booleans = [true, false]) sunny: Boolean,
): Unit = timeoutRunBlocking {
val messageToUser = "abc123"
val shell = eelHolder.eel.exec.getShell().first
val result = ExecService().executeInteractive(WhatToExec.Binary(shell.asNioPath()), emptyList(), processInteractiveHandler = ProcessSemiInteractiveHandler<Unit> { channel, exitCode ->
val result = ExecService().executeInteractive(WhatToExec.Binary(shell.asNioPath()), emptyList(), processInteractiveHandler = processSemiInteractiveHandler<Unit> { channel, exitCode ->
channel.sendWholeText("exit\n").getOrThrow()
assertEquals(0, exitCode.await(), "Wrong exit code")
if (sunny) {