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