@@ -3,10 +3,10 @@ mod redact;
33use std:: {
44 env:: { self , join_paths, split_paths} ,
55 ffi:: OsStr ,
6- io:: Write ,
6+ io:: { Read , Write } ,
77 process:: Stdio ,
8- sync:: { Arc , mpsc} ,
9- time:: Duration ,
8+ sync:: { Arc , Mutex , mpsc} ,
9+ time:: { Duration , Instant } ,
1010} ;
1111
1212use copy_dir:: copy_dir;
@@ -207,20 +207,8 @@ enum TerminationState {
207207 TimedOut ,
208208}
209209
210- fn kill_process ( pid : u32 ) {
211- let pid_arg = vite_str:: format!( "{pid}" ) ;
212-
213- #[ cfg( unix) ]
214- {
215- let _ = std:: process:: Command :: new ( "kill" ) . arg ( "-9" ) . arg ( pid_arg. as_str ( ) ) . status ( ) ;
216- }
217-
218- #[ cfg( windows) ]
219- {
220- let _ = std:: process:: Command :: new ( "taskkill" )
221- . args ( [ "/PID" , pid_arg. as_str ( ) , "/T" , "/F" ] )
222- . status ( ) ;
223- }
210+ fn kill_process ( child : & mut std:: process:: Child ) {
211+ let _ = child. kill ( ) ;
224212}
225213
226214#[ expect(
@@ -334,15 +322,16 @@ fn run_case_inner(tmpdir: &AbsolutePath, fixture_path: &std::path::Path, fixture
334322 let terminal = TestTerminal :: spawn ( SCREEN_SIZE , cmd) . unwrap ( ) ;
335323 let mut killer = terminal. child_handle . clone ( ) ;
336324 let interactions = step. interactions ( ) . to_vec ( ) ;
325+ let output = Arc :: new ( Mutex :: new ( String :: new ( ) ) ) ;
326+ let output_for_thread = Arc :: clone ( & output) ;
337327 let ( tx, rx) = mpsc:: channel ( ) ;
338328 std:: thread:: spawn ( move || {
339329 let mut terminal = terminal;
340- let mut interaction_output = String :: new ( ) ;
341330
342331 for interaction in interactions {
343332 match interaction {
344333 Interaction :: ExpectMilestone ( expect) => {
345- interaction_output . push_str (
334+ output_for_thread . lock ( ) . unwrap ( ) . push_str (
346335 vite_str:: format!(
347336 "@ expect-milestone: {}\n " ,
348337 expect. expect_milestone
@@ -352,18 +341,19 @@ fn run_case_inner(tmpdir: &AbsolutePath, fixture_path: &std::path::Path, fixture
352341 let milestone_screen = terminal
353342 . reader
354343 . expect_milestone ( expect. expect_milestone . as_str ( ) ) ;
355- interaction_output. push_str ( & milestone_screen) ;
356- interaction_output. push ( '\n' ) ;
344+ let mut output = output_for_thread. lock ( ) . unwrap ( ) ;
345+ output. push_str ( & milestone_screen) ;
346+ output. push ( '\n' ) ;
357347 }
358348 Interaction :: Write ( write) => {
359- interaction_output . push_str (
349+ output_for_thread . lock ( ) . unwrap ( ) . push_str (
360350 vite_str:: format!( "@ write: {}\n " , write. write) . as_str ( ) ,
361351 ) ;
362352 terminal. writer . write_all ( write. write . as_str ( ) . as_bytes ( ) ) . unwrap ( ) ;
363353 terminal. writer . flush ( ) . unwrap ( ) ;
364354 }
365355 Interaction :: WriteLine ( write_line) => {
366- interaction_output . push_str (
356+ output_for_thread . lock ( ) . unwrap ( ) . push_str (
367357 vite_str:: format!( "@ write-line: {}\n " , write_line. write_line)
368358 . as_str ( ) ,
369359 ) ;
@@ -374,7 +364,7 @@ fn run_case_inner(tmpdir: &AbsolutePath, fixture_path: &std::path::Path, fixture
374364 }
375365 Interaction :: WriteKey ( write_key) => {
376366 let key_name = write_key. write_key . as_str ( ) ;
377- interaction_output . push_str (
367+ output_for_thread . lock ( ) . unwrap ( ) . push_str (
378368 vite_str:: format!( "@ write-key: {key_name}\n " ) . as_str ( ) ,
379369 ) ;
380370 terminal. writer . write_all ( write_key. write_key . bytes ( ) ) . unwrap ( ) ;
@@ -386,20 +376,25 @@ fn run_case_inner(tmpdir: &AbsolutePath, fixture_path: &std::path::Path, fixture
386376 let status = terminal. reader . wait_for_exit ( ) ;
387377 let screen = terminal. reader . screen_contents ( ) ;
388378
389- let mut output = interaction_output;
390- if !output. is_empty ( ) && !output. ends_with ( '\n' ) {
391- output. push ( '\n' ) ;
379+ {
380+ let mut output = output_for_thread. lock ( ) . unwrap ( ) ;
381+ if !output. is_empty ( ) && !output. ends_with ( '\n' ) {
382+ output. push ( '\n' ) ;
383+ }
384+ output. push_str ( & screen) ;
392385 }
393- output. push_str ( & screen) ;
394386
395- let _ = tx. send ( ( i64:: from ( status. exit_code ( ) ) , output ) ) ;
387+ let _ = tx. send ( i64:: from ( status. exit_code ( ) ) ) ;
396388 } ) ;
397389
398390 match rx. recv_timeout ( STEP_TIMEOUT ) {
399- Ok ( ( exit_code, output) ) => ( TerminationState :: Exited ( exit_code) , output) ,
391+ Ok ( exit_code) => {
392+ let output = output. lock ( ) . unwrap ( ) . clone ( ) ;
393+ ( TerminationState :: Exited ( exit_code) , output)
394+ }
400395 Err ( mpsc:: RecvTimeoutError :: Timeout ) => {
401396 let _ = killer. kill ( ) ;
402- let ( _ , output) = rx . recv ( ) . unwrap ( ) ;
397+ let output = output . lock ( ) . unwrap ( ) . clone ( ) ;
403398 ( TerminationState :: TimedOut , output)
404399 }
405400 Err ( mpsc:: RecvTimeoutError :: Disconnected ) => {
@@ -426,37 +421,78 @@ fn run_case_inner(tmpdir: &AbsolutePath, fixture_path: &std::path::Path, fixture
426421 cmd. env ( "PATHEXT" , pathext) ;
427422 }
428423
429- let child = cmd. spawn ( ) . unwrap ( ) ;
430- let pid = child. id ( ) ;
431- let ( tx, rx) = mpsc:: channel ( ) ;
432- std:: thread:: spawn ( move || {
433- let output = child. wait_with_output ( ) ;
434- let _ = tx. send ( output) ;
424+ let mut child = cmd. spawn ( ) . unwrap ( ) ;
425+
426+ let stdout_output = Arc :: new ( Mutex :: new ( Vec :: < u8 > :: new ( ) ) ) ;
427+ let stderr_output = Arc :: new ( Mutex :: new ( Vec :: < u8 > :: new ( ) ) ) ;
428+
429+ let stdout = child. stdout . take ( ) . unwrap ( ) ;
430+ let stdout_output_for_thread = Arc :: clone ( & stdout_output) ;
431+ let stdout_thread = std:: thread:: spawn ( move || {
432+ let mut stdout = stdout;
433+ let mut buf = [ 0u8 ; 4096 ] ;
434+ loop {
435+ match stdout. read ( & mut buf) {
436+ Ok ( 0 ) => break ,
437+ Ok ( n) => {
438+ stdout_output_for_thread
439+ . lock ( )
440+ . unwrap ( )
441+ . extend_from_slice ( & buf[ ..n] ) ;
442+ }
443+ Err ( err) if err. kind ( ) == std:: io:: ErrorKind :: Interrupted => { }
444+ Err ( _) => break ,
445+ }
446+ }
435447 } ) ;
436448
437- match rx. recv_timeout ( STEP_TIMEOUT ) {
438- Ok ( output) => {
439- let output = output. unwrap ( ) ;
440- let mut combined_output =
441- String :: from_utf8_lossy ( & output. stdout ) . into_owned ( ) ;
442- combined_output. push_str ( String :: from_utf8_lossy ( & output. stderr ) . as_ref ( ) ) ;
443-
444- let exit_code = i64:: from ( output. status . code ( ) . unwrap_or ( 1 ) ) ;
445- ( TerminationState :: Exited ( exit_code) , combined_output)
449+ let stderr = child. stderr . take ( ) . unwrap ( ) ;
450+ let stderr_output_for_thread = Arc :: clone ( & stderr_output) ;
451+ let stderr_thread = std:: thread:: spawn ( move || {
452+ let mut stderr = stderr;
453+ let mut buf = [ 0u8 ; 4096 ] ;
454+ loop {
455+ match stderr. read ( & mut buf) {
456+ Ok ( 0 ) => break ,
457+ Ok ( n) => {
458+ stderr_output_for_thread
459+ . lock ( )
460+ . unwrap ( )
461+ . extend_from_slice ( & buf[ ..n] ) ;
462+ }
463+ Err ( err) if err. kind ( ) == std:: io:: ErrorKind :: Interrupted => { }
464+ Err ( _) => break ,
465+ }
446466 }
447- Err ( mpsc:: RecvTimeoutError :: Timeout ) => {
448- kill_process ( pid) ;
449- let output = rx. recv ( ) . unwrap ( ) . unwrap ( ) ;
467+ } ) ;
450468
451- let mut combined_output =
452- String :: from_utf8_lossy ( & output . stdout ) . into_owned ( ) ;
453- combined_output . push_str ( String :: from_utf8_lossy ( & output . stderr ) . as_ref ( ) ) ;
469+ let snapshot_output = || {
470+ let stdout = { stdout_output . lock ( ) . unwrap ( ) . clone ( ) } ;
471+ let stderr = { stderr_output . lock ( ) . unwrap ( ) . clone ( ) } ;
454472
455- ( TerminationState :: TimedOut , combined_output)
473+ let mut combined_output = String :: from_utf8_lossy ( & stdout) . into_owned ( ) ;
474+ combined_output. push_str ( String :: from_utf8_lossy ( & stderr) . as_ref ( ) ) ;
475+ combined_output
476+ } ;
477+
478+ let start = Instant :: now ( ) ;
479+ loop {
480+ if let Some ( status) = child. try_wait ( ) . unwrap ( ) {
481+ let _ = stdout_thread. join ( ) ;
482+ let _ = stderr_thread. join ( ) ;
483+ let combined_output = snapshot_output ( ) ;
484+
485+ let exit_code = i64:: from ( status. code ( ) . unwrap_or ( 1 ) ) ;
486+ break ( TerminationState :: Exited ( exit_code) , combined_output) ;
456487 }
457- Err ( mpsc:: RecvTimeoutError :: Disconnected ) => {
458- panic ! ( "Process wait thread panicked" ) ;
488+
489+ if start. elapsed ( ) >= STEP_TIMEOUT {
490+ kill_process ( & mut child) ;
491+ let combined_output = snapshot_output ( ) ;
492+ break ( TerminationState :: TimedOut , combined_output) ;
459493 }
494+
495+ std:: thread:: park_timeout ( Duration :: from_millis ( 10 ) ) ;
460496 }
461497 } ;
462498
0 commit comments