Skip to content

Commit 8ac8740

Browse files
authored
Fix defaultInterpreterPath variable expansion and stabilize interpreter selection tests (#1234)
Fixes interpreter selection when `python.defaultInterpreterPath` uses VS Code variables like `${workspaceFolder}`. #1233
1 parent a06de4b commit 8ac8740

File tree

2 files changed

+49
-1
lines changed

2 files changed

+49
-1
lines changed

src/features/interpreterSelection.ts

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import { commands, ConfigurationChangeEvent, Disposable, l10n, Uri } from 'vscod
66
import { PythonEnvironment, PythonEnvironmentApi } from '../api';
77
import { SYSTEM_MANAGER_ID, VENV_MANAGER_ID } from '../common/constants';
88
import { traceError, traceInfo, traceVerbose, traceWarn } from '../common/logging';
9+
import { resolveVariables } from '../common/utils/internalVariables';
910
import { showWarningMessage } from '../common/window.apis';
1011
import {
1112
getConfiguration,
@@ -105,7 +106,8 @@ async function resolvePriorityChainCore(
105106
// PRIORITY 3: User-configured python.defaultInterpreterPath
106107
const userInterpreterPath = getUserConfiguredSetting<string>('python', 'defaultInterpreterPath', scope);
107108
if (userInterpreterPath) {
108-
const resolved = await tryResolveInterpreterPath(nativeFinder, api, userInterpreterPath, envManagers);
109+
const expandedInterpreterPath = resolveVariables(userInterpreterPath, scope);
110+
const resolved = await tryResolveInterpreterPath(nativeFinder, api, expandedInterpreterPath, envManagers);
109111
if (resolved) {
110112
traceVerbose(`${logPrefix} Priority 3: Using defaultInterpreterPath: ${userInterpreterPath}`);
111113
return { result: resolved, errors };

src/test/features/interpreterSelection.unit.test.ts

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -174,6 +174,7 @@ suite('Interpreter Selection - Priority Chain', () => {
174174
// Setup: No pythonProjects[], no user-configured defaultEnvManager (returns undefined)
175175
// But there IS a user-configured defaultInterpreterPath
176176
sandbox.stub(workspaceApis, 'getConfiguration').returns(createMockConfig([]) as WorkspaceConfiguration);
177+
sandbox.stub(workspaceApis, 'getWorkspaceFolders').returns([]);
177178
sandbox.stub(helpers, 'getUserConfiguredSetting').callsFake((section: string, key: string) => {
178179
if (section === 'python' && key === 'defaultInterpreterPath') {
179180
return '/usr/bin/python3.11';
@@ -198,6 +199,7 @@ suite('Interpreter Selection - Priority Chain', () => {
198199
suite('Priority 3: python.defaultInterpreterPath', () => {
199200
test('should use defaultInterpreterPath when set and resolvable', async () => {
200201
sandbox.stub(workspaceApis, 'getConfiguration').returns(createMockConfig([]) as WorkspaceConfiguration);
202+
sandbox.stub(workspaceApis, 'getWorkspaceFolders').returns([]);
201203
sandbox.stub(helpers, 'getUserConfiguredSetting').callsFake((section: string, key: string) => {
202204
if (section === 'python' && key === 'defaultInterpreterPath') {
203205
return '/usr/bin/python3.11';
@@ -220,8 +222,49 @@ suite('Interpreter Selection - Priority Chain', () => {
220222
assert.strictEqual(result.environment.displayPath, '/usr/bin/python3.11');
221223
});
222224

225+
test('should resolve ${workspaceFolder} in defaultInterpreterPath before native resolution', async () => {
226+
const workspaceUri = Uri.file('/test/workspace');
227+
const expandedInterpreterPath = '/test/workspace/backend/.venv/bin/python';
228+
const workspaceFolder = { name: 'workspace', uri: workspaceUri } as WorkspaceFolder;
229+
230+
sandbox.stub(workspaceApis, 'getConfiguration').returns(createMockConfig([]) as WorkspaceConfiguration);
231+
sandbox.stub(workspaceApis, 'getWorkspaceFolder').returns(workspaceFolder);
232+
sandbox.stub(workspaceApis, 'getWorkspaceFolders').returns([workspaceFolder]);
233+
sandbox.stub(helpers, 'getUserConfiguredSetting').callsFake((section: string, key: string) => {
234+
if (section === 'python' && key === 'defaultInterpreterPath') {
235+
return '${workspaceFolder}/backend/.venv/bin/python';
236+
}
237+
return undefined;
238+
});
239+
mockNativeFinder.resolve.resolves({
240+
executable: expandedInterpreterPath,
241+
version: '3.11.0',
242+
prefix: '/test/workspace/backend/.venv',
243+
});
244+
mockApi.resolveEnvironment.resolves({
245+
...mockVenvEnv,
246+
displayPath: expandedInterpreterPath,
247+
environmentPath: Uri.file(expandedInterpreterPath),
248+
execInfo: { run: { executable: expandedInterpreterPath } },
249+
});
250+
251+
const result = await resolveEnvironmentByPriority(
252+
workspaceUri,
253+
mockEnvManagers as unknown as EnvironmentManagers,
254+
mockProjectManager as unknown as PythonProjectManager,
255+
mockNativeFinder as unknown as NativePythonFinder,
256+
mockApi as unknown as PythonEnvironmentApi,
257+
);
258+
259+
assert.strictEqual(result.source, 'defaultInterpreterPath');
260+
assert.ok(result.environment);
261+
assert.strictEqual(result.environment.displayPath, expandedInterpreterPath);
262+
assert.ok(mockNativeFinder.resolve.calledOnceWithExactly(expandedInterpreterPath));
263+
});
264+
223265
test('should fall through to Priority 4 when defaultInterpreterPath cannot be resolved', async () => {
224266
sandbox.stub(workspaceApis, 'getConfiguration').returns(createMockConfig([]) as WorkspaceConfiguration);
267+
sandbox.stub(workspaceApis, 'getWorkspaceFolders').returns([]);
225268
sandbox.stub(helpers, 'getUserConfiguredSetting').callsFake((section: string, key: string) => {
226269
if (section === 'python' && key === 'defaultInterpreterPath') {
227270
return '/nonexistent/python';
@@ -249,6 +292,7 @@ suite('Interpreter Selection - Priority Chain', () => {
249292
const resolvedHomebrewPath = '/opt/homebrew/bin/python3';
250293

251294
sandbox.stub(workspaceApis, 'getConfiguration').returns(createMockConfig([]) as WorkspaceConfiguration);
295+
sandbox.stub(workspaceApis, 'getWorkspaceFolders').returns([]);
252296
sandbox.stub(helpers, 'getUserConfiguredSetting').callsFake((section: string, key: string) => {
253297
if (section === 'python' && key === 'defaultInterpreterPath') {
254298
return userPyenvPath;
@@ -371,6 +415,7 @@ suite('Interpreter Selection - Priority Chain', () => {
371415
suite('Edge Cases', () => {
372416
test('should fall through when nativeFinder resolves but returns no executable', async () => {
373417
sandbox.stub(workspaceApis, 'getConfiguration').returns(createMockConfig([]) as WorkspaceConfiguration);
418+
sandbox.stub(workspaceApis, 'getWorkspaceFolders').returns([]);
374419
sandbox.stub(helpers, 'getUserConfiguredSetting').callsFake((section: string, key: string) => {
375420
if (section === 'python' && key === 'defaultInterpreterPath') {
376421
return '/some/path/python';
@@ -395,6 +440,7 @@ suite('Interpreter Selection - Priority Chain', () => {
395440

396441
test('should fall through when api.resolveEnvironment returns undefined', async () => {
397442
sandbox.stub(workspaceApis, 'getConfiguration').returns(createMockConfig([]) as WorkspaceConfiguration);
443+
sandbox.stub(workspaceApis, 'getWorkspaceFolders').returns([]);
398444
sandbox.stub(helpers, 'getUserConfiguredSetting').callsFake((section: string, key: string) => {
399445
if (section === 'python' && key === 'defaultInterpreterPath') {
400446
return '/usr/bin/python3.11';

0 commit comments

Comments
 (0)