mirror of
https://gitflic.ru/project/openide/openide.git
synced 2026-03-22 06:50:54 +07:00
EelLocalExecApiTest: use Kotlin helper instead of python.
There might be no Python, but java is always available. Helper is now written in Kotlin in `intellij.platform.tests.eelHelper` module. This module is executed by test. I couldn't use platform jline because old version doesn't play well with signals and new version comes with ffm compiled against jvm21 and can't be used in our platform. I had to pick up versions carefully skipping ffm. GitOrigin-RevId: e5d997f23122e7fa8b6703847cc324170fa2d382
This commit is contained in:
committed by
intellij-monorepo-bot
parent
41c068e2c0
commit
1b76e04151
38
.idea/libraries/jline_terminal_jansi.xml
generated
Normal file
38
.idea/libraries/jline_terminal_jansi.xml
generated
Normal file
@@ -0,0 +1,38 @@
|
||||
<component name="libraryTable">
|
||||
<library name="jline.terminal.jansi" type="repository">
|
||||
<properties maven-id="org.jline:jline-terminal-jansi:3.27.0">
|
||||
<verification>
|
||||
<artifact url="file://$MAVEN_REPOSITORY$/org/jline/jline-terminal-jansi/3.27.0/jline-terminal-jansi-3.27.0.jar">
|
||||
<sha256sum>a7b369a2e697215fed314a58b736f10ae8bdedf218096916a6bdb7bc886fe470</sha256sum>
|
||||
</artifact>
|
||||
<artifact url="file://$MAVEN_REPOSITORY$/org/fusesource/jansi/jansi/2.4.1/jansi-2.4.1.jar">
|
||||
<sha256sum>2e5e775a9dc58ffa6bbd6aa6f099d62f8b62dcdeb4c3c3bbbe5cf2301bc2dcc1</sha256sum>
|
||||
</artifact>
|
||||
<artifact url="file://$MAVEN_REPOSITORY$/org/jline/jline-terminal/3.27.0/jline-terminal-3.27.0.jar">
|
||||
<sha256sum>2f461c75091ec3810a42184afc80c96910f54e9dd2110e9ecb9902a5ee6c244b</sha256sum>
|
||||
</artifact>
|
||||
<artifact url="file://$MAVEN_REPOSITORY$/org/jline/jline-native/3.27.0/jline-native-3.27.0.jar">
|
||||
<sha256sum>d8bc77bc80edc7d9a02a06a069b2d7085629421db0843aba7c153a869ffe525d</sha256sum>
|
||||
</artifact>
|
||||
</verification>
|
||||
</properties>
|
||||
<CLASSES>
|
||||
<root url="jar://$MAVEN_REPOSITORY$/org/jline/jline-terminal-jansi/3.27.0/jline-terminal-jansi-3.27.0.jar!/" />
|
||||
<root url="jar://$MAVEN_REPOSITORY$/org/fusesource/jansi/jansi/2.4.1/jansi-2.4.1.jar!/" />
|
||||
<root url="jar://$MAVEN_REPOSITORY$/org/jline/jline-terminal/3.27.0/jline-terminal-3.27.0.jar!/" />
|
||||
<root url="jar://$MAVEN_REPOSITORY$/org/jline/jline-native/3.27.0/jline-native-3.27.0.jar!/" />
|
||||
</CLASSES>
|
||||
<JAVADOC>
|
||||
<root url="jar://$MAVEN_REPOSITORY$/org/jline/jline-terminal-jansi/3.27.0/jline-terminal-jansi-3.27.0-javadoc.jar!/" />
|
||||
<root url="jar://$MAVEN_REPOSITORY$/org/fusesource/jansi/jansi/2.4.1/jansi-2.4.1-javadoc.jar!/" />
|
||||
<root url="jar://$MAVEN_REPOSITORY$/org/jline/jline-terminal/3.27.0/jline-terminal-3.27.0-javadoc.jar!/" />
|
||||
<root url="jar://$MAVEN_REPOSITORY$/org/jline/jline-native/3.27.0/jline-native-3.27.0-javadoc.jar!/" />
|
||||
</JAVADOC>
|
||||
<SOURCES>
|
||||
<root url="jar://$MAVEN_REPOSITORY$/org/jline/jline-terminal-jansi/3.27.0/jline-terminal-jansi-3.27.0-sources.jar!/" />
|
||||
<root url="jar://$MAVEN_REPOSITORY$/org/fusesource/jansi/jansi/2.4.1/jansi-2.4.1-sources.jar!/" />
|
||||
<root url="jar://$MAVEN_REPOSITORY$/org/jline/jline-terminal/3.27.0/jline-terminal-3.27.0-sources.jar!/" />
|
||||
<root url="jar://$MAVEN_REPOSITORY$/org/jline/jline-native/3.27.0/jline-native-3.27.0-sources.jar!/" />
|
||||
</SOURCES>
|
||||
</library>
|
||||
</component>
|
||||
38
.idea/libraries/jline_terminal_jna.xml
generated
Normal file
38
.idea/libraries/jline_terminal_jna.xml
generated
Normal file
@@ -0,0 +1,38 @@
|
||||
<component name="libraryTable">
|
||||
<library name="jline.terminal.jna" type="repository">
|
||||
<properties maven-id="org.jline:jline-terminal-jna:3.27.0">
|
||||
<verification>
|
||||
<artifact url="file://$MAVEN_REPOSITORY$/org/jline/jline-terminal-jna/3.27.0/jline-terminal-jna-3.27.0.jar">
|
||||
<sha256sum>e7322e6d8dc1bd69f507b91865af741af4a95c649ccab36e79e8be89696bb432</sha256sum>
|
||||
</artifact>
|
||||
<artifact url="file://$MAVEN_REPOSITORY$/net/java/dev/jna/jna/5.15.0/jna-5.15.0.jar">
|
||||
<sha256sum>a564158d28ab5127fc6a958028ed54279fe0999662c46425b6a3b09a2a52094d</sha256sum>
|
||||
</artifact>
|
||||
<artifact url="file://$MAVEN_REPOSITORY$/org/jline/jline-terminal/3.27.0/jline-terminal-3.27.0.jar">
|
||||
<sha256sum>2f461c75091ec3810a42184afc80c96910f54e9dd2110e9ecb9902a5ee6c244b</sha256sum>
|
||||
</artifact>
|
||||
<artifact url="file://$MAVEN_REPOSITORY$/org/jline/jline-native/3.27.0/jline-native-3.27.0.jar">
|
||||
<sha256sum>d8bc77bc80edc7d9a02a06a069b2d7085629421db0843aba7c153a869ffe525d</sha256sum>
|
||||
</artifact>
|
||||
</verification>
|
||||
</properties>
|
||||
<CLASSES>
|
||||
<root url="jar://$MAVEN_REPOSITORY$/org/jline/jline-terminal-jna/3.27.0/jline-terminal-jna-3.27.0.jar!/" />
|
||||
<root url="jar://$MAVEN_REPOSITORY$/net/java/dev/jna/jna/5.15.0/jna-5.15.0.jar!/" />
|
||||
<root url="jar://$MAVEN_REPOSITORY$/org/jline/jline-terminal/3.27.0/jline-terminal-3.27.0.jar!/" />
|
||||
<root url="jar://$MAVEN_REPOSITORY$/org/jline/jline-native/3.27.0/jline-native-3.27.0.jar!/" />
|
||||
</CLASSES>
|
||||
<JAVADOC>
|
||||
<root url="jar://$MAVEN_REPOSITORY$/org/jline/jline-terminal-jna/3.27.0/jline-terminal-jna-3.27.0-javadoc.jar!/" />
|
||||
<root url="jar://$MAVEN_REPOSITORY$/net/java/dev/jna/jna/5.15.0/jna-5.15.0-javadoc.jar!/" />
|
||||
<root url="jar://$MAVEN_REPOSITORY$/org/jline/jline-terminal/3.27.0/jline-terminal-3.27.0-javadoc.jar!/" />
|
||||
<root url="jar://$MAVEN_REPOSITORY$/org/jline/jline-native/3.27.0/jline-native-3.27.0-javadoc.jar!/" />
|
||||
</JAVADOC>
|
||||
<SOURCES>
|
||||
<root url="jar://$MAVEN_REPOSITORY$/org/jline/jline-terminal-jna/3.27.0/jline-terminal-jna-3.27.0-sources.jar!/" />
|
||||
<root url="jar://$MAVEN_REPOSITORY$/net/java/dev/jna/jna/5.15.0/jna-5.15.0-sources.jar!/" />
|
||||
<root url="jar://$MAVEN_REPOSITORY$/org/jline/jline-terminal/3.27.0/jline-terminal-3.27.0-sources.jar!/" />
|
||||
<root url="jar://$MAVEN_REPOSITORY$/org/jline/jline-native/3.27.0/jline-native-3.27.0-sources.jar!/" />
|
||||
</SOURCES>
|
||||
</library>
|
||||
</component>
|
||||
1
.idea/modules.xml
generated
1
.idea/modules.xml
generated
@@ -647,6 +647,7 @@
|
||||
<module fileurl="file://$PROJECT_DIR$/platform/eel/intellij.platform.eel.iml" filepath="$PROJECT_DIR$/platform/eel/intellij.platform.eel.iml" />
|
||||
<module fileurl="file://$PROJECT_DIR$/platform/eelProvider/intellij.platform.eel.provider.iml" filepath="$PROJECT_DIR$/platform/eelProvider/intellij.platform.eel.provider.iml" />
|
||||
<module fileurl="file://$PROJECT_DIR$/platform/eel/intellij.platform.eel.tests.iml" filepath="$PROJECT_DIR$/platform/eel/intellij.platform.eel.tests.iml" />
|
||||
<module fileurl="file://$PROJECT_DIR$/platform/platform-tests/eel-helper/intellij.platform.eelHelper.iml" filepath="$PROJECT_DIR$/platform/platform-tests/eel-helper/intellij.platform.eelHelper.iml" />
|
||||
<module fileurl="file://$PROJECT_DIR$/platform/execution/intellij.platform.execution.iml" filepath="$PROJECT_DIR$/platform/execution/intellij.platform.execution.iml" />
|
||||
<module fileurl="file://$PROJECT_DIR$/platform/execution.dashboard/intellij.platform.execution.dashboard.iml" filepath="$PROJECT_DIR$/platform/execution.dashboard/intellij.platform.execution.dashboard.iml" />
|
||||
<module fileurl="file://$PROJECT_DIR$/platform/execution-impl/intellij.platform.execution.impl.iml" filepath="$PROJECT_DIR$/platform/execution-impl/intellij.platform.execution.impl.iml" />
|
||||
|
||||
@@ -639,6 +639,19 @@ object CommunityLibraryLicenses {
|
||||
.newBsd("https://opensource.org/license/bsd-3-clause/")
|
||||
.suppliedByOrganizations("Thai Open Source Software Center Ltd"),
|
||||
|
||||
LibraryLicense(null, libraryName = "jline.terminal", url = "https://github.com/jline/jline3")
|
||||
.newBsd("https://github.com/jline/jline3/blob/master/LICENSE.txt")
|
||||
.suppliedByPersons("Guillaume Nodet"),
|
||||
|
||||
LibraryLicense(null, libraryName = "jline.terminal.jansi", url = "https://github.com/jline/jline3")
|
||||
.newBsd("https://github.com/jline/jline3/blob/master/LICENSE.txt")
|
||||
.suppliedByPersons("Guillaume Nodet"),
|
||||
|
||||
LibraryLicense(null, libraryName = "jline.terminal.jna", url = "https://github.com/jline/jline3")
|
||||
.newBsd("https://github.com/jline/jline3/blob/master/LICENSE.txt")
|
||||
.suppliedByPersons("Guillaume Nodet"),
|
||||
|
||||
|
||||
LibraryLicense("JNA", libraryName = "jna", url = "https://github.com/java-native-access/jna")
|
||||
.apache("https://github.com/java-native-access/jna/blob/master/LICENSE"),
|
||||
|
||||
|
||||
5
platform/platform-tests/eel-helper/README.txt
Normal file
5
platform/platform-tests/eel-helper/README.txt
Normal file
@@ -0,0 +1,5 @@
|
||||
Helper for EEL local execution test
|
||||
1. prints hello into stderr
|
||||
2. prints tty and its size to stdout
|
||||
3. waits for command exit (exit 0) or sleep (sleep 15_000)
|
||||
4. installs signal for SIGINT to return 42
|
||||
0
platform/platform-tests/eel-helper/api-dump.txt
Normal file
0
platform/platform-tests/eel-helper/api-dump.txt
Normal file
@@ -0,0 +1,46 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<module type="JAVA_MODULE" version="4">
|
||||
<component name="NewModuleRootManager" inherit-compiler-output="true">
|
||||
<exclude-output />
|
||||
<content url="file://$MODULE_DIR$">
|
||||
<sourceFolder url="file://$MODULE_DIR$/src" isTestSource="false" />
|
||||
</content>
|
||||
<orderEntry type="inheritedJdk" />
|
||||
<orderEntry type="sourceFolder" forTests="false" />
|
||||
<orderEntry type="library" name="kotlin-stdlib" level="project" />
|
||||
<orderEntry type="library" name="jackson" level="project" />
|
||||
<orderEntry type="library" name="jackson-module-kotlin" level="project" />
|
||||
<orderEntry type="library" name="jackson-databind" level="project" />
|
||||
<orderEntry type="library" name="kotlin-reflect" level="project" />
|
||||
<orderEntry type="module" module-name="intellij.platform.util.rt" />
|
||||
<orderEntry type="library" name="jetbrains-annotations" level="project" />
|
||||
<orderEntry type="module-library">
|
||||
<library name="jline.terminal" type="repository">
|
||||
<properties maven-id="org.jline:jline-terminal:3.27.0">
|
||||
<verification>
|
||||
<artifact url="file://$MAVEN_REPOSITORY$/org/jline/jline-terminal/3.27.0/jline-terminal-3.27.0.jar">
|
||||
<sha256sum>2f461c75091ec3810a42184afc80c96910f54e9dd2110e9ecb9902a5ee6c244b</sha256sum>
|
||||
</artifact>
|
||||
<artifact url="file://$MAVEN_REPOSITORY$/org/jline/jline-native/3.27.0/jline-native-3.27.0.jar">
|
||||
<sha256sum>d8bc77bc80edc7d9a02a06a069b2d7085629421db0843aba7c153a869ffe525d</sha256sum>
|
||||
</artifact>
|
||||
</verification>
|
||||
</properties>
|
||||
<CLASSES>
|
||||
<root url="jar://$MAVEN_REPOSITORY$/org/jline/jline-terminal/3.27.0/jline-terminal-3.27.0.jar!/" />
|
||||
<root url="jar://$MAVEN_REPOSITORY$/org/jline/jline-native/3.27.0/jline-native-3.27.0.jar!/" />
|
||||
</CLASSES>
|
||||
<JAVADOC>
|
||||
<root url="jar://$MAVEN_REPOSITORY$/org/jline/jline-terminal/3.27.0/jline-terminal-3.27.0-javadoc.jar!/" />
|
||||
<root url="jar://$MAVEN_REPOSITORY$/org/jline/jline-native/3.27.0/jline-native-3.27.0-javadoc.jar!/" />
|
||||
</JAVADOC>
|
||||
<SOURCES>
|
||||
<root url="jar://$MAVEN_REPOSITORY$/org/jline/jline-terminal/3.27.0/jline-terminal-3.27.0-sources.jar!/" />
|
||||
<root url="jar://$MAVEN_REPOSITORY$/org/jline/jline-native/3.27.0/jline-native-3.27.0-sources.jar!/" />
|
||||
</SOURCES>
|
||||
</library>
|
||||
</orderEntry>
|
||||
<orderEntry type="library" name="jline.terminal.jansi" level="project" />
|
||||
<orderEntry type="library" name="jline.terminal.jna" level="project" />
|
||||
</component>
|
||||
</module>
|
||||
@@ -0,0 +1,3 @@
|
||||
Manifest-Version: 1.0
|
||||
Main-Class: com.intellij.platform.tests.eelHelper.EelHelper
|
||||
|
||||
@@ -0,0 +1,20 @@
|
||||
// Copyright 2000-2024 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
|
||||
package com.intellij.platform.tests.eelHelper
|
||||
|
||||
import org.jetbrains.annotations.TestOnly
|
||||
|
||||
/**
|
||||
* Commands to be sent from test to this helper
|
||||
*/
|
||||
@TestOnly
|
||||
enum class Command {
|
||||
/**
|
||||
* Exit with [GRACEFUL_EXIT_CODE]
|
||||
*/
|
||||
EXIT,
|
||||
|
||||
/**
|
||||
* Sleep for a long time
|
||||
*/
|
||||
SLEEP
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
// Copyright 2000-2024 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
|
||||
package com.intellij.platform.tests.eelHelper;
|
||||
|
||||
import org.jetbrains.annotations.TestOnly;
|
||||
|
||||
import static com.intellij.platform.tests.eelHelper.ImplKt.startHelper;
|
||||
|
||||
/**
|
||||
* Helper that is run by EEL test: should react on signals and commands
|
||||
*/
|
||||
@TestOnly
|
||||
public final class EelHelper {
|
||||
private EelHelper() {
|
||||
}
|
||||
|
||||
@TestOnly
|
||||
public static void main(String[] args) {
|
||||
startHelper();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,4 @@
|
||||
// Copyright 2000-2024 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
|
||||
package com.intellij.platform.tests.eelHelper
|
||||
|
||||
data class Size(val cols:Int, val rows: Int)
|
||||
@@ -0,0 +1,19 @@
|
||||
// Copyright 2000-2024 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
|
||||
package com.intellij.platform.tests.eelHelper
|
||||
|
||||
import com.fasterxml.jackson.databind.ObjectMapper
|
||||
import com.fasterxml.jackson.module.kotlin.registerKotlinModule
|
||||
import org.jetbrains.annotations.TestOnly
|
||||
|
||||
@TestOnly
|
||||
data class TTYState(val size: Size?) {
|
||||
companion object {
|
||||
private val mapper = ObjectMapper().registerKotlinModule()
|
||||
|
||||
@TestOnly
|
||||
fun deserialize(str: String): TTYState = mapper.readValue(str, TTYState::class.java)
|
||||
}
|
||||
|
||||
@TestOnly
|
||||
fun serialize(): String = mapper.writeValueAsString(this)
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
// Copyright 2000-2024 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
|
||||
package com.intellij.platform.tests.eelHelper
|
||||
|
||||
import org.jetbrains.annotations.TestOnly
|
||||
|
||||
/**
|
||||
* Messages to be printed to stderr by this helper
|
||||
*/
|
||||
@TestOnly
|
||||
const val HELLO: String = "hello"
|
||||
|
||||
/**
|
||||
* Exit code to be used on `SIGINT`
|
||||
*/
|
||||
|
||||
@TestOnly
|
||||
const val INTERRUPT_EXIT_CODE: Int = 42
|
||||
|
||||
/**
|
||||
* Exit code for [Command.EXIT]
|
||||
*/
|
||||
@TestOnly
|
||||
const val GRACEFUL_EXIT_CODE: Int = 0
|
||||
@@ -0,0 +1,57 @@
|
||||
// Copyright 2000-2024 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
|
||||
package com.intellij.platform.tests.eelHelper
|
||||
|
||||
import org.jetbrains.annotations.TestOnly
|
||||
import org.jline.terminal.Terminal
|
||||
import org.jline.terminal.TerminalBuilder
|
||||
import sun.misc.Signal
|
||||
import sun.misc.SignalHandler
|
||||
import kotlin.system.exitProcess
|
||||
|
||||
@TestOnly
|
||||
private object OnSigint : SignalHandler, Terminal.SignalHandler {
|
||||
override fun handle(sig: Signal?) {
|
||||
onSigInt()
|
||||
}
|
||||
|
||||
override fun handle(signal: Terminal.Signal?) {
|
||||
onSigInt()
|
||||
}
|
||||
|
||||
private fun onSigInt() {
|
||||
exitProcess(INTERRUPT_EXIT_CODE)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@TestOnly
|
||||
internal fun startHelper() {
|
||||
|
||||
// Exit once SIGINT sent without terminal
|
||||
Signal.handle(Signal("INT"), OnSigint)
|
||||
|
||||
val terminal = TerminalBuilder.terminal()
|
||||
val terminalSize = if (terminal.type == Terminal.TYPE_DUMB) null else terminal.size
|
||||
|
||||
// Exit once SIGINT sent with terminal
|
||||
terminal.handle(Terminal.Signal.INT, OnSigint)
|
||||
|
||||
// First thing to do is to print this to the stderr
|
||||
System.err.print(HELLO + "\n")
|
||||
System.err.flush()
|
||||
|
||||
val ttyState = TTYState(terminalSize?.let{ Size(cols = it.columns, rows = it.rows )})
|
||||
|
||||
// Then, print terminal info to the stdout
|
||||
println(ttyState.serialize())
|
||||
System.out.flush()
|
||||
|
||||
when (Command.valueOf(readln().trim().uppercase())) {
|
||||
Command.EXIT -> {
|
||||
exitProcess(GRACEFUL_EXIT_CODE)
|
||||
}
|
||||
Command.SLEEP -> {
|
||||
Thread.sleep(15_000)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,5 @@
|
||||
// Copyright 2000-2024 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
|
||||
@ApiStatus.Internal
|
||||
package com.intellij.platform.tests.eelHelper;
|
||||
|
||||
import org.jetbrains.annotations.ApiStatus;
|
||||
@@ -107,5 +107,6 @@
|
||||
<orderEntry type="library" scope="TEST" name="rhino" level="project" />
|
||||
<orderEntry type="module" module-name="intellij.platform.eel.provider" />
|
||||
<orderEntry type="library" scope="TEST" name="JUnit5Pioneer" level="project" />
|
||||
<orderEntry type="module" module-name="intellij.platform.eelHelper" scope="TEST" />
|
||||
</component>
|
||||
</module>
|
||||
@@ -1,64 +1,46 @@
|
||||
// Copyright 2000-2024 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
|
||||
package com.intellij.execution.eel
|
||||
|
||||
import com.google.gson.Gson
|
||||
import com.intellij.execution.process.UnixSignal
|
||||
import com.intellij.openapi.util.SystemInfoRt
|
||||
import com.intellij.platform.eel.EelExecApi
|
||||
import com.intellij.platform.eel.EelExecApi.Pty
|
||||
import com.intellij.platform.eel.EelProcess
|
||||
import com.intellij.platform.eel.EelResult
|
||||
import com.intellij.platform.eel.impl.local.EelLocalExecApi
|
||||
import com.intellij.testFramework.UsefulTestCase.IS_UNDER_TEAMCITY
|
||||
import com.intellij.platform.tests.eelHelper.*
|
||||
import com.intellij.platform.tests.eelHelper.Size
|
||||
import com.intellij.testFramework.common.timeoutRunBlocking
|
||||
import com.intellij.testFramework.junit5.TestApplication
|
||||
import com.intellij.util.io.write
|
||||
import io.ktor.util.decodeString
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.withContext
|
||||
import kotlinx.coroutines.withTimeoutOrNull
|
||||
import org.hamcrest.CoreMatchers
|
||||
import org.hamcrest.CoreMatchers.anyOf
|
||||
import org.hamcrest.CoreMatchers.`is`
|
||||
import org.hamcrest.MatcherAssert.assertThat
|
||||
import org.junit.jupiter.api.Assertions
|
||||
import org.junit.jupiter.api.Assumptions.assumeFalse
|
||||
import org.junit.jupiter.api.BeforeAll
|
||||
import org.junit.jupiter.api.BeforeEach
|
||||
import org.junit.jupiter.api.io.TempDir
|
||||
import org.junitpioneer.jupiter.cartesian.CartesianTest
|
||||
import java.nio.file.Path
|
||||
import kotlin.io.path.isExecutable
|
||||
import java.nio.ByteBuffer
|
||||
import kotlin.test.assertEquals
|
||||
import kotlin.test.assertNotEquals
|
||||
import kotlin.time.Duration.Companion.minutes
|
||||
import kotlin.time.Duration.Companion.seconds
|
||||
|
||||
@TestApplication
|
||||
class EelLocalExecApiTest {
|
||||
companion object {
|
||||
const val PYTHON_ENV = "PYTHON"
|
||||
private const val PTY_COLS = 42
|
||||
private const val PTY_ROWS = 24
|
||||
private val NEW_LINES = Regex("\r?\n")
|
||||
|
||||
// TODO: Remove as soon as we migrate to kotlin script from the python
|
||||
private lateinit var executor: JavaMainClassExecutor
|
||||
|
||||
@BeforeAll
|
||||
@JvmStatic
|
||||
fun skipTestOnTcWindows() {
|
||||
assumeFalse(SystemInfoRt.isWindows && IS_UNDER_TEAMCITY, "Test is disabled on TC@WIN as there is no python by default there")
|
||||
}
|
||||
}
|
||||
|
||||
private data class TtySize(val cols: Int, val rows: Int)
|
||||
private data class PythonOutput(
|
||||
val tty: Boolean,
|
||||
val size: TtySize?,
|
||||
)
|
||||
|
||||
private val helperContent = EelLocalExecApiTest::class.java.classLoader.getResource("helper.py")!!.readBytes()
|
||||
|
||||
// TODO: This tests depends on python interpreter. Rewrite to kotlin script
|
||||
private val python = Path.of(System.getenv(PYTHON_ENV)
|
||||
?: if (!SystemInfoRt.isWindows) "/usr/bin/python3" else error("Provide $PYTHON_ENV env var with path to python"))
|
||||
|
||||
@BeforeEach
|
||||
fun setUp() {
|
||||
assert(python.isExecutable()) {
|
||||
"Can't find python or $python isn't executable. Please set $PYTHON_ENV env var to the path to python binary"
|
||||
fun createExecutor() {
|
||||
executor = JavaMainClassExecutor(EelHelper::class.java)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -73,18 +55,15 @@ class EelLocalExecApiTest {
|
||||
|
||||
|
||||
/**
|
||||
* Test runs `helper.py` checking stdin/stdout iteration, exit code, tty and signal/termination handling.
|
||||
* Test runs [EelHelper] checking stdin/stdout iteration, exit code, tty and signal/termination handling.
|
||||
*/
|
||||
@CartesianTest
|
||||
fun testOutput(
|
||||
@CartesianTest.Enum exitType: ExitType,
|
||||
@CartesianTest.Enum ptyManagement: PTYManagement,
|
||||
@TempDir tempDir: Path,
|
||||
): Unit = timeoutRunBlocking {
|
||||
val helperScript = tempDir.resolve("helper.py")
|
||||
helperScript.write(helperContent)
|
||||
): Unit = timeoutRunBlocking(1.minutes) {
|
||||
|
||||
val builder = EelExecApi.executeProcessBuilder(python.toString()).args(listOf(helperScript.toString()))
|
||||
val builder = executor.createBuilderToExecuteMain()
|
||||
builder.pty(when (ptyManagement) {
|
||||
PTYManagement.NO_PTY -> null
|
||||
PTYManagement.PTY_SIZE_FROM_START -> Pty(PTY_COLS, PTY_ROWS, true)
|
||||
@@ -111,19 +90,29 @@ class EelLocalExecApiTest {
|
||||
}
|
||||
}
|
||||
|
||||
Assertions.assertEquals("hello", process.stderr.receive().decodeToString().trim())
|
||||
withContext(Dispatchers.Default) {
|
||||
val text = ByteBuffer.allocate(1024)
|
||||
withTimeoutOrNull(10.seconds) {
|
||||
for (chunk in process.stderr) {
|
||||
text.put(chunk)
|
||||
if (HELLO in chunk.decodeToString()) break
|
||||
}
|
||||
}
|
||||
text.limit(text.position()).rewind()
|
||||
assertThat("No $HELLO reported in stderr", text.decodeString(), CoreMatchers.containsString(HELLO))
|
||||
}
|
||||
|
||||
|
||||
// Test tty api
|
||||
// tty might insert "\r\n", we need to remove them. Hence, NEW_LINES.
|
||||
val pyOutputStr = process.stdout.receive().decodeToString().replace(NEW_LINES, "")
|
||||
val pyOutputObj = Gson().fromJson<PythonOutput>(pyOutputStr, PythonOutput::class.java)
|
||||
val outputStr = process.stdout.receive().decodeToString().replace(NEW_LINES, "")
|
||||
val pyOutputObj = TTYState.deserialize(outputStr)
|
||||
when (ptyManagement) {
|
||||
PTYManagement.PTY_SIZE_FROM_START, PTYManagement.PTY_RESIZE_LATER -> {
|
||||
Assertions.assertTrue(pyOutputObj.tty)
|
||||
Assertions.assertEquals(TtySize(PTY_COLS, PTY_ROWS), pyOutputObj.size, "size must be set for tty")
|
||||
Assertions.assertNotNull(pyOutputObj.size)
|
||||
Assertions.assertEquals(Size(PTY_COLS, PTY_ROWS), pyOutputObj.size, "size must be set for tty")
|
||||
}
|
||||
PTYManagement.NO_PTY -> {
|
||||
Assertions.assertFalse(pyOutputObj.tty)
|
||||
Assertions.assertNull(pyOutputObj.size, "size must not be set if no tty")
|
||||
}
|
||||
}
|
||||
@@ -135,13 +124,12 @@ class EelLocalExecApiTest {
|
||||
ExitType.TERMINATE -> process.terminate()
|
||||
ExitType.INTERRUPT -> {
|
||||
// Terminate sleep with interrupt/CTRL+C signal
|
||||
process.stdin.send("sleep\n".encodeToByteArray())
|
||||
assertEquals("sleeping", process.stdout.receive().decodeToString().trim())
|
||||
process.sendCommand(Command.SLEEP)
|
||||
process.interrupt()
|
||||
}
|
||||
ExitType.EXIT_WITH_COMMAND -> {
|
||||
// Just command to ask script return gracefully
|
||||
process.stdin.send("exit\n".encodeToByteArray())
|
||||
process.sendCommand(Command.EXIT)
|
||||
}
|
||||
}
|
||||
val exitCode = process.exitCode.await()
|
||||
@@ -161,13 +149,27 @@ class EelLocalExecApiTest {
|
||||
}
|
||||
}
|
||||
ExitType.INTERRUPT -> {
|
||||
assertEquals(42, exitCode) // CTRL+C/SIGINT handler returns 42, see script
|
||||
when (ptyManagement) {
|
||||
PTYManagement.NO_PTY -> Unit // SIGINT is doubtful without PTY especially without console on Windows
|
||||
PTYManagement.PTY_SIZE_FROM_START, PTYManagement.PTY_RESIZE_LATER -> {
|
||||
// CTRL+C/SIGINT handler returns 42, see script
|
||||
assertEquals(INTERRUPT_EXIT_CODE, exitCode)
|
||||
}
|
||||
}
|
||||
}
|
||||
ExitType.EXIT_WITH_COMMAND -> {
|
||||
assertEquals(0, exitCode) // Graceful exit
|
||||
assertEquals(GRACEFUL_EXIT_CODE, exitCode) // Graceful exit
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Sends [command] to the helper and flush
|
||||
*/
|
||||
private suspend fun EelProcess.sendCommand(command: Command) {
|
||||
val text = command.name + "\n"
|
||||
stdin.send(text.encodeToByteArray())
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,100 @@
|
||||
// Copyright 2000-2024 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
|
||||
package com.intellij.execution.eel
|
||||
|
||||
import com.intellij.application.options.PathMacrosImpl
|
||||
import com.intellij.openapi.application.PathManager
|
||||
import com.intellij.openapi.diagnostic.fileLogger
|
||||
import com.intellij.openapi.util.SystemInfoRt
|
||||
import com.intellij.platform.eel.EelExecApi
|
||||
import com.intellij.util.SystemProperties
|
||||
import org.jetbrains.jps.model.java.JpsJavaExtensionService
|
||||
import org.jetbrains.jps.model.library.JpsOrderRootType
|
||||
import org.jetbrains.jps.model.module.JpsModule
|
||||
import org.jetbrains.jps.model.serialization.JpsSerializationManager
|
||||
import java.io.File
|
||||
import java.nio.file.Path
|
||||
import kotlin.io.path.Path
|
||||
import kotlin.io.path.exists
|
||||
import kotlin.io.path.name
|
||||
import kotlin.io.path.pathString
|
||||
|
||||
|
||||
/**
|
||||
* Searches for module with [clazz] in [PathManager.ourHomePath] an executes [clazz] `main` with all dependencies
|
||||
*/
|
||||
internal class JavaMainClassExecutor(clazz: Class<*>) {
|
||||
private val exe = Path(ProcessHandle.current().info().command().get()).toString()
|
||||
private val env = mapOf("CLASSPATH" to getClassPathForClass(clazz))
|
||||
private val args = listOf(clazz.canonicalName)
|
||||
|
||||
/**
|
||||
* Execute `main` method
|
||||
*/
|
||||
fun createBuilderToExecuteMain(): EelExecApi.ExecuteProcessBuilder = EelExecApi.executeProcessBuilder(exe).env(env).args(args)
|
||||
|
||||
private companion object {
|
||||
private fun getClassPathForClass(clazz: Class<*>): String {
|
||||
val mavenPath = Path(SystemProperties.getUserHome()).resolve(".m2").resolve("repository").toString()
|
||||
val helperModuleOutputPath = getJpsModuleOutputPathForClass(clazz)
|
||||
logger.value.info("helper module path: $helperModuleOutputPath")
|
||||
val helperModuleName = helperModuleOutputPath.name
|
||||
|
||||
val helperModule = module(helperModuleName)
|
||||
|
||||
|
||||
var dependencies = JpsJavaExtensionService
|
||||
.dependencies(helperModule)
|
||||
.recursively()
|
||||
|
||||
val libraries = dependencies
|
||||
.libraries
|
||||
.flatMap { it.getPaths(JpsOrderRootType.COMPILED) }
|
||||
.map { Path(it.pathString.replace('$' + PathMacrosImpl.MAVEN_REPOSITORY + '$', mavenPath)) }
|
||||
val modulesOutputPath = helperModuleOutputPath.parent
|
||||
|
||||
val modules = dependencies
|
||||
.modules
|
||||
.map { module -> modulesOutputPath.resolve(module.name) }
|
||||
|
||||
return (modules + libraries)
|
||||
.filter { path ->
|
||||
path.exists().also {
|
||||
if (!it) {
|
||||
logger.value.info("$path doesn't exist")
|
||||
}
|
||||
}
|
||||
}
|
||||
.joinToString(File.pathSeparator)
|
||||
}
|
||||
|
||||
private fun module(helperModuleName: String): JpsModule {
|
||||
for (homePath in arrayOf(PathManager.getHomePath(), PathManager.getCommunityHomePath())) {
|
||||
val jpsProject = JpsSerializationManager.getInstance().loadProject(homePath, mapOf())
|
||||
val helperModule = jpsProject.modules.firstOrNull { module -> module.name == helperModuleName }
|
||||
if (helperModule != null) return helperModule
|
||||
logger.value.warn("$helperModuleName not found in $homePath modules. Checked: ${jpsProject.modules}")
|
||||
}
|
||||
throw AssertionError("Couldn't find module $helperModuleName")
|
||||
}
|
||||
|
||||
|
||||
private fun getJpsModuleOutputPathForClass(clazz: Class<*>): Path {
|
||||
val classFile = clazz.name.replace(".", "/") + ".class"
|
||||
|
||||
val absoluteClassPath = clazz.classLoader.getResource(classFile)!!.path.let { path ->
|
||||
Path(if (SystemInfoRt.isWindows) path.trimStart('/') else path)
|
||||
}
|
||||
logger.value.info("Looking for class file $absoluteClassPath")
|
||||
|
||||
val relativeClassPath = Path(classFile)
|
||||
var pathToModule = absoluteClassPath
|
||||
while (pathToModule.toList().size > 1) {
|
||||
if (pathToModule.resolve(relativeClassPath).exists()) break
|
||||
pathToModule = pathToModule.parent
|
||||
}
|
||||
return pathToModule
|
||||
}
|
||||
|
||||
private val logger = lazy { fileLogger() }
|
||||
}
|
||||
}
|
||||
@@ -1,42 +0,0 @@
|
||||
# Script for EEL local execution test
|
||||
# 1.prints tty and its size
|
||||
# 2.waits for command exit (exit 0) or sleep (sleep 10_000)
|
||||
# 3. installs signal for SIGINT to return 42
|
||||
import os
|
||||
import signal
|
||||
import sys
|
||||
import json
|
||||
from time import sleep
|
||||
|
||||
|
||||
def exit_42(*_):
|
||||
exit(42)
|
||||
|
||||
signal.signal(signal.SIGINT, exit_42)
|
||||
|
||||
is_tty = sys.stdin.isatty()
|
||||
terminal_size = None
|
||||
|
||||
try:
|
||||
terminal_size = os.get_terminal_size()
|
||||
except OSError:
|
||||
pass
|
||||
|
||||
|
||||
sys.stderr.write("hello\n")
|
||||
sys.stderr.flush()
|
||||
|
||||
json.dump({
|
||||
"tty": is_tty,
|
||||
"size": {"cols": terminal_size.columns, "rows":terminal_size.lines} if terminal_size else None
|
||||
}, sys.stdout)
|
||||
sys.stdout.flush()
|
||||
|
||||
|
||||
command = input().strip()
|
||||
if command == "exit":
|
||||
exit(0)
|
||||
elif command == "sleep":
|
||||
print("sleeping")
|
||||
sys.stdout.flush()
|
||||
sleep(10_000)
|
||||
Reference in New Issue
Block a user