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 [`Command`] 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_fail
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-
31102 let output = self . command_output ( ) ;
32103 if !output. status ( ) . success ( ) {
33- handle_failed_output ( & self , output, caller_line_number ) ;
104+ handle_failed_output ( & self , output, panic :: Location :: caller ( ) . line ( ) ) ;
34105 }
35106 output
36107 }
37108
38109 /// Run the constructed command and assert that it does not successfully run.
39110 #[ track_caller]
40111 pub fn run_fail ( & mut self ) -> CompletedProcess {
41- let caller_location = std:: panic:: Location :: caller ( ) ;
42- let caller_line_number = caller_location. line ( ) ;
43-
44112 let output = self . command_output ( ) ;
45113 if output. status ( ) . success ( ) {
46- handle_failed_output ( & self , output, caller_line_number ) ;
114+ handle_failed_output ( & self , output, panic :: Location :: caller ( ) . line ( ) ) ;
47115 }
48116 output
49117 }
50118
51119 #[ track_caller]
52- pub ( crate ) fn command_output ( & mut self ) -> CompletedProcess {
120+ fn command_output ( & mut self ) -> CompletedProcess {
121+ self . drop_bomb . defuse ( ) ;
53122 // let's make sure we piped all the input and outputs
54123 self . cmd . stdin ( Stdio :: piped ( ) ) ;
55124 self . cmd . stdout ( Stdio :: piped ( ) ) ;
@@ -71,20 +140,6 @@ impl Command {
71140 }
72141}
73142
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-
88143/// Represents the result of an executed process.
89144/// The various `assert_` helper methods should preferably be used for
90145/// checking the contents of stdout/stderr.
0 commit comments