11// Copyright (c) Microsoft Corporation. All rights reserved.
22// Licensed under the MIT License.
33import * as path from 'path' ;
4- import { CancellationToken , CancellationTokenSource , Uri } from 'vscode' ;
4+ import { CancellationToken , Uri } from 'vscode' ;
55import { ChildProcess } from 'child_process' ;
66import {
77 ExecutionFactoryCreateWithEnvironmentOptions ,
@@ -12,48 +12,13 @@ import { IConfigurationService } from '../../../common/types';
1212import { Deferred } from '../../../common/utils/async' ;
1313import { EXTENSION_ROOT_DIR } from '../../../constants' ;
1414import { traceError , traceInfo , traceVerbose } from '../../../logging' ;
15- import { DiscoveredTestPayload , ITestDiscoveryAdapter , ITestResultResolver } from '../common/types' ;
16- import { createTestingDeferred , startDiscoveryNamedPipe } from '../common/utils' ;
15+ import { ITestDiscoveryAdapter , ITestResultResolver } from '../common/types' ;
16+ import { createTestingDeferred } from '../common/utils' ;
1717import { IEnvironmentVariablesProvider } from '../../../common/variables/types' ;
1818import { PythonEnvironment } from '../../../pythonEnvironments/info' ;
1919import { useEnvExtension , getEnvironment , runInBackground } from '../../../envExt/api.internal' ;
2020import { buildPytestEnv as configureSubprocessEnv , handleSymlinkAndRootDir } from './pytestHelpers' ;
21- import { cleanupOnCancellation , createProcessHandlers } from '../common/discoveryHelpers' ;
22-
23- /**
24- * Sets up the discovery named pipe and wires up cancellation.
25- * @param resultResolver The resolver to handle discovered test data
26- * @param token Optional cancellation token from the caller
27- * @param uri Workspace URI for logging
28- * @returns Object containing the pipe name and cancellation source
29- */
30- async function setupDiscoveryPipe (
31- resultResolver : ITestResultResolver | undefined ,
32- token : CancellationToken | undefined ,
33- uri : Uri ,
34- ) : Promise < { pipeName : string ; cancellation : CancellationTokenSource } > {
35- const discoveryPipeCancellation = new CancellationTokenSource ( ) ;
36-
37- // Wire up cancellation from external token
38- token ?. onCancellationRequested ( ( ) => {
39- traceInfo ( `Test discovery cancelled.` ) ;
40- discoveryPipeCancellation . cancel ( ) ;
41- } ) ;
42-
43- // Start the named pipe with the discovery listener
44- const discoveryPipeName = await startDiscoveryNamedPipe ( ( data : DiscoveredTestPayload ) => {
45- if ( ! token ?. isCancellationRequested ) {
46- resultResolver ?. resolveDiscovery ( data ) ;
47- }
48- } , discoveryPipeCancellation . token ) ;
49-
50- traceVerbose ( `Created discovery pipe: ${ discoveryPipeName } for workspace ${ uri . fsPath } ` ) ;
51-
52- return {
53- pipeName : discoveryPipeName ,
54- cancellation : discoveryPipeCancellation ,
55- } ;
56- }
21+ import { cleanupOnCancellation , createProcessHandlers , setupDiscoveryPipe } from '../common/discoveryHelpers' ;
5722
5823/**
5924 * Configures the subprocess environment for pytest discovery.
@@ -69,7 +34,7 @@ async function configureDiscoveryEnv(
6934) : Promise < NodeJS . ProcessEnv > {
7035 const fullPluginPath = path . join ( EXTENSION_ROOT_DIR , 'python_files' ) ;
7136 const envVars = await envVarsService ?. getEnvironmentVariables ( uri ) ;
72- const mutableEnv = await configureSubprocessEnv ( envVars , fullPluginPath , discoveryPipeName ) ;
37+ const mutableEnv = configureSubprocessEnv ( envVars , fullPluginPath , discoveryPipeName ) ;
7338 return mutableEnv ;
7439}
7540
@@ -90,11 +55,11 @@ export class PytestTestDiscoveryAdapter implements ITestDiscoveryAdapter {
9055 interpreter ?: PythonEnvironment ,
9156 ) : Promise < void > {
9257 // Setup discovery pipe and cancellation
93- const { pipeName : discoveryPipeName , cancellation : discoveryPipeCancellation } = await setupDiscoveryPipe (
94- this . resultResolver ,
95- token ,
96- uri ,
97- ) ;
58+ const {
59+ pipeName : discoveryPipeName ,
60+ cancellation : discoveryPipeCancellation ,
61+ tokenDisposable ,
62+ } = await setupDiscoveryPipe ( this . resultResolver , token , uri ) ;
9863
9964 // Setup process handlers deferred (used by both execution paths)
10065 const deferredTillExecClose : Deferred < void > = createTestingDeferred ( ) ;
@@ -137,7 +102,7 @@ export class PytestTestDiscoveryAdapter implements ITestDiscoveryAdapter {
137102 traceInfo ( `Started pytest discovery subprocess (environment extension) for workspace ${ uri . fsPath } ` ) ;
138103
139104 // Wire up cancellation and process events
140- token ?. onCancellationRequested ( ( ) => {
105+ const envExtCancellationHandler = token ?. onCancellationRequested ( ( ) => {
141106 cleanupOnCancellation ( 'pytest' , proc , deferredTillExecClose , discoveryPipeCancellation , uri ) ;
142107 } ) ;
143108 proc . stdout . on ( 'data' , handlers . onStdout ) ;
@@ -148,6 +113,7 @@ export class PytestTestDiscoveryAdapter implements ITestDiscoveryAdapter {
148113 } ) ;
149114
150115 await deferredTillExecClose . promise ;
116+ envExtCancellationHandler ?. dispose ( ) ;
151117 traceInfo ( `Pytest discovery completed for workspace ${ uri . fsPath } ` ) ;
152118 return ;
153119 }
@@ -185,9 +151,10 @@ export class PytestTestDiscoveryAdapter implements ITestDiscoveryAdapter {
185151 } ;
186152
187153 let resultProc : ChildProcess | undefined ;
154+ let cancellationHandler : import ( 'vscode' ) . Disposable | undefined ;
188155
189- // Set up cancellation handler before execObservable to catch early cancellations
190- const cancellationHandler = token ?. onCancellationRequested ( ( ) => {
156+ // Set up cancellation handler after all early return checks
157+ cancellationHandler = token ?. onCancellationRequested ( ( ) => {
191158 traceInfo ( `Cancellation requested during pytest discovery for workspace ${ uri . fsPath } ` ) ;
192159 cleanupOnCancellation ( 'pytest' , resultProc , deferredTillExecClose , discoveryPipeCancellation , uri ) ;
193160 } ) ;
@@ -199,6 +166,7 @@ export class PytestTestDiscoveryAdapter implements ITestDiscoveryAdapter {
199166 if ( ! resultProc ) {
200167 traceError ( `Failed to spawn pytest discovery subprocess for workspace ${ uri . fsPath } ` ) ;
201168 deferredTillExecClose . resolve ( ) ;
169+ cancellationHandler ?. dispose ( ) ;
202170 return ;
203171 }
204172 traceInfo ( `Started pytest discovery subprocess (execution factory) for workspace ${ uri . fsPath } ` ) ;
@@ -215,7 +183,7 @@ export class PytestTestDiscoveryAdapter implements ITestDiscoveryAdapter {
215183
216184 cancellationHandler ?. dispose ( ) ;
217185
218- // Check for early cancellation before awaitingre awaiting
186+ // Check for early cancellation before awaiting
219187 if ( token ?. isCancellationRequested ) {
220188 traceInfo ( `Pytest discovery was cancelled before process completion for workspace ${ uri . fsPath } ` ) ;
221189 deferredTillExecClose . resolve ( ) ;
@@ -230,6 +198,7 @@ export class PytestTestDiscoveryAdapter implements ITestDiscoveryAdapter {
230198 throw error ;
231199 } finally {
232200 traceVerbose ( `Cleaning up pytest discovery resources for workspace ${ uri . fsPath } ` ) ;
201+ tokenDisposable ?. dispose ( ) ;
233202 discoveryPipeCancellation . dispose ( ) ;
234203 }
235204 }
0 commit comments