@@ -4,7 +4,7 @@ import { Runner, RunnerEvent, Options } from 'jest-editor-support';
4
4
import { JestExtContext , WatchMode } from '../JestExt/types' ;
5
5
import { extensionId } from '../appGlobals' ;
6
6
import { Logging } from '../logging' ;
7
- import { JestProcessInfo , JestProcessRequest , UserDataType } from './types' ;
7
+ import { JestProcessInfo , JestProcessRequest , ProcessStatus , UserDataType } from './types' ;
8
8
import { requestString } from './helper' ;
9
9
import { toFilePath , removeSurroundingQuote , escapeRegExp , shellQuote } from '../helpers' ;
10
10
@@ -23,20 +23,18 @@ interface RunnerTask {
23
23
reject : ( reason : unknown ) => unknown ;
24
24
runner : Runner ;
25
25
}
26
- export type StopReason = 'on-demand' | 'process-end' ;
27
26
28
27
let SEQ = 0 ;
29
28
30
29
export class JestProcess implements JestProcessInfo {
31
- static readonly stopHangTimeout = 500 ;
32
-
33
30
private task ?: RunnerTask ;
34
31
private extContext : JestExtContext ;
35
32
private logging : Logging ;
36
- private _stopReason ?: StopReason ;
37
33
public readonly id : string ;
38
34
private desc : string ;
39
35
public readonly request : JestProcessRequest ;
36
+ public _status : ProcessStatus ;
37
+ private autoStopTimer ?: NodeJS . Timeout ;
40
38
41
39
constructor (
42
40
extContext : JestExtContext ,
@@ -48,10 +46,11 @@ export class JestProcess implements JestProcessInfo {
48
46
this . logging = extContext . loggingFactory . create ( `JestProcess ${ request . type } ` ) ;
49
47
this . id = `${ request . type } -${ SEQ ++ } ` ;
50
48
this . desc = `id: ${ this . id } , request: ${ requestString ( request ) } ` ;
49
+ this . _status = ProcessStatus . Pending ;
51
50
}
52
51
53
- public get stopReason ( ) : StopReason | undefined {
54
- return this . _stopReason ;
52
+ public get status ( ) : ProcessStatus {
53
+ return this . _status ;
55
54
}
56
55
57
56
private get watchMode ( ) : WatchMode {
@@ -64,15 +63,39 @@ export class JestProcess implements JestProcessInfo {
64
63
return WatchMode . None ;
65
64
}
66
65
66
+ public get isWatchMode ( ) : boolean {
67
+ return this . watchMode !== WatchMode . None ;
68
+ }
69
+
67
70
public toString ( ) : string {
68
- return `JestProcess: ${ this . desc } ; stopReason: ${ this . stopReason } ` ;
71
+ return `JestProcess: ${ this . desc } ; status: " ${ this . status } " ` ;
69
72
}
70
- public start ( ) : Promise < void > {
71
- this . _stopReason = undefined ;
72
- return this . startRunner ( ) ;
73
+
74
+ /**
75
+ * To prevent zombie process, this method will automatically stops the Jest process if it is running for too long. The process will be marked as "Cancelled" and stopped.
76
+ * Warning: This should only be called when you are certain the process should end soon, for example a non-watch mode process should end after the test results have been processed.
77
+ * @param delay The delay in milliseconds after which the process will be considered hung and stopped. Default is 30000 milliseconds (30 seconds ).
78
+ */
79
+ public autoStop ( delay = 30000 , onStop ?: ( process : JestProcessInfo ) => void ) : void {
80
+ if ( this . status === ProcessStatus . Running ) {
81
+ if ( this . autoStopTimer ) {
82
+ clearTimeout ( this . autoStopTimer ) ;
83
+ }
84
+ this . autoStopTimer = setTimeout ( ( ) => {
85
+ if ( this . status === ProcessStatus . Running ) {
86
+ console . warn (
87
+ `Jest Process "${ this . id } ": will be force closed due to the autoStop Timer (${ delay } msec) `
88
+ ) ;
89
+ this . stop ( ) ;
90
+ onStop ?.( this ) ;
91
+ }
92
+ } , delay ) ;
93
+ }
73
94
}
95
+
74
96
public stop ( ) : Promise < void > {
75
- this . _stopReason = 'on-demand' ;
97
+ this . _status = ProcessStatus . Cancelled ;
98
+
76
99
if ( ! this . task ) {
77
100
this . logging ( 'debug' , 'nothing to stop, no pending runner/promise' ) ;
78
101
this . taskDone ( ) ;
@@ -99,12 +122,19 @@ export class JestProcess implements JestProcessInfo {
99
122
return `"${ removeSurroundingQuote ( aString ) } "` ;
100
123
}
101
124
102
- private startRunner ( ) : Promise < void > {
125
+ public start ( ) : Promise < void > {
126
+ if ( this . status === ProcessStatus . Cancelled ) {
127
+ this . logging ( 'warn' , `the runner task has been cancelled!` ) ;
128
+ return Promise . resolve ( ) ;
129
+ }
130
+
103
131
if ( this . task ) {
104
132
this . logging ( 'warn' , `the runner task has already started!` ) ;
105
133
return this . task . promise ;
106
134
}
107
135
136
+ this . _status = ProcessStatus . Running ;
137
+
108
138
const options : Options = {
109
139
noColor : false ,
110
140
reporters : [ 'default' , `"${ this . getReporterPath ( ) } "` ] ,
@@ -196,7 +226,13 @@ export class JestProcess implements JestProcessInfo {
196
226
if ( event === 'processClose' || event === 'processExit' ) {
197
227
this . task ?. resolve ( ) ;
198
228
this . task = undefined ;
199
- this . _stopReason = this . _stopReason ?? 'process-end' ;
229
+
230
+ clearTimeout ( this . autoStopTimer ) ;
231
+ this . autoStopTimer = undefined ;
232
+
233
+ if ( this . _status !== ProcessStatus . Cancelled ) {
234
+ this . _status = ProcessStatus . Done ;
235
+ }
200
236
}
201
237
this . request . listener . onEvent ( this , event , ...args ) ;
202
238
}
0 commit comments