11//! Custom wrapper for Command that supports timeouts and contains custom error handling.
22
3- use crate :: {
4- error:: { CommandError , FileIo } ,
5- file_util, TmcError ,
6- } ;
3+ use crate :: { error:: CommandError , TmcError } ;
74use std:: fs:: File ;
85use std:: io:: Read ;
96use std:: time:: Duration ;
107use std:: { ffi:: OsStr , thread:: JoinHandle } ;
11- use subprocess:: { Exec , ExitStatus , PopenError } ;
8+ use subprocess:: { Exec , ExitStatus , PopenError , Redirection } ;
129
1310/// Wrapper around subprocess::Exec
1411#[ must_use]
1512pub struct TmcCommand {
1613 exec : Exec ,
17- stdout : Option < File > ,
18- stderr : Option < File > ,
1914}
2015
2116impl TmcCommand {
2217 /// Creates a new command
2318 pub fn new ( cmd : impl AsRef < OsStr > ) -> Self {
2419 Self {
2520 exec : Exec :: cmd ( cmd) . env ( "LANG" , "en_US.UTF-8" ) ,
26- stdout : None ,
27- stderr : None ,
2821 }
2922 }
3023
31- /// Creates a new command with stdout and stderr redirected to files
32- // todo: remember why this is useful
33- pub fn new_with_file_io ( cmd : impl AsRef < OsStr > ) -> Result < Self , TmcError > {
34- let stdout = file_util:: temp_file ( ) ?;
35- let stderr = file_util:: temp_file ( ) ?;
36- Ok ( Self {
24+ /// Creates a new command, defaults to piped stdout/stderr.
25+ pub fn piped ( cmd : impl AsRef < OsStr > ) -> Self {
26+ Self {
3727 exec : Exec :: cmd ( cmd)
38- . stdout ( stdout. try_clone ( ) . map_err ( FileIo :: FileHandleClone ) ?)
39- . stderr ( stderr. try_clone ( ) . map_err ( FileIo :: FileHandleClone ) ?)
40- . env ( "LANG" , "en_US.UTF-8" ) , // some languages may error on UTF-8 files if the LANG variable is unset or set to some non-UTF-8 value
41- stdout : Some ( stdout) ,
42- stderr : Some ( stderr) ,
43- } )
28+ . stdout ( Redirection :: Pipe )
29+ . stderr ( Redirection :: Pipe )
30+ . env ( "LANG" , "en_US.UTF-8" ) ,
31+ }
4432 }
4533
4634 /// Allows modification of the internal command without providing access to it.
4735 pub fn with ( self , f : impl FnOnce ( Exec ) -> Exec ) -> Self {
48- Self {
49- exec : f ( self . exec ) ,
50- ..self
51- }
36+ Self { exec : f ( self . exec ) }
5237 }
5338
5439 // executes the given command and collects its output
5540 fn execute ( self , timeout : Option < Duration > , checked : bool ) -> Result < Output , TmcError > {
5641 let cmd = self . exec . to_cmdline_lossy ( ) ;
5742 log:: info!( "executing {}" , cmd) ;
5843
59- let Self {
60- exec,
61- stdout,
62- stderr,
63- } = self ;
44+ let Self { exec } = self ;
6445
6546 // starts executing the command
6647 let mut popen = exec. popen ( ) . map_err ( |e| popen_to_tmc_err ( cmd. clone ( ) , e) ) ?;
67- let stdout_handle = spawn_reader ( stdout , popen. stdout . take ( ) ) ;
68- let stderr_handle = spawn_reader ( stderr , popen. stderr . take ( ) ) ;
48+ let stdout_handle = spawn_reader ( popen. stdout . take ( ) ) ;
49+ let stderr_handle = spawn_reader ( popen. stderr . take ( ) ) ;
6950
7051 let exit_status = if let Some ( timeout) = timeout {
7152 // timeout set
@@ -164,13 +145,9 @@ impl TmcCommand {
164145}
165146
166147// it's assumed the thread will never panic
167- fn spawn_reader ( temp_file : Option < File > , file : Option < File > ) -> JoinHandle < Vec < u8 > > {
148+ fn spawn_reader ( file : Option < File > ) -> JoinHandle < Vec < u8 > > {
168149 std:: thread:: spawn ( move || {
169- if let Some ( mut temp_file) = temp_file {
170- let mut buf = vec ! [ ] ;
171- let _ = temp_file. read_to_end ( & mut buf) ;
172- buf
173- } else if let Some ( mut file) = file {
150+ if let Some ( mut file) = file {
174151 let mut buf = vec ! [ ] ;
175152 let _ = file. read_to_end ( & mut buf) ;
176153 buf
@@ -206,9 +183,7 @@ mod test {
206183
207184 #[ test]
208185 fn timeout ( ) {
209- let cmd = TmcCommand :: new_with_file_io ( "sleep" )
210- . unwrap ( )
211- . with ( |e| e. arg ( "1" ) ) ;
186+ let cmd = TmcCommand :: piped ( "sleep" ) . with ( |e| e. arg ( "1" ) ) ;
212187 assert ! ( matches!(
213188 cmd. output_with_timeout( Duration :: from_nanos( 1 ) ) ,
214189 Err ( TmcError :: Command ( CommandError :: TimeOut { .. } ) )
@@ -217,7 +192,7 @@ mod test {
217192
218193 #[ test]
219194 fn not_found ( ) {
220- let cmd = TmcCommand :: new_with_file_io ( "nonexistent command" ) . unwrap ( ) ;
195+ let cmd = TmcCommand :: piped ( "nonexistent command" ) ;
221196 assert ! ( matches!(
222197 cmd. output( ) ,
223198 Err ( TmcError :: Command ( CommandError :: NotFound { .. } ) )
0 commit comments