mirror of
https://gitflic.ru/project/openide/openide.git
synced 2026-09-18 09:34:34 +07:00
[python] (PY-78749) single dialog for all types of Python SDKs
+ old dialogs were removed GitOrigin-RevId: 00ed85ba578c6419373d8605ed50954e7aa58f0a
This commit is contained in:
committed by
intellij-monorepo-bot
parent
ab8455f916
commit
5e2028d129
@@ -1,6 +1,7 @@
|
||||
// Copyright 2000-2023 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
|
||||
package com.jetbrains.env.python.conda
|
||||
|
||||
import com.intellij.python.community.execService.BinaryToExec
|
||||
import com.intellij.util.io.delete
|
||||
import com.jetbrains.python.getOrThrow
|
||||
import com.jetbrains.python.psi.LanguageLevel
|
||||
@@ -27,11 +28,12 @@ internal class CondaYamlFileRule(private val condaRule: LocalCondaRule,
|
||||
override fun before() {
|
||||
val fullPathOnTarget = condaRule.condaPathOnTarget
|
||||
val command = PyCondaCommand(fullPathOnTarget, null, null)
|
||||
|
||||
val condaEnvRequest = NewCondaEnvRequest.EmptyNamedEnv(languageLevel, envName)
|
||||
val env = PyCondaEnvIdentity.NamedEnv(condaEnvRequest.envName)
|
||||
val yamlFileText = runBlocking {
|
||||
PyCondaEnv.createEnv(command, condaEnvRequest).getOrThrow()
|
||||
CondaExecutor.exportEnvironmentFile(Path.of(fullPathOnTarget), env)
|
||||
CondaExecutor.exportEnvironmentFile(command.asBinaryToExec(), env)
|
||||
}.getOrThrow()
|
||||
|
||||
val file = File.createTempFile("ijconda", ".yaml")
|
||||
|
||||
@@ -2,7 +2,12 @@
|
||||
package com.jetbrains.env.python.conda
|
||||
|
||||
import com.intellij.execution.target.FullPathOnTarget
|
||||
import com.intellij.python.community.execService.BinOnEel
|
||||
import com.intellij.python.community.execService.BinaryToExec
|
||||
import com.intellij.python.community.testFramework.testEnv.conda.TypeConda
|
||||
import com.jetbrains.python.getOrThrow
|
||||
import com.jetbrains.python.sdk.add.v2.Version
|
||||
import com.jetbrains.python.sdk.add.v2.conda.getCondaVersion
|
||||
import com.jetbrains.python.sdk.flavors.conda.PyCondaCommand
|
||||
import kotlinx.coroutines.runBlocking
|
||||
import org.junit.AssumptionViolatedException
|
||||
@@ -20,6 +25,9 @@ class LocalCondaRule : ExternalResource() {
|
||||
lateinit var condaPath: Path
|
||||
private set
|
||||
|
||||
lateinit var condaVersion: Version
|
||||
private set
|
||||
|
||||
private lateinit var autoCloseable: AutoCloseable
|
||||
|
||||
val condaPathOnTarget: FullPathOnTarget get() = condaPath.toString()
|
||||
@@ -35,10 +43,16 @@ class LocalCondaRule : ExternalResource() {
|
||||
if (!condaPath.isExecutable()) {
|
||||
throw AssumptionViolatedException("$condaPath is not executable")
|
||||
}
|
||||
condaVersion = runBlocking { BinOnEel(condaPath).getCondaVersion().getOrThrow() }
|
||||
|
||||
this.autoCloseable = autoCloseable
|
||||
}
|
||||
|
||||
override fun after() {
|
||||
autoCloseable.close()
|
||||
}
|
||||
|
||||
fun getCondaBinaryToExec(): BinaryToExec {
|
||||
return BinOnEel(Path.of(condaPathOnTarget))
|
||||
}
|
||||
}
|
||||
@@ -1,162 +0,0 @@
|
||||
// Copyright 2000-2023 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
|
||||
package com.jetbrains.env.python.conda
|
||||
|
||||
import com.intellij.execution.target.local.LocalTargetEnvironmentRequest
|
||||
import com.intellij.openapi.diagnostic.Logger
|
||||
import com.intellij.openapi.util.registry.Registry
|
||||
import com.intellij.testFramework.ProjectRule
|
||||
import com.intellij.testFramework.common.timeoutRunBlocking
|
||||
import com.jetbrains.getPythonVersion
|
||||
import com.jetbrains.python.PyBundle
|
||||
import com.jetbrains.python.getOrThrow
|
||||
import com.jetbrains.python.psi.LanguageLevel
|
||||
import com.jetbrains.python.sdk.flavors.conda.NewCondaEnvRequest
|
||||
import com.jetbrains.python.sdk.flavors.conda.PyCondaEnv
|
||||
import com.jetbrains.python.sdk.flavors.conda.PyCondaEnvIdentity
|
||||
import com.jetbrains.python.sdk.flavors.conda.PyCondaFlavorData
|
||||
import com.jetbrains.python.sdk.getOrCreateAdditionalData
|
||||
import com.jetbrains.python.target.ui.PyAddCondaPanelModel
|
||||
import org.hamcrest.MatcherAssert
|
||||
import org.hamcrest.Matchers.hasItem
|
||||
import org.hamcrest.Matchers.not
|
||||
import org.junit.Assert
|
||||
import org.junit.Before
|
||||
import org.junit.Rule
|
||||
import org.junit.Test
|
||||
import org.junit.runner.RunWith
|
||||
import org.junit.runners.Parameterized
|
||||
import java.nio.file.Path
|
||||
import kotlin.io.path.exists
|
||||
import kotlin.time.Duration.Companion.seconds
|
||||
|
||||
|
||||
@RunWith(Parameterized::class)
|
||||
class PyAddCondaPanelModelTest {
|
||||
|
||||
@JvmField
|
||||
@Rule
|
||||
val condaRule: LocalCondaRule = LocalCondaRule()
|
||||
|
||||
@JvmField
|
||||
@Rule
|
||||
val projectRule: ProjectRule = ProjectRule()
|
||||
|
||||
@Parameterized.Parameter(0)
|
||||
@JvmField
|
||||
var useLegacy: Boolean = false
|
||||
|
||||
companion object {
|
||||
private val timeout = 60.seconds
|
||||
|
||||
@JvmStatic
|
||||
@Parameterized.Parameters
|
||||
fun data(): Collection<Array<Any>> = listOf(arrayOf(false), arrayOf(true))
|
||||
}
|
||||
|
||||
@Before
|
||||
fun before() {
|
||||
Registry.get("use.python.for.local.conda").setValue(useLegacy)
|
||||
Logger.getInstance(PyCondaSdkTest::class.java).info("Legacy: $useLegacy")
|
||||
}
|
||||
|
||||
|
||||
@Test
|
||||
fun testCondaDetection(): Unit = timeoutRunBlocking(timeout) {
|
||||
val model = PyAddCondaPanelModel(null, emptyList(), projectRule.project)
|
||||
model.detectConda(coroutineContext)
|
||||
val detectedPath = model.condaPathTextBoxRwProp.get()
|
||||
if (detectedPath.isNotEmpty()) {
|
||||
Assert.assertEquals("Wrong path detected", condaRule.condaPathOnTarget, detectedPath)
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testCondaCreateNewEnv(): Unit = timeoutRunBlocking(timeout) {
|
||||
val condaName = "someNewCondaEnv"
|
||||
val model = PyAddCondaPanelModel(null, emptyList(), projectRule.project)
|
||||
model.condaPathTextBoxRwProp.set(condaRule.condaPath.toString())
|
||||
model.condaActionCreateNewEnvRadioRwProp.set(true)
|
||||
model.condaActionUseExistingEnvRadioRwProp.set(false)
|
||||
|
||||
MatcherAssert.assertThat("No 3.9 suggested", model.languageLevels, hasItem(LanguageLevel.PYTHON39))
|
||||
MatcherAssert.assertThat("2.6 suggested", model.languageLevels, not(hasItem(LanguageLevel.PYTHON26)))
|
||||
model.newEnvLanguageLevelRwProperty.set(LanguageLevel.PYTHON38)
|
||||
Assert.assertNotNull("Empty conda env name didn't lead to validation", model.getValidationError())
|
||||
model.newEnvNameRwProperty.set("d f --- ")
|
||||
Assert.assertNotNull("Bad conda name didn't lead to validation", model.getValidationError())
|
||||
model.newEnvNameRwProperty.set(condaName)
|
||||
|
||||
val sdk = model.onCondaCreateSdkClicked(coroutineContext, null, targetConfiguration = null).getOrThrow()
|
||||
val newName = ((sdk.getOrCreateAdditionalData().flavorAndData.data as PyCondaFlavorData).env.envIdentity as PyCondaEnvIdentity.NamedEnv).envName
|
||||
Assert.assertEquals("Wrong conda name", condaName, newName)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testCondaCantUseNameUsedAlready(): Unit = timeoutRunBlocking(timeout) {
|
||||
val name = "cond_env_" + Math.random().toString().replace('.', '_')
|
||||
|
||||
// Create env
|
||||
PyCondaEnv.createEnv(condaRule.condaCommand,
|
||||
NewCondaEnvRequest.EmptyNamedEnv(LanguageLevel.PYTHON38, name)).getOrThrow()
|
||||
|
||||
val model = PyAddCondaPanelModel(null, emptyList(), projectRule.project)
|
||||
|
||||
// Trying to create env with same name
|
||||
model.condaPathTextBoxRwProp.set(condaRule.condaPath.toString())
|
||||
model.onLoadEnvsClicked(coroutineContext)
|
||||
model.condaActionCreateNewEnvRadioRwProp.set(true)
|
||||
model.condaActionUseExistingEnvRadioRwProp.set(false)
|
||||
model.newEnvLanguageLevelRwProperty.set(LanguageLevel.PYTHON38)
|
||||
model.newEnvNameRwProperty.set(name)
|
||||
Assert.assertEquals("Name duplicate should lead to error", PyBundle.message("python.sdk.conda.problem.env.name.used"),
|
||||
model.getValidationError())
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testCondaUseExistingEnv(): Unit = timeoutRunBlocking(timeout) {
|
||||
val model = PyAddCondaPanelModel(null, emptyList(), projectRule.project)
|
||||
model.condaPathTextBoxRwProp.set(condaRule.condaPath.toString())
|
||||
model.onLoadEnvsClicked(coroutineContext)
|
||||
model.condaActionUseExistingEnvRadioRwProp.set(true)
|
||||
model.condaActionCreateNewEnvRadioRwProp.set(false)
|
||||
model.condaEnvModel.selectedItem = model.condaEnvModel.getElementAt(0)
|
||||
val sdk = model.onCondaCreateSdkClicked(coroutineContext, null, targetConfiguration = null).getOrThrow()
|
||||
Assert.assertTrue(getPythonVersion(sdk, LocalTargetEnvironmentRequest())!!.isNotBlank())
|
||||
Assert.assertTrue(Path.of(sdk.homePath!!).exists())
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testCondaModelValidation(): Unit = timeoutRunBlocking(timeout) {
|
||||
val model = PyAddCondaPanelModel(null, emptyList(), projectRule.project)
|
||||
Assert.assertNotNull("No validation error, even though path not set", model.getValidationError())
|
||||
|
||||
Assert.assertFalse(model.showCondaPathSetOkButtonRoProp.get())
|
||||
model.condaPathTextBoxRwProp.set("Some random path that doesn't exist and doesn't contain conda")
|
||||
Assert.assertFalse("Wrong path, but button not disabled", model.showCondaPathSetOkButtonRoProp.get())
|
||||
|
||||
model.condaPathTextBoxRwProp.set("foo/conda")
|
||||
Assert.assertTrue("Path that passes regex, but button disabled", model.showCondaPathSetOkButtonRoProp.get())
|
||||
|
||||
model.onLoadEnvsClicked(coroutineContext)
|
||||
Assert.assertNotNull("No validation error, but path is incorrect", model.getValidationError())
|
||||
|
||||
model.condaPathTextBoxRwProp.set(condaRule.condaPath.toString())
|
||||
model.onLoadEnvsClicked(coroutineContext)
|
||||
Assert.assertNull("Unexpected validation error", model.getValidationError())
|
||||
|
||||
Assert.assertTrue(model.showCondaActionsPanelRoProp.get())
|
||||
model.condaActionCreateNewEnvRadioRwProp.set(true)
|
||||
Assert.assertTrue("No conda envs loaded", model.condaEnvModel.size > 0)
|
||||
|
||||
model.condaActionCreateNewEnvRadioRwProp.set(true)
|
||||
model.condaActionUseExistingEnvRadioRwProp.set(false)
|
||||
Assert.assertEquals("Env name not set", projectRule.project.name, model.newEnvNameRwProperty.get())
|
||||
|
||||
model.newEnvNameRwProperty.set("")
|
||||
Assert.assertNotNull("No validation error, but conda env name not set", model.getValidationError())
|
||||
|
||||
model.newEnvNameRwProperty.set("SomeEnv-Name")
|
||||
Assert.assertNull("Unexpected error", model.getValidationError())
|
||||
}
|
||||
|
||||
}
|
||||
@@ -25,9 +25,14 @@ class PyCondaAdditionalDataTest {
|
||||
|
||||
@Test
|
||||
fun testSerialize() {
|
||||
val flavorData = PyCondaFlavorData(PyCondaEnv(PyCondaEnvIdentity.NamedEnv("D"), "foo"))
|
||||
val flavorData = PyCondaFlavorData(
|
||||
env = PyCondaEnv(
|
||||
envIdentity = PyCondaEnvIdentity.NamedEnv("D"),
|
||||
fullCondaPathOnTarget = "foo",
|
||||
)
|
||||
)
|
||||
val data = PythonSdkAdditionalData(
|
||||
PyFlavorAndData(flavorData, CondaEnvSdkFlavor.getInstance()))
|
||||
PyFlavorAndData(flavorData, CondaEnvSdkFlavor))
|
||||
val rootElement = Element("root")
|
||||
data.save(rootElement)
|
||||
|
||||
@@ -36,7 +41,7 @@ class PyCondaAdditionalDataTest {
|
||||
|
||||
|
||||
val reloadedData = PythonSdkAdditionalData.loadFromElement(rootElement)
|
||||
Assert.assertEquals(reloadedData.flavor, CondaEnvSdkFlavor.getInstance())
|
||||
Assert.assertEquals(reloadedData.flavor, CondaEnvSdkFlavor)
|
||||
Assert.assertEquals(reloadedData.flavorAndData.data, flavorData)
|
||||
}
|
||||
}
|
||||
@@ -70,7 +70,10 @@ internal class PyCondaSdkTest {
|
||||
private suspend fun createCondaEnv(): PyCondaEnv {
|
||||
val name = "condaForTests"
|
||||
PyCondaEnv.createEnv(condaRule.condaCommand, NewCondaEnvRequest.EmptyNamedEnv(LanguageLevel.PYTHON38, name)).getOrThrow()
|
||||
return PyCondaEnv(PyCondaEnvIdentity.NamedEnv(name), condaRule.condaPathOnTarget)
|
||||
return PyCondaEnv(
|
||||
envIdentity = PyCondaEnvIdentity.NamedEnv(name),
|
||||
fullCondaPathOnTarget = condaRule.condaPathOnTarget,
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -87,12 +90,16 @@ internal class PyCondaSdkTest {
|
||||
""".trimIndent())
|
||||
|
||||
Assume.assumeTrue("Windows only", SystemInfoRt.isWindows)
|
||||
val condaEnvs = PyCondaEnv.getEnvs(condaRule.condaPathOnTarget).getOrThrow()
|
||||
val condaEnvs = PyCondaEnv.getEnvs(condaRule.getCondaBinaryToExec()).getOrThrow()
|
||||
val baseEnv = condaEnvs.first { (it.envIdentity as? PyCondaEnvIdentity.UnnamedEnv)?.isBase == true }
|
||||
val nonBaseEnv = condaEnvs.firstOrNull { it.envIdentity is PyCondaEnvIdentity.NamedEnv } ?: createCondaEnv()
|
||||
|
||||
for (condaEnv in arrayOf(nonBaseEnv, baseEnv)) {
|
||||
val condaSdk = condaRule.condaCommand.createCondaSdkFromExistingEnv(condaEnv.envIdentity, emptyList(), projectRule.project)
|
||||
val condaSdk = condaRule.condaCommand.createCondaSdkFromExistingEnv(
|
||||
condaIdentity = condaEnv.envIdentity,
|
||||
existingSdks = emptyList(),
|
||||
project = projectRule.project,
|
||||
)
|
||||
val request = LocalTargetEnvironmentRequest()
|
||||
val targetEnvironment = LocalTargetEnvironment(request)
|
||||
|
||||
@@ -117,8 +124,12 @@ internal class PyCondaSdkTest {
|
||||
|
||||
@Test
|
||||
fun testExecuteCommandOnSdk(): Unit = timeoutRunBlocking(60.seconds) {
|
||||
val condaEnv = PyCondaEnv.getEnvs(condaRule.condaPathOnTarget).getOrThrow().first()
|
||||
val sdk = condaRule.condaCommand.createCondaSdkFromExistingEnv(condaEnv.envIdentity, emptyList(), projectRule.project)
|
||||
val condaEnv = PyCondaEnv.getEnvs(condaRule.getCondaBinaryToExec()).getOrThrow().first()
|
||||
val sdk = condaRule.condaCommand.createCondaSdkFromExistingEnv(
|
||||
condaIdentity = condaEnv.envIdentity,
|
||||
existingSdks = emptyList(),
|
||||
project = projectRule.project,
|
||||
)
|
||||
val request = LocalTargetEnvironmentRequest()
|
||||
|
||||
repeat(10) { // To measure time to compare legacy and local
|
||||
@@ -140,9 +151,14 @@ internal class PyCondaSdkTest {
|
||||
|
||||
@Test
|
||||
fun testCreateFromExisting() = timeoutRunBlocking(10.minutes) {
|
||||
val env = PyCondaEnv.getEnvs(condaRule.condaPathOnTarget).getOrThrow().first()
|
||||
val sdk = condaRule.condaCommand.createCondaSdkFromExistingEnv(env.envIdentity, emptyList(), projectRule.project)
|
||||
Assert.assertEquals(sdk.getOrCreateAdditionalData().flavor, CondaEnvSdkFlavor.getInstance())
|
||||
val env = PyCondaEnv.getEnvs(condaRule.getCondaBinaryToExec()).getOrThrow().first()
|
||||
val sdk = condaRule.condaCommand.createCondaSdkFromExistingEnv(
|
||||
condaIdentity = env.envIdentity,
|
||||
existingSdks = emptyList(),
|
||||
project = projectRule.project,
|
||||
)
|
||||
|
||||
Assert.assertEquals(sdk.getOrCreateAdditionalData().flavor, CondaEnvSdkFlavor)
|
||||
Assert.assertTrue(env.toString(), getPythonVersion(sdk, LocalTargetEnvironmentRequest())?.isNotBlank() == true)
|
||||
|
||||
Assert.assertTrue("Bad home path", Files.isExecutable(Path.of(sdk.homePath!!)))
|
||||
|
||||
@@ -63,7 +63,7 @@ internal class PyCondaTest {
|
||||
|
||||
@Test
|
||||
fun testBasePython(): Unit = timeoutRunBlocking(10.minutes) {
|
||||
val baseConda = PyCondaEnv.getEnvs(condaRule.condaPathOnTarget).getOrThrow()
|
||||
val baseConda = PyCondaEnv.getEnvs(condaRule.getCondaBinaryToExec()).getOrThrow()
|
||||
.first { (it.envIdentity as? PyCondaEnvIdentity.UnnamedEnv)?.isBase == true }
|
||||
val targetRequest = LocalTargetEnvironmentRequest()
|
||||
val commandLineBuilder = TargetedCommandLineBuilder(targetRequest)
|
||||
@@ -87,7 +87,7 @@ internal class PyCondaTest {
|
||||
fun testCondaCreateByYaml() = timeoutRunBlocking(60.seconds) {
|
||||
PyCondaEnv.createEnv(condaRule.condaCommand,
|
||||
LocalEnvByLocalEnvironmentFile(yamlRule.yamlFilePath, emptyList())).getOrThrow()
|
||||
val condaEnv = PyCondaEnv.getEnvs(condaRule.condaPathOnTarget)
|
||||
val condaEnv = PyCondaEnv.getEnvs(condaRule.getCondaBinaryToExec())
|
||||
.getOrThrow().first { (it.envIdentity as? PyCondaEnvIdentity.NamedEnv)?.envName == yamlRule.envName }
|
||||
|
||||
// Python version contains word "Python", LanguageLevel doesn't expect it
|
||||
@@ -100,13 +100,13 @@ internal class PyCondaTest {
|
||||
val envName = "myNewEnvForTests"
|
||||
PyCondaEnv.createEnv(condaRule.condaCommand,
|
||||
EmptyNamedEnv(LanguageLevel.PYTHON39, envName)).getOrThrow()
|
||||
PyCondaEnv.getEnvs(condaRule.condaPathOnTarget)
|
||||
PyCondaEnv.getEnvs(condaRule.getCondaBinaryToExec())
|
||||
.getOrThrow().first { (it.envIdentity as? PyCondaEnvIdentity.NamedEnv)?.envName == envName }
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testCondaListEnvs(): Unit = timeoutRunBlocking(10.minutes) {
|
||||
val condaEnvs = PyCondaEnv.getEnvs(condaRule.condaPathOnTarget).getOrThrow()
|
||||
val condaEnvs = PyCondaEnv.getEnvs(condaRule.getCondaBinaryToExec()).getOrThrow()
|
||||
Assert.assertTrue("No environments returned", condaEnvs.isNotEmpty())
|
||||
|
||||
var baseFound = false
|
||||
@@ -129,7 +129,7 @@ internal class PyCondaTest {
|
||||
|
||||
@Test
|
||||
fun testCondaListUnnamedEnvs(): Unit = timeoutRunBlocking(90.seconds) {
|
||||
val envDirs = CondaExecutor.listEnvs(Path.of(condaRule.condaPathOnTarget)).mapSuccess { it.envsDirs }.getOrThrow()
|
||||
val envDirs = CondaExecutor.listEnvs(condaRule.getCondaBinaryToExec()).mapSuccess { it.envsDirs }.getOrThrow()
|
||||
val envsDirs = Path.of(envDirs.first())
|
||||
val childDir = envsDirs.resolve("child")
|
||||
val childEnvPrefix = childDir.resolve("childEnv").toString()
|
||||
@@ -143,7 +143,7 @@ internal class PyCondaTest {
|
||||
|
||||
// Important to check that envIdentity is UnnamedEnv as this is testing to make sure that
|
||||
// getEnvs doesn't mistakenly return a NamedEnv for an environment that isn't a direct child of envsDirs
|
||||
val envs = PyCondaEnv.getEnvs(condaRule.condaPathOnTarget).getOrThrow()
|
||||
val envs = PyCondaEnv.getEnvs(condaRule.getCondaBinaryToExec()).getOrThrow()
|
||||
.map { it.envIdentity }
|
||||
.filterIsInstance<PyCondaEnvIdentity.UnnamedEnv>()
|
||||
Assert.assertTrue("No child $childEnvPrefix in $envs", envs.any { it.envPath == childEnvPrefix })
|
||||
@@ -154,6 +154,6 @@ internal class PyCondaTest {
|
||||
val req = LocalTargetEnvironmentRequest()
|
||||
val commandLine = TargetedCommandLineBuilder(req).also { condaEnv.addCondaToTargetBuilder(it) }
|
||||
commandLine.addParameter("python")
|
||||
return getPythonVersion(commandLine, CondaEnvSdkFlavor.getInstance(), req) ?: error("No version for $condaEnv")
|
||||
return getPythonVersion(commandLine, CondaEnvSdkFlavor, req) ?: error("No version for $condaEnv")
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user