@@ -12,22 +12,25 @@ const {
1212 ArrayPrototypeSort,
1313 ObjectAssign,
1414 PromisePrototypeThen,
15- SafePromiseAll,
1615 SafePromiseAllReturnVoid,
1716 SafePromiseAllSettledReturnVoid,
1817 PromiseResolve,
1918 SafeMap,
2019 SafeSet,
20+ String,
2121 StringPrototypeIndexOf,
2222 StringPrototypeSlice,
2323 StringPrototypeStartsWith,
24+ TypedArrayPrototypeSubarray,
2425} = primordials ;
2526
2627const { spawn } = require ( 'child_process' ) ;
2728const { readdirSync, statSync } = require ( 'fs' ) ;
28- const { finished } = require ( 'internal/streams/end-of-stream ' ) ;
29+ const { DefaultDeserializer , DefaultSerializer } = require ( 'v8 ' ) ;
2930// TODO(aduh95): switch to internal/readline/interface when backporting to Node.js 16.x is no longer a concern.
3031const { createInterface } = require ( 'readline' ) ;
32+ const { deserializeError } = require ( 'internal/error_serdes' ) ;
33+ const { Buffer } = require ( 'buffer' ) ;
3134const { FilesWatcher } = require ( 'internal/watch_mode/files_watcher' ) ;
3235const console = require ( 'internal/console/global' ) ;
3336const {
@@ -40,6 +43,7 @@ const { validateArray, validateBoolean, validateFunction } = require('internal/v
4043const { getInspectPort, isUsingInspector, isInspectorMessage } = require ( 'internal/util/inspector' ) ;
4144const { isRegExp } = require ( 'internal/util/types' ) ;
4245const { kEmptyObject } = require ( 'internal/util' ) ;
46+ const { kEmitMessage } = require ( 'internal/test_runner/tests_stream' ) ;
4347const { createTestTree } = require ( 'internal/test_runner/harness' ) ;
4448const {
4549 kAborted,
@@ -49,9 +53,6 @@ const {
4953 kTestTimeoutFailure,
5054 Test,
5155} = 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' ) ;
5556
5657const {
5758 convertStringToRegExp,
@@ -153,92 +154,62 @@ function getRunArgs({ path, inspectPort, testNamePatterns }) {
153154 return argv ;
154155}
155156
157+ const serializer = new DefaultSerializer ( ) ;
158+ serializer . writeHeader ( ) ;
159+ const v8Header = serializer . releaseBuffer ( ) ;
160+ const v8HeaderAndSize = 4 + v8Header . length ;
161+
156162class FileTest extends Test {
157163 #buffer = [ ] ;
164+ #messageBuffer = [ ] ;
165+ #messageBufferSize = 0 ;
158166 #reportedChildren = 0 ;
159167 failedSubtests = false ;
160168 #skipReporting( ) {
161169 return this . #reportedChildren > 0 && ( ! this . error || this . error . failureType === kSubtestsFailed ) ;
162170 }
163- #checkNestedComment( { comment } ) {
171+ #checkNestedComment( comment ) {
164172 const firstSpaceIndex = StringPrototypeIndexOf ( comment , ' ' ) ;
165173 if ( firstSpaceIndex === - 1 ) return false ;
166174 const secondSpaceIndex = StringPrototypeIndexOf ( comment , ' ' , firstSpaceIndex + 1 ) ;
167175 return secondSpaceIndex === - 1 &&
168176 ArrayPrototypeIncludes ( kDiagnosticsFilterArgs , StringPrototypeSlice ( comment , 0 , firstSpaceIndex ) ) ;
169177 }
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 ;
222183 }
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 ) ;
234190 }
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 ) ;
235206 }
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' ) {
238209 return ;
239210 }
240211 this . #reportedChildren++ ;
241- if ( nesting === 0 && ! node . status . pass ) {
212+ if ( item . data . nesting === 0 && item . type === 'test:fail' ) {
242213 this . failedSubtests = true ;
243214 }
244215 }
@@ -248,14 +219,65 @@ class FileTest extends Test {
248219 this . #buffer = [ ] ;
249220 }
250221 }
251- addToReport ( ast ) {
252- this . #accumulateReportItem( ast ) ;
222+ addToReport ( item ) {
223+ this . #accumulateReportItem( item ) ;
253224 if ( ! this . isClearToSend ( ) ) {
254- ArrayPrototypePush ( this . #buffer, ast ) ;
225+ ArrayPrototypePush ( this . #buffer, item ) ;
255226 return ;
256227 }
257228 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+ }
259281 }
260282 reportStarted ( ) { }
261283 report ( ) {
@@ -275,7 +297,7 @@ function runTestFile(path, root, inspectPort, filesWatcher, testNamePatterns) {
275297 const subtest = root . createSubtest ( FileTest , path , async ( t ) => {
276298 const args = getRunArgs ( { path, inspectPort, testNamePatterns } ) ;
277299 const stdio = [ 'pipe' , 'pipe' , 'pipe' ] ;
278- const env = { ...process . env , NODE_TEST_CONTEXT : 'child' } ;
300+ const env = { ...process . env , NODE_TEST_CONTEXT : 'child-v8 ' } ;
279301 if ( filesWatcher ) {
280302 stdio . push ( 'ipc' ) ;
281303 env . WATCH_REPORT_DEPENDENCIES = '1' ;
@@ -292,6 +314,10 @@ function runTestFile(path, root, inspectPort, filesWatcher, testNamePatterns) {
292314 err = error ;
293315 } ) ;
294316
317+ child . stdout . on ( 'data' , ( data ) => {
318+ subtest . parseMessage ( data ) ;
319+ } ) ;
320+
295321 const rl = createInterface ( { input : child . stderr } ) ;
296322 rl . on ( 'line' , ( line ) => {
297323 if ( isInspectorMessage ( line ) ) {
@@ -303,26 +329,14 @@ function runTestFile(path, root, inspectPort, filesWatcher, testNamePatterns) {
303329 // surface stderr lines as TAP diagnostics to improve the DX. Inject
304330 // each line into the test output as an unknown token as if it came
305331 // 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+ } ) ;
320337 } ) ;
321338
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 } ) ;
326340
327341 runningProcesses . delete ( path ) ;
328342 runningSubtests . delete ( path ) ;
0 commit comments