@@ -203,6 +203,7 @@ class MptestRunner {
203203 if ( taskControl . isResolved ( ) ) {
204204 return ;
205205 }
206+ this . logError ( `task failure client=${ clientId } ` , err ) ;
206207 taskControl . reject ( err ) ;
207208 throw err ;
208209 } ) ;
@@ -236,13 +237,15 @@ class MptestRunner {
236237 }
237238 const waitPromise = Promise . all ( promises ) . then ( ( ) => undefined ) ;
238239 if ( timeoutMs <= 0 ) {
239- await waitPromise ;
240+ await this . withProgress ( waitPromise , label ) ;
240241 return ;
241242 }
242243 let handle : ReturnType < typeof setTimeout > | undefined ;
244+ let progressHandle : ReturnType < typeof setInterval > | undefined ;
243245 try {
246+ progressHandle = this . createProgressLogger ( label , timeoutMs ) ;
244247 await Promise . race ( [
245- waitPromise ,
248+ this . withProgress ( waitPromise , label , progressHandle ) ,
246249 new Promise ( ( _ , reject ) => {
247250 handle = setTimeout (
248251 ( ) =>
@@ -257,9 +260,57 @@ class MptestRunner {
257260 if ( handle ) {
258261 clearTimeout ( handle ) ;
259262 }
263+ if ( progressHandle ) {
264+ clearInterval ( progressHandle ) ;
265+ }
266+ }
267+ }
268+
269+ private createProgressLogger (
270+ label : string ,
271+ timeoutMs : number
272+ ) : ReturnType < typeof setInterval > {
273+ const start = Date . now ( ) ;
274+ return setInterval (
275+ ( ) => {
276+ const elapsed = Date . now ( ) - start ;
277+ const pending = this . describePending ( ) ;
278+ console . log (
279+ `[wait] ${ label } elapsed=${ elapsed } ms timeout=${ timeoutMs } ms pending=${ pending } `
280+ ) ;
281+ } ,
282+ Math . min ( 1000 , Math . max ( 250 , timeoutMs / 10 ) )
283+ ) ;
284+ }
285+
286+ private async withProgress (
287+ promise : Promise < unknown > ,
288+ label : string ,
289+ progressHandle ?: ReturnType < typeof setInterval >
290+ ) : Promise < void > {
291+ try {
292+ await promise ;
293+ } finally {
294+ if ( progressHandle ) {
295+ clearInterval ( progressHandle ) ;
296+ }
260297 }
261298 }
262299
300+ private describePending ( ) : string {
301+ const entries : string [ ] = [ ] ;
302+ for ( const [ clientId , client ] of this . clients . entries ( ) ) {
303+ const size = client . pending . size ;
304+ if ( size > 0 ) {
305+ entries . push ( `client${ clientId } :${ size } ` ) ;
306+ }
307+ }
308+ if ( entries . length === 0 ) {
309+ return 'none' ;
310+ }
311+ return entries . join ( ',' ) ;
312+ }
313+
263314 private async runScriptInternal (
264315 context : ScriptContext ,
265316 script : string ,
@@ -292,10 +343,12 @@ class MptestRunner {
292343 }
293344 if ( index > begin ) {
294345 const sqlChunk = script . slice ( begin , index ) ;
346+ this . logSqlChunk ( context , prevLine , sqlChunk ) ;
295347 await this . executeSql ( context , sqlChunk , result ) ;
296348 }
297349 let consumed = token . length ;
298350 const command = parseCommand ( script , index , token . length ) ;
351+ this . logCommand ( context , prevLine , command . name , command . payload ) ;
299352 switch ( command . name ) {
300353 case 'sleep' :
301354 await sleepMs ( Number ( command . args [ 0 ] ?? '0' ) ) ;
@@ -308,14 +361,17 @@ class MptestRunner {
308361 throw new Error (
309362 `${ context . displayName } :${ prevLine } expected [${ expectedRaw } ] but got [${ result . toString ( ) } ]`
310363 ) ;
311- } else {
312- console . log (
313- `${ context . displayName } :${ prevLine } expected [${ expectedRaw } ] matched [${ result . toString ( ) } ]`
314- ) ;
315364 }
316365 result . reset ( ) ;
317366 break ;
318367 }
368+ case 'print' : {
369+ const message = command . payload . replace ( / ^ \s * / , '' ) ;
370+ if ( message . length > 0 ) {
371+ this . logMessage ( context , message ) ;
372+ }
373+ break ;
374+ }
319375 case 'task' : {
320376 const clientId = Number ( command . args [ 0 ] ) ;
321377 if ( ! Number . isInteger ( clientId ) || clientId < 0 ) {
@@ -346,9 +402,15 @@ class MptestRunner {
346402 taskControl ?. resolve ( ) ;
347403 break ;
348404 }
405+ case 'exit' : {
406+ const code = Number ( command . args [ 0 ] ?? '0' ) ;
407+ taskControl ?. resolve ( ) ;
408+ await this . terminateClient ( context , code ) ;
409+ return ;
410+ }
349411 case 'wait' : {
350412 const target = command . args [ 0 ] ?? 'all' ;
351- const timeout = command . args [ 1 ] ? Number ( command . args [ 1 ] ) : 10000 ;
413+ const timeout = command . args [ 1 ] ? Number ( command . args [ 1 ] ) : 30_000 ;
352414 if ( target === 'all' ) {
353415 await this . waitForAll ( timeout ) ;
354416 } else {
@@ -411,6 +473,7 @@ class MptestRunner {
411473 }
412474 if ( begin < script . length ) {
413475 const remainder = script . slice ( begin ) ;
476+ this . logSqlChunk ( context , line , remainder ) ;
414477 await this . executeSql ( context , remainder , result ) ;
415478 }
416479 }
@@ -464,6 +527,7 @@ class MptestRunner {
464527 const { rows } = await statement . step ( ) ;
465528 if ( rows ) {
466529 const rawRows = rows as SqliteRowRaw [ ] ;
530+ this . logSqlExecution ( context , sql , rawRows ) ;
467531 for ( const row of rawRows ) {
468532 for ( const value of row ) {
469533 result . append ( value ) ;
@@ -472,20 +536,108 @@ class MptestRunner {
472536 }
473537 } catch ( err ) {
474538 if ( isSqliteError ( err ) ) {
539+ this . logError ( 'sqlite error' , err ) ;
475540 result . appendError ( err ) ;
476541 } else {
542+ this . logError ( 'sql execution error' , err ) ;
477543 throw err ;
478544 }
479545 } finally {
480546 statement . finalize ( ) ;
481547 }
482548 }
483549 }
550+
551+ private logMessage ( context : ScriptContext , message : string ) : void {
552+ const trimmed = message . replace ( / \s + $ / , '' ) ;
553+ if ( ! trimmed ) {
554+ return ;
555+ }
556+ console . log ( `${ context . displayName } ${ trimmed } ` ) ;
557+ }
558+
559+ private logCommand (
560+ context : ScriptContext ,
561+ line : number ,
562+ command : string ,
563+ payload : string
564+ ) : void {
565+ const detail = payload ? ` ${ payload . trim ( ) } ` : '' ;
566+ console . log ( `${ context . displayName } :${ line } --${ command } ${ detail } ` ) ;
567+ }
568+
569+ private logSqlChunk (
570+ context : ScriptContext ,
571+ line : number ,
572+ chunk : string
573+ ) : void {
574+ const summary = chunk . trim ( ) . replace ( / \s + / g, ' ' ) ;
575+ if ( ! summary ) {
576+ return ;
577+ }
578+ console . log ( `${ context . displayName } :${ line } SQL ${ summary } ` ) ;
579+ }
580+
581+ private logError ( prefix : string , err : unknown ) : void {
582+ if ( err instanceof Error ) {
583+ console . error ( `[error] ${ prefix } : ${ err . message } \n${ err . stack } ` ) ;
584+ } else {
585+ console . error ( `[error] ${ prefix } : ${ String ( err ) } ` ) ;
586+ }
587+ }
588+
589+ private logSqlExecution (
590+ context : ScriptContext ,
591+ placeholder : string ,
592+ values : SqliteRowRaw [ ]
593+ ) : void {
594+ console . log (
595+ `${ context . displayName } SQL values ${ placeholder } => ${ JSON . stringify ( values ) } `
596+ ) ;
597+ }
598+
599+ private async terminateClient (
600+ context : ScriptContext ,
601+ exitCode : number
602+ ) : Promise < void > {
603+ const { clientId, displayName } = context ;
604+ const client = this . clients . get ( clientId ) ;
605+ if ( ! client ) {
606+ return ;
607+ }
608+ this . clients . delete ( clientId ) ;
609+ try {
610+ await client . reserved . release ( ) ;
611+ } catch ( err ) {
612+ this . logError ( `release error client=${ clientId } ` , err ) ;
613+ if ( exitCode === 0 ) {
614+ throw err ;
615+ }
616+ }
617+ try {
618+ await client . pool . close ( ) ;
619+ } catch ( err ) {
620+ this . logError ( `close error client=${ clientId } ` , err ) ;
621+ if ( exitCode === 0 ) {
622+ throw err ;
623+ }
624+ }
625+ if ( exitCode !== 0 ) {
626+ console . log ( `${ displayName } exited with code ${ exitCode } ` ) ;
627+ }
628+ }
484629}
485630
486- describe ( 'mptest scripts' , ( ) => {
631+ describe ( 'mptest scripts' , { timeout : 60_000 } , ( ) => {
487632 // const scripts = topLevelScripts;
488- const scripts = [ 'mptest/multiwrite01.test' ] ;
633+ // const scripts = ['mptest/multiwrite01.test'];
634+ // const scripts = ['mptest/config02.test'];
635+ // const scripts = ['mptest/crash01.test'];
636+ const scripts = [
637+ // 'mptest/multiwrite01.test',
638+ 'mptest/config02.test'
639+ // 'mptest/crash01.test'
640+ ] ;
489641 for ( const script of scripts ) {
490642 test ( script , async ( ) => {
491643 const dbPath = `mptest-${ sanitizeForFilename ( script ) } -${ Math . random ( )
0 commit comments