1+ /* eslint-disable @typescript-eslint/no-explicit-any */
12// Copyright (c) Microsoft Corporation. All rights reserved.
23// Licensed under the MIT License.
34
@@ -22,13 +23,31 @@ import * as externalDependencies from '../../../../../client/pythonEnvironments/
2223import { noop } from '../../../../core' ;
2324import { TEST_LAYOUT_ROOT } from '../../../common/commonTestConstants' ;
2425import { SimpleLocator } from '../../common' ;
25- import { assertEnvEqual , assertEnvsEqual } from '../envTestUtils' ;
26+ import { assertEnvEqual , assertEnvsEqual , createFile , deleteFile } from '../envTestUtils' ;
27+ import { OSType , getOSType } from '../../../../common' ;
2628
2729suite ( 'Python envs locator - Environments Collection' , async ( ) => {
2830 let collectionService : EnvsCollectionService ;
2931 let storage : PythonEnvInfo [ ] ;
3032
3133 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+ ) ;
3251
3352 function applyChangeEventToEnvList ( envs : PythonEnvInfo [ ] , event : PythonEnvCollectionChangedEvent ) {
3453 const env = event . old ?? event . new ;
@@ -49,8 +68,17 @@ suite('Python envs locator - Environments Collection', async () => {
4968 return envs ;
5069 }
5170
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 ;
5482 }
5583
5684 function getLocatorEnvs ( ) {
@@ -77,12 +105,7 @@ suite('Python envs locator - Environments Collection', async () => {
77105 path . join ( TEST_LAYOUT_ROOT , 'pipenv' , 'project1' , '.venv' , 'Scripts' , 'python.exe' ) ,
78106 Uri . file ( TEST_LAYOUT_ROOT ) ,
79107 ) ;
80- const envCached3 = createEnv (
81- 'python' ,
82- undefined ,
83- undefined ,
84- path . join ( TEST_LAYOUT_ROOT , 'envsWithoutPython' , 'condaLackingPython' ) ,
85- ) ;
108+ const envCached3 = condaEnvWithoutPython ;
86109 return [ cachedEnvForWorkspace , envCached1 , envCached2 , envCached3 ] ;
87110 }
88111
@@ -123,7 +146,8 @@ suite('Python envs locator - Environments Collection', async () => {
123146 collectionService = new EnvsCollectionService ( cache , parentLocator ) ;
124147 } ) ;
125148
126- teardown ( ( ) => {
149+ teardown ( async ( ) => {
150+ await deleteFile ( condaEnvWithPython . executable . filename ) ; // Restore to the original state
127151 sinon . restore ( ) ;
128152 } ) ;
129153
@@ -404,7 +428,7 @@ suite('Python envs locator - Environments Collection', async () => {
404428 env . executable . mtime = 100 ;
405429 sinon . stub ( externalDependencies , 'getFileInfo' ) . resolves ( { ctime : 100 , mtime : 100 } ) ;
406430 const parentLocator = new SimpleLocator ( [ ] , {
407- resolve : async ( e : PythonEnvInfo ) => {
431+ resolve : async ( e : any ) => {
408432 if ( env . executable . filename === e . executable . filename ) {
409433 return resolvedViaLocator ;
410434 }
@@ -434,7 +458,7 @@ suite('Python envs locator - Environments Collection', async () => {
434458 waitDeferred . resolve ( ) ;
435459 await deferred . promise ;
436460 } ,
437- resolve : async ( e : PythonEnvInfo ) => {
461+ resolve : async ( e : any ) => {
438462 if ( env . executable . filename === e . executable . filename ) {
439463 return resolvedViaLocator ;
440464 }
@@ -464,7 +488,7 @@ suite('Python envs locator - Environments Collection', async () => {
464488 env . executable . mtime = 90 ;
465489 sinon . stub ( externalDependencies , 'getFileInfo' ) . resolves ( { ctime : 100 , mtime : 100 } ) ;
466490 const parentLocator = new SimpleLocator ( [ ] , {
467- resolve : async ( e : PythonEnvInfo ) => {
491+ resolve : async ( e : any ) => {
468492 if ( env . executable . filename === e . executable . filename ) {
469493 return resolvedViaLocator ;
470494 }
@@ -483,7 +507,7 @@ suite('Python envs locator - Environments Collection', async () => {
483507 test ( 'resolveEnv() adds env to cache after resolving using downstream locator' , async ( ) => {
484508 const resolvedViaLocator = buildEnvInfo ( { executable : 'Resolved via locator' } ) ;
485509 const parentLocator = new SimpleLocator ( [ ] , {
486- resolve : async ( e : PythonEnvInfo ) => {
510+ resolve : async ( e : any ) => {
487511 if ( resolvedViaLocator . executable . filename === e . executable . filename ) {
488512 return resolvedViaLocator ;
489513 }
@@ -500,6 +524,49 @@ suite('Python envs locator - Environments Collection', async () => {
500524 assertEnvsEqual ( envs , [ resolved ] ) ;
501525 } ) ;
502526
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+
503570 test ( 'Ensure events from downstream locators do not trigger new refreshes if a refresh is already scheduled' , async ( ) => {
504571 const refreshDeferred = createDeferred ( ) ;
505572 let refreshCount = 0 ;
0 commit comments