@@ -23,6 +23,45 @@ import { PythonEnvironment, PythonEnvironmentApi } from '../../api';
2323import { ENVS_EXTENSION_ID } from '../constants' ;
2424import { TestEventHandler , waitForCondition } from '../testUtils' ;
2525
26+ const ENV_CHANGE_TIMEOUT_MS = 15_000 ;
27+
28+ function getDifferentEnvironment (
29+ environments : PythonEnvironment [ ] ,
30+ currentEnv : PythonEnvironment | undefined ,
31+ ) : PythonEnvironment | undefined {
32+ return environments . find ( ( env ) => env . envId . id !== currentEnv ?. envId . id ) ;
33+ }
34+
35+ async function setEnvironmentAndWaitForChange (
36+ api : PythonEnvironmentApi ,
37+ projectUri : vscode . Uri ,
38+ env : PythonEnvironment ,
39+ timeoutMs = ENV_CHANGE_TIMEOUT_MS ,
40+ ) : Promise < void > {
41+ await new Promise < void > ( ( resolve , reject ) => {
42+ let subscription : vscode . Disposable | undefined ;
43+ const timeout = setTimeout ( ( ) => {
44+ subscription ?. dispose ( ) ;
45+ reject ( new Error ( `onDidChangeEnvironment did not fire within ${ timeoutMs } ms. Expected envId: ${ env . envId . id } ` ) ) ;
46+ } , timeoutMs ) ;
47+
48+ subscription = api . onDidChangeEnvironment ( ( e ) => {
49+ if ( e . uri ?. toString ( ) === projectUri . toString ( ) && e . new ?. envId . id === env . envId . id ) {
50+ clearTimeout ( timeout ) ;
51+ subscription ?. dispose ( ) ;
52+ resolve ( ) ;
53+ }
54+ } ) ;
55+
56+ // Set environment after subscribing so we don't miss the event.
57+ api . setEnvironment ( projectUri , env ) . catch ( ( err ) => {
58+ clearTimeout ( timeout ) ;
59+ subscription ?. dispose ( ) ;
60+ reject ( err ) ;
61+ } ) ;
62+ } ) ;
63+ }
64+
2665suite ( 'Integration: Python Projects' , function ( ) {
2766 this . timeout ( 60_000 ) ;
2867
@@ -170,43 +209,19 @@ suite('Integration: Python Projects', function () {
170209 const project = projects [ 0 ] ;
171210
172211 // Pick an environment different from the current one so setEnvironment
173- // actually triggers a change event. If we pick the already-active env,
174- // the event never fires and the test times out .
212+ // actually triggers a change event. If all candidates map to the same env,
213+ // skip instead of hanging on an event that will never fire .
175214 const currentEnv = await api . getEnvironment ( project . uri ) ;
176- let env = environments [ 0 ] ;
177- if ( currentEnv && currentEnv . envId . id === env . envId . id ) {
178- env = environments [ 1 ] ;
215+ const env = getDifferentEnvironment ( environments , currentEnv ) ;
216+ if ( ! env ) {
217+ this . skip ( ) ;
218+ return ;
179219 }
180220
181- // Wait for the change event, then verify getEnvironment.
182221 // Using an event-driven approach instead of polling avoids a race condition where
183222 // setEnvironment's async settings write hasn't landed by the time getEnvironment
184223 // reads back the manager from settings.
185- await new Promise < void > ( ( resolve , reject ) => {
186- const timeout = setTimeout ( ( ) => {
187- subscription . dispose ( ) ;
188- reject (
189- new Error (
190- `onDidChangeEnvironment did not fire for project within 15s. Expected envId: ${ env . envId . id } ` ,
191- ) ,
192- ) ;
193- } , 15_000 ) ;
194-
195- const subscription = api . onDidChangeEnvironment ( ( e ) => {
196- if ( e . uri ?. toString ( ) === project . uri . toString ( ) && e . new ?. envId . id === env . envId . id ) {
197- clearTimeout ( timeout ) ;
198- subscription . dispose ( ) ;
199- resolve ( ) ;
200- }
201- } ) ;
202-
203- // Set environment after subscribing so we don't miss the event
204- api . setEnvironment ( project . uri , env ) . catch ( ( err ) => {
205- clearTimeout ( timeout ) ;
206- subscription . dispose ( ) ;
207- reject ( err ) ;
208- } ) ;
209- } ) ;
224+ await setEnvironmentAndWaitForChange ( api , project . uri , env ) ;
210225
211226 // Verify getEnvironment returns the correct value now that setEnvironment has fully completed
212227 const retrievedEnv = await api . getEnvironment ( project . uri ) ;
@@ -231,13 +246,12 @@ suite('Integration: Python Projects', function () {
231246
232247 const project = projects [ 0 ] ;
233248
234- // Get current environment to pick a different one
249+ // Pick an environment different from the current one so a change event is guaranteed.
235250 const currentEnv = await api . getEnvironment ( project . uri ) ;
236-
237- // Pick an environment different from current
238- let targetEnv = environments [ 0 ] ;
239- if ( currentEnv && currentEnv . envId . id === targetEnv . envId . id ) {
240- targetEnv = environments [ 1 ] ;
251+ const targetEnv = getDifferentEnvironment ( environments , currentEnv ) ;
252+ if ( ! targetEnv ) {
253+ this . skip ( ) ;
254+ return ;
241255 }
242256
243257 // Register handler BEFORE making the change
@@ -283,32 +297,14 @@ suite('Integration: Python Projects', function () {
283297
284298 // Pick an environment different from the current one to guarantee a change event
285299 const currentEnv = await api . getEnvironment ( project . uri ) ;
286- let env = environments [ 0 ] ;
287- if ( currentEnv && currentEnv . envId . id === env . envId . id ) {
288- env = environments [ 1 ] ;
300+ const env = getDifferentEnvironment ( environments , currentEnv ) ;
301+ if ( ! env ) {
302+ this . skip ( ) ;
303+ return ;
289304 }
290305
291- // Set environment first, using event-driven wait
292- await new Promise < void > ( ( resolve , reject ) => {
293- const timeout = setTimeout ( ( ) => {
294- subscription . dispose ( ) ;
295- reject ( new Error ( `onDidChangeEnvironment did not fire within 15s. Expected envId: ${ env . envId . id } ` ) ) ;
296- } , 15_000 ) ;
297-
298- const subscription = api . onDidChangeEnvironment ( ( e ) => {
299- if ( e . uri ?. toString ( ) === project . uri . toString ( ) && e . new ?. envId . id === env . envId . id ) {
300- clearTimeout ( timeout ) ;
301- subscription . dispose ( ) ;
302- resolve ( ) ;
303- }
304- } ) ;
305-
306- api . setEnvironment ( project . uri , env ) . catch ( ( err ) => {
307- clearTimeout ( timeout ) ;
308- subscription . dispose ( ) ;
309- reject ( err ) ;
310- } ) ;
311- } ) ;
306+ // Set environment first, using event-driven wait.
307+ await setEnvironmentAndWaitForChange ( api , project . uri , env ) ;
312308
313309 // Verify it was set
314310 const beforeClear = await api . getEnvironment ( project . uri ) ;
@@ -356,32 +352,14 @@ suite('Integration: Python Projects', function () {
356352
357353 // Pick an environment different from the current one to guarantee a change event
358354 const currentEnv = await api . getEnvironment ( project . uri ) ;
359- let env = environments [ 0 ] ;
360- if ( currentEnv && currentEnv . envId . id === env . envId . id ) {
361- env = environments [ 1 ] ;
355+ const env = getDifferentEnvironment ( environments , currentEnv ) ;
356+ if ( ! env ) {
357+ this . skip ( ) ;
358+ return ;
362359 }
363360
364- // Set environment for project, using event-driven wait
365- await new Promise < void > ( ( resolve , reject ) => {
366- const timeout = setTimeout ( ( ) => {
367- subscription . dispose ( ) ;
368- reject ( new Error ( `onDidChangeEnvironment did not fire within 15s. Expected envId: ${ env . envId . id } ` ) ) ;
369- } , 15_000 ) ;
370-
371- const subscription = api . onDidChangeEnvironment ( ( e ) => {
372- if ( e . uri ?. toString ( ) === project . uri . toString ( ) && e . new ?. envId . id === env . envId . id ) {
373- clearTimeout ( timeout ) ;
374- subscription . dispose ( ) ;
375- resolve ( ) ;
376- }
377- } ) ;
378-
379- api . setEnvironment ( project . uri , env ) . catch ( ( err ) => {
380- clearTimeout ( timeout ) ;
381- subscription . dispose ( ) ;
382- reject ( err ) ;
383- } ) ;
384- } ) ;
361+ // Set environment for project, using event-driven wait.
362+ await setEnvironmentAndWaitForChange ( api , project . uri , env ) ;
385363
386364 // Create a hypothetical file path inside the project
387365 const fileUri = vscode . Uri . joinPath ( project . uri , 'some_script.py' ) ;
0 commit comments