@@ -168,6 +168,56 @@ impl Processo {
168168
169169}
170170
171+ use std:: process:: { ChildStdin , ChildStdout , ChildStderr } ;
172+
173+ /// Representa um processo em execução com acesso a stdin, stdout e stderr.
174+ pub struct PopenProcess {
175+ pub child : Child ,
176+ pub stdin : Option < ChildStdin > ,
177+ pub stdout : Option < ChildStdout > ,
178+ pub stderr : Option < ChildStderr > ,
179+ }
180+
181+ /// Executa um comando e retorna um processo com streams abertos (estilo popen)
182+ pub fn popen_command (
183+ command : Vec < String > ,
184+ options : RunOptions ,
185+ ) -> Result < PopenProcess , SubprocessError > {
186+ if command. is_empty ( ) {
187+ return Err ( SubprocessError :: InvalidArguments ( "Command cannot be empty" . to_string ( ) ) ) ;
188+ }
189+
190+ let program = & command[ 0 ] ;
191+ let args = & command[ 1 ..] ;
192+
193+ let mut cmd = Command :: new ( program) ;
194+ cmd. args ( args) ;
195+ cmd. stdin ( Stdio :: piped ( ) ) ;
196+
197+ // Redireciona stdout/stderr para pipes conforme solicitado
198+ if options. capture_output {
199+ cmd. stdout ( Stdio :: piped ( ) ) ;
200+ cmd. stderr ( Stdio :: piped ( ) ) ;
201+ }
202+
203+ match cmd. spawn ( ) {
204+ Ok ( mut child) => {
205+ let stdin = child. stdin . take ( ) ;
206+ let stdout = if options. capture_output { child. stdout . take ( ) } else { None } ;
207+ let stderr = if options. capture_output { child. stderr . take ( ) } else { None } ;
208+
209+ Ok ( PopenProcess {
210+ child,
211+ stdin,
212+ stdout,
213+ stderr,
214+ } )
215+ }
216+ Err ( e) => Err ( SubprocessError :: from_io_error ( e, program) ) ,
217+ }
218+ }
219+
220+
171221
172222#[ cfg( test) ]
173223mod tests {
@@ -649,4 +699,51 @@ mod tests {
649699 let exit_code = processo. wait ( ) . expect ( "Falha ao esperar pelo processo" ) ;
650700 assert_eq ! ( exit_code, 0 ) ;
651701 }
702+
703+ use std:: io:: { Read , Write } ;
704+
705+ #[ test]
706+ fn test_popen_cat_stdin_stdout ( ) {
707+ // Comando que apenas reflete a entrada
708+ let mut process = popen_command (
709+ vec ! [ "cat" . to_string( ) ] ,
710+ RunOptions { shell : false , capture_output : true }
711+ ) . expect ( "Falha ao iniciar processo" ) ;
712+
713+ let input = "Mensagem via stdin\n Outra linha\n " ;
714+
715+ // Escreve no stdin do processo
716+ if let Some ( stdin) = process. stdin . as_mut ( ) {
717+ stdin. write_all ( input. as_bytes ( ) ) . expect ( "Falha ao escrever no stdin" ) ;
718+ }
719+
720+ // Fecha stdin para que o processo finalize (cat só sai quando stdin fecha)
721+ drop ( process. stdin . take ( ) ) ;
722+
723+ // Espera a saída do processo
724+ let output = process. child . wait_with_output ( ) . expect ( "Falha ao esperar processo" ) ;
725+
726+ // Verifica se a saída é igual à entrada
727+ let stdout = String :: from_utf8_lossy ( & output. stdout ) ;
728+ assert_eq ! ( stdout, input) ;
729+
730+ // stderr deve estar vazio
731+ assert ! ( output. stderr. is_empty( ) ) ;
732+ assert_eq ! ( output. status. code( ) . unwrap_or( -1 ) , 0 ) ;
733+ }
734+
735+ #[ test]
736+ fn test_popen_error_output ( ) {
737+ let mut process = popen_command (
738+ vec ! [ "ls" . to_string( ) , "/naoexiste" . to_string( ) ] ,
739+ RunOptions { shell : false , capture_output : true }
740+ ) . expect ( "Falha ao iniciar processo" ) ;
741+
742+ let output = process. child . wait_with_output ( ) . unwrap ( ) ;
743+
744+ assert_ne ! ( output. status. code( ) . unwrap_or( -1 ) , 0 ) ;
745+ let stderr = String :: from_utf8_lossy ( & output. stderr ) ;
746+ assert ! ( stderr. contains( "No such file" ) || stderr. contains( "não existe" ) ) ;
747+ }
748+
652749}
0 commit comments