mirror of
https://gitflic.ru/project/openide/openide.git
synced 2026-09-27 10:03:11 +07:00
PY-78951 Fix run a script with a configuration to a non exisiting .env file
GitOrigin-RevId: bdf1fd3e1e6c4af1e004e36897ed72937a3f01a0
This commit is contained in:
committed by
intellij-monorepo-bot
parent
943655cbdc
commit
908477caab
@@ -1,8 +1,6 @@
|
||||
// Copyright 2000-2024 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
|
||||
package com.jetbrains.python.run;
|
||||
|
||||
import com.intellij.codeWithMe.ClientId;
|
||||
import com.intellij.execution.ExecutionException;
|
||||
import com.intellij.execution.ExecutionResult;
|
||||
import com.intellij.execution.configurations.RunProfile;
|
||||
import com.intellij.execution.configurations.RunProfileState;
|
||||
@@ -12,16 +10,18 @@ import com.intellij.execution.runners.AsyncProgramRunner;
|
||||
import com.intellij.execution.runners.DefaultProgramRunnerKt;
|
||||
import com.intellij.execution.runners.ExecutionEnvironment;
|
||||
import com.intellij.execution.ui.RunContentDescriptor;
|
||||
import com.intellij.openapi.application.ApplicationManager;
|
||||
import com.intellij.openapi.application.ModalityState;
|
||||
import com.intellij.openapi.application.AppUIExecutor;
|
||||
import com.intellij.openapi.fileEditor.FileDocumentManager;
|
||||
import com.intellij.util.concurrency.AppExecutorUtil;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
import org.jetbrains.concurrency.AsyncPromise;
|
||||
import org.jetbrains.concurrency.CancellablePromise;
|
||||
import org.jetbrains.concurrency.Promise;
|
||||
import org.jetbrains.concurrency.Promises;
|
||||
|
||||
import java.util.concurrent.Callable;
|
||||
|
||||
public class PythonRunner extends AsyncProgramRunner<RunnerSettings> {
|
||||
@Override
|
||||
public @NotNull String getRunnerId() {
|
||||
@@ -37,12 +37,12 @@ public class PythonRunner extends AsyncProgramRunner<RunnerSettings> {
|
||||
* {@link PythonCommandLineState} inheritors must be ready to be called on any thread, so we can run then on the background thread.
|
||||
* Any other state must be invoked on EDT only
|
||||
*/
|
||||
private static void execute(@NotNull RunProfileState profileState, @NotNull Runnable runnable) {
|
||||
var clientIdRunnable = ClientId.decorateRunnable(runnable);
|
||||
private static <R> CancellablePromise<R> execute(@NotNull RunProfileState profileState, @NotNull Callable<R> supplier) {
|
||||
if (profileState instanceof PythonCommandLineState) {
|
||||
AppExecutorUtil.getAppExecutorService().execute(clientIdRunnable);
|
||||
} else {
|
||||
ApplicationManager.getApplication().invokeAndWait(clientIdRunnable);
|
||||
return executeInBgt(supplier);
|
||||
}
|
||||
else {
|
||||
return executeInEdt(supplier);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -58,24 +58,34 @@ public class PythonRunner extends AsyncProgramRunner<RunnerSettings> {
|
||||
|
||||
FileDocumentManager.getInstance().saveAllDocuments();
|
||||
|
||||
AsyncPromise<RunContentDescriptor> promise = new AsyncPromise<>();
|
||||
execute(state, () -> {
|
||||
try {
|
||||
|
||||
ExecutionResult executionResult;
|
||||
if (state instanceof PythonCommandLineState) {
|
||||
// TODO [cloud-api.python] profile functionality must be applied here:
|
||||
// - com.jetbrains.django.run.DjangoServerRunConfiguration.patchCommandLineFirst() - host:port is put in user data
|
||||
executionResult = ((PythonCommandLineState)state).execute(env.getExecutor());
|
||||
}
|
||||
else {
|
||||
executionResult = state.execute(env.getExecutor(), this);
|
||||
}
|
||||
ApplicationManager.getApplication().invokeLater(
|
||||
() -> promise.setResult(DefaultProgramRunnerKt.showRunContent(executionResult, env)),
|
||||
ModalityState.any());
|
||||
return execute(state, () -> {
|
||||
ExecutionResult executionResult;
|
||||
if (state instanceof PythonCommandLineState) {
|
||||
// TODO [cloud-api.python] profile functionality must be applied here:
|
||||
// - com.jetbrains.django.run.DjangoServerRunConfiguration.patchCommandLineFirst() - host:port is put in user data
|
||||
executionResult = ((PythonCommandLineState)state).execute(env.getExecutor());
|
||||
}
|
||||
catch (ExecutionException e) {
|
||||
else {
|
||||
executionResult = state.execute(env.getExecutor(), this);
|
||||
}
|
||||
return executionResult;
|
||||
}).thenAsync((ExecutionResult executionResult) -> {
|
||||
return executeInEdt(() -> DefaultProgramRunnerKt.showRunContent(executionResult, env));
|
||||
});
|
||||
}
|
||||
|
||||
private static <V> CancellablePromise<V> executeInEdt(Callable<V> callable) {
|
||||
return AppUIExecutor.onUiThread().submit(callable);
|
||||
}
|
||||
|
||||
private static <V> CancellablePromise<V> executeInBgt(Callable<V> callable) {
|
||||
AsyncPromise<V> promise = new AsyncPromise<>();
|
||||
AppExecutorUtil.getAppExecutorService().execute(() -> {
|
||||
try {
|
||||
V result = callable.call();
|
||||
promise.setResult(result);
|
||||
}
|
||||
catch (Exception e) {
|
||||
promise.setError(e);
|
||||
}
|
||||
});
|
||||
|
||||
+79
-13
@@ -15,12 +15,13 @@ import com.intellij.openapi.application.runInEdt
|
||||
import com.intellij.openapi.projectRoots.Sdk
|
||||
import com.intellij.openapi.util.Key
|
||||
import com.intellij.openapi.util.Ref
|
||||
import com.intellij.testFramework.UsefulTestCase.assertSameElements
|
||||
import com.jetbrains.env.PyEnvTestCase
|
||||
import com.jetbrains.env.PyExecutionFixtureTestTask
|
||||
import com.jetbrains.env.PyTestTask
|
||||
import com.jetbrains.python.run.PythonConfigurationType
|
||||
import com.jetbrains.python.run.PythonRunConfiguration
|
||||
import com.jetbrains.python.run.PythonRunner
|
||||
import org.assertj.core.api.Assertions.assertThat
|
||||
import org.junit.Assert
|
||||
import org.junit.Test
|
||||
import java.util.concurrent.CountDownLatch
|
||||
@@ -29,10 +30,59 @@ import java.util.concurrent.TimeUnit
|
||||
class PyRunTest : PyEnvTestCase() {
|
||||
@Test
|
||||
fun testInputRedirection() {
|
||||
test {
|
||||
val result = runConfiguration {
|
||||
redirectInput = true
|
||||
inputFile = "input.txt"
|
||||
}
|
||||
assertThat(result.output).hasSameElementsAs(listOf("100500\n")).describedAs("Output should match expected values")
|
||||
assertThat(result.state).isEqualTo(ProcessState.Finished(0))
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testRunSucceedsWhenEnvFileAbsent() {
|
||||
test {
|
||||
val result = runConfiguration {
|
||||
envFile = absent()
|
||||
}
|
||||
assertThat(result.state).isEqualTo(ProcessState.NotStarted)
|
||||
}
|
||||
}
|
||||
|
||||
// DSL entry-point bound to the test class to access protected helpers
|
||||
private fun test(block: TestDslBuilder.() -> Unit) {
|
||||
val builder = TestDslBuilder { runPythonTestWithException(it) }
|
||||
builder.block()
|
||||
}
|
||||
}
|
||||
|
||||
// --- Minimal DSL for tests in this file ---
|
||||
|
||||
private class RunConfigurationDsl {
|
||||
var envFile: EnvFileSpec = EnvFileSpec.None
|
||||
var redirectInput: Boolean? = null
|
||||
var inputFile: String? = null
|
||||
}
|
||||
|
||||
private sealed interface EnvFileSpec {
|
||||
data object None : EnvFileSpec
|
||||
data object Absent : EnvFileSpec
|
||||
}
|
||||
|
||||
private fun absent(): EnvFileSpec = EnvFileSpec.Absent
|
||||
|
||||
private class TestDslBuilder(private val executor: (PyTestTask) -> Unit) {
|
||||
fun runConfiguration(block: RunConfigurationDsl.() -> Unit): RunResult {
|
||||
val runCfg = RunConfigurationDsl().apply { block() }
|
||||
val output = mutableListOf<String>()
|
||||
var processState: ProcessState = ProcessState.NotStarted
|
||||
|
||||
// Execute the same scenario as input redirection test, but honoring the DSL
|
||||
val task = object : PyExecutionFixtureTestTask("/runConfig/inputRedirection") {
|
||||
override fun runTestOn(sdkHome: String, existingSdk: Sdk?) {
|
||||
val project = project
|
||||
val settings = RunManager.getInstance(project).createConfiguration("test", PythonConfigurationType::class.java)
|
||||
val settings = RunManager.getInstance(project).createConfiguration("test-missing-env", PythonConfigurationType::class.java)
|
||||
|
||||
val runConfiguration = settings.configuration as PythonRunConfiguration
|
||||
runConfiguration.apply {
|
||||
@@ -40,33 +90,42 @@ class PyRunTest : PyEnvTestCase() {
|
||||
this.sdk = existingSdk
|
||||
scriptName = "inputRedirection.py"
|
||||
workingDirectory = myFixture.tempDirPath
|
||||
isRedirectInput = true
|
||||
inputFile = "${myFixture.tempDirPath}/input.txt"
|
||||
// Input redirection: use DSL value if provided, default to true and input.txt (to keep older tests working)
|
||||
isRedirectInput = runCfg.redirectInput ?: false
|
||||
val input = runCfg.inputFile ?: "input.txt"
|
||||
inputFile = "${myFixture.tempDirPath}/$input"
|
||||
|
||||
when (runCfg.envFile) {
|
||||
is EnvFileSpec.Absent -> {
|
||||
// point to a non-existent .env file; run must still succeed
|
||||
envFilePaths = listOf("${myFixture.tempDirPath}/.env")
|
||||
}
|
||||
else -> Unit
|
||||
}
|
||||
}
|
||||
|
||||
val executor = DefaultRunExecutor.getRunExecutorInstance()
|
||||
val runner = ProgramRunner.getRunner(executor.id, settings.configuration) as PythonRunner
|
||||
val env = ExecutionEnvironment(executor, runner, settings, project)
|
||||
|
||||
var processNotStarted = false
|
||||
val connection = project.messageBus.connect()
|
||||
val latch = CountDownLatch(1)
|
||||
val output = mutableListOf<String>()
|
||||
val handlerRef = Ref<ProcessHandler>()
|
||||
connection.subscribe(ExecutionManager.EXECUTION_TOPIC, object : ExecutionListener {
|
||||
override fun processStarted(executorId: String, env: ExecutionEnvironment, handler: ProcessHandler) {
|
||||
super.processStarted(executorId, env, handler)
|
||||
processState = ProcessState.Started
|
||||
handlerRef.set(handler)
|
||||
}
|
||||
|
||||
override fun processTerminated(executorId: String, env: ExecutionEnvironment, handler: ProcessHandler, exitCode: Int) {
|
||||
super.processTerminated(executorId, env, handler, exitCode)
|
||||
processState = ProcessState.Finished(exitCode)
|
||||
latch.countDown()
|
||||
}
|
||||
|
||||
override fun processNotStarted(executorId: String, e: ExecutionEnvironment) {
|
||||
if (executorId == executor.id && e == env) {
|
||||
processNotStarted = true
|
||||
latch.countDown()
|
||||
}
|
||||
}
|
||||
@@ -82,6 +141,7 @@ class PyRunTest : PyEnvTestCase() {
|
||||
}
|
||||
|
||||
override fun processTerminated(event: ProcessEvent) {
|
||||
processState = ProcessState.Finished(event.exitCode)
|
||||
processHandler.removeProcessListener(this)
|
||||
latch.countDown()
|
||||
}
|
||||
@@ -94,13 +154,19 @@ class PyRunTest : PyEnvTestCase() {
|
||||
handlerRef.get()?.destroyProcess()
|
||||
Assert.assertTrue("Test frozen", await)
|
||||
connection.disconnect()
|
||||
if (processNotStarted) {
|
||||
Assert.fail("Failed to start the script")
|
||||
}
|
||||
assertSameElements("Incorrect output", output, listOf("100500\n"))
|
||||
}
|
||||
}
|
||||
|
||||
runPythonTestWithException(task)
|
||||
executor(task)
|
||||
return RunResult(output, processState)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private data class RunResult(val output: List<String>, val state: ProcessState)
|
||||
|
||||
// Represent the process state for the executed run configuration
|
||||
private sealed interface ProcessState {
|
||||
data object NotStarted : ProcessState
|
||||
data object Started : ProcessState
|
||||
data class Finished(val exitCode: Int) : ProcessState
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user