11// Copyright (c) Microsoft Corporation. All rights reserved.
22// Licensed under the MIT License.
33import * as path from 'path' ;
4- import { CancellationToken , Uri } from 'vscode' ;
4+ import { CancellationToken , Disposable , Uri } from 'vscode' ;
55import { ChildProcess } from 'child_process' ;
66import {
77 ExecutionFactoryCreateWithEnvironmentOptions ,
@@ -64,6 +64,12 @@ export class PytestTestDiscoveryAdapter implements ITestDiscoveryAdapter {
6464 // Setup process handlers deferred (used by both execution paths)
6565 const deferredTillExecClose : Deferred < void > = createTestingDeferred ( ) ;
6666
67+ // Collect all disposables related to discovery to handle cleanup in finally block
68+ const disposables : Disposable [ ] = [ ] ;
69+ if ( tokenDisposable ) {
70+ disposables . push ( tokenDisposable ) ;
71+ }
72+
6773 try {
6874 // Build pytest command and arguments
6975 const settings = this . configSettings . getSettings ( uri ) ;
@@ -105,6 +111,9 @@ export class PytestTestDiscoveryAdapter implements ITestDiscoveryAdapter {
105111 const envExtCancellationHandler = token ?. onCancellationRequested ( ( ) => {
106112 cleanupOnCancellation ( 'pytest' , proc , deferredTillExecClose , discoveryPipeCancellation , uri ) ;
107113 } ) ;
114+ if ( envExtCancellationHandler ) {
115+ disposables . push ( envExtCancellationHandler ) ;
116+ }
108117 proc . stdout . on ( 'data' , handlers . onStdout ) ;
109118 proc . stderr . on ( 'data' , handlers . onStderr ) ;
110119 proc . onExit ( ( code , signal ) => {
@@ -113,7 +122,6 @@ export class PytestTestDiscoveryAdapter implements ITestDiscoveryAdapter {
113122 } ) ;
114123
115124 await deferredTillExecClose . promise ;
116- envExtCancellationHandler ?. dispose ( ) ;
117125 traceInfo ( `Pytest discovery completed for workspace ${ uri . fsPath } ` ) ;
118126 return ;
119127 }
@@ -151,13 +159,15 @@ export class PytestTestDiscoveryAdapter implements ITestDiscoveryAdapter {
151159 } ;
152160
153161 let resultProc : ChildProcess | undefined ;
154- let cancellationHandler : import ( 'vscode' ) . Disposable | undefined ;
155162
156163 // Set up cancellation handler after all early return checks
157- cancellationHandler = token ?. onCancellationRequested ( ( ) => {
164+ const cancellationHandler = token ?. onCancellationRequested ( ( ) => {
158165 traceInfo ( `Cancellation requested during pytest discovery for workspace ${ uri . fsPath } ` ) ;
159166 cleanupOnCancellation ( 'pytest' , resultProc , deferredTillExecClose , discoveryPipeCancellation , uri ) ;
160167 } ) ;
168+ if ( cancellationHandler ) {
169+ disposables . push ( cancellationHandler ) ;
170+ }
161171
162172 try {
163173 const result = execService . execObservable ( commandArgs , spawnOptions ) ;
@@ -166,29 +176,19 @@ export class PytestTestDiscoveryAdapter implements ITestDiscoveryAdapter {
166176 if ( ! resultProc ) {
167177 traceError ( `Failed to spawn pytest discovery subprocess for workspace ${ uri . fsPath } ` ) ;
168178 deferredTillExecClose . resolve ( ) ;
169- cancellationHandler ?. dispose ( ) ;
170179 return ;
171180 }
172181 traceInfo ( `Started pytest discovery subprocess (execution factory) for workspace ${ uri . fsPath } ` ) ;
173182 } catch ( error ) {
174183 traceError ( `Error spawning pytest discovery subprocess for workspace ${ uri . fsPath } : ${ error } ` ) ;
175184 deferredTillExecClose . resolve ( ) ;
176- cancellationHandler ?. dispose ( ) ;
177185 throw error ;
178186 }
179187 resultProc . stdout ?. on ( 'data' , handlers . onStdout ) ;
180188 resultProc . stderr ?. on ( 'data' , handlers . onStderr ) ;
181189 resultProc . on ( 'exit' , handlers . onExit ) ;
182190 resultProc . on ( 'close' , handlers . onClose ) ;
183191
184- cancellationHandler ?. dispose ( ) ;
185-
186- // Check for early cancellation before awaiting
187- if ( token ?. isCancellationRequested ) {
188- traceInfo ( `Pytest discovery was cancelled before process completion for workspace ${ uri . fsPath } ` ) ;
189- deferredTillExecClose . resolve ( ) ;
190- }
191-
192192 traceVerbose ( `Waiting for pytest discovery subprocess to complete for workspace ${ uri . fsPath } ` ) ;
193193 await deferredTillExecClose . promise ;
194194 traceInfo ( `Pytest discovery completed for workspace ${ uri . fsPath } ` ) ;
@@ -197,8 +197,9 @@ export class PytestTestDiscoveryAdapter implements ITestDiscoveryAdapter {
197197 deferredTillExecClose . resolve ( ) ;
198198 throw error ;
199199 } finally {
200- traceVerbose ( `Cleaning up pytest discovery resources for workspace ${ uri . fsPath } ` ) ;
201- tokenDisposable ?. dispose ( ) ;
200+ // Dispose all cancellation handlers and event subscriptions
201+ disposables . forEach ( ( d ) => d . dispose ( ) ) ;
202+ // Dispose the discovery pipe cancellation token
202203 discoveryPipeCancellation . dispose ( ) ;
203204 }
204205 }
0 commit comments