mirror of
https://gitflic.ru/project/openide/openide.git
synced 2026-09-27 10:03:11 +07:00
Python tests: separate conda from vanilla python env types for tests.
With this change we can move conda into the separate module to decouple test framework from community.impl GitOrigin-RevId: 589dc29f39d9d46252681c8f31b8fe482e63595f
This commit is contained in:
committed by
intellij-monorepo-bot
parent
88dc070d16
commit
1be9caaac9
@@ -15,5 +15,6 @@
|
||||
<orderEntry type="module" module-name="intellij.python.community.impl" />
|
||||
<orderEntry type="module" module-name="intellij.platform.execution" />
|
||||
<orderEntry type="library" name="kotlin-reflect" level="project" />
|
||||
<orderEntry type="module" module-name="intellij.platform.lang.impl" />
|
||||
</component>
|
||||
</module>
|
||||
+11
-70
@@ -1,32 +1,22 @@
|
||||
// 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.testFramework.testEnv
|
||||
|
||||
import com.intellij.execution.processTools.getResultStdout
|
||||
import com.intellij.execution.target.local.LocalTargetEnvironmentRequest
|
||||
import com.intellij.openapi.diagnostic.logger
|
||||
import com.intellij.openapi.util.Disposer
|
||||
import com.intellij.openapi.vfs.newvfs.impl.VfsRootAccess
|
||||
import com.intellij.openapi.projectRoots.Sdk
|
||||
import com.intellij.util.concurrency.annotations.RequiresBackgroundThread
|
||||
import com.jetbrains.python.PythonBinary
|
||||
import com.jetbrains.python.packaging.findCondaExecutableRelativeToEnv
|
||||
import com.jetbrains.python.sdk.conda.TargetEnvironmentRequestCommandExecutor
|
||||
import com.jetbrains.python.sdk.flavors.PythonSdkFlavor
|
||||
import com.jetbrains.python.sdk.flavors.conda.PyCondaEnv
|
||||
import com.jetbrains.python.sdk.flavors.conda.PyCondaEnvIdentity
|
||||
import com.jetbrains.python.venvReader.VirtualEnvReader
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.flow.*
|
||||
import kotlinx.coroutines.runBlocking
|
||||
import org.jetbrains.annotations.NonNls
|
||||
import java.nio.file.Path
|
||||
import java.util.concurrent.ConcurrentHashMap
|
||||
import kotlin.Result.Companion.failure
|
||||
|
||||
/**
|
||||
* Gradle script installs two types of python: conda and vanilla. Env could be obtained by [getTestEnvironment] which also provides closable
|
||||
* Gradle script installs two types of python: conda and vanilla. Env could be obtained by [createSdkClosableEnv] which also provides closable
|
||||
* to clean up after the usage
|
||||
*/
|
||||
sealed class PythonType<T : Any>(private val tag: @NonNls String? = null) {
|
||||
abstract class PythonType<T : Any>(private val tag: @NonNls String) {
|
||||
private companion object {
|
||||
val cache: MutableMap<Set<String>, List<Path>> = ConcurrentHashMap()
|
||||
}
|
||||
@@ -65,72 +55,23 @@ sealed class PythonType<T : Any>(private val tag: @NonNls String? = null) {
|
||||
|
||||
|
||||
/**
|
||||
* Returns first (whatever it means) test environment and closable that must be closed after the test
|
||||
* Returns sdk, (whatever it means) test environment and closable that must be closed after the test
|
||||
*/
|
||||
suspend fun getTestEnvironment(vararg additionalTags: @NonNls String): Result<Pair<T, AutoCloseable>> =
|
||||
getTestEnvironments(*additionalTags).firstOrNull()?.let { Result.success(it) }
|
||||
suspend fun createSdkClosableEnv(vararg additionalTags: @NonNls String): Result<Triple<Sdk, AutoCloseable, T>> =
|
||||
getTestEnvironments(*additionalTags).firstOrNull()?.let { (env, closable) ->
|
||||
Result.success(Triple(createSdkFor(env), closable, env))
|
||||
}
|
||||
?: failure(AssertionError("No python found. See ${PyEnvTestSettings::class} class for more info"))
|
||||
|
||||
protected abstract suspend fun createSdkFor(t: T): Sdk
|
||||
|
||||
|
||||
protected abstract suspend fun pythonPathToEnvironment(pythonBinary: PythonBinary, envDir: Path): Pair<T, AutoCloseable>
|
||||
|
||||
|
||||
data object VanillaPython3 : PythonType<PythonBinary>() {
|
||||
// Python is directly executable
|
||||
override suspend fun pythonPathToEnvironment(pythonBinary: PythonBinary, envDir: Path): Pair<PythonBinary, AutoCloseable> {
|
||||
val disposable = Disposer.newDisposable("Python tests disposable for VfsRootAccess")
|
||||
// We might have python installation outside the project root, but we still need to have access to it.
|
||||
VfsRootAccess.allowRootAccess(disposable, pythonBinary.parent.toString())
|
||||
return Pair(pythonBinary, AutoCloseable {
|
||||
Disposer.dispose(disposable)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
data object Conda : PythonType<PyCondaEnv>("conda") {
|
||||
|
||||
override suspend fun pythonPathToEnvironment(pythonBinary: PythonBinary, envDir: Path): Pair<PyCondaEnv, AutoCloseable> {
|
||||
// First, find python binary, then calculate conda from it as env stores "conda" as a regular env
|
||||
val condaPath = findCondaExecutableRelativeToEnv(pythonBinary) ?: error("Conda root $pythonBinary doesn't have conda binary")
|
||||
|
||||
// We'll remove then on close
|
||||
val condaEnvsBeforeTest = getCondaNames(condaPath)
|
||||
|
||||
val cleanupCondas = AutoCloseable {
|
||||
runBlocking(Dispatchers.IO) {
|
||||
val condasToRemove = getCondaNames(condaPath)
|
||||
condasToRemove.removeAll(condaEnvsBeforeTest)
|
||||
for (envName in condasToRemove) {
|
||||
println("Removing $envName")
|
||||
|
||||
for (arg in arrayOf("--name", "-p")) {
|
||||
val args = arrayOf(condaPath.toString(), "remove", arg, envName, "--all", "-y")
|
||||
Runtime.getRuntime().exec(args).getResultStdout().getOrElse {
|
||||
logger<Conda>().warn(it)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return Pair(PyCondaEnv(PyCondaEnvIdentity.UnnamedEnv(envDir.toString(), isBase = true), condaPath.toString()), cleanupCondas)
|
||||
}
|
||||
|
||||
private suspend fun getCondaNames(condaPath: Path) =
|
||||
PyCondaEnv.getEnvs(TargetEnvironmentRequestCommandExecutor(LocalTargetEnvironmentRequest()),
|
||||
condaPath.toString()).getOrThrow()
|
||||
.map { it.envIdentity.userReadableName }
|
||||
.toMutableSet()
|
||||
}
|
||||
|
||||
|
||||
@RequiresBackgroundThread
|
||||
private fun typeMatchesEnv(env: Path, vararg additionalTags: @NonNls String): Boolean {
|
||||
val envTags = loadEnvTags(env)
|
||||
|
||||
for (badTag in PythonType::class.sealedSubclasses.filterNot { it.isInstance(this) }.map { it.objectInstance!!.tag }) {
|
||||
if (badTag != null && badTag in envTags) return false
|
||||
}
|
||||
return (tag == null || tag in envTags) && additionalTags.all { it in envTags }
|
||||
return tag in envTags && additionalTags.all { it in envTags }
|
||||
}
|
||||
}
|
||||
+52
@@ -0,0 +1,52 @@
|
||||
// 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.testFramework.testEnv
|
||||
|
||||
import com.intellij.execution.processTools.getResultStdout
|
||||
import com.intellij.execution.target.local.LocalTargetEnvironmentRequest
|
||||
import com.intellij.openapi.diagnostic.logger
|
||||
import com.intellij.openapi.projectRoots.Sdk
|
||||
import com.jetbrains.python.PythonBinary
|
||||
import com.jetbrains.python.packaging.findCondaExecutableRelativeToEnv
|
||||
import com.jetbrains.python.sdk.conda.TargetEnvironmentRequestCommandExecutor
|
||||
import com.jetbrains.python.sdk.flavors.conda.PyCondaEnv
|
||||
import com.jetbrains.python.sdk.flavors.conda.PyCondaEnvIdentity
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.runBlocking
|
||||
import java.nio.file.Path
|
||||
|
||||
data object TypeConda : PythonType<PyCondaEnv>("conda") {
|
||||
override suspend fun createSdkFor(env: PyCondaEnv): Sdk =
|
||||
env.createSdkFromThisEnv(null, emptyList())
|
||||
|
||||
override suspend fun pythonPathToEnvironment(pythonBinary: PythonBinary, envDir: Path): Pair<PyCondaEnv, AutoCloseable> {
|
||||
// First, find python binary, then calculate conda from it as env stores "conda" as a regular env
|
||||
val condaPath = findCondaExecutableRelativeToEnv(pythonBinary) ?: error("Conda root $pythonBinary doesn't have conda binary")
|
||||
|
||||
// We'll remove then on close
|
||||
val condaEnvsBeforeTest = getCondaNames(condaPath)
|
||||
|
||||
val cleanupCondas = AutoCloseable {
|
||||
runBlocking(Dispatchers.IO) {
|
||||
val condasToRemove = getCondaNames(condaPath)
|
||||
condasToRemove.removeAll(condaEnvsBeforeTest)
|
||||
for (envName in condasToRemove) {
|
||||
println("Removing $envName")
|
||||
|
||||
for (arg in arrayOf("--name", "-p")) {
|
||||
val args = arrayOf(condaPath.toString(), "remove", arg, envName, "--all", "-y")
|
||||
Runtime.getRuntime().exec(args).getResultStdout().getOrElse {
|
||||
logger<TypeConda>().warn(it)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return Pair(PyCondaEnv(PyCondaEnvIdentity.UnnamedEnv(envDir.toString(), isBase = true), condaPath.toString()), cleanupCondas)
|
||||
}
|
||||
|
||||
private suspend fun getCondaNames(condaPath: Path) =
|
||||
PyCondaEnv.Companion.getEnvs(TargetEnvironmentRequestCommandExecutor(LocalTargetEnvironmentRequest()),
|
||||
condaPath.toString()).getOrThrow()
|
||||
.map { it.envIdentity.userReadableName }
|
||||
.toMutableSet()
|
||||
}
|
||||
+28
@@ -0,0 +1,28 @@
|
||||
// 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.testFramework.testEnv
|
||||
|
||||
import com.intellij.openapi.projectRoots.Sdk
|
||||
import com.intellij.openapi.projectRoots.SdkType
|
||||
import com.intellij.openapi.projectRoots.impl.SdkConfigurationUtil
|
||||
import com.intellij.openapi.util.Disposer
|
||||
import com.intellij.openapi.vfs.newvfs.impl.VfsRootAccess
|
||||
import com.intellij.testFramework.utils.vfs.refreshAndGetVirtualFile
|
||||
import com.jetbrains.python.PyNames
|
||||
import com.jetbrains.python.PythonBinary
|
||||
import java.nio.file.Path
|
||||
|
||||
data object TypeVanillaPython3 : PythonType<PythonBinary>("python3") {
|
||||
override suspend fun createSdkFor(python: PythonBinary): Sdk =
|
||||
SdkConfigurationUtil.setupSdk(emptyArray(), python.refreshAndGetVirtualFile(),
|
||||
SdkType.findByName(PyNames.PYTHON_SDK_ID_NAME)!!, null, null)
|
||||
|
||||
// Python is directly executable
|
||||
override suspend fun pythonPathToEnvironment(pythonBinary: PythonBinary, envDir: Path): Pair<PythonBinary, AutoCloseable> {
|
||||
val disposable = Disposer.newDisposable("Python tests disposable for VfsRootAccess")
|
||||
// We might have python installation outside the project root, but we still need to have access to it.
|
||||
VfsRootAccess.allowRootAccess(disposable, pythonBinary.parent.toString())
|
||||
return Pair(pythonBinary, AutoCloseable {
|
||||
Disposer.dispose(disposable)
|
||||
})
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user