@@ -10,20 +10,24 @@ const {
10
10
ObjectAssign,
11
11
PromisePrototypeThen,
12
12
SafePromiseAll,
13
+ SafePromiseAllReturnVoid,
14
+ SafePromiseAllSettledReturnVoid,
15
+ SafeMap,
13
16
SafeSet,
14
17
} = primordials ;
15
18
16
19
const { spawn } = require ( 'child_process' ) ;
17
20
const { readdirSync, statSync } = require ( 'fs' ) ;
18
21
// TODO(aduh95): switch to internal/readline/interface when backporting to Node.js 16.x is no longer a concern.
19
22
const { createInterface } = require ( 'readline' ) ;
23
+ const { FilesWatcher } = require ( 'internal/watch_mode/files_watcher' ) ;
20
24
const console = require ( 'internal/console/global' ) ;
21
25
const {
22
26
codes : {
23
27
ERR_TEST_FAILURE ,
24
28
} ,
25
29
} = require ( 'internal/errors' ) ;
26
- const { validateArray } = require ( 'internal/validators' ) ;
30
+ const { validateArray, validateBoolean } = require ( 'internal/validators' ) ;
27
31
const { getInspectPort, isUsingInspector, isInspectorMessage } = require ( 'internal/util/inspector' ) ;
28
32
const { kEmptyObject } = require ( 'internal/util' ) ;
29
33
const { createTestTree } = require ( 'internal/test_runner/harness' ) ;
@@ -34,8 +38,11 @@ const {
34
38
} = require ( 'internal/test_runner/utils' ) ;
35
39
const { basename, join, resolve } = require ( 'path' ) ;
36
40
const { once } = require ( 'events' ) ;
41
+ const {
42
+ triggerUncaughtException,
43
+ } = internalBinding ( 'errors' ) ;
37
44
38
- const kFilterArgs = [ '--test' ] ;
45
+ const kFilterArgs = [ '--test' , '--watch' ] ;
39
46
40
47
// TODO(cjihrig): Replace this with recursive readdir once it lands.
41
48
function processPath ( path , testFiles , options ) {
@@ -112,17 +119,28 @@ function getRunArgs({ path, inspectPort }) {
112
119
return argv ;
113
120
}
114
121
122
+ const runningProcesses = new SafeMap ( ) ;
123
+ const runningSubtests = new SafeMap ( ) ;
115
124
116
- function runTestFile ( path , root , inspectPort ) {
125
+ function runTestFile ( path , root , inspectPort , filesWatcher ) {
117
126
const subtest = root . createSubtest ( Test , path , async ( t ) => {
118
127
const args = getRunArgs ( { path, inspectPort } ) ;
128
+ const stdio = [ 'pipe' , 'pipe' , 'pipe' ] ;
129
+ const env = { ...process . env } ;
130
+ if ( filesWatcher ) {
131
+ stdio . push ( 'ipc' ) ;
132
+ env . WATCH_REPORT_DEPENDENCIES = '1' ;
133
+ }
119
134
120
- const child = spawn ( process . execPath , args , { signal : t . signal , encoding : 'utf8' } ) ;
135
+ const child = spawn ( process . execPath , args , { signal : t . signal , encoding : 'utf8' , env, stdio } ) ;
136
+ runningProcesses . set ( path , child ) ;
121
137
// TODO(cjihrig): Implement a TAP parser to read the child's stdout
122
138
// instead of just displaying it all if the child fails.
123
139
let err ;
124
140
let stderr = '' ;
125
141
142
+ filesWatcher ?. watchChildProcessModules ( child , path ) ;
143
+
126
144
child . on ( 'error' , ( error ) => {
127
145
err = error ;
128
146
} ) ;
@@ -145,6 +163,8 @@ function runTestFile(path, root, inspectPort) {
145
163
child . stdout . toArray ( { signal : t . signal } ) ,
146
164
] ) ;
147
165
166
+ runningProcesses . delete ( path ) ;
167
+ runningSubtests . delete ( path ) ;
148
168
if ( code !== 0 || signal !== null ) {
149
169
if ( ! err ) {
150
170
err = ObjectAssign ( new ERR_TEST_FAILURE ( 'test failed' , kSubtestsFailed ) , {
@@ -165,21 +185,57 @@ function runTestFile(path, root, inspectPort) {
165
185
return subtest . start ( ) ;
166
186
}
167
187
188
+ function watchFiles ( testFiles , root , inspectPort ) {
189
+ const filesWatcher = new FilesWatcher ( { throttle : 500 , mode : 'filter' } ) ;
190
+ filesWatcher . on ( 'changed' , ( { owners } ) => {
191
+ filesWatcher . unfilterFilesOwnedBy ( owners ) ;
192
+ PromisePrototypeThen ( SafePromiseAllReturnVoid ( testFiles , async ( file ) => {
193
+ if ( ! owners . has ( file ) ) {
194
+ return ;
195
+ }
196
+ const runningProcess = runningProcesses . get ( file ) ;
197
+ if ( runningProcess ) {
198
+ runningProcess . kill ( ) ;
199
+ await once ( runningProcess , 'exit' ) ;
200
+ }
201
+ await runningSubtests . get ( file ) ;
202
+ runningSubtests . set ( file , runTestFile ( file , root , inspectPort , filesWatcher ) ) ;
203
+ } , undefined , ( error ) => {
204
+ triggerUncaughtException ( error , true /* fromPromise */ ) ;
205
+ } ) ) ;
206
+ } ) ;
207
+ return filesWatcher ;
208
+ }
209
+
168
210
function run ( options ) {
169
211
if ( options === null || typeof options !== 'object' ) {
170
212
options = kEmptyObject ;
171
213
}
172
- const { concurrency, timeout, signal, files, inspectPort } = options ;
214
+ const { concurrency, timeout, signal, files, inspectPort, watch } = options ;
173
215
174
216
if ( files != null ) {
175
217
validateArray ( files , 'options.files' ) ;
176
218
}
219
+ if ( watch != null ) {
220
+ validateBoolean ( watch , 'options.watch' ) ;
221
+ }
177
222
178
223
const root = createTestTree ( { concurrency, timeout, signal } ) ;
179
224
const testFiles = files ?? createTestFileList ( ) ;
180
225
181
- PromisePrototypeThen ( SafePromiseAll ( testFiles , ( path ) => runTestFile ( path , root , inspectPort ) ) ,
182
- ( ) => root . postRun ( ) ) ;
226
+ let postRun = ( ) => root . postRun ( ) ;
227
+ let filesWatcher ;
228
+ if ( watch ) {
229
+ filesWatcher = watchFiles ( testFiles , root , inspectPort ) ;
230
+ postRun = undefined ;
231
+ }
232
+
233
+ PromisePrototypeThen ( SafePromiseAllSettledReturnVoid ( testFiles , ( path ) => {
234
+ const subtest = runTestFile ( path , root , inspectPort , filesWatcher ) ;
235
+ runningSubtests . set ( path , subtest ) ;
236
+ return subtest ;
237
+ } ) , postRun ) ;
238
+
183
239
184
240
return root . reporter ;
185
241
}
0 commit comments