@@ -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