@@ -326,25 +326,34 @@ fn direct_child_pids() -> io::Result<HashSet<i32>> {
326326 Ok ( children)
327327}
328328
329- #[ cfg( test) ]
329+ #[ cfg( all ( test, target_os = "linux" ) ) ]
330330mod tests {
331331 use super :: * ;
332332 use nix:: sys:: wait:: { Id , WaitPidFlag , WaitStatus , waitid, waitpid} ;
333333 use nix:: unistd:: Pid ;
334- use std:: process:: Command ;
334+ use std:: process:: { Command , Stdio } ;
335335 use std:: sync:: mpsc;
336336 use std:: time:: Duration ;
337+ use tokio:: process:: Command as TokioCommand ;
338+
339+ #[ test]
340+ fn spawned_child_is_registered_before_returning ( ) {
341+ let pid = 1_000_000_u32 ;
342+ let child = ManagedChild :: spawn ( || Ok :: < _ , ( ) > ( pid) , |pid| Some ( * pid) ) . unwrap ( ) ;
343+
344+ assert ! ( is_managed( i32 :: try_from( child. id( ) . unwrap( ) ) . unwrap( ) ) ) ;
345+ drop ( child) ;
346+ assert ! ( !is_managed( i32 :: try_from( pid) . unwrap( ) ) ) ;
347+ }
337348
338349 #[ test]
339350 fn fast_child_remains_waitable_after_orphan_reap_attempt ( ) {
340- let mut registry = lock ( ) ;
341- let mut child = Command :: new ( "sh" )
342- . args ( [ "-c" , "exit 11" ] )
343- . spawn ( )
344- . expect ( "spawn fast child" ) ;
345- let child_pid = child. id ( ) ;
346- let managed_child = registry. register ( child_pid) . expect ( "register fast child" ) ;
347- drop ( registry) ;
351+ let mut child = ManagedChild :: spawn (
352+ || Command :: new ( "sh" ) . args ( [ "-c" , "exit 11" ] ) . spawn ( ) ,
353+ |child| Some ( child. id ( ) ) ,
354+ )
355+ . expect ( "spawn and register fast child" ) ;
356+ let child_pid = child. id ( ) . unwrap ( ) ;
348357 let pid = Pid :: from_raw ( i32:: try_from ( child_pid) . unwrap ( ) ) ;
349358
350359 // Observe the completed child without consuming its status, exactly
@@ -373,11 +382,11 @@ mod tests {
373382 ) ;
374383
375384 let wait = child. wait ( ) ;
376- unregister ( managed_child) ;
377385 assert ! (
378386 wait. is_ok( ) ,
379387 "the explicit child waiter must retain the exit status"
380388 ) ;
389+ assert ! ( !is_managed( pid. as_raw( ) ) ) ;
381390 }
382391
383392 #[ test]
@@ -452,4 +461,80 @@ mod tests {
452461 child. kill ( ) . expect ( "kill child" ) ;
453462 child. wait ( ) . expect ( "wait for child" ) ;
454463 }
464+
465+ #[ tokio:: test]
466+ async fn tokio_try_wait_retains_then_releases_management ( ) {
467+ let mut child = ManagedChild :: spawn (
468+ || {
469+ let mut command = TokioCommand :: new ( "sh" ) ;
470+ command. args ( [ "-c" , "read -r _" ] ) . stdin ( Stdio :: piped ( ) ) ;
471+ command. spawn ( )
472+ } ,
473+ tokio:: process:: Child :: id,
474+ )
475+ . expect ( "spawn managed Tokio child" ) ;
476+ let pid = i32:: try_from ( child. id ( ) . unwrap ( ) ) . unwrap ( ) ;
477+ let stdin = child. take_stdin ( ) . expect ( "stdin must be piped" ) ;
478+
479+ assert ! ( child. try_wait( ) . expect( "observe Tokio child" ) . is_none( ) ) ;
480+ assert ! ( is_managed( pid) , "a running child must remain managed" ) ;
481+
482+ drop ( stdin) ;
483+ child. wait ( ) . await . expect ( "wait for Tokio child" ) ;
484+ assert ! (
485+ !is_managed( pid) ,
486+ "a waited child must release its managed PID"
487+ ) ;
488+ }
489+ }
490+
491+ #[ cfg( test) ]
492+ mod cross_platform_tests {
493+ use super :: ManagedChild ;
494+ use std:: process:: { Command , Stdio } ;
495+
496+ #[ test]
497+ fn managed_child_retains_valid_pid_on_every_platform ( ) {
498+ let child = ManagedChild :: spawn ( || Ok :: < _ , ( ) > ( 1_000_003_u32 ) , |pid| Some ( * pid) )
499+ . expect ( "construct managed child" ) ;
500+
501+ assert_eq ! ( child. id( ) , Some ( 1_000_003 ) ) ;
502+ }
503+
504+ #[ test]
505+ fn standard_child_waits_through_managed_wrapper ( ) {
506+ let executable = std:: env:: current_exe ( ) . expect ( "current test executable" ) ;
507+ let mut child = ManagedChild :: spawn (
508+ || {
509+ Command :: new ( executable)
510+ . arg ( "--help" )
511+ . stdout ( Stdio :: null ( ) )
512+ . stderr ( Stdio :: null ( ) )
513+ . spawn ( )
514+ } ,
515+ |child| Some ( child. id ( ) ) ,
516+ )
517+ . expect ( "spawn test child" ) ;
518+
519+ assert ! ( child. wait( ) . expect( "wait for test child" ) . success( ) ) ;
520+ }
521+
522+ #[ tokio:: test]
523+ async fn tokio_child_waits_through_managed_wrapper ( ) {
524+ let executable = std:: env:: current_exe ( ) . expect ( "current test executable" ) ;
525+ let mut child = ManagedChild :: spawn (
526+ || {
527+ let mut command = tokio:: process:: Command :: new ( executable) ;
528+ command
529+ . arg ( "--help" )
530+ . stdout ( Stdio :: null ( ) )
531+ . stderr ( Stdio :: null ( ) ) ;
532+ command. spawn ( )
533+ } ,
534+ tokio:: process:: Child :: id,
535+ )
536+ . expect ( "spawn test child" ) ;
537+
538+ assert ! ( child. wait( ) . await . expect( "wait for test child" ) . success( ) ) ;
539+ }
455540}
0 commit comments