From 6c1409d8f9a17e2dcc75bf6a0dc86f826f35462f Mon Sep 17 00:00:00 2001 From: Thrizzio Date: Sat, 10 Oct 2026 05:38:26 +0000 Subject: [PATCH] Fix global interpreter selection readback routing (#1922) Prefer cached global environment manager over default manager when resolving global context. Ensure getEnvironment(undefined) and Python Projects sidebar reflect cross-manager selections. Add regression test for cross-manager global selection. --- src/features/envManagers.ts | 9 +- src/test/features/envManagers.unit.test.ts | 102 +++++++++++++++++++++ 2 files changed, 110 insertions(+), 1 deletion(-) diff --git a/src/features/envManagers.ts b/src/features/envManagers.ts index a1253f0f8..767dec8ae 100644 --- a/src/features/envManagers.ts +++ b/src/features/envManagers.ts @@ -1086,6 +1086,14 @@ export class PythonEnvironmentManagers implements EnvironmentManagers { context: Uri | undefined, project: PythonProject | undefined, ): InternalEnvironmentManager | undefined { + const cachedEnv = this._activeSelection.get(this.getProjectSelectionKey(project)); + if (context === undefined && cachedEnv) { + const cachedManager = this._environmentManagers.get(cachedEnv.envId.managerId); + if (cachedManager) { + return cachedManager; + } + } + const defaultEnvManagerId = getDefaultEnvManagerSetting(this.pm, context); if (defaultEnvManagerId !== undefined) { const settingsManager = this._environmentManagers.get(defaultEnvManagerId); @@ -1094,7 +1102,6 @@ export class PythonEnvironmentManagers implements EnvironmentManagers { } } - const cachedEnv = this._activeSelection.get(this.getProjectSelectionKey(project)); if (cachedEnv) { const cachedManager = this._environmentManagers.get(cachedEnv.envId.managerId); if (cachedManager) { diff --git a/src/test/features/envManagers.unit.test.ts b/src/test/features/envManagers.unit.test.ts index 63db6773f..2ca55959f 100644 --- a/src/test/features/envManagers.unit.test.ts +++ b/src/test/features/envManagers.unit.test.ts @@ -215,6 +215,108 @@ suite('PythonEnvironmentManagers - getEnvironment', () => { }); }); +suite('PythonEnvironmentManagers - cross-manager global selection', () => { + let sandbox: sinon.SinonSandbox; + let envManagers: PythonEnvironmentManagers; + let mockProjectManager: sinon.SinonStubbedInstance; + + const selectedSystemEnvironment: PythonEnvironment = { + envId: { id: 'selected-system', managerId: 'ms-python.python:system' }, + name: 'Selected system Python', + displayName: 'Selected system Python', + version: '3.13.0', + displayPath: '/usr/bin/python3.13', + environmentPath: Uri.file('/usr/bin/python3.13'), + sysPrefix: '/usr', + execInfo: { run: { executable: '/usr/bin/python3.13' } }, + }; + + const configuredVenvEnvironment: PythonEnvironment = { + envId: { id: 'configured-venv', managerId: 'ms-python.python:venv' }, + name: 'Configured venv', + displayName: 'Configured venv', + version: '3.12.0', + displayPath: '/workspace/.venv/bin/python', + environmentPath: Uri.file('/workspace/.venv/bin/python'), + sysPrefix: '/workspace/.venv', + execInfo: { run: { executable: '/workspace/.venv/bin/python' } }, + }; + + setup(() => { + sandbox = sinon.createSandbox(); + sandbox.stub(frameUtils, 'getCallingExtension').returns('ms-python.python'); + + sandbox.stub(workspaceApis, 'getConfiguration').returns({ + get: (key: string, defaultValue?: unknown) => { + if (key === 'defaultEnvManager') { + return 'ms-python.python:venv'; + } + if (key === 'pythonProjects') { + return []; + } + return defaultValue; + }, + has: () => false, + inspect: () => undefined, + update: () => Promise.resolve(), + } as any); + + mockProjectManager = { + getProjects: sandbox.stub().returns([]), + get: sandbox.stub().returns(undefined), + } as unknown as sinon.SinonStubbedInstance; + + envManagers = new PythonEnvironmentManagers(mockProjectManager as unknown as PythonProjectManager); + }); + + teardown(() => { + sandbox.restore(); + }); + + function registerFakeManager( + managerId: string, + getStub: sinon.SinonStub, + setStub: sinon.SinonStub = sandbox.stub().resolves(), + ): string { + const fakeManager = { + name: managerId.split(':')[1], + displayName: managerId, + preferredPackageManagerId: 'ms-python.python:pip', + get: getStub, + set: setStub, + resolve: sandbox.stub().resolves(undefined), + refresh: sandbox.stub().resolves(), + getEnvironments: sandbox.stub().resolves([]), + onDidChangeEnvironments: sandbox.stub().returns({ dispose: () => {} }), + onDidChangeEnvironment: sandbox.stub().returns({ dispose: () => {} }), + }; + envManagers.registerEnvironmentManager(fakeManager as any, { extensionId: 'ms-python.python' }); + return managerId; + } + + test('routes and reads a global selection through its selected manager', async () => { + const configuredVenvGet = sandbox.stub().resolves(configuredVenvEnvironment); + registerFakeManager('ms-python.python:venv', configuredVenvGet); + + const selectedSystemGet = sandbox.stub().resolves(selectedSystemEnvironment); + registerFakeManager('ms-python.python:system', selectedSystemGet); + + await envManagers.setEnvironments('global', selectedSystemEnvironment, false); + + assert.strictEqual( + envManagers.getEnvironmentManager(undefined)?.id, + 'ms-python.python:system', + 'A global selection must route through the selected environment manager', + ); + assert.strictEqual( + (await envManagers.getEnvironment(undefined))?.envId.id, + selectedSystemEnvironment.envId.id, + 'Global readback must return the selected non-venv environment', + ); + assert.ok(configuredVenvGet.notCalled, 'The configured venv manager must not serve the selected global readback'); + }); +}); + suite('PythonEnvironmentManagers - refreshEnvironment', () => { let sandbox: sinon.SinonSandbox; let envManagers: PythonEnvironmentManagers;