From fe66f186e7a4f2bddded6295294ba91238bc5281 Mon Sep 17 00:00:00 2001 From: "Ilya.Kazakevich" Date: Fri, 26 Dec 2025 06:47:41 +0100 Subject: [PATCH] cleanup [python]: Move test to `VirtualEnvReaderTest` to run it on Windows as well. GitOrigin-RevId: 65e3aed0ba560e5c00d8fe3852c830d853f0b4f5 --- .../python/venvReader/VirtualEnvReader.kt | 17 +- .../python/sdk/VirtualEnvReaderTest.kt | 247 ------------------ 2 files changed, 11 insertions(+), 253 deletions(-) delete mode 100644 python/testSrc/com/jetbrains/python/sdk/VirtualEnvReaderTest.kt diff --git a/python/openapi/src/com/jetbrains/python/venvReader/VirtualEnvReader.kt b/python/openapi/src/com/jetbrains/python/venvReader/VirtualEnvReader.kt index 01ad3d5a5c50..b09fbe4881bb 100644 --- a/python/openapi/src/com/jetbrains/python/venvReader/VirtualEnvReader.kt +++ b/python/openapi/src/com/jetbrains/python/venvReader/VirtualEnvReader.kt @@ -1,6 +1,7 @@ // Copyright 2000-2025 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license. package com.jetbrains.python.venvReader +import com.intellij.openapi.application.ApplicationManager import com.intellij.openapi.progress.runBlockingMaybeCancellable import com.intellij.openapi.util.io.toCanonicalPath import com.intellij.platform.eel.EelApi @@ -47,7 +48,7 @@ class VirtualEnvReader private constructor( * Dir with virtual envs */ @RequiresBackgroundThread - fun getVEnvRootDir(eel: EelApi = localEel): Directory { + fun getVEnvRootDir(eel: EelApi? = getLocalEelIfApp()): Directory { return resolveDirFromEnvOrElseGetDirInHomePath(eel, "WORKON_HOME", DEFAULT_VIRTUALENVS_DIR) } @@ -59,7 +60,7 @@ class VirtualEnvReader private constructor( findLocalInterpreters(getVEnvRootDir()) @RequiresBackgroundThread - fun getPyenvRootDir(eel: EelApi = localEel): Directory { + fun getPyenvRootDir(eel: EelApi? = getLocalEelIfApp()): Directory { return resolveDirFromEnvOrElseGetDirInHomePath(eel, "PYENV_ROOT", ".pyenv") } @@ -176,10 +177,13 @@ class VirtualEnvReader private constructor( } @RequiresBackgroundThread - private fun resolveDirFromEnvOrElseGetDirInHomePath(eel: EelApi, env: String, dirName: String): Path { - val envs = forcedVars ?: runBlockingMaybeCancellable { eel.exec.environmentVariables().eelIt().await() } - return envs[env]?.let { tryResolvePath(it, eel.descriptor) } - ?: eel.userInfo.home.asNioPath().resolve(dirName) + private fun resolveDirFromEnvOrElseGetDirInHomePath(eel: EelApi?, env: String, dirName: String): Path { + val envs = forcedVars + ?: eel?.let { eel -> runBlockingMaybeCancellable { eel.exec.environmentVariables().eelIt().await() } } + ?: System.getenv() + return envs[env]?.let { tryResolvePath(it, eel?.descriptor) } + ?: (eel?.userInfo?.home?.asNioPath() + ?: Path(System.getProperty("user.home"))).resolve(dirName) } @@ -197,5 +201,6 @@ class VirtualEnvReader private constructor( const val DEFAULT_VIRTUALENVS_DIR: String = ".virtualenvs" const val DEFAULT_VIRTUALENV_DIRNAME: String = ".venv" + private fun getLocalEelIfApp(): EelApi? = if (ApplicationManager.getApplication() != null) localEel else null } } \ No newline at end of file diff --git a/python/testSrc/com/jetbrains/python/sdk/VirtualEnvReaderTest.kt b/python/testSrc/com/jetbrains/python/sdk/VirtualEnvReaderTest.kt deleted file mode 100644 index 4a47e40264df..000000000000 --- a/python/testSrc/com/jetbrains/python/sdk/VirtualEnvReaderTest.kt +++ /dev/null @@ -1,247 +0,0 @@ -// 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.sdk - -import com.intellij.grazie.grammar.assertIsEmpty -import com.intellij.openapi.util.SystemInfoRt -import com.intellij.openapi.util.io.FileUtilRt -import com.intellij.testFramework.junit5.TestApplication -import com.intellij.testFramework.utils.io.deleteRecursively -import com.jetbrains.python.venvReader.VirtualEnvReader -import com.jetbrains.python.venvReader.tryResolvePath -import org.junit.jupiter.api.Assertions -import org.junit.jupiter.api.Test -import org.junit.jupiter.params.ParameterizedTest -import org.junit.jupiter.params.provider.Arguments -import org.junit.jupiter.params.provider.MethodSource -import java.nio.file.Files -import java.nio.file.Path -import kotlin.io.path.absolutePathString -import kotlin.io.path.exists - -@TestApplication -class VirtualEnvReaderTest { - inner class Bootstrap { - val PYENV_ROOT = "PYENV_ROOT" - - val cwd = FileUtilRt.createTempDirectory("venvreader", "").toPath() - val pyenv = cwd.resolve(".pyenv") - - val env = HashMap() - val virtualEnvReader = VirtualEnvReader(env) - - fun setupPyenv(versions: List, binary: String) { - for (version in versions) { - addVersion(pyenv, version, binary) - } - - env[PYENV_ROOT] = pyenv.absolutePathString() - } - - fun addVersion(root: Path, version: String, binary: String) { - val vsdir = ensureVersions(root) - - val vdir = vsdir.resolve(version) - Files.createDirectory(vdir) - - if (binary.isNotEmpty()) { - Files.createFile(vdir.resolve(binary)) - } - } - - fun removeVersion(root: Path, version: String) { - val vsdir = ensureVersions(root) - val vdir = vsdir.resolve(version) - - vdir.deleteRecursively() - } - - fun ensureVersions(root: Path): Path { - if (!root.exists()) { - Files.createDirectory(root); - } - - val versions = root.resolve("versions") - if (!versions.exists()) { - Files.createDirectory(versions) - } - - return versions - } - } - - @Test - fun testHandleEmptyDirs() { - val bootstrap = Bootstrap() - - // non existent dir - var interpreters = bootstrap.virtualEnvReader.findLocalInterpreters(bootstrap.pyenv) - assertIsEmpty(interpreters) - - // invalid data - bootstrap.env[bootstrap.PYENV_ROOT] = "aa\u0000bb" - interpreters = bootstrap.virtualEnvReader.findPyenvInterpreters() - assertIsEmpty(interpreters) - - // empty dir - interpreters = bootstrap.virtualEnvReader.findPyenvInterpreters() - assertIsEmpty(interpreters) - - // empty dir - bootstrap.setupPyenv(listOf(), "") - interpreters = bootstrap.virtualEnvReader.findPyenvInterpreters() - assertIsEmpty(interpreters) - - // .pyenv but no versions - Files.createDirectory(bootstrap.pyenv) - interpreters = bootstrap.virtualEnvReader.findPyenvInterpreters() - assertIsEmpty(interpreters) - } - - @Test - fun testCollectPaths() { - val bootstrap = Bootstrap() - - // just version - val binary = if (SystemInfoRt.isWindows) "python.exe" else "python" - bootstrap.setupPyenv(listOf("3.1.1"), binary) - var interpreters = bootstrap.virtualEnvReader.findPyenvInterpreters() - Assertions.assertEquals(1, interpreters.size) - assert(interpreters[0].absolutePathString().startsWith(bootstrap.pyenv.absolutePathString())) - assert(interpreters[0].absolutePathString().endsWith(binary)) - - // another version w/o match - bootstrap.setupPyenv(listOf("3.2.1"), "xxx") - interpreters = bootstrap.virtualEnvReader.findPyenvInterpreters() - Assertions.assertEquals(1, interpreters.size) - - // both in names - val pypyBinary = if (SystemInfoRt.isWindows) "pypy.exe" else "pypy" - bootstrap.setupPyenv(listOf("3.2.2"), pypyBinary) - interpreters = bootstrap.virtualEnvReader.findPyenvInterpreters() - Assertions.assertEquals(2, interpreters.size) - assert(interpreters[0] != interpreters[1]) - - bootstrap.removeVersion(bootstrap.pyenv, "3.2.2") - interpreters = bootstrap.virtualEnvReader.findPyenvInterpreters() - Assertions.assertEquals(1, interpreters.size) - assert(interpreters[0].absolutePathString().endsWith(binary)) - } - - @Test - fun testIsPyenvSdk() { - val bootstrap = Bootstrap() - - Assertions.assertFalse(bootstrap.virtualEnvReader.isPyenvSdk(null as String?)) - Assertions.assertFalse(bootstrap.virtualEnvReader.isPyenvSdk("")) - Assertions.assertFalse(bootstrap.virtualEnvReader.isPyenvSdk("aa\u0000bb")) - Assertions.assertFalse(bootstrap.virtualEnvReader.isPyenvSdk("a/b/c/d")) - Assertions.assertFalse(bootstrap.virtualEnvReader.isPyenvSdk(bootstrap.cwd)) - Assertions.assertFalse(bootstrap.virtualEnvReader.isPyenvSdk(bootstrap.cwd.resolve("smthg"))) - - bootstrap.setupPyenv(listOf("3.2.1"), "xxxx") - - Assertions.assertFalse(bootstrap.virtualEnvReader.isPyenvSdk(null as String?)) - Assertions.assertFalse(bootstrap.virtualEnvReader.isPyenvSdk("")) - Assertions.assertFalse(bootstrap.virtualEnvReader.isPyenvSdk("aa\u0000bb")) - Assertions.assertFalse(bootstrap.virtualEnvReader.isPyenvSdk("a/b/c/d")) - Assertions.assertFalse(bootstrap.virtualEnvReader.isPyenvSdk(bootstrap.cwd)) - Assertions.assertFalse(bootstrap.virtualEnvReader.isPyenvSdk(bootstrap.cwd.resolve("smthg"))) - - // particularly any path inside pyenv root will work - Assertions.assertTrue(bootstrap.virtualEnvReader.isPyenvSdk(bootstrap.pyenv.resolve("xxx"))) - - // should resolve symlinks - val link = bootstrap.cwd.resolve("smthg") - val target = bootstrap.pyenv.resolve("xxx") - - // hanging links, should not resolve it - if (!SystemInfoRt.isWindows) { - // links require UAC on Windows - Files.createSymbolicLink(link, target) - } - Assertions.assertFalse(bootstrap.virtualEnvReader.isPyenvSdk(link)) - - if (!SystemInfoRt.isWindows) { - Files.createFile(target) - Assertions.assertTrue(bootstrap.virtualEnvReader.isPyenvSdk(link)) - } - } - - @ParameterizedTest(name = "{0}") - @MethodSource("getVenvRootPathTestCases") - fun getVenvRootPathTests(name: String, isWindows: Boolean, path: Path, expectedReturnValue: Path?) { - val result = VirtualEnvReader(isWindows = isWindows).getVenvRootPath(path) - - Assertions.assertEquals(expectedReturnValue, result) - } - - companion object { - @JvmStatic - fun getVenvRootPathTestCases(): List = listOf( - GetVenvRootPathTestCase( - "returns null when no parent is found", - false, - "python", - null - ), - - GetVenvRootPathTestCase( - "returns null when bin dir is named Scripts on non-windows", - false, - "root/.venv/Scripts/python", - null - ), - - GetVenvRootPathTestCase( - "returns null when bin dir is named bin on windows", - true, - "root/.venv/bin/python.exe", - null - ), - - GetVenvRootPathTestCase( - "returns root when bin dir is named Scripts on windows", - true, - "root/.venv/Scripts/python.exe", - "root" - ), - - GetVenvRootPathTestCase( - "returns root when bin dir is named bin on non-windows", - false, - "root/.venv/bin/python.exe", - "root" - ), - - GetVenvRootPathTestCase( - "returns null when bin dir has no parent", - false, - "bin/python.exe", - null - ), - - GetVenvRootPathTestCase( - "returns null when .venv dir has no parent", - false, - ".venv/bin/python.exe", - null - ), - - GetVenvRootPathTestCase( - "returns root with a custom .venv dir name", - false, - "root/.venv_custom/bin/python.exe", - "root" - ), - ).map { - Arguments.of(it.name, it.isWindows, Path.of(it.path), tryResolvePath(it.expectedReturnValue)) - } - - data class GetVenvRootPathTestCase( - val name: String, - val isWindows: Boolean, - val path: String, - val expectedReturnValue: String?, - ) - } -} \ No newline at end of file