@@ -31,6 +31,7 @@ suite('getAllExtraSearchPaths Integration Tests', () => {
3131 mockGetWorkspaceFolders = sinon . stub ( workspaceApis , 'getWorkspaceFolders' ) ;
3232 mockUntildify = sinon . stub ( pathUtils , 'untildify' ) ;
3333 // Also stub the namespace import version that might be used by untildifyArray
34+ // Handle both Unix (~/) and Windows-style paths
3435 sinon
3536 . stub ( pathUtils , 'untildifyArray' )
3637 . callsFake ( ( paths : string [ ] ) =>
@@ -103,8 +104,8 @@ suite('getAllExtraSearchPaths Integration Tests', () => {
103104 assert . deepStrictEqual ( result , [ ] ) ;
104105 } ) ;
105106
106- test ( 'Legacy and global paths are consolidated' , async ( ) => {
107- // Mock → Legacy paths and globalSearchPaths both exist
107+ test ( 'Legacy and global paths are consolidated (Unix) ' , async ( ) => {
108+ // Mock → Legacy paths and globalSearchPaths both exist (Unix-style)
108109 pythonConfig . get . withArgs ( 'venvPath' ) . returns ( '/home/user/.virtualenvs' ) ;
109110 pythonConfig . get . withArgs ( 'venvFolders' ) . returns ( [ '/home/user/venvs' ] ) ;
110111 envConfig . inspect . withArgs ( 'globalSearchPaths' ) . returns ( {
@@ -122,8 +123,27 @@ suite('getAllExtraSearchPaths Integration Tests', () => {
122123 assert . deepStrictEqual ( actual , expected , 'Should contain exactly the expected paths' ) ;
123124 } ) ;
124125
125- test ( 'Legacy paths included alongside new settings' , async ( ) => {
126- // Mock → Legacy paths exist, no globalSearchPaths
126+ test ( 'Legacy and global paths are consolidated (Windows)' , async ( ) => {
127+ // Mock → Legacy paths and globalSearchPaths both exist (Windows-style)
128+ pythonConfig . get . withArgs ( 'venvPath' ) . returns ( 'C:\\Users\\dev\\.virtualenvs' ) ;
129+ pythonConfig . get . withArgs ( 'venvFolders' ) . returns ( [ 'D:\\shared\\venvs' ] ) ;
130+ envConfig . inspect . withArgs ( 'globalSearchPaths' ) . returns ( {
131+ globalValue : [ 'C:\\Users\\dev\\.virtualenvs' , 'D:\\shared\\venvs' , 'E:\\additional\\path' ] ,
132+ } ) ;
133+ envConfig . inspect . withArgs ( 'workspaceSearchPaths' ) . returns ( { } ) ;
134+
135+ // Run
136+ const result = await getAllExtraSearchPaths ( ) ;
137+
138+ // Assert - Should consolidate all paths (duplicates removed), normalized to forward slashes
139+ const expected = new Set ( [ 'C:/Users/dev/.virtualenvs' , 'D:/shared/venvs' , 'E:/additional/path' ] ) ;
140+ const actual = new Set ( result ) ;
141+ assert . strictEqual ( actual . size , expected . size , 'Should have correct number of unique paths' ) ;
142+ assert . deepStrictEqual ( actual , expected , 'Should contain exactly the expected paths' ) ;
143+ } ) ;
144+
145+ test ( 'Legacy paths included alongside new settings (Unix)' , async ( ) => {
146+ // Mock → Legacy paths exist, no globalSearchPaths (Unix-style)
127147 pythonConfig . get . withArgs ( 'venvPath' ) . returns ( '/home/user/.virtualenvs' ) ;
128148 pythonConfig . get . withArgs ( 'venvFolders' ) . returns ( [ '/home/user/venvs' , '/home/user/conda' ] ) ;
129149 envConfig . inspect . withArgs ( 'globalSearchPaths' ) . returns ( { globalValue : [ ] } ) ;
@@ -139,6 +159,23 @@ suite('getAllExtraSearchPaths Integration Tests', () => {
139159 assert . deepStrictEqual ( actual , expected , 'Should contain exactly the expected paths' ) ;
140160 } ) ;
141161
162+ test ( 'Legacy paths included alongside new settings (Windows)' , async ( ) => {
163+ // Mock → Legacy paths exist, no globalSearchPaths (Windows-style)
164+ pythonConfig . get . withArgs ( 'venvPath' ) . returns ( 'C:\\Users\\dev\\.virtualenvs' ) ;
165+ pythonConfig . get . withArgs ( 'venvFolders' ) . returns ( [ 'C:\\Users\\dev\\venvs' , 'D:\\conda\\envs' ] ) ;
166+ envConfig . inspect . withArgs ( 'globalSearchPaths' ) . returns ( { globalValue : [ ] } ) ;
167+ envConfig . inspect . withArgs ( 'workspaceSearchPaths' ) . returns ( { } ) ;
168+
169+ // Run
170+ const result = await getAllExtraSearchPaths ( ) ;
171+
172+ // Assert - Should include all legacy paths, normalized to forward slashes
173+ const expected = new Set ( [ 'C:/Users/dev/.virtualenvs' , 'C:/Users/dev/venvs' , 'D:/conda/envs' ] ) ;
174+ const actual = new Set ( result ) ;
175+ assert . strictEqual ( actual . size , expected . size , 'Should have correct number of unique paths' ) ;
176+ assert . deepStrictEqual ( actual , expected , 'Should contain exactly the expected paths' ) ;
177+ } ) ;
178+
142179 test ( 'Legacy and global paths combined with deduplication' , async ( ) => {
143180 // Mock → Some overlap between legacy and global paths
144181 pythonConfig . get . withArgs ( 'venvPath' ) . returns ( '/home/user/.virtualenvs' ) ;
@@ -184,8 +221,8 @@ suite('getAllExtraSearchPaths Integration Tests', () => {
184221 } ) ;
185222
186223 suite ( 'Configuration Source Tests' , ( ) => {
187- test ( 'Global search paths with tilde expansion' , async ( ) => {
188- // Mock → No legacy, global paths with tildes
224+ test ( 'Global search paths with tilde expansion (Unix) ' , async ( ) => {
225+ // Mock → No legacy, global paths with tildes (Unix ~ expansion)
189226 pythonConfig . get . withArgs ( 'venvPath' ) . returns ( undefined ) ;
190227 pythonConfig . get . withArgs ( 'venvFolders' ) . returns ( undefined ) ;
191228 envConfig . inspect . withArgs ( 'globalSearchPaths' ) . returns ( {
@@ -206,8 +243,27 @@ suite('getAllExtraSearchPaths Integration Tests', () => {
206243 assert . deepStrictEqual ( actual , expected , 'Should contain exactly the expected paths' ) ;
207244 } ) ;
208245
209- test ( 'Workspace folder setting preferred over workspace setting' , async ( ) => {
210- // Mock → Workspace settings at different levels
246+ test ( 'Global search paths with absolute paths (Windows)' , async ( ) => {
247+ // Mock → No legacy, global paths with Windows absolute paths
248+ pythonConfig . get . withArgs ( 'venvPath' ) . returns ( undefined ) ;
249+ pythonConfig . get . withArgs ( 'venvFolders' ) . returns ( undefined ) ;
250+ envConfig . inspect . withArgs ( 'globalSearchPaths' ) . returns ( {
251+ globalValue : [ 'C:\\Users\\dev\\virtualenvs' , 'D:\\conda\\envs' ] ,
252+ } ) ;
253+ envConfig . inspect . withArgs ( 'workspaceSearchPaths' ) . returns ( { } ) ;
254+
255+ // Run
256+ const result = await getAllExtraSearchPaths ( ) ;
257+
258+ // Assert - Paths normalized to forward slashes
259+ const expected = new Set ( [ 'C:/Users/dev/virtualenvs' , 'D:/conda/envs' ] ) ;
260+ const actual = new Set ( result ) ;
261+ assert . strictEqual ( actual . size , expected . size , 'Should have correct number of unique paths' ) ;
262+ assert . deepStrictEqual ( actual , expected , 'Should contain exactly the expected paths' ) ;
263+ } ) ;
264+
265+ test ( 'Workspace folder setting preferred over workspace setting (Unix)' , async ( ) => {
266+ // Mock → Workspace settings at different levels (Unix-style)
211267 pythonConfig . get . withArgs ( 'venvPath' ) . returns ( undefined ) ;
212268 pythonConfig . get . withArgs ( 'venvFolders' ) . returns ( undefined ) ;
213269 envConfig . inspect . withArgs ( 'globalSearchPaths' ) . returns ( { globalValue : [ ] } ) ;
@@ -230,6 +286,30 @@ suite('getAllExtraSearchPaths Integration Tests', () => {
230286 assert . deepStrictEqual ( actual , expected , 'Should contain exactly the expected paths' ) ;
231287 } ) ;
232288
289+ test ( 'Workspace folder setting preferred over workspace setting (Windows)' , async ( ) => {
290+ // Mock → Workspace settings at different levels (Windows-style)
291+ pythonConfig . get . withArgs ( 'venvPath' ) . returns ( undefined ) ;
292+ pythonConfig . get . withArgs ( 'venvFolders' ) . returns ( undefined ) ;
293+ envConfig . inspect . withArgs ( 'globalSearchPaths' ) . returns ( { globalValue : [ ] } ) ;
294+ envConfig . inspect . withArgs ( 'workspaceSearchPaths' ) . returns ( {
295+ workspaceValue : [ 'D:\\workspace-level' ] ,
296+ workspaceFolderValue : [ 'C:\\folder-level\\path' ] ,
297+ } ) ;
298+
299+ const workspace1 = Uri . file ( 'C:\\Projects\\project1' ) ;
300+ const workspace2 = Uri . file ( 'C:\\Projects\\project2' ) ;
301+ mockGetWorkspaceFolders . returns ( [ { uri : workspace1 } , { uri : workspace2 } ] ) ;
302+
303+ // Run
304+ const result = await getAllExtraSearchPaths ( ) ;
305+
306+ // Assert - workspaceFolderValue takes priority, normalized to forward slashes
307+ const expected = new Set ( [ 'C:/folder-level/path' ] ) ;
308+ const actual = new Set ( result ) ;
309+ assert . strictEqual ( actual . size , expected . size , 'Should have correct number of unique paths' ) ;
310+ assert . deepStrictEqual ( actual , expected , 'Should contain exactly the expected paths' ) ;
311+ } ) ;
312+
233313 test ( 'Global workspace setting logs error and is ignored' , async ( ) => {
234314 // Mock → Workspace setting incorrectly set at global level
235315 pythonConfig . get . withArgs ( 'venvPath' ) . returns ( undefined ) ;
@@ -276,8 +356,8 @@ suite('getAllExtraSearchPaths Integration Tests', () => {
276356 } ) ;
277357
278358 suite ( 'Path Resolution Tests' , ( ) => {
279- test ( 'Absolute paths used as-is' , async ( ) => {
280- // Mock → Mix of absolute paths
359+ test ( 'Absolute paths used as-is (Unix) ' , async ( ) => {
360+ // Mock → Mix of absolute paths (Unix-style)
281361 pythonConfig . get . withArgs ( 'venvPath' ) . returns ( undefined ) ;
282362 pythonConfig . get . withArgs ( 'venvFolders' ) . returns ( undefined ) ;
283363 envConfig . inspect . withArgs ( 'globalSearchPaths' ) . returns ( {
@@ -293,13 +373,37 @@ suite('getAllExtraSearchPaths Integration Tests', () => {
293373 // Run
294374 const result = await getAllExtraSearchPaths ( ) ;
295375
296- // Assert - For absolute paths, they should remain unchanged regardless of platform
376+ // Assert - For absolute paths, they should remain unchanged
297377 const expected = new Set ( [ '/absolute/path1' , '/absolute/path2' , '/absolute/workspace/path' ] ) ;
298378 const actual = new Set ( result ) ;
299379 assert . strictEqual ( actual . size , expected . size , 'Should have correct number of unique paths' ) ;
300380 assert . deepStrictEqual ( actual , expected , 'Should contain exactly the expected paths' ) ;
301381 } ) ;
302382
383+ test ( 'Absolute paths used as-is (Windows)' , async ( ) => {
384+ // Mock → Mix of absolute paths (Windows-style)
385+ pythonConfig . get . withArgs ( 'venvPath' ) . returns ( undefined ) ;
386+ pythonConfig . get . withArgs ( 'venvFolders' ) . returns ( undefined ) ;
387+ envConfig . inspect . withArgs ( 'globalSearchPaths' ) . returns ( {
388+ globalValue : [ 'C:\\absolute\\path1' , 'D:\\absolute\\path2' ] ,
389+ } ) ;
390+ envConfig . inspect . withArgs ( 'workspaceSearchPaths' ) . returns ( {
391+ workspaceFolderValue : [ 'E:\\workspace\\envs' ] ,
392+ } ) ;
393+
394+ const workspace = Uri . file ( 'C:\\workspace' ) ;
395+ mockGetWorkspaceFolders . returns ( [ { uri : workspace } ] ) ;
396+
397+ // Run
398+ const result = await getAllExtraSearchPaths ( ) ;
399+
400+ // Assert - Windows paths normalized to forward slashes
401+ const expected = new Set ( [ 'C:/absolute/path1' , 'D:/absolute/path2' , 'E:/workspace/envs' ] ) ;
402+ const actual = new Set ( result ) ;
403+ assert . strictEqual ( actual . size , expected . size , 'Should have correct number of unique paths' ) ;
404+ assert . deepStrictEqual ( actual , expected , 'Should contain exactly the expected paths' ) ;
405+ } ) ;
406+
303407 test ( 'Relative paths are resolved against workspace folders' , async ( ) => {
304408 // Mock → Relative workspace paths with multiple workspace folders
305409 pythonConfig . get . withArgs ( 'venvPath' ) . returns ( undefined ) ;
@@ -384,8 +488,8 @@ suite('getAllExtraSearchPaths Integration Tests', () => {
384488 assert . deepStrictEqual ( result , [ ] ) ;
385489 } ) ;
386490
387- test ( 'Power user - complex mix of all source types' , async ( ) => {
388- // Mock → Complex real-world scenario
491+ test ( 'Power user - complex mix of all source types (Unix) ' , async ( ) => {
492+ // Mock → Complex real-world scenario (Unix-style)
389493 pythonConfig . get . withArgs ( 'venvPath' ) . returns ( '/legacy/venv/path' ) ;
390494 pythonConfig . get . withArgs ( 'venvFolders' ) . returns ( [ '/legacy/venvs' ] ) ;
391495 envConfig . inspect . withArgs ( 'globalSearchPaths' ) . returns ( {
@@ -405,7 +509,6 @@ suite('getAllExtraSearchPaths Integration Tests', () => {
405509 const result = await getAllExtraSearchPaths ( ) ;
406510
407511 // Assert - Relative paths are resolved against workspace folders, absolutes kept as-is
408- // Expected: 4 from legacy/global + 1 absolute workspace path + 2 resolved .venv paths
409512 assert . ok ( result . includes ( '/legacy/venv/path' ) ) ;
410513 assert . ok ( result . includes ( '/legacy/venvs' ) ) ;
411514 assert . ok ( result . includes ( '/global/conda' ) ) ;
@@ -416,8 +519,40 @@ suite('getAllExtraSearchPaths Integration Tests', () => {
416519 assert . ok ( result . some ( ( p ) => p . includes ( 'project2' ) && p . endsWith ( '.venv' ) ) ) ;
417520 } ) ;
418521
419- test ( 'Overlapping paths are deduplicated' , async ( ) => {
420- // Mock → Duplicate paths from different sources (using absolute paths to test dedup)
522+ test ( 'Power user - complex mix of all source types (Windows)' , async ( ) => {
523+ // Mock → Complex real-world scenario (Windows-style)
524+ pythonConfig . get . withArgs ( 'venvPath' ) . returns ( 'C:\\legacy\\venv\\path' ) ;
525+ pythonConfig . get . withArgs ( 'venvFolders' ) . returns ( [ 'D:\\legacy\\venvs' ] ) ;
526+ envConfig . inspect . withArgs ( 'globalSearchPaths' ) . returns ( {
527+ globalValue : [ 'C:\\legacy\\venv\\path' , 'D:\\legacy\\venvs' , 'E:\\global\\conda' ] ,
528+ } ) ;
529+ envConfig . inspect . withArgs ( 'workspaceSearchPaths' ) . returns ( {
530+ workspaceFolderValue : [ '.venv' , 'F:\\shared\\team\\envs' ] ,
531+ } ) ;
532+
533+ const workspace1 = Uri . file ( 'C:\\workspace\\project1' ) ;
534+ const workspace2 = Uri . file ( 'C:\\workspace\\project2' ) ;
535+ mockGetWorkspaceFolders . returns ( [ { uri : workspace1 } , { uri : workspace2 } ] ) ;
536+
537+ // Run
538+ const result = await getAllExtraSearchPaths ( ) ;
539+
540+ // Assert - All paths normalized to forward slashes
541+ assert . ok ( result . includes ( 'C:/legacy/venv/path' ) ) ;
542+ assert . ok ( result . includes ( 'D:/legacy/venvs' ) ) ;
543+ assert . ok ( result . includes ( 'E:/global/conda' ) ) ;
544+ assert . ok ( result . includes ( 'F:/shared/team/envs' ) ) ;
545+ // .venv resolved against both workspace folders
546+ assert . ok ( result . some ( ( p ) => p . includes ( 'project1' ) && p . endsWith ( '.venv' ) ) ) ;
547+ assert . ok ( result . some ( ( p ) => p . includes ( 'project2' ) && p . endsWith ( '.venv' ) ) ) ;
548+ // Verify no backslashes remain
549+ for ( const p of result ) {
550+ assert . ok ( ! p . includes ( '\\' ) , `Path should not contain backslashes: ${ p } ` ) ;
551+ }
552+ } ) ;
553+
554+ test ( 'Overlapping paths are deduplicated (Unix)' , async ( ) => {
555+ // Mock → Duplicate paths from different sources (Unix-style)
421556 pythonConfig . get . withArgs ( 'venvPath' ) . returns ( undefined ) ;
422557 pythonConfig . get . withArgs ( 'venvFolders' ) . returns ( undefined ) ;
423558 envConfig . inspect . withArgs ( 'globalSearchPaths' ) . returns ( {
@@ -440,6 +575,30 @@ suite('getAllExtraSearchPaths Integration Tests', () => {
440575 assert . deepStrictEqual ( actual , expected , 'Should contain exactly the expected paths' ) ;
441576 } ) ;
442577
578+ test ( 'Overlapping paths are deduplicated (Windows)' , async ( ) => {
579+ // Mock → Duplicate paths from different sources (Windows-style)
580+ pythonConfig . get . withArgs ( 'venvPath' ) . returns ( undefined ) ;
581+ pythonConfig . get . withArgs ( 'venvFolders' ) . returns ( undefined ) ;
582+ envConfig . inspect . withArgs ( 'globalSearchPaths' ) . returns ( {
583+ globalValue : [ 'C:\\shared\\path' , 'D:\\global\\unique' ] ,
584+ } ) ;
585+ envConfig . inspect . withArgs ( 'workspaceSearchPaths' ) . returns ( {
586+ workspaceFolderValue : [ 'C:\\shared\\path' , 'E:\\workspace\\unique' ] ,
587+ } ) ;
588+
589+ const workspace = Uri . file ( 'C:\\workspace' ) ;
590+ mockGetWorkspaceFolders . returns ( [ { uri : workspace } ] ) ;
591+
592+ // Run
593+ const result = await getAllExtraSearchPaths ( ) ;
594+
595+ // Assert - Duplicates should be removed, normalized to forward slashes
596+ const expected = new Set ( [ 'C:/shared/path' , 'D:/global/unique' , 'E:/workspace/unique' ] ) ;
597+ const actual = new Set ( result ) ;
598+ assert . strictEqual ( actual . size , expected . size , 'Should have correct number of unique paths' ) ;
599+ assert . deepStrictEqual ( actual , expected , 'Should contain exactly the expected paths' ) ;
600+ } ) ;
601+
443602 test ( 'All path types consolidated together' , async ( ) => {
444603 // Mock → Multiple path types from different sources
445604 pythonConfig . get . withArgs ( 'venvPath' ) . returns ( '/legacy/path' ) ;
0 commit comments