@@ -10,8 +10,12 @@ const {
1010 ArrayPrototypePushApply,
1111 ArrayPrototypeSome,
1212 ArrayPrototypeSort,
13+ NumberIsFinite,
1314 NumberIsSafeInteger,
1415 ObjectGetOwnPropertyDescriptor,
16+ ObjectGetPrototypeOf,
17+ ObjectPrototype,
18+ ObjectValues,
1519 Promise,
1620 PromisePrototypeThen,
1721 PromiseReject,
@@ -46,6 +50,7 @@ const {
4650 } ,
4751} = require ( 'internal/errors' ) ;
4852const { getOptionValue, getOptionsAsFlagsFromBinding } = require ( 'internal/options' ) ;
53+ const { TIMEOUT_MAX } = require ( 'internal/timers' ) ;
4954const { kEmptyObject } = require ( 'internal/util' ) ;
5055const { validateUint32 } = require ( 'internal/validators' ) ;
5156const { pathToFileURL } = require ( 'internal/url' ) ;
@@ -68,6 +73,7 @@ const kBuiltinReporters = new SafeMap([
6873const kChildAckMessageType = 'node:bench:ack' ;
6974const kChildMessageType = 'node:bench:record' ;
7075const kEventTypes = new SafeSet ( [
76+ 'bench:plan' ,
7177 'bench:start' ,
7278 'bench:sample' ,
7379 'bench:complete' ,
@@ -256,6 +262,8 @@ async function finishReporters(state) {
256262
257263function emitRecord ( stream , record ) {
258264 switch ( record . type ) {
265+ case 'bench:plan' :
266+ return stream . plan ( record . data ) ;
259267 case 'bench:start' :
260268 return stream . start ( record . data ) ;
261269 case 'bench:sample' :
@@ -311,6 +319,14 @@ function deserializeRecord(record) {
311319 } ;
312320}
313321
322+ function isStringArray ( value ) {
323+ if ( ! ArrayIsArray ( value ) ) return false ;
324+ for ( let i = 0 ; i < value . length ; i ++ ) {
325+ if ( typeof value [ i ] !== 'string' ) return false ;
326+ }
327+ return true ;
328+ }
329+
314330function validateRecord ( record ) {
315331 if ( record === null || typeof record !== 'object' ||
316332 ! kEventTypes . has ( record . type ) || record . data === null ||
@@ -323,18 +339,52 @@ function validateRecord(record) {
323339 throw new ERR_INVALID_ARG_VALUE (
324340 'benchmark child message' , record , 'is not a valid benchmark record' ) ;
325341 }
326- if ( ( record . type === 'bench:start' || record . type === 'bench:sample' ||
327- record . type === 'bench:complete' ) &&
328- ( typeof record . data . benchId !== 'string' ||
342+ if ( ( record . type === 'bench:plan' || record . type === 'bench:start' ||
343+ record . type === 'bench:sample' || record . type === 'bench:complete' ) &&
344+ ( typeof record . data . fileRunId !== 'string' ||
345+ typeof record . data . benchId !== 'string' ||
329346 ( record . data . parentId !== null &&
330- typeof record . data . parentId !== 'string' ) ||
331- typeof record . data . name !== 'string' ||
332- ! ArrayIsArray ( record . data . namePath ) ||
333- ArrayPrototypeSome (
334- record . data . namePath , ( name ) => typeof name !== 'string' ) ) ) {
347+ typeof record . data . parentId !== 'string' ) ||
348+ typeof record . data . name !== 'string' ||
349+ ! isStringArray ( record . data . namePath ) ) ) {
335350 throw new ERR_INVALID_ARG_VALUE (
336351 'benchmark child message' , record , 'is not a valid benchmark record' ) ;
337352 }
353+ if ( record . type === 'bench:plan' ) {
354+ const {
355+ samples,
356+ selected,
357+ skip,
358+ timeout,
359+ warmup,
360+ yieldBetweenSamples,
361+ } = record . data ;
362+ if ( typeof record . data . file !== 'string' ||
363+ ! NumberIsSafeInteger ( record . data . line ) || record . data . line < 0 ||
364+ ! NumberIsSafeInteger ( record . data . column ) || record . data . column < 0 ||
365+ ! isStringArray ( record . data . tags ) ||
366+ record . data . params === null ||
367+ typeof record . data . params !== 'object' ||
368+ ArrayIsArray ( record . data . params ) ||
369+ ( ObjectGetPrototypeOf ( record . data . params ) !== null &&
370+ ObjectGetPrototypeOf ( record . data . params ) !== ObjectPrototype ) ||
371+ ArrayPrototypeSome ( ObjectValues ( record . data . params ) , ( value ) =>
372+ typeof value !== 'string' && typeof value !== 'boolean' &&
373+ ( typeof value !== 'number' || ! NumberIsFinite ( value ) ) ) ||
374+ ! NumberIsSafeInteger ( samples ) || samples <= 0 ||
375+ samples > 0xFFFFFFFF ||
376+ ! NumberIsSafeInteger ( warmup ) || warmup < 0 || warmup > 0xFFFFFFFF ||
377+ ( timeout !== null &&
378+ ( ! NumberIsFinite ( timeout ) || timeout < 0 || timeout > TIMEOUT_MAX ) ) ||
379+ typeof yieldBetweenSamples !== 'boolean' ||
380+ typeof selected !== 'boolean' ||
381+ ( selected && skip !== undefined ) ||
382+ ( ! selected && skip !== true && typeof skip !== 'string' ) ) {
383+ throw new ERR_INVALID_ARG_VALUE (
384+ 'benchmark child plan' , record . data ,
385+ 'is not a valid benchmark plan' ) ;
386+ }
387+ }
338388 if ( record . type === 'bench:summary' ) {
339389 const { counts, duration_ns, success } = record . data ;
340390 if ( typeof success !== 'boolean' || typeof duration_ns !== 'bigint' ||
@@ -552,7 +602,9 @@ async function runChild(path, options, scope, onRecord) {
552602 stdio : [ 'inherit' , 'pipe' , 'pipe' , 'ipc' ] ,
553603 } ) ;
554604 let protocolError ;
605+ let plansComplete = false ;
555606 let recordPending = false ;
607+ let summaryReceived = false ;
556608 const pendingRecords = new SafeSet ( ) ;
557609 const handleRecord = ( record ) => {
558610 if ( protocolError !== undefined ) return ;
@@ -571,7 +623,7 @@ async function runChild(path, options, scope, onRecord) {
571623 source ?. resume ( ) ;
572624 } , ( error ) => {
573625 pendingRecords . delete ( tracked ) ;
574- protocolError = error ;
626+ protocolError ?? = error ;
575627 child . kill ( ) ;
576628 } ) ;
577629 pendingRecords . add ( tracked ) ;
@@ -610,10 +662,18 @@ async function runChild(path, options, scope, onRecord) {
610662 }
611663 recordPending = true ;
612664 const record = deserializeRecord ( validateRecord ( message . record ) ) ;
613- record . data . runId = options . runId ;
614- if ( record . data . fileRunId !== null ) {
615- record . data . fileRunId = scope . fileRunId ;
665+ if ( summaryReceived || ( record . type === 'bench:plan' && plansComplete ) ) {
666+ throw new ERR_INVALID_ARG_VALUE (
667+ 'benchmark child message' , message ,
668+ 'does not have a valid lifecycle sequence' ) ;
616669 }
670+ if ( record . type !== 'bench:plan' &&
671+ record . type !== 'bench:diagnostic' ) {
672+ plansComplete = true ;
673+ }
674+ if ( record . type === 'bench:summary' ) summaryReceived = true ;
675+ record . data . runId = options . runId ;
676+ record . data . fileRunId = scope . fileRunId ;
617677 if ( record . data . entryFile !== null ) {
618678 record . data . entryFile = scope . entryFile ;
619679 }
@@ -626,7 +686,7 @@ async function runChild(path, options, scope, onRecord) {
626686 } ) ) ;
627687 }
628688 } catch ( error ) {
629- protocolError = error ;
689+ protocolError ?? = error ;
630690 child . kill ( ) ;
631691 }
632692 } ) ;
0 commit comments