33
44import { TestRun , Uri } from 'vscode' ;
55import * as path from 'path' ;
6+ import { ChildProcess } from 'child_process' ;
67import { IConfigurationService , ITestOutputChannel } from '../../../common/types' ;
7- import { Deferred , createDeferred } from '../../../common/utils/async' ;
8- import { traceError , traceInfo } from '../../../logging' ;
8+ import { Deferred } from '../../../common/utils/async' ;
9+ import { traceError , traceInfo , traceVerbose } from '../../../logging' ;
910import {
1011 DataReceivedEvent ,
1112 ExecutionTestPayload ,
@@ -15,7 +16,6 @@ import {
1516} from '../common/types' ;
1617import {
1718 ExecutionFactoryCreateWithEnvironmentOptions ,
18- ExecutionResult ,
1919 IPythonExecutionFactory ,
2020 SpawnOptions ,
2121} from '../../../common/process/types' ;
@@ -42,7 +42,9 @@ export class PytestTestExecutionAdapter implements ITestExecutionAdapter {
4242 debugLauncher ?: ITestDebugLauncher ,
4343 ) : Promise < ExecutionTestPayload > {
4444 const uuid = this . testServer . createUUID ( uri . fsPath ) ;
45- const deferredTillEOT : Deferred < void > = utils . createEOTDeferred ( ) ;
45+ // deferredTillEOT is resolved when all data sent over payload is received
46+ const deferredTillEOT : Deferred < void > = utils . createTestingDeferred ( ) ;
47+
4648 const dataReceivedDisposable = this . testServer . onRunDataReceived ( ( e : DataReceivedEvent ) => {
4749 if ( runInstance ) {
4850 const eParsed = JSON . parse ( e . data ) ;
@@ -60,8 +62,9 @@ export class PytestTestExecutionAdapter implements ITestExecutionAdapter {
6062 traceInfo ( "Test run cancelled, resolving 'till EOT' deferred." ) ;
6163 deferredTillEOT . resolve ( ) ;
6264 } ) ;
65+
6366 try {
64- this . runTestsNew (
67+ await this . runTestsNew (
6568 uri ,
6669 testIds ,
6770 uuid ,
@@ -73,6 +76,7 @@ export class PytestTestExecutionAdapter implements ITestExecutionAdapter {
7376 ) ;
7477 } finally {
7578 await deferredTillEOT . promise ;
79+ traceVerbose ( 'deferredTill EOT resolved' ) ;
7680 disposeDataReceiver ( this . testServer ) ;
7781 }
7882
@@ -123,7 +127,6 @@ export class PytestTestExecutionAdapter implements ITestExecutionAdapter {
123127 } ;
124128 // need to check what will happen in the exec service is NOT defined and is null
125129 const execService = await executionFactory ?. createActivatedEnvironment ( creationOptions ) ;
126-
127130 try {
128131 // Remove positional test folders and files, we will add as needed per node
129132 const testArgs = removePositionalFoldersAndFiles ( pytestArgs ) ;
@@ -159,19 +162,28 @@ export class PytestTestExecutionAdapter implements ITestExecutionAdapter {
159162 deferredTillEOT ?. resolve ( ) ;
160163 } ) ;
161164 } else {
165+ // deferredTillExecClose is resolved when all stdout and stderr is read
166+ const deferredTillExecClose : Deferred < void > = utils . createTestingDeferred ( ) ;
162167 // combine path to run script with run args
163168 const scriptPath = path . join ( fullPluginPath , 'vscode_pytest' , 'run_pytest_script.py' ) ;
164169 const runArgs = [ scriptPath , ...testArgs ] ;
165170 traceInfo ( `Running pytest with arguments: ${ runArgs . join ( ' ' ) } \r\n` ) ;
166171
167- const deferredExec = createDeferred < ExecutionResult < string > > ( ) ;
168- const result = execService ?. execObservable ( runArgs , spawnOptions ) ;
172+ let resultProc : ChildProcess | undefined ;
169173
170174 runInstance ?. token . onCancellationRequested ( ( ) => {
171175 traceInfo ( 'Test run cancelled, killing pytest subprocess.' ) ;
172- result ?. proc ?. kill ( ) ;
176+ // if the resultProc exists just call kill on it which will handle resolving the ExecClose deferred, otherwise resolve the deferred here.
177+ if ( resultProc ) {
178+ resultProc ?. kill ( ) ;
179+ } else {
180+ deferredTillExecClose ?. resolve ( ) ;
181+ }
173182 } ) ;
174183
184+ const result = execService ?. execObservable ( runArgs , spawnOptions ) ;
185+ resultProc = result ?. proc ;
186+
175187 // Take all output from the subprocess and add it to the test output channel. This will be the pytest output.
176188 // Displays output to user and ensure the subprocess doesn't run into buffer overflow.
177189 result ?. proc ?. stdout ?. on ( 'data' , ( data ) => {
@@ -180,15 +192,20 @@ export class PytestTestExecutionAdapter implements ITestExecutionAdapter {
180192 result ?. proc ?. stderr ?. on ( 'data' , ( data ) => {
181193 this . outputChannel ?. append ( data . toString ( ) ) ;
182194 } ) ;
183-
184195 result ?. proc ?. on ( 'exit' , ( code , signal ) => {
185- traceInfo ( 'Test run finished, subprocess exited.' ) ;
196+ if ( code !== 0 && testIds ) {
197+ traceError ( `Subprocess exited unsuccessfully with exit code ${ code } and signal ${ signal } .` ) ;
198+ }
199+ } ) ;
200+
201+ result ?. proc ?. on ( 'close' , ( code , signal ) => {
202+ traceVerbose ( 'Test run finished, subprocess closed.' ) ;
186203 // if the child has testIds then this is a run request
204+ // if the child process exited with a non-zero exit code, then we need to send the error payload.
187205 if ( code !== 0 && testIds ) {
188206 traceError (
189- `Subprocess exited unsuccessfully with exit code ${ code } and signal ${ signal } . Creating and sending error execution payload` ,
207+ `Subprocess closed unsuccessfully with exit code ${ code } and signal ${ signal } . Creating and sending error execution payload` ,
190208 ) ;
191- // if the child process exited with a non-zero exit code, then we need to send the error payload.
192209 this . testServer . triggerRunDataReceivedEvent ( {
193210 uuid,
194211 data : JSON . stringify ( utils . createExecutionErrorPayload ( code , signal , testIds , cwd ) ) ,
@@ -199,16 +216,22 @@ export class PytestTestExecutionAdapter implements ITestExecutionAdapter {
199216 data : JSON . stringify ( utils . createEOTPayload ( true ) ) ,
200217 } ) ;
201218 }
202- deferredExec . resolve ( { stdout : '' , stderr : '' } ) ;
219+ // deferredTillEOT is resolved when all data sent on stdout and stderr is received, close event is only called when this occurs
220+ // due to the sync reading of the output.
221+ deferredTillExecClose ?. resolve ( ) ;
203222 } ) ;
204- await deferredExec . promise ;
223+ await deferredTillExecClose ? .promise ;
205224 }
206225 } catch ( ex ) {
207226 traceError ( `Error while running tests: ${ testIds } \r\n${ ex } \r\n\r\n` ) ;
208227 return Promise . reject ( ex ) ;
209228 }
210229
211- const executionPayload : ExecutionTestPayload = { cwd, status : 'success' , error : '' } ;
230+ const executionPayload : ExecutionTestPayload = {
231+ cwd,
232+ status : 'success' ,
233+ error : '' ,
234+ } ;
212235 return executionPayload ;
213236 }
214237}
0 commit comments