@@ -12,22 +12,25 @@ const {
12
12
ArrayPrototypeSort,
13
13
ObjectAssign,
14
14
PromisePrototypeThen,
15
- SafePromiseAll,
16
15
SafePromiseAllReturnVoid,
17
16
SafePromiseAllSettledReturnVoid,
18
17
PromiseResolve,
19
18
SafeMap,
20
19
SafeSet,
20
+ String,
21
21
StringPrototypeIndexOf,
22
22
StringPrototypeSlice,
23
23
StringPrototypeStartsWith,
24
+ TypedArrayPrototypeSubarray,
24
25
} = primordials ;
25
26
26
27
const { spawn } = require ( 'child_process' ) ;
27
28
const { readdirSync, statSync } = require ( 'fs' ) ;
28
- const { finished } = require ( 'internal/streams/end-of-stream ' ) ;
29
+ const { DefaultDeserializer , DefaultSerializer } = require ( 'v8 ' ) ;
29
30
// TODO(aduh95): switch to internal/readline/interface when backporting to Node.js 16.x is no longer a concern.
30
31
const { createInterface } = require ( 'readline' ) ;
32
+ const { deserializeError } = require ( 'internal/error_serdes' ) ;
33
+ const { Buffer } = require ( 'buffer' ) ;
31
34
const { FilesWatcher } = require ( 'internal/watch_mode/files_watcher' ) ;
32
35
const console = require ( 'internal/console/global' ) ;
33
36
const {
@@ -40,6 +43,7 @@ const { validateArray, validateBoolean, validateFunction } = require('internal/v
40
43
const { getInspectPort, isUsingInspector, isInspectorMessage } = require ( 'internal/util/inspector' ) ;
41
44
const { isRegExp } = require ( 'internal/util/types' ) ;
42
45
const { kEmptyObject } = require ( 'internal/util' ) ;
46
+ const { kEmitMessage } = require ( 'internal/test_runner/tests_stream' ) ;
43
47
const { createTestTree } = require ( 'internal/test_runner/harness' ) ;
44
48
const {
45
49
kAborted,
@@ -49,9 +53,6 @@ const {
49
53
kTestTimeoutFailure,
50
54
Test,
51
55
} = require ( 'internal/test_runner/test' ) ;
52
- const { TapParser } = require ( 'internal/test_runner/tap_parser' ) ;
53
- const { YAMLToJs } = require ( 'internal/test_runner/yaml_to_js' ) ;
54
- const { TokenKind } = require ( 'internal/test_runner/tap_lexer' ) ;
55
56
56
57
const {
57
58
convertStringToRegExp,
@@ -153,92 +154,62 @@ function getRunArgs({ path, inspectPort, testNamePatterns }) {
153
154
return argv ;
154
155
}
155
156
157
+ const serializer = new DefaultSerializer ( ) ;
158
+ serializer . writeHeader ( ) ;
159
+ const v8Header = serializer . releaseBuffer ( ) ;
160
+ const v8HeaderAndSize = 4 + v8Header . length ;
161
+
156
162
class FileTest extends Test {
157
163
#buffer = [ ] ;
164
+ #messageBuffer = [ ] ;
165
+ #messageBufferSize = 0 ;
158
166
#reportedChildren = 0 ;
159
167
failedSubtests = false ;
160
168
#skipReporting( ) {
161
169
return this . #reportedChildren > 0 && ( ! this . error || this . error . failureType === kSubtestsFailed ) ;
162
170
}
163
- #checkNestedComment( { comment } ) {
171
+ #checkNestedComment( comment ) {
164
172
const firstSpaceIndex = StringPrototypeIndexOf ( comment , ' ' ) ;
165
173
if ( firstSpaceIndex === - 1 ) return false ;
166
174
const secondSpaceIndex = StringPrototypeIndexOf ( comment , ' ' , firstSpaceIndex + 1 ) ;
167
175
return secondSpaceIndex === - 1 &&
168
176
ArrayPrototypeIncludes ( kDiagnosticsFilterArgs , StringPrototypeSlice ( comment , 0 , firstSpaceIndex ) ) ;
169
177
}
170
- #handleReportItem( { kind, node, comments, nesting = 0 } ) {
171
- if ( comments ) {
172
- ArrayPrototypeForEach ( comments , ( comment ) => this . reporter . diagnostic ( nesting , this . name , comment ) ) ;
173
- }
174
- switch ( kind ) {
175
- case TokenKind . TAP_VERSION :
176
- // TODO(manekinekko): handle TAP version coming from the parser.
177
- // this.reporter.version(node.version);
178
- break ;
179
-
180
- case TokenKind . TAP_PLAN :
181
- if ( nesting === 0 && this . #skipReporting( ) ) {
182
- break ;
183
- }
184
- this . reporter . plan ( nesting , this . name , node . end - node . start + 1 ) ;
185
- break ;
186
-
187
- case TokenKind . TAP_SUBTEST_POINT :
188
- this . reporter . start ( nesting , this . name , node . name ) ;
189
- break ;
190
-
191
- case TokenKind . TAP_TEST_POINT : {
192
-
193
- const { todo, skip, pass } = node . status ;
194
-
195
- let directive ;
196
-
197
- if ( skip ) {
198
- directive = this . reporter . getSkip ( node . reason || true ) ;
199
- } else if ( todo ) {
200
- directive = this . reporter . getTodo ( node . reason || true ) ;
201
- } else {
202
- directive = kEmptyObject ;
203
- }
204
-
205
- const diagnostics = YAMLToJs ( node . diagnostics ) ;
206
- const cancelled = kCanceledTests . has ( diagnostics . error ?. failureType ) ;
207
- const testNumber = nesting === 0 ? ( this . root . harness . counters . topLevel + 1 ) : node . id ;
208
- const method = pass ? 'ok' : 'fail' ;
209
- this . reporter [ method ] ( nesting , this . name , testNumber , node . description , diagnostics , directive ) ;
210
- countCompletedTest ( {
211
- name : node . description ,
212
- finished : true ,
213
- skipped : skip ,
214
- isTodo : todo ,
215
- passed : pass ,
216
- cancelled,
217
- nesting,
218
- reportedType : diagnostics . type ,
219
- } , this . root . harness ) ;
220
- break ;
221
-
178
+ #handleReportItem( item ) {
179
+ const isTopLevel = item . data . nesting === 0 ;
180
+ if ( isTopLevel ) {
181
+ if ( item . type === 'test:plan' && this . #skipReporting( ) ) {
182
+ return ;
222
183
}
223
- case TokenKind . COMMENT :
224
- if ( nesting === 0 && this . #checkNestedComment( node ) ) {
225
- // Ignore file top level diagnostics
226
- break ;
227
- }
228
- this . reporter . diagnostic ( nesting , this . name , node . comment ) ;
229
- break ;
230
-
231
- case TokenKind . UNKNOWN :
232
- this . reporter . diagnostic ( nesting , this . name , node . value ) ;
233
- break ;
184
+ if ( item . type === 'test:diagnostic' && this . #checkNestedComment( item . data . message ) ) {
185
+ return ;
186
+ }
187
+ }
188
+ if ( item . data . details ?. error ) {
189
+ item . data . details . error = deserializeError ( item . data . details . error ) ;
234
190
}
191
+ if ( item . type === 'test:pass' || item . type === 'test:fail' ) {
192
+ item . data . testNumber = isTopLevel ? ( this . root . harness . counters . topLevel + 1 ) : item . data . testNumber ;
193
+ countCompletedTest ( {
194
+ __proto__ : null ,
195
+ name : item . data . name ,
196
+ finished : true ,
197
+ skipped : item . data . skip !== undefined ,
198
+ isTodo : item . data . todo !== undefined ,
199
+ passed : item . type === 'test:pass' ,
200
+ cancelled : kCanceledTests . has ( item . data . details ?. error ?. failureType ) ,
201
+ nesting : item . data . nesting ,
202
+ reportedType : item . data . details ?. type ,
203
+ } , this . root . harness ) ;
204
+ }
205
+ this . reporter [ kEmitMessage ] ( item . type , item . data ) ;
235
206
}
236
- #accumulateReportItem( { kind , node , comments , nesting = 0 } ) {
237
- if ( kind !== TokenKind . TAP_TEST_POINT ) {
207
+ #accumulateReportItem( item ) {
208
+ if ( item . type !== 'test:pass' && item . type !== 'test:fail' ) {
238
209
return ;
239
210
}
240
211
this . #reportedChildren++ ;
241
- if ( nesting === 0 && ! node . status . pass ) {
212
+ if ( item . data . nesting === 0 && item . type === 'test:fail' ) {
242
213
this . failedSubtests = true ;
243
214
}
244
215
}
@@ -248,14 +219,65 @@ class FileTest extends Test {
248
219
this . #buffer = [ ] ;
249
220
}
250
221
}
251
- addToReport ( ast ) {
252
- this . #accumulateReportItem( ast ) ;
222
+ addToReport ( item ) {
223
+ this . #accumulateReportItem( item ) ;
253
224
if ( ! this . isClearToSend ( ) ) {
254
- ArrayPrototypePush ( this . #buffer, ast ) ;
225
+ ArrayPrototypePush ( this . #buffer, item ) ;
255
226
return ;
256
227
}
257
228
this . #drainBuffer( ) ;
258
- this . #handleReportItem( ast ) ;
229
+ this . #handleReportItem( item ) ;
230
+ }
231
+ parseMessage ( readData ) {
232
+ if ( readData . length === 0 ) return ;
233
+
234
+ ArrayPrototypePush ( this . #messageBuffer, readData ) ;
235
+ this . #messageBufferSize += readData . length ;
236
+
237
+ // Index 0 should always be present because we just pushed data into it.
238
+ let messageBufferHead = this . #messageBuffer[ 0 ] ;
239
+
240
+ while ( messageBufferHead . length >= 4 ) {
241
+ const isSerializedMessage = messageBufferHead . length >= v8HeaderAndSize &&
242
+ v8Header . compare ( messageBufferHead , 4 , v8HeaderAndSize ) === 0 ;
243
+ if ( ! isSerializedMessage ) {
244
+ const message = Buffer . concat ( this . #messageBuffer, this . #messageBufferSize) ;
245
+ this . #messageBufferSize = 0 ;
246
+ this . #messageBuffer = [ ] ;
247
+ this . addToReport ( {
248
+ __proto__ : null ,
249
+ type : 'test:diagnostic' ,
250
+ data : { __proto__ : null , nesting : 0 , file : this . name , message : String ( message ) } ,
251
+ } ) ;
252
+ return ;
253
+ }
254
+
255
+ // We call `readUInt32BE` manually here, because this is faster than first converting
256
+ // it to a buffer and using `readUInt32BE` on that.
257
+ const fullMessageSize = (
258
+ messageBufferHead [ 0 ] << 24 |
259
+ messageBufferHead [ 1 ] << 16 |
260
+ messageBufferHead [ 2 ] << 8 |
261
+ messageBufferHead [ 3 ]
262
+ ) + 4 ;
263
+
264
+ if ( this . #messageBufferSize < fullMessageSize ) break ;
265
+
266
+ const concatenatedBuffer = this . #messageBuffer. length === 1 ?
267
+ this . #messageBuffer[ 0 ] : Buffer . concat ( this . #messageBuffer, this . #messageBufferSize) ;
268
+
269
+ const deserializer = new DefaultDeserializer (
270
+ TypedArrayPrototypeSubarray ( concatenatedBuffer , 4 , fullMessageSize ) ,
271
+ ) ;
272
+
273
+ messageBufferHead = TypedArrayPrototypeSubarray ( concatenatedBuffer , fullMessageSize ) ;
274
+ this . #messageBufferSize = messageBufferHead . length ;
275
+ this . #messageBuffer = this . #messageBufferSize !== 0 ? [ messageBufferHead ] : [ ] ;
276
+
277
+ deserializer . readHeader ( ) ;
278
+ const item = deserializer . readValue ( ) ;
279
+ this . addToReport ( item ) ;
280
+ }
259
281
}
260
282
reportStarted ( ) { }
261
283
report ( ) {
@@ -275,7 +297,7 @@ function runTestFile(path, root, inspectPort, filesWatcher, testNamePatterns) {
275
297
const subtest = root . createSubtest ( FileTest , path , async ( t ) => {
276
298
const args = getRunArgs ( { path, inspectPort, testNamePatterns } ) ;
277
299
const stdio = [ 'pipe' , 'pipe' , 'pipe' ] ;
278
- const env = { ...process . env , NODE_TEST_CONTEXT : 'child' } ;
300
+ const env = { ...process . env , NODE_TEST_CONTEXT : 'child-v8 ' } ;
279
301
if ( filesWatcher ) {
280
302
stdio . push ( 'ipc' ) ;
281
303
env . WATCH_REPORT_DEPENDENCIES = '1' ;
@@ -292,6 +314,10 @@ function runTestFile(path, root, inspectPort, filesWatcher, testNamePatterns) {
292
314
err = error ;
293
315
} ) ;
294
316
317
+ child . stdout . on ( 'data' , ( data ) => {
318
+ subtest . parseMessage ( data ) ;
319
+ } ) ;
320
+
295
321
const rl = createInterface ( { input : child . stderr } ) ;
296
322
rl . on ( 'line' , ( line ) => {
297
323
if ( isInspectorMessage ( line ) ) {
@@ -303,26 +329,14 @@ function runTestFile(path, root, inspectPort, filesWatcher, testNamePatterns) {
303
329
// surface stderr lines as TAP diagnostics to improve the DX. Inject
304
330
// each line into the test output as an unknown token as if it came
305
331
// from the TAP parser.
306
- const node = {
307
- kind : TokenKind . UNKNOWN ,
308
- node : {
309
- value : line ,
310
- } ,
311
- } ;
312
-
313
- subtest . addToReport ( node ) ;
314
- } ) ;
315
-
316
- const parser = new TapParser ( ) ;
317
-
318
- child . stdout . pipe ( parser ) . on ( 'data' , ( ast ) => {
319
- subtest . addToReport ( ast ) ;
332
+ subtest . addToReport ( {
333
+ __proto__ : null ,
334
+ type : 'test:diagnostic' ,
335
+ data : { __proto__ : null , nesting : 0 , file : path , message : line } ,
336
+ } ) ;
320
337
} ) ;
321
338
322
- const { 0 : { 0 : code , 1 : signal } } = await SafePromiseAll ( [
323
- once ( child , 'exit' , { signal : t . signal } ) ,
324
- finished ( parser , { signal : t . signal } ) ,
325
- ] ) ;
339
+ const { 0 : code , 1 : signal } = await once ( child , 'exit' , { signal : t . signal } ) ;
326
340
327
341
runningProcesses . delete ( path ) ;
328
342
runningSubtests . delete ( path ) ;
0 commit comments