From 52196e86ad74a2e22d5cf5c03da5a5a29c9f487f Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Wed, 23 Sep 2026 20:34:24 +0000 Subject: [PATCH 1/3] Initial plan From deb3e66f99a4381eca785250747cbd8318043e2b Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Wed, 23 Sep 2026 20:45:41 +0000 Subject: [PATCH 2/3] Fix environmentPath to use Python executable Co-authored-by: edvilme <5952839+edvilme@users.noreply.github.com> --- api/CHANGELOG.md | 6 ++ api/package-lock.json | 4 +- api/package.json | 2 +- src/managers/builtin/venvUtils.ts | 4 +- src/managers/conda/condaEnvManager.ts | 4 +- src/managers/conda/condaPackageManager.ts | 4 +- src/managers/conda/condaUtils.ts | 12 ++- src/managers/pipenv/pipenvManager.ts | 1 + src/managers/pipenv/pipenvUtils.ts | 5 +- src/managers/poetry/poetryUtils.ts | 2 +- src/managers/pyenv/pyenvUtils.ts | 5 +- .../conda/condaUtils.createNamed.unit.test.ts | 3 +- .../condaUtils.pythonExePath.unit.test.ts | 4 + .../managers/environmentPath.unit.test.ts | 93 +++++++++++++++++++ src/types.ts | 2 +- 15 files changed, 131 insertions(+), 20 deletions(-) create mode 100644 src/test/managers/environmentPath.unit.test.ts diff --git a/api/CHANGELOG.md b/api/CHANGELOG.md index 35ad498f7..865dfbc95 100644 --- a/api/CHANGELOG.md +++ b/api/CHANGELOG.md @@ -5,6 +5,12 @@ All notable changes to the `@vscode/python-environments` API package are documen The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). +## [1.4.1] + +### Fixed + +- Clarified that `PythonEnvironmentInfo.environmentPath` always identifies the Python executable. + ## [1.4.0] ### Changed diff --git a/api/package-lock.json b/api/package-lock.json index 4c8908c6b..ca94da5cc 100644 --- a/api/package-lock.json +++ b/api/package-lock.json @@ -1,12 +1,12 @@ { "name": "@vscode/python-environments", - "version": "1.4.0", + "version": "1.4.1", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "@vscode/python-environments", - "version": "1.4.0", + "version": "1.4.1", "license": "MIT", "dependencies": { "@renovatebot/pep440": "^3.1.0" diff --git a/api/package.json b/api/package.json index 84bb1e735..7654eeef5 100644 --- a/api/package.json +++ b/api/package.json @@ -1,7 +1,7 @@ { "name": "@vscode/python-environments", "description": "An API facade for the Python Environments extension in VS Code", - "version": "1.4.0", + "version": "1.4.1", "author": { "name": "Microsoft Corporation" }, diff --git a/src/managers/builtin/venvUtils.ts b/src/managers/builtin/venvUtils.ts index 7b2de8544..c8dbcfdd1 100644 --- a/src/managers/builtin/venvUtils.ts +++ b/src/managers/builtin/venvUtils.ts @@ -173,7 +173,9 @@ async function getPythonInfo(env: NativeEnvInfo, nameStyle: VenvNameStyle = 'def version: env.version ?? 'Unknown', description: env.error, tooltip: env.error, - environmentPath: Uri.file(env.prefix ?? env.executable ?? ''), + environmentPath: Uri.file( + env.executable ?? (env.prefix ? getVenvPythonPath(env.prefix) : ''), + ), iconPath: new ThemeIcon('warning'), sysPrefix: env.prefix ?? '', execInfo: { diff --git a/src/managers/conda/condaEnvManager.ts b/src/managers/conda/condaEnvManager.ts index ad4ce00c1..81adf00ae 100644 --- a/src/managers/conda/condaEnvManager.ts +++ b/src/managers/conda/condaEnvManager.ts @@ -248,7 +248,7 @@ export class CondaEnvManager implements EnvironmentManager, Disposable { // If the environment is inside the workspace, add a .gitignore file try { const projectUris = this.api.getPythonProjects().map((p) => p.uri.fsPath); - const envPath = result.environmentPath?.fsPath; + const envPath = result.sysPrefix; if (envPath && projectUris.some((root) => envPath.startsWith(root))) { const gitignorePath = path.join(envPath, '.gitignore'); await fs.writeFile(gitignorePath, '*\n', { flag: 'w' }); @@ -394,7 +394,7 @@ export class CondaEnvManager implements EnvironmentManager, Disposable { // Notify user if CONDA_PREFIX is set and they're trying to select a different environment if (process.env.CONDA_PREFIX && checkedEnv) { const condaPrefixPath = process.env.CONDA_PREFIX; - const selectedPath = checkedEnv.environmentPath.fsPath; + const selectedPath = checkedEnv.sysPrefix; // Only show notification if they selected a different environment if (condaPrefixPath !== selectedPath) { showInformationMessage(CondaStrings.condaCondaPrefixActive); diff --git a/src/managers/conda/condaPackageManager.ts b/src/managers/conda/condaPackageManager.ts index f0d9ca4c2..4ab45db40 100644 --- a/src/managers/conda/condaPackageManager.ts +++ b/src/managers/conda/condaPackageManager.ts @@ -80,7 +80,7 @@ export class CondaPackageManager implements PackageManager, Disposable { try { const commandOptions = { pythonExecutable: 'conda', - condaEnvironmentPath: environment.environmentPath.fsPath, + condaEnvironmentPath: environment.sysPrefix, log: this.log, }; @@ -172,7 +172,7 @@ export class CondaPackageManager implements PackageManager, Disposable { private async fetchPackages(environment: PythonEnvironment): Promise { const listCmd = new CondaListCommand({ pythonExecutable: 'conda', - condaEnvironmentPath: environment.environmentPath.fsPath, + condaEnvironmentPath: environment.sysPrefix, log: this.log, }); try { diff --git a/src/managers/conda/condaUtils.ts b/src/managers/conda/condaUtils.ts index 9034a8e03..c1cc4c9ce 100644 --- a/src/managers/conda/condaUtils.ts +++ b/src/managers/conda/condaUtils.ts @@ -331,7 +331,7 @@ export async function getNamedCondaPythonInfo( return { name: name, - environmentPath: Uri.file(prefix), + environmentPath: Uri.file(executable), displayName: `${name} (${sv})`, shortDisplayName: `${name}:${sv}`, displayPath: prefix, @@ -376,7 +376,7 @@ export async function getPrefixesCondaPythonInfo( const basename = path.basename(prefix); return { name: basename, - environmentPath: Uri.file(prefix), + environmentPath: Uri.file(executable), displayName: `${basename} (${sv})`, shortDisplayName: `${basename}:${sv}`, displayPath: prefix, @@ -749,7 +749,9 @@ export function nonWindowsGenerateConfig( function getCondaWithoutPython(name: string, prefix: string, conda: string): PythonEnvironmentInfo { return { name: name, - environmentPath: Uri.file(prefix), + environmentPath: Uri.file( + isWindows() ? path.join(prefix, 'python.exe') : path.join(prefix, 'bin', 'python'), + ), displayName: `${name} (no-python)`, shortDisplayName: `${name} (no-python)`, displayPath: prefix, @@ -1224,11 +1226,11 @@ export async function quickCreateConda( } export async function deleteCondaEnvironment(environment: PythonEnvironment, log: LogOutputChannel): Promise { - let args = ['env', 'remove', '--yes', '--prefix', environment.environmentPath.fsPath]; + let args = ['env', 'remove', '--yes', '--prefix', environment.sysPrefix]; return await withProgress( { location: ProgressLocation.Notification, - title: l10n.t('Deleting conda environment: {0}', environment.environmentPath.fsPath), + title: l10n.t('Deleting conda environment: {0}', environment.sysPrefix), }, async () => { try { diff --git a/src/managers/pipenv/pipenvManager.ts b/src/managers/pipenv/pipenvManager.ts index 583811e69..e4ba17205 100644 --- a/src/managers/pipenv/pipenvManager.ts +++ b/src/managers/pipenv/pipenvManager.ts @@ -166,6 +166,7 @@ export class PipenvManager implements EnvironmentManager, Disposable { return this.collection.find( (env) => normalizePath(env.environmentPath.fsPath) === normalized || + normalizePath(env.sysPrefix) === normalized || (env.execInfo?.run.executable && normalizePath(env.execInfo.run.executable) === normalized), ); } diff --git a/src/managers/pipenv/pipenvUtils.ts b/src/managers/pipenv/pipenvUtils.ts index 55e375e91..d6e6ff062 100644 --- a/src/managers/pipenv/pipenvUtils.ts +++ b/src/managers/pipenv/pipenvUtils.ts @@ -105,7 +105,8 @@ export async function getPipenv(): Promise { } } -async function nativeToPythonEnv( +/** Converts native finder data to a Pipenv environment. @internal Exported for testing. */ +export async function nativeToPythonEnv( info: NativeEnvInfo, api: PythonEnvironmentApi, manager: EnvironmentManager, @@ -139,7 +140,7 @@ async function nativeToPythonEnv( shortDisplayName: displayName, displayPath: info.prefix, version: info.version, - environmentPath: Uri.file(info.prefix), + environmentPath: Uri.file(info.executable), description: undefined, tooltip: info.prefix, execInfo: { diff --git a/src/managers/poetry/poetryUtils.ts b/src/managers/poetry/poetryUtils.ts index 9d14e1cf9..3978acbf6 100644 --- a/src/managers/poetry/poetryUtils.ts +++ b/src/managers/poetry/poetryUtils.ts @@ -381,7 +381,7 @@ export async function nativeToPythonEnv( shortDisplayName: displayName, displayPath: info.prefix, version: info.version, - environmentPath: Uri.file(info.prefix), + environmentPath: Uri.file(info.executable), description: undefined, tooltip: info.prefix, execInfo: { diff --git a/src/managers/pyenv/pyenvUtils.ts b/src/managers/pyenv/pyenvUtils.ts index 2e49ad0e2..36d65c337 100644 --- a/src/managers/pyenv/pyenvUtils.ts +++ b/src/managers/pyenv/pyenvUtils.ts @@ -170,7 +170,8 @@ export async function getPyenv(): Promise { } } -function nativeToPythonEnv( +/** Converts native finder data to a pyenv environment. @internal Exported for testing. */ +export function nativeToPythonEnv( info: NativeEnvInfo, api: PythonEnvironmentApi, manager: EnvironmentManager, @@ -211,7 +212,7 @@ function nativeToPythonEnv( shortDisplayName: displayName, displayPath: info.prefix, version: info.version, - environmentPath: Uri.file(info.prefix), + environmentPath: Uri.file(info.executable), description: undefined, tooltip: info.prefix, execInfo: { diff --git a/src/test/managers/conda/condaUtils.createNamed.unit.test.ts b/src/test/managers/conda/condaUtils.createNamed.unit.test.ts index 40501716a..9b6458799 100644 --- a/src/test/managers/conda/condaUtils.createNamed.unit.test.ts +++ b/src/test/managers/conda/condaUtils.createNamed.unit.test.ts @@ -106,7 +106,8 @@ suite('Conda Utils - createNamedCondaEnvironment', () => { assert.ok(!mockState.get.calledWith(CONDA_PREFIXES_KEY)); const info = createEnvironmentItem.firstCall.args[0] as PythonEnvironmentInfo; - assert.strictEqual(info.environmentPath.fsPath, Uri.file(envPrefix).fsPath); + const executable = path.join(envPrefix, os.platform() === 'win32' ? 'python.exe' : path.join('bin', 'python')); + assert.strictEqual(info.environmentPath.fsPath, Uri.file(executable).fsPath); }); }); }); diff --git a/src/test/managers/conda/condaUtils.pythonExePath.unit.test.ts b/src/test/managers/conda/condaUtils.pythonExePath.unit.test.ts index 4bc23b673..f7a160115 100644 --- a/src/test/managers/conda/condaUtils.pythonExePath.unit.test.ts +++ b/src/test/managers/conda/condaUtils.pythonExePath.unit.test.ts @@ -2,6 +2,7 @@ import assert from 'assert'; import * as path from 'path'; import * as sinon from 'sinon'; +import { Uri } from 'vscode'; import { EnvironmentManager } from '../../../api'; import { CondaEnvManager } from '../../../managers/conda/condaEnvManager'; import { getNamedCondaPythonInfo, getPrefixesCondaPythonInfo } from '../../../managers/conda/condaUtils'; @@ -30,6 +31,7 @@ suite('Conda Python executable path construction', () => { const executable = path.posix.join(prefix, 'bin', 'python'); const info = await getNamedCondaPythonInfo('myenv', prefix, executable, '3.12.0', '/usr/bin/conda', mockManager); + assert.strictEqual(info.environmentPath.fsPath, Uri.file(executable).fsPath); assert.ok( info.execInfo.run.executable.includes(path.join('bin', 'python')) || info.execInfo.run.executable.endsWith('python'), @@ -44,6 +46,7 @@ suite('Conda Python executable path construction', () => { const executable = path.posix.join(prefix, 'bin', 'python'); const info = await getPrefixesCondaPythonInfo(prefix, executable, '3.12.0', '/usr/bin/conda', mockManager); + assert.strictEqual(info.environmentPath.fsPath, Uri.file(executable).fsPath); assert.ok( info.execInfo.run.executable.includes(path.join('bin', 'python')) || info.execInfo.run.executable.endsWith('python'), @@ -58,6 +61,7 @@ suite('Conda Python executable path construction', () => { const executable = path.win32.join(prefix, 'python.exe'); const info = await getNamedCondaPythonInfo('myenv', prefix, executable, '3.12.0', 'C:\\conda\\conda.exe', mockManager); + assert.strictEqual(info.environmentPath.fsPath, Uri.file(executable).fsPath); assert.ok( info.execInfo.run.executable.endsWith('python.exe'), `executable should end with python.exe, got: ${info.execInfo.run.executable}`, diff --git a/src/test/managers/environmentPath.unit.test.ts b/src/test/managers/environmentPath.unit.test.ts new file mode 100644 index 000000000..26040339a --- /dev/null +++ b/src/test/managers/environmentPath.unit.test.ts @@ -0,0 +1,93 @@ +import assert from 'node:assert'; +import * as path from 'node:path'; +import * as sinon from 'sinon'; +import { Uri } from 'vscode'; +import { + EnvironmentManager, + PythonEnvironment, + PythonEnvironmentApi, + PythonEnvironmentInfo, +} from '../../api'; +import { NativeEnvInfo } from '../../managers/common/nativePythonFinder'; +import * as managerUtils from '../../managers/common/utils'; +import { nativeToPythonEnv as pipenvNativeToPythonEnv } from '../../managers/pipenv/pipenvUtils'; +import { nativeToPythonEnv as poetryNativeToPythonEnv } from '../../managers/poetry/poetryUtils'; +import { nativeToPythonEnv as pyenvNativeToPythonEnv } from '../../managers/pyenv/pyenvUtils'; + +suite('Manager environmentPath', () => { + const prefix = path.join(path.sep, 'test', 'environment'); + const executable = path.join( + prefix, + process.platform === 'win32' ? 'Scripts' : 'bin', + process.platform === 'win32' ? 'python.exe' : 'python', + ); + const nativeInfo: NativeEnvInfo = { + prefix, + executable, + version: '3.12.0', + name: 'test-environment', + project: path.dirname(prefix), + }; + const manager = {} as EnvironmentManager; + let capturedInfo: PythonEnvironmentInfo | undefined; + let originalPoetryInProject: string | undefined; + + const api = { + createPythonEnvironmentItem: (info: PythonEnvironmentInfo) => { + capturedInfo = info; + return { ...info, envId: { id: 'test-id', managerId: 'test-manager' } } as PythonEnvironment; + }, + } as unknown as PythonEnvironmentApi; + + setup(() => { + capturedInfo = undefined; + originalPoetryInProject = process.env.POETRY_VIRTUALENVS_IN_PROJECT; + process.env.POETRY_VIRTUALENVS_IN_PROJECT = 'true'; + sinon.stub(managerUtils, 'getShellActivationCommands').resolves({ + shellActivation: new Map(), + shellDeactivation: new Map(), + }); + }); + + teardown(() => { + sinon.restore(); + if (originalPoetryInProject === undefined) { + delete process.env.POETRY_VIRTUALENVS_IN_PROJECT; + } else { + process.env.POETRY_VIRTUALENVS_IN_PROJECT = originalPoetryInProject; + } + }); + + const converters: { + name: string; + convert: () => PythonEnvironment | undefined | Promise; + }[] = [ + { + name: 'Pipenv', + convert: () => pipenvNativeToPythonEnv(nativeInfo, api, manager), + }, + { + name: 'Poetry', + convert: () => poetryNativeToPythonEnv(nativeInfo, api, manager, path.join(path.sep, 'tools', 'poetry')), + }, + { + name: 'pyenv', + convert: () => + pyenvNativeToPythonEnv( + nativeInfo, + api, + manager, + path.join(path.sep, 'tools', 'pyenv', 'bin', 'pyenv'), + ), + }, + ]; + + converters.forEach(({ name, convert }) => { + test(`${name} uses the Python executable`, async () => { + await convert(); + + assert.ok(capturedInfo); + assert.strictEqual(capturedInfo.environmentPath.fsPath, Uri.file(executable).fsPath); + }); + }); +}); diff --git a/src/types.ts b/src/types.ts index 12c93df99..1031eba90 100644 --- a/src/types.ts +++ b/src/types.ts @@ -196,7 +196,7 @@ export interface PythonEnvironmentInfo { readonly version: string; /** - * Path to the python binary or environment folder. + * Path to the Python executable. */ readonly environmentPath: Uri; From 9fbf46161c36f7648437bb6ceb1ff739c982f3a0 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Wed, 23 Sep 2026 21:33:02 +0000 Subject: [PATCH 3/3] Apply remaining changes Co-authored-by: edvilme <5952839+edvilme@users.noreply.github.com> --- src/managers/builtin/venvUtils.ts | 5 +-- src/managers/conda/condaUtils.ts | 11 ++++-- .../builtin/venvUtils.removeVenv.unit.test.ts | 7 +++- .../conda/condaPackageManager.unit.test.ts | 28 +++++++++++++++ .../managers/environmentPath.unit.test.ts | 35 +++++++++++++++++-- 5 files changed, 76 insertions(+), 10 deletions(-) diff --git a/src/managers/builtin/venvUtils.ts b/src/managers/builtin/venvUtils.ts index c8dbcfdd1..bfbf84aa1 100644 --- a/src/managers/builtin/venvUtils.ts +++ b/src/managers/builtin/venvUtils.ts @@ -618,10 +618,7 @@ export async function removeVenv( log: LogOutputChannel, options?: RemoveEnvironmentOptions, ): Promise { - const pythonPath = os.platform() === 'win32' ? 'python.exe' : 'python'; - - const envFsPath = path.normalize(environment.environmentPath.fsPath); - const envPath = envFsPath.endsWith(pythonPath) ? path.dirname(path.dirname(envFsPath)) : envFsPath; + const envPath = path.normalize(environment.sysPrefix); const validationError = await validateVenvRemovalPath(envPath, log); if (validationError) { diff --git a/src/managers/conda/condaUtils.ts b/src/managers/conda/condaUtils.ts index c1cc4c9ce..20c2ff496 100644 --- a/src/managers/conda/condaUtils.ts +++ b/src/managers/conda/condaUtils.ts @@ -767,7 +767,8 @@ function getCondaWithoutPython(name: string, prefix: string, conda: string): Pyt }; } -async function nativeToPythonEnv( +/** Converts native finder data to a Conda environment. @internal Exported for testing. */ +export async function nativeToPythonEnv( e: NativeEnvInfo, api: PythonEnvironmentApi, manager: EnvironmentManager, @@ -779,10 +780,14 @@ async function nativeToPythonEnv( traceWarn('nativeToPythonEnv received null/undefined NativeEnvInfo'); return undefined; } - if (!(e.prefix && e.executable && e.version)) { + if (!e.prefix) { + traceWarn('Ignoring Conda environment without a prefix'); + return undefined; + } + if (!(e.executable && e.version)) { let name = e.name; const environment = api.createPythonEnvironmentItem( - getCondaWithoutPython(name ?? '', e.prefix ?? '', conda), + getCondaWithoutPython(name ?? '', e.prefix, conda), manager, ); log.info(`Found a No-Python conda environment: ${e.executable ?? e.prefix ?? 'conda-no-python'}`); diff --git a/src/test/managers/builtin/venvUtils.removeVenv.unit.test.ts b/src/test/managers/builtin/venvUtils.removeVenv.unit.test.ts index ea25f971c..2f5dbe1de 100644 --- a/src/test/managers/builtin/venvUtils.removeVenv.unit.test.ts +++ b/src/test/managers/builtin/venvUtils.removeVenv.unit.test.ts @@ -158,6 +158,11 @@ suite('venvUtils removeVenv validation integration', () => { test('headless removal skips confirmation and removes the environment', async () => { const tempRoot = await fse.mkdtemp(path.join(os.tmpdir(), 'remove-venv-')); const envPath = path.join(tempRoot, '.venv'); + const executable = path.join( + envPath, + os.platform() === 'win32' ? 'Scripts' : 'bin', + os.platform() === 'win32' ? 'python3.12.exe' : 'python3.12', + ); await fse.outputFile(path.join(envPath, 'pyvenv.cfg'), 'home = base'); const showWarningMessageStub = sinon.stub(windowApis, 'showWarningMessage'); sinon.stub(windowApis, 'withProgress').callsFake(async (_options, task) => task({} as never, {} as never)); @@ -165,7 +170,7 @@ suite('venvUtils removeVenv validation integration', () => { try { const removed = await removeVenv( - createMockPythonEnvironment({ name: '.venv', envPath }), + createMockPythonEnvironment({ name: '.venv', envPath: executable, sysPrefix: envPath }), createMockLogOutputChannel(), { runHeadless: true }, ); diff --git a/src/test/managers/conda/condaPackageManager.unit.test.ts b/src/test/managers/conda/condaPackageManager.unit.test.ts index 4ed4deff8..83d5f982d 100644 --- a/src/test/managers/conda/condaPackageManager.unit.test.ts +++ b/src/test/managers/conda/condaPackageManager.unit.test.ts @@ -60,6 +60,34 @@ suite('CondaPackageManager', () => { assert.ok(logError.calledOnceWithExactly('Error parsing installed Conda packages', parseError)); }); + test('uses sysPrefix for Conda package commands', async () => { + const prefix = Uri.file('environment').fsPath; + const executable = Uri.joinPath(Uri.file(prefix), 'bin', 'python').fsPath; + const environment = { + envId: { id: 'test-environment', managerId: 'test-manager' }, + environmentPath: Uri.file(executable), + sysPrefix: prefix, + } as PythonEnvironment; + const manager = new CondaPackageManager( + { createPackageItem: sinon.stub() } as unknown as PythonEnvironmentApi, + { error: sinon.stub() } as unknown as LogOutputChannel, + ); + let installPrefix: string | undefined; + let listPrefix: string | undefined; + sinon.stub(CondaInstallCommand.prototype, 'execute').callsFake(async function (this: CondaInstallCommand) { + installPrefix = (this as unknown as { condaEnvironmentPath: string }).condaEnvironmentPath; + }); + sinon.stub(CondaListCommand.prototype, 'execute').callsFake(async function (this: CondaListCommand) { + listPrefix = (this as unknown as { condaEnvironmentPath: string }).condaEnvironmentPath; + return []; + }); + + await manager.manage(environment, { install: ['requests'], runHeadless: true }); + + assert.strictEqual(installPrefix, prefix); + assert.strictEqual(listPrefix, prefix); + }); + test('propagates package version lookup failures', async () => { const environment = { envId: { id: 'test-environment', managerId: 'test-manager' }, diff --git a/src/test/managers/environmentPath.unit.test.ts b/src/test/managers/environmentPath.unit.test.ts index 26040339a..f54237e13 100644 --- a/src/test/managers/environmentPath.unit.test.ts +++ b/src/test/managers/environmentPath.unit.test.ts @@ -1,15 +1,17 @@ import assert from 'node:assert'; import * as path from 'node:path'; import * as sinon from 'sinon'; -import { Uri } from 'vscode'; +import { LogOutputChannel, Uri } from 'vscode'; import { EnvironmentManager, PythonEnvironment, PythonEnvironmentApi, PythonEnvironmentInfo, } from '../../api'; -import { NativeEnvInfo } from '../../managers/common/nativePythonFinder'; +import { NativeEnvInfo, NativePythonFinder } from '../../managers/common/nativePythonFinder'; import * as managerUtils from '../../managers/common/utils'; +import { nativeToPythonEnv as condaNativeToPythonEnv } from '../../managers/conda/condaUtils'; +import { PipenvManager } from '../../managers/pipenv/pipenvManager'; import { nativeToPythonEnv as pipenvNativeToPythonEnv } from '../../managers/pipenv/pipenvUtils'; import { nativeToPythonEnv as poetryNativeToPythonEnv } from '../../managers/poetry/poetryUtils'; import { nativeToPythonEnv as pyenvNativeToPythonEnv } from '../../managers/pyenv/pyenvUtils'; @@ -90,4 +92,33 @@ suite('Manager environmentPath', () => { assert.strictEqual(capturedInfo.environmentPath.fsPath, Uri.file(executable).fsPath); }); }); + + test('Pipenv resolves a legacy persisted environment prefix', () => { + const environment = { + environmentPath: Uri.file(executable), + sysPrefix: prefix, + execInfo: { run: { executable } }, + } as PythonEnvironment; + const pipenvManager = new PipenvManager({} as NativePythonFinder, api); + const internals = pipenvManager as unknown as { + collection: PythonEnvironment[]; + findEnvironmentByPath(fsPath: string): PythonEnvironment | undefined; + }; + internals.collection = [environment]; + + assert.strictEqual(internals.findEnvironmentByPath(prefix), environment); + }); + + test('Conda ignores a native record without a prefix', async () => { + const result = await condaNativeToPythonEnv( + { executable, version: '3.12.0' }, + api, + manager, + {} as LogOutputChannel, + path.join(path.sep, 'tools', 'conda'), + ); + + assert.strictEqual(result, undefined); + assert.strictEqual(capturedInfo, undefined); + }); });