@@ -19,6 +19,7 @@ import {
1919import { ActivationStrings , Common } from '../../common/localize' ;
2020import * as logging from '../../common/logging' ;
2121import * as persistentState from '../../common/persistentState' ;
22+ import { createDeferred } from '../../common/utils/deferred' ;
2223import * as windowApis from '../../common/window.apis' ;
2324import * as workspaceApis from '../../common/workspace.apis' ;
2425import { EnvVarManager } from '../../features/execution/envVariableManager' ;
@@ -153,16 +154,21 @@ suite('TerminalEnvVarInjector', () => {
153154 } ) ;
154155
155156 suite ( 'env file changes' , ( ) => {
156- let envChangeCallback : ( ( args : { uri ?: Uri ; changeType : number } ) => void ) | undefined ;
157+ let envChangeCallback : ( ( args : { uri ?: Uri ; changeType : number } ) => Promise < void > ) | undefined ;
157158 let variables : Record < string , string > ;
159+ let variableReads : Promise < Record < string , string > > [ ] ;
158160 let existingFiles : Set < string > ;
159161 const defaultEnvFile = path . join ( testWorkspaceFolder . uri . fsPath , '.env' ) ;
160162 const configuredEnvFile = path . join ( testWorkspaceFolder . uri . fsPath , 'configured.env' ) ;
161163
162164 setup ( ( ) => {
163165 variables = { } ;
166+ variableReads = [ ] ;
164167 existingFiles = new Set ( ) ;
165- sinon . stub ( fse , 'pathExists' ) . callsFake ( async ( filePath ) => existingFiles . has ( path . resolve ( filePath . toString ( ) ) ) ) ;
168+ workspaceFoldersValue = undefined ;
169+ sinon
170+ . stub ( fse , 'pathExists' )
171+ . callsFake ( async ( filePath ) => existingFiles . has ( path . resolve ( filePath . toString ( ) ) ) ) ;
166172 sinon . stub ( workspaceApis , 'getWorkspaceFolder' ) . returns ( testWorkspaceFolder ) ;
167173 envVarManager . reset ( ) ;
168174 envVarManager . setup ( ( m ) => m . onDidChangeEnvironmentVariables ) . returns (
@@ -173,13 +179,12 @@ suite('TerminalEnvVarInjector', () => {
173179 ) ;
174180 envVarManager
175181 . setup ( ( m ) => m . getEnvironmentVariables ( typeMoq . It . isAny ( ) ) )
176- . returns ( ( ) => Promise . resolve ( { ...variables } ) ) ;
182+ . returns ( ( ) => variableReads . shift ( ) ?? Promise . resolve ( { ...variables } ) ) ;
177183 } ) ;
178184
179185 async function fireChange ( changeType : number , filePath = defaultEnvFile ) : Promise < void > {
180186 assert . ok ( envChangeCallback ) ;
181- envChangeCallback ( { uri : Uri . file ( filePath ) , changeType } ) ;
182- await new Promise ( ( resolve ) => setTimeout ( resolve , 20 ) ) ;
187+ await envChangeCallback ( { uri : Uri . file ( filePath ) , changeType } ) ;
183188 }
184189
185190 test ( 'creating an env file with injection disabled preserves shell activation variables' , async ( ) => {
@@ -195,7 +200,6 @@ suite('TerminalEnvVarInjector', () => {
195200 test ( 'creating, editing, and deleting an env file updates only its injected variables' , async ( ) => {
196201 getConfigurationStub . returns ( createMockConfig ( { useEnvFile : true } ) as WorkspaceConfiguration ) ;
197202 injector = new TerminalEnvVarInjector ( envVarCollection . object , envVarManager . object ) ;
198- await new Promise ( ( resolve ) => setTimeout ( resolve , 20 ) ) ;
199203
200204 existingFiles . add ( defaultEnvFile ) ;
201205 variables = { TERMINAL_PROBE_VALUE : 'created' } ;
@@ -222,7 +226,7 @@ suite('TerminalEnvVarInjector', () => {
222226 existingFiles . add ( configuredEnvFile ) ;
223227 variables = { CONFIGURED_VALUE : 'configured' , PROJECT_VALUE : 'project' } ;
224228 injector = new TerminalEnvVarInjector ( envVarCollection . object , envVarManager . object ) ;
225- await new Promise ( ( resolve ) => setTimeout ( resolve , 20 ) ) ;
229+ await fireChange ( 2 , configuredEnvFile ) ;
226230
227231 existingFiles . delete ( configuredEnvFile ) ;
228232 variables = { PROJECT_VALUE : 'project' } ;
@@ -241,7 +245,7 @@ suite('TerminalEnvVarInjector', () => {
241245 existingFiles . add ( configuredEnvFile ) ;
242246 variables = { CONFIGURED_VALUE : 'configured' , PROJECT_VALUE : 'project' } ;
243247 injector = new TerminalEnvVarInjector ( envVarCollection . object , envVarManager . object ) ;
244- await new Promise ( ( resolve ) => setTimeout ( resolve , 20 ) ) ;
248+ await fireChange ( 2 ) ;
245249
246250 existingFiles . delete ( defaultEnvFile ) ;
247251 variables = { CONFIGURED_VALUE : 'configured' } ;
@@ -251,6 +255,27 @@ suite('TerminalEnvVarInjector', () => {
251255 sinon . assert . neverCalledWith ( mockScopedCollection . delete , 'CONFIGURED_VALUE' ) ;
252256 sinon . assert . notCalled ( mockScopedCollection . clear ) ;
253257 } ) ;
258+
259+ test ( 'does not let an older env file refresh overwrite newer variables' , async ( ) => {
260+ getConfigurationStub . returns ( createMockConfig ( { useEnvFile : true } ) as WorkspaceConfiguration ) ;
261+ existingFiles . add ( defaultEnvFile ) ;
262+ const olderRefresh = createDeferred < Record < string , string > > ( ) ;
263+ const newerRefresh = createDeferred < Record < string , string > > ( ) ;
264+ variableReads . push ( olderRefresh . promise , newerRefresh . promise ) ;
265+ injector = new TerminalEnvVarInjector ( envVarCollection . object , envVarManager . object ) ;
266+
267+ assert . ok ( envChangeCallback ) ;
268+ const firstChange = envChangeCallback ( { uri : Uri . file ( defaultEnvFile ) , changeType : 3 } ) ;
269+ const secondChange = envChangeCallback ( { uri : Uri . file ( defaultEnvFile ) , changeType : 2 } ) ;
270+
271+ newerRefresh . resolve ( { NEW_VALUE : 'new' } ) ;
272+ await secondChange ;
273+ olderRefresh . resolve ( { OLD_VALUE : 'old' } ) ;
274+ await firstChange ;
275+
276+ sinon . assert . calledOnceWithExactly ( mockScopedCollection . replace , 'NEW_VALUE' , 'new' ) ;
277+ sinon . assert . neverCalledWith ( mockScopedCollection . replace , 'OLD_VALUE' , 'old' ) ;
278+ } ) ;
254279 } ) ;
255280
256281 test ( 'should NOT inject when useEnvFile is false even with python.envFile configured' , async ( ) => {
0 commit comments