1- use crate :: { assert_not_contains , handle_failed_output } ;
1+ use std :: ffi ;
22use std:: ffi:: OsStr ;
33use std:: io:: Write ;
4- use std:: ops:: { Deref , DerefMut } ;
4+ use std:: panic;
5+ use std:: path:: Path ;
56use std:: process:: { Command as StdCommand , ExitStatus , Output , Stdio } ;
67
7- /// This is a custom command wrapper that simplifies working with commands
8- /// and makes it easier to ensure that we check the exit status of executed
9- /// processes.
8+ use crate :: drop_bomb:: DropBomb ;
9+ use crate :: { assert_not_contains, handle_failed_output} ;
10+
11+ /// This is a custom command wrapper that simplifies working with commands and makes it easier to
12+ /// ensure that we check the exit status of executed processes.
13+ ///
14+ /// # A [`Command`] must be executed
15+ ///
16+ /// A [`Command`] is armed by a [`DropBomb`] on construction to enforce that it will be executed. If
17+ /// a [`Commmand`] is constructed but never executed, the drop bomb will explode and cause the test
18+ /// to panic. Execution methods [`run`] and [`run_fail`] will defuse the drop bomb. A test
19+ /// containing constructed but never executed commands is dangerous because it can give a false
20+ /// sense of confidence.
21+ ///
22+ /// [`run`]: Self::run
23+ /// [`run_fail`]: Self::run
1024#[ derive( Debug ) ]
1125pub struct Command {
1226 cmd : StdCommand ,
1327 stdin : Option < Box < [ u8 ] > > ,
28+ drop_bomb : DropBomb ,
1429}
1530
1631impl Command {
17- pub fn new < S : AsRef < OsStr > > ( program : S ) -> Self {
18- Self { cmd : StdCommand :: new ( program) , stdin : None }
32+ #[ track_caller]
33+ pub fn new < P : AsRef < OsStr > > ( program : P ) -> Self {
34+ let program = program. as_ref ( ) ;
35+ Self { cmd : StdCommand :: new ( program) , stdin : None , drop_bomb : DropBomb :: arm ( program) }
1936 }
2037
2138 pub fn set_stdin ( & mut self , stdin : Box < [ u8 ] > ) {
2239 self . stdin = Some ( stdin) ;
2340 }
2441
42+ /// Specify an environment variable.
43+ pub fn env < K , V > ( & mut self , key : K , value : V ) -> & mut Self
44+ where
45+ K : AsRef < ffi:: OsStr > ,
46+ V : AsRef < ffi:: OsStr > ,
47+ {
48+ self . cmd . env ( key, value) ;
49+ self
50+ }
51+
52+ /// Remove an environmental variable.
53+ pub fn env_remove < K > ( & mut self , key : K ) -> & mut Self
54+ where
55+ K : AsRef < ffi:: OsStr > ,
56+ {
57+ self . cmd . env_remove ( key) ;
58+ self
59+ }
60+
61+ /// Generic command argument provider. Prefer specific helper methods if possible.
62+ /// Note that for some executables, arguments might be platform specific. For C/C++
63+ /// compilers, arguments might be platform *and* compiler specific.
64+ pub fn arg < S > ( & mut self , arg : S ) -> & mut Self
65+ where
66+ S : AsRef < ffi:: OsStr > ,
67+ {
68+ self . cmd . arg ( arg) ;
69+ self
70+ }
71+
72+ /// Generic command arguments provider. Prefer specific helper methods if possible.
73+ /// Note that for some executables, arguments might be platform specific. For C/C++
74+ /// compilers, arguments might be platform *and* compiler specific.
75+ pub fn args < S > ( & mut self , args : & [ S ] ) -> & mut Self
76+ where
77+ S : AsRef < ffi:: OsStr > ,
78+ {
79+ self . cmd . args ( args) ;
80+ self
81+ }
82+
83+ /// Inspect what the underlying [`std::process::Command`] is up to the
84+ /// current construction.
85+ pub fn inspect < I > ( & mut self , inspector : I ) -> & mut Self
86+ where
87+ I : FnOnce ( & StdCommand ) ,
88+ {
89+ inspector ( & self . cmd ) ;
90+ self
91+ }
92+
93+ /// Set the path where the command will be run.
94+ pub fn current_dir < P : AsRef < Path > > ( & mut self , path : P ) -> & mut Self {
95+ self . cmd . current_dir ( path) ;
96+ self
97+ }
98+
2599 /// Run the constructed command and assert that it is successfully run.
26100 #[ track_caller]
27101 pub fn run ( & mut self ) -> CompletedProcess {
28- let caller_location = std:: panic:: Location :: caller ( ) ;
29- let caller_line_number = caller_location. line ( ) ;
30-
102+ self . drop_bomb . defuse ( ) ;
31103 let output = self . command_output ( ) ;
32104 if !output. status ( ) . success ( ) {
33- handle_failed_output ( & self , output, caller_line_number ) ;
105+ handle_failed_output ( & self , output, panic :: Location :: caller ( ) . line ( ) ) ;
34106 }
35107 output
36108 }
37109
38110 /// Run the constructed command and assert that it does not successfully run.
39111 #[ track_caller]
40112 pub fn run_fail ( & mut self ) -> CompletedProcess {
41- let caller_location = std:: panic:: Location :: caller ( ) ;
42- let caller_line_number = caller_location. line ( ) ;
43-
113+ self . drop_bomb . defuse ( ) ;
44114 let output = self . command_output ( ) ;
45115 if output. status ( ) . success ( ) {
46- handle_failed_output ( & self , output, caller_line_number ) ;
116+ handle_failed_output ( & self , output, panic :: Location :: caller ( ) . line ( ) ) ;
47117 }
48118 output
49119 }
50120
51121 #[ track_caller]
52- pub ( crate ) fn command_output ( & mut self ) -> CompletedProcess {
122+ fn command_output ( & mut self ) -> CompletedProcess {
53123 // let's make sure we piped all the input and outputs
54124 self . cmd . stdin ( Stdio :: piped ( ) ) ;
55125 self . cmd . stdout ( Stdio :: piped ( ) ) ;
@@ -71,20 +141,6 @@ impl Command {
71141 }
72142}
73143
74- impl Deref for Command {
75- type Target = StdCommand ;
76-
77- fn deref ( & self ) -> & Self :: Target {
78- & self . cmd
79- }
80- }
81-
82- impl DerefMut for Command {
83- fn deref_mut ( & mut self ) -> & mut Self :: Target {
84- & mut self . cmd
85- }
86- }
87-
88144/// Represents the result of an executed process.
89145/// The various `assert_` helper methods should preferably be used for
90146/// checking the contents of stdout/stderr.
@@ -93,14 +149,17 @@ pub struct CompletedProcess {
93149}
94150
95151impl CompletedProcess {
152+ #[ must_use]
96153 pub fn stdout_utf8 ( & self ) -> String {
97154 String :: from_utf8 ( self . output . stdout . clone ( ) ) . expect ( "stdout is not valid UTF-8" )
98155 }
99156
157+ #[ must_use]
100158 pub fn stderr_utf8 ( & self ) -> String {
101159 String :: from_utf8 ( self . output . stderr . clone ( ) ) . expect ( "stderr is not valid UTF-8" )
102160 }
103161
162+ #[ must_use]
104163 pub fn status ( & self ) -> ExitStatus {
105164 self . output . status
106165 }
0 commit comments