char/avril
(wip) why'd you have to go and make things so complicaaaaated?
git clone https://git.t4t.associates/char/avril
f4a0f5b
main
1#! [ cfg ( any ( target_os = "linux" , target_os = "macos" ))] 2 3use std:: path::{ Path , PathBuf }; 4use std:: time:: Duration ; 5 6use avril_subprocess::{ Executable , Launch , null, pipe, spawn}; 7use tokio:: io::{ AsyncReadExt , AsyncWriteExt }; 8 9fn shell () ->PathBuf { 10 std:: env:: split_paths ( & std:: env:: var_os ( "PATH" ). unwrap ()) 11. map ( |directory| directory. join ( "sh" )) 12. find ( |path| path. is_file ()) 13. unwrap () 14. canonicalize () 15. unwrap () 16} 17 18fn launch < ' a >( program : &' a Path , args : &' a [ String ], cwd : &' a Path ) ->Launch < ' a > { 19# [ cfg ( target_os = "linux" )] 20let ( executable, _) =Executable :: open_at ( 21& std:: fs:: File :: open ( program. parent (). unwrap ()). unwrap (), 22 program. file_name (). unwrap (), 23) 24. unwrap (); 25# [ cfg ( target_os = "macos" )] 26let ( executable, _) =Executable :: open ( program). unwrap (); 27Launch { 28 executable, 29 program, 30 args, 31env : Vec :: new (), 32 cwd, 33group : true , 34join : None , 35terminal : false , 36endpoints : ( 0 ..3 ). map ( |fd|( fd, null ( fd ==0 ). unwrap ())). collect (), 37} 38} 39 40# [ tokio :: test ] 41async fn launches_with_explicit_environment_directory_and_pipes () { 42let program =shell (); 43let directory = tempfile:: tempdir (). unwrap (); 44let cwd = directory. path (). canonicalize (). unwrap (); 45let args =[ 46"-c" . into (), 47"read -r value; printf '%s:%s:%s' \"$VALUE\" \"$value\" \"$PWD\"; exit 7" . into (), 48]; 49let mut command =launch ( & program, & args, & cwd); 50 command 51. env 52. push (( "VALUE" . into (), "from environment" . into ())); 53let ( mut input, stdin) =pipe ( true ). unwrap (); 54let ( mut output, stdout) =pipe ( false ). unwrap (); 55 command. endpoints =vec! [( 0 , stdin), ( 1 , stdout), ( 2 , null ( false ). unwrap ())]; 56let mut child =spawn ( command). unwrap (); 57let text = tokio:: time:: timeout ( Duration :: from_secs ( 5 ), async { 58 input. write_all ( b"from input\n" ). await . unwrap (); 59drop ( input); 60let mut text =String :: new (); 61 output. read_to_string ( & mut text). await . unwrap (); 62 text 63}) 64. await 65. unwrap (); 66assert_eq! ( 67 text, 68format! ( "from environment:from input:{}" , cwd. display ()) 69); 70let deadline = std:: time:: Instant :: now () +Duration :: from_secs ( 5 ); 71let observed =loop { 72if let Some ( status) = child. observe_exit (). unwrap () { 73break status; 74} 75assert! ( std:: time:: Instant :: now () < deadline, "child did not exit" ); 76 tokio:: time:: sleep ( Duration :: from_millis ( 5 )). await ; 77}; 78assert_eq! ( observed. code (), Some ( 7 )); 79assert_eq! ( child. observe_exit (). unwrap (), Some ( observed)); 80assert_eq! ( child. wait (). unwrap (), observed); 81assert! ( !child. signal ( true , "SIGKILL" ). unwrap ()); 82} 83 84# [ test ] 85fn rejects_invalid_descriptor_assignments_before_spawn () { 86let program =shell (); 87let args =[ "-c" . into (), "exit 0" . into ()]; 88for descriptorin [ 0 , u32:: MAX ] { 89let mut command =launch ( & program, & args, Path :: new ( "/" )); 90 command. endpoints . push (( descriptor, null ( false ). unwrap ())); 91assert_eq! ( 92spawn ( command). err (). unwrap (). kind (), 93 std:: io:: ErrorKind :: InvalidInput 94); 95} 96} 97 98# [ test ] 99fn exec_failure_is_a_spawn_error () { 100let directory = tempfile:: tempdir (). unwrap (); 101let program = directory. path (). join ( "not-executable" ); 102 std:: fs:: write ( & program, "not a program" ). unwrap (); 103let command =launch ( & program, & [], directory. path ()); 104assert_eq! ( 105spawn ( command). err (). unwrap (). kind (), 106 std:: io:: ErrorKind :: PermissionDenied 107); 108} 109 110# [ cfg ( target_os = "linux" )] 111# [ test ] 112fn opened_executable_survives_path_replacement () { 113use std:: os:: unix:: fs:: PermissionsExt ; 114 115let directory = tempfile:: tempdir (). unwrap (); 116let program = directory. path (). join ( "program" ); 117 std:: fs:: write ( & program, format! ( "#!{}\nexit 7\n" , shell (). display ())). unwrap (); 118 std:: fs:: set_permissions ( & program, std:: fs:: Permissions :: from_mode ( 0o755 )). unwrap (); 119let command =launch ( & program, & [], directory. path ()); 120 std:: fs:: rename ( & program, directory. path (). join ( "original" )). unwrap (); 121 std:: fs:: write ( & program, format! ( "#!{}\nexit 42\n" , shell (). display ())). unwrap (); 122 std:: fs:: set_permissions ( & program, std:: fs:: Permissions :: from_mode ( 0o755 )). unwrap (); 123assert_eq! ( spawn ( command). unwrap (). wait (). unwrap (). code (), Some ( 7 )); 124}