1
+ /* eslint-disable @typescript-eslint/no-explicit-any */
1
2
// Copyright (c) Microsoft Corporation. All rights reserved.
2
3
// Licensed under the MIT License.
3
4
@@ -22,13 +23,31 @@ import * as externalDependencies from '../../../../../client/pythonEnvironments/
22
23
import { noop } from '../../../../core' ;
23
24
import { TEST_LAYOUT_ROOT } from '../../../common/commonTestConstants' ;
24
25
import { SimpleLocator } from '../../common' ;
25
- import { assertEnvEqual , assertEnvsEqual } from '../envTestUtils' ;
26
+ import { assertEnvEqual , assertEnvsEqual , createFile , deleteFile } from '../envTestUtils' ;
27
+ import { OSType , getOSType } from '../../../../common' ;
26
28
27
29
suite ( 'Python envs locator - Environments Collection' , async ( ) => {
28
30
let collectionService : EnvsCollectionService ;
29
31
let storage : PythonEnvInfo [ ] ;
30
32
31
33
const updatedName = 'updatedName' ;
34
+ const pathToCondaPython = getOSType ( ) === OSType . Windows ? 'python.exe' : path . join ( 'bin' , 'python' ) ;
35
+ const condaEnvWithoutPython = createEnv (
36
+ 'python' ,
37
+ undefined ,
38
+ undefined ,
39
+ path . join ( TEST_LAYOUT_ROOT , 'envsWithoutPython' , 'condaLackingPython' ) ,
40
+ PythonEnvKind . Conda ,
41
+ path . join ( TEST_LAYOUT_ROOT , 'envsWithoutPython' , 'condaLackingPython' , pathToCondaPython ) ,
42
+ ) ;
43
+ const condaEnvWithPython = createEnv (
44
+ path . join ( TEST_LAYOUT_ROOT , 'envsWithoutPython' , 'condaLackingPython' , pathToCondaPython ) ,
45
+ undefined ,
46
+ undefined ,
47
+ path . join ( TEST_LAYOUT_ROOT , 'envsWithoutPython' , 'condaLackingPython' ) ,
48
+ PythonEnvKind . Conda ,
49
+ path . join ( TEST_LAYOUT_ROOT , 'envsWithoutPython' , 'condaLackingPython' , pathToCondaPython ) ,
50
+ ) ;
32
51
33
52
function applyChangeEventToEnvList ( envs : PythonEnvInfo [ ] , event : PythonEnvCollectionChangedEvent ) {
34
53
const env = event . old ?? event . new ;
@@ -49,8 +68,17 @@ suite('Python envs locator - Environments Collection', async () => {
49
68
return envs ;
50
69
}
51
70
52
- function createEnv ( executable : string , searchLocation ?: Uri , name ?: string , location ?: string ) {
53
- return buildEnvInfo ( { executable, searchLocation, name, location } ) ;
71
+ function createEnv (
72
+ executable : string ,
73
+ searchLocation ?: Uri ,
74
+ name ?: string ,
75
+ location ?: string ,
76
+ kind ?: PythonEnvKind ,
77
+ id ?: string ,
78
+ ) {
79
+ const env = buildEnvInfo ( { executable, searchLocation, name, location, kind } ) ;
80
+ env . id = id ?? env . id ;
81
+ return env ;
54
82
}
55
83
56
84
function getLocatorEnvs ( ) {
@@ -77,12 +105,7 @@ suite('Python envs locator - Environments Collection', async () => {
77
105
path . join ( TEST_LAYOUT_ROOT , 'pipenv' , 'project1' , '.venv' , 'Scripts' , 'python.exe' ) ,
78
106
Uri . file ( TEST_LAYOUT_ROOT ) ,
79
107
) ;
80
- const envCached3 = createEnv (
81
- 'python' ,
82
- undefined ,
83
- undefined ,
84
- path . join ( TEST_LAYOUT_ROOT , 'envsWithoutPython' , 'condaLackingPython' ) ,
85
- ) ;
108
+ const envCached3 = condaEnvWithoutPython ;
86
109
return [ cachedEnvForWorkspace , envCached1 , envCached2 , envCached3 ] ;
87
110
}
88
111
@@ -123,7 +146,8 @@ suite('Python envs locator - Environments Collection', async () => {
123
146
collectionService = new EnvsCollectionService ( cache , parentLocator ) ;
124
147
} ) ;
125
148
126
- teardown ( ( ) => {
149
+ teardown ( async ( ) => {
150
+ await deleteFile ( condaEnvWithPython . executable . filename ) ; // Restore to the original state
127
151
sinon . restore ( ) ;
128
152
} ) ;
129
153
@@ -404,7 +428,7 @@ suite('Python envs locator - Environments Collection', async () => {
404
428
env . executable . mtime = 100 ;
405
429
sinon . stub ( externalDependencies , 'getFileInfo' ) . resolves ( { ctime : 100 , mtime : 100 } ) ;
406
430
const parentLocator = new SimpleLocator ( [ ] , {
407
- resolve : async ( e : PythonEnvInfo ) => {
431
+ resolve : async ( e : any ) => {
408
432
if ( env . executable . filename === e . executable . filename ) {
409
433
return resolvedViaLocator ;
410
434
}
@@ -434,7 +458,7 @@ suite('Python envs locator - Environments Collection', async () => {
434
458
waitDeferred . resolve ( ) ;
435
459
await deferred . promise ;
436
460
} ,
437
- resolve : async ( e : PythonEnvInfo ) => {
461
+ resolve : async ( e : any ) => {
438
462
if ( env . executable . filename === e . executable . filename ) {
439
463
return resolvedViaLocator ;
440
464
}
@@ -464,7 +488,7 @@ suite('Python envs locator - Environments Collection', async () => {
464
488
env . executable . mtime = 90 ;
465
489
sinon . stub ( externalDependencies , 'getFileInfo' ) . resolves ( { ctime : 100 , mtime : 100 } ) ;
466
490
const parentLocator = new SimpleLocator ( [ ] , {
467
- resolve : async ( e : PythonEnvInfo ) => {
491
+ resolve : async ( e : any ) => {
468
492
if ( env . executable . filename === e . executable . filename ) {
469
493
return resolvedViaLocator ;
470
494
}
@@ -483,7 +507,7 @@ suite('Python envs locator - Environments Collection', async () => {
483
507
test ( 'resolveEnv() adds env to cache after resolving using downstream locator' , async ( ) => {
484
508
const resolvedViaLocator = buildEnvInfo ( { executable : 'Resolved via locator' } ) ;
485
509
const parentLocator = new SimpleLocator ( [ ] , {
486
- resolve : async ( e : PythonEnvInfo ) => {
510
+ resolve : async ( e : any ) => {
487
511
if ( resolvedViaLocator . executable . filename === e . executable . filename ) {
488
512
return resolvedViaLocator ;
489
513
}
@@ -500,6 +524,49 @@ suite('Python envs locator - Environments Collection', async () => {
500
524
assertEnvsEqual ( envs , [ resolved ] ) ;
501
525
} ) ;
502
526
527
+ test ( 'resolveEnv() uses underlying locator once conda envs without python get a python installed' , async ( ) => {
528
+ const cachedEnvs = [ condaEnvWithoutPython ] ;
529
+ const parentLocator = new SimpleLocator (
530
+ [ ] ,
531
+ {
532
+ resolve : async ( e ) => {
533
+ if ( condaEnvWithoutPython . location === ( e as string ) ) {
534
+ return condaEnvWithPython ;
535
+ }
536
+ return undefined ;
537
+ } ,
538
+ } ,
539
+ { resolveAsString : true } ,
540
+ ) ;
541
+ const cache = await createCollectionCache ( {
542
+ get : ( ) => cachedEnvs ,
543
+ store : async ( ) => noop ( ) ,
544
+ } ) ;
545
+ collectionService = new EnvsCollectionService ( cache , parentLocator ) ;
546
+ let resolved = await collectionService . resolveEnv ( condaEnvWithoutPython . location ) ;
547
+ assertEnvEqual ( resolved , condaEnvWithoutPython ) ; // Ensure cache is used to resolve such envs.
548
+
549
+ condaEnvWithPython . executable . ctime = 100 ;
550
+ condaEnvWithPython . executable . mtime = 100 ;
551
+ sinon . stub ( externalDependencies , 'getFileInfo' ) . resolves ( { ctime : 100 , mtime : 100 } ) ;
552
+
553
+ const events : PythonEnvCollectionChangedEvent [ ] = [ ] ;
554
+ collectionService . onChanged ( ( e ) => {
555
+ events . push ( e ) ;
556
+ } ) ;
557
+
558
+ await createFile ( condaEnvWithPython . executable . filename ) ; // Install Python into the env
559
+
560
+ resolved = await collectionService . resolveEnv ( condaEnvWithoutPython . location ) ;
561
+ assertEnvEqual ( resolved , condaEnvWithPython ) ; // Ensure it resolves latest info.
562
+
563
+ // Verify conda env without python in cache is replaced with updated info.
564
+ const envs = collectionService . getEnvs ( ) ;
565
+ assertEnvsEqual ( envs , [ condaEnvWithPython ] ) ;
566
+
567
+ expect ( events . length ) . to . equal ( 1 , 'Update event should be fired' ) ;
568
+ } ) ;
569
+
503
570
test ( 'Ensure events from downstream locators do not trigger new refreshes if a refresh is already scheduled' , async ( ) => {
504
571
const refreshDeferred = createDeferred ( ) ;
505
572
let refreshCount = 0 ;
0 commit comments