@@ -17,7 +17,7 @@ use pgrx_pg_config::{createdb, dropdb, PgConfig};
17
17
use std:: collections:: HashSet ;
18
18
use std:: env:: temp_dir;
19
19
use std:: fs:: { DirEntry , File } ;
20
- use std:: io:: { IsTerminal , Write } ;
20
+ use std:: io:: { BufRead , BufReader , IsTerminal , Write } ;
21
21
use std:: path:: { Path , PathBuf } ;
22
22
use std:: process:: { Command , ExitStatus , Stdio } ;
23
23
@@ -424,11 +424,8 @@ fn run_tests(
424
424
. parent ( )
425
425
. expect ( "test file should be in a directory named `sql/`" )
426
426
. to_path_buf ( ) ;
427
- let ( status, output) = pg_regress ( pg_config, pg_regress_bin, dbname, & input_dir, test_files) ?;
428
-
429
- println ! ( "{output}" ) ;
430
-
431
- Ok ( status. success ( ) )
427
+ pg_regress ( pg_config, pg_regress_bin, dbname, & input_dir, test_files)
428
+ . map ( |status| status. success ( ) )
432
429
}
433
430
434
431
fn create_regress_output (
@@ -446,7 +443,7 @@ fn create_regress_output(
446
443
. parent ( )
447
444
. expect ( "test file should be in a directory named `sql/`" )
448
445
. to_path_buf ( ) ;
449
- let ( status, output ) = pg_regress ( pg_config, pg_regress_bin, dbname, & input_dir, & [ test_file] ) ?;
446
+ let status = pg_regress ( pg_config, pg_regress_bin, dbname, & input_dir, & [ test_file] ) ?;
450
447
451
448
if !status. success ( ) {
452
449
// pg_regress returned with an error code, but that is most likely because the test's output file
@@ -457,7 +454,6 @@ fn create_regress_output(
457
454
if out_file. exists ( ) {
458
455
return Ok ( Some ( out_file) ) ;
459
456
} else {
460
- println ! ( "{output}" ) ;
461
457
std:: process:: exit ( status. code ( ) . unwrap_or ( 1 ) ) ;
462
458
}
463
459
}
@@ -471,7 +467,7 @@ fn pg_regress(
471
467
dbname : & str ,
472
468
input_dir : impl AsRef < Path > ,
473
469
tests : & [ & DirEntry ] ,
474
- ) -> eyre:: Result < ( ExitStatus , String ) > {
470
+ ) -> eyre:: Result < ExitStatus > {
475
471
if tests. is_empty ( ) {
476
472
eyre:: bail!( "no tests to run" ) ;
477
473
}
@@ -520,101 +516,116 @@ fn pg_regress(
520
516
521
517
tracing:: trace!( "running {command:?}" ) ;
522
518
523
- let output = command. output ( ) ?;
524
- let stdout = decorate_output ( & String :: from_utf8_lossy ( & output. stdout ) ) ;
525
- let stderr = decorate_output ( & String :: from_utf8_lossy ( & output. stderr ) ) ;
519
+ let mut child = command. spawn ( ) ?;
520
+ let ( Some ( stdout) , Some ( stderr) ) = ( child. stdout . take ( ) , child. stderr . take ( ) ) else {
521
+ panic ! ( "unable to take stdout or stderr from pg_regress process" ) ;
522
+ } ;
526
523
527
- let cmd_output = if !stdout. is_empty ( ) && !stderr. is_empty ( ) {
528
- format ! ( "{stdout}\n {stderr}" )
529
- } else if !stdout. is_empty ( ) {
530
- stdout. to_string ( )
531
- } else {
532
- stderr. to_string ( )
533
- }
534
- . trim ( )
535
- . to_string ( ) ;
524
+ let output_monitor = std:: thread:: spawn ( move || {
525
+ let mut passed_cnt = 0 ;
526
+ let mut failed_cnt = 0 ;
527
+ let stdout = BufReader :: new ( stdout) ;
528
+ let stderr = BufReader :: new ( stderr) ;
529
+ for line in stdout. lines ( ) . chain ( stderr. lines ( ) ) {
530
+ let line = line. unwrap ( ) ;
531
+ let Some ( ( line, result) ) = decorate_output ( line) else {
532
+ continue ;
533
+ } ;
534
+
535
+ match result {
536
+ Some ( TestResult :: Passed ) => passed_cnt += 1 ,
537
+ Some ( TestResult :: Failed ) => failed_cnt += 1 ,
538
+ None => ( ) ,
539
+ }
540
+
541
+ println ! ( "{line}" ) ;
542
+ }
543
+ ( passed_cnt, failed_cnt)
544
+ } ) ;
545
+ let status = child. wait ( ) ?;
546
+ let ( passed_cnt, failed_cnt) =
547
+ output_monitor. join ( ) . map_err ( |_| eyre:: eyre!( "failed to join output monitor thread" ) ) ?;
548
+ println ! ( "passed={passed_cnt} failed={failed_cnt}" ) ;
536
549
537
550
#[ cfg( not( target_os = "windows" ) ) ]
538
551
{
539
552
std:: fs:: remove_file ( launcher_script) ?;
540
553
}
541
554
542
- Ok ( ( output. status , cmd_output) )
555
+ Ok ( status)
556
+ }
557
+
558
+ enum TestResult {
559
+ Passed ,
560
+ Failed ,
543
561
}
544
562
545
- fn decorate_output ( input : & str ) -> String {
546
- let mut decorated = String :: with_capacity ( input. len ( ) ) ;
547
- let ( mut total_passed, mut total_failed) = ( 0 , 0 ) ;
548
- for line in input. lines ( ) {
549
- let mut line = line. to_string ( ) ;
550
- let mut is_old_line = false ;
551
- let mut is_new_line = false ;
552
-
553
- if line. starts_with ( "ok" ) {
554
- // for pg_regress from pg16 forward, rewrite the "ok" into a colored PASS"
555
- is_new_line = true ;
556
- } else if line. starts_with ( "not ok" ) {
557
- // for pg_regress from pg16 forward, rewrite the "no ok" into a colored FAIL"
558
- line = line. replace ( "not ok" , "not_ok" ) ; // to make parsing easier down below
559
- is_new_line = true ;
560
- } else if line. contains ( "... ok" ) || line. contains ( "... FAILED" ) {
561
- is_old_line = true ;
563
+ fn decorate_output ( mut line : String ) -> Option < ( String , Option < TestResult > ) > {
564
+ let mut decorated = String :: with_capacity ( line. len ( ) ) ;
565
+ let mut test_result: Option < TestResult > = None ;
566
+ let mut is_old_line = false ;
567
+ let mut is_new_line = false ;
568
+
569
+ if line. starts_with ( "ok" ) {
570
+ // for pg_regress from pg16 forward, rewrite the "ok" into a colored PASS"
571
+ is_new_line = true ;
572
+ } else if line. starts_with ( "not ok" ) {
573
+ // for pg_regress from pg16 forward, rewrite the "no ok" into a colored FAIL"
574
+ line = line. replace ( "not ok" , "not_ok" ) ; // to make parsing easier down below
575
+ is_new_line = true ;
576
+ } else if line. contains ( "... ok" ) || line. contains ( "... FAILED" ) {
577
+ is_old_line = true ;
578
+ }
579
+
580
+ let parsed_test_line = if is_new_line {
581
+ fn split_line ( line : & str ) -> Option < ( & str , bool , & str , & str ) > {
582
+ let mut parts = line. split_whitespace ( ) ;
583
+
584
+ let passed = parts. next ( ) ? == "ok" ;
585
+ parts. next ( ) ?; // throw away the test number
586
+ parts. next ( ) ?; // throw away the dash (-)
587
+ let test_name = parts. next ( ) ?;
588
+ let execution_time = parts. next ( ) ?;
589
+ let execution_units = parts. next ( ) ?;
590
+ Some ( ( test_name, passed, execution_time, execution_units) )
562
591
}
592
+ split_line ( & line)
593
+ } else if is_old_line {
594
+ fn split_line ( line : & str ) -> Option < ( & str , bool , & str , & str ) > {
595
+ let mut parts = line. split_whitespace ( ) ;
596
+
597
+ parts. next ( ) ?; // throw away "test"
598
+ let test_name = parts. next ( ) ?;
599
+ parts. next ( ) ?; // throw away "..."
600
+ let passed = parts. next ( ) ? == "ok" ;
601
+ let execution_time = parts. next ( ) ?;
602
+ let execution_units = parts. next ( ) ?;
603
+ Some ( ( test_name, passed, execution_time, execution_units) )
604
+ }
605
+ split_line ( & line)
606
+ } else {
607
+ // not a line we care about
608
+ return None ;
609
+ } ;
563
610
564
- let parsed_test_line = if is_new_line {
565
- fn split_line ( line : & str ) -> Option < ( & str , bool , & str , & str ) > {
566
- let mut parts = line. split_whitespace ( ) ;
567
-
568
- let passed = parts. next ( ) ? == "ok" ;
569
- parts. next ( ) ?; // throw away the test number
570
- parts. next ( ) ?; // throw away the dash (-)
571
- let test_name = parts. next ( ) ?;
572
- let execution_time = parts. next ( ) ?;
573
- let execution_units = parts. next ( ) ?;
574
- Some ( ( test_name, passed, execution_time, execution_units) )
575
- }
576
- split_line ( & line)
577
- } else if is_old_line {
578
- fn split_line ( line : & str ) -> Option < ( & str , bool , & str , & str ) > {
579
- let mut parts = line. split_whitespace ( ) ;
580
-
581
- parts. next ( ) ?; // throw away "test"
582
- let test_name = parts. next ( ) ?;
583
- parts. next ( ) ?; // throw away "..."
584
- let passed = parts. next ( ) ? == "ok" ;
585
- let execution_time = parts. next ( ) ?;
586
- let execution_units = parts. next ( ) ?;
587
- Some ( ( test_name, passed, execution_time, execution_units) )
588
- }
589
- split_line ( & line)
611
+ if let Some ( ( test_name, passed, execution_time, execution_units) ) = parsed_test_line {
612
+ if passed {
613
+ test_result = Some ( TestResult :: Passed ) ;
590
614
} else {
591
- // not a line we care about
592
- continue ;
593
- } ;
615
+ test_result = Some ( TestResult :: Failed ) ;
616
+ }
594
617
595
- if let Some ( ( test_name, passed, execution_time, execution_units) ) = parsed_test_line {
618
+ decorated. push_str ( & format ! (
619
+ "{} {test_name} {execution_time}{execution_units}" ,
596
620
if passed {
597
- total_passed += 1
621
+ "PASS" . bold ( ) . bright_green ( ) . to_string ( )
598
622
} else {
599
- total_failed += 1
623
+ "FAIL" . bold ( ) . bright_red ( ) . to_string ( )
600
624
}
601
-
602
- decorated. push_str ( & format ! (
603
- "{} {test_name} {execution_time}{execution_units}\n " ,
604
- if passed {
605
- "PASS" . bold( ) . bright_green( ) . to_string( )
606
- } else {
607
- "FAIL" . bold( ) . bright_red( ) . to_string( )
608
- }
609
- ) )
610
- }
611
- }
612
-
613
- if total_passed + total_failed > 0 {
614
- decorated. push_str ( & format ! ( "passed={total_passed}, failed={total_failed}\n " ) )
625
+ ) )
615
626
}
616
627
617
- decorated
628
+ Some ( ( decorated, test_result ) )
618
629
}
619
630
620
631
fn make_test_name ( entry : & DirEntry ) -> String {
0 commit comments