1
- use crate :: { assert_not_contains , handle_failed_output } ;
1
+ use std :: ffi ;
2
2
use std:: ffi:: OsStr ;
3
3
use std:: io:: Write ;
4
- use std:: ops:: { Deref , DerefMut } ;
4
+ use std:: panic;
5
+ use std:: path:: Path ;
5
6
use std:: process:: { Command as StdCommand , ExitStatus , Output , Stdio } ;
6
7
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
10
24
#[ derive( Debug ) ]
11
25
pub struct Command {
12
26
cmd : StdCommand ,
13
27
stdin : Option < Box < [ u8 ] > > ,
28
+ drop_bomb : DropBomb ,
14
29
}
15
30
16
31
impl 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) }
19
36
}
20
37
21
38
pub fn set_stdin ( & mut self , stdin : Box < [ u8 ] > ) {
22
39
self . stdin = Some ( stdin) ;
23
40
}
24
41
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
+
25
99
/// Run the constructed command and assert that it is successfully run.
26
100
#[ track_caller]
27
101
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 ( ) ;
31
103
let output = self . command_output ( ) ;
32
104
if !output. status ( ) . success ( ) {
33
- handle_failed_output ( & self , output, caller_line_number ) ;
105
+ handle_failed_output ( & self , output, panic :: Location :: caller ( ) . line ( ) ) ;
34
106
}
35
107
output
36
108
}
37
109
38
110
/// Run the constructed command and assert that it does not successfully run.
39
111
#[ track_caller]
40
112
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 ( ) ;
44
114
let output = self . command_output ( ) ;
45
115
if output. status ( ) . success ( ) {
46
- handle_failed_output ( & self , output, caller_line_number ) ;
116
+ handle_failed_output ( & self , output, panic :: Location :: caller ( ) . line ( ) ) ;
47
117
}
48
118
output
49
119
}
50
120
51
121
#[ track_caller]
52
- pub ( crate ) fn command_output ( & mut self ) -> CompletedProcess {
122
+ fn command_output ( & mut self ) -> CompletedProcess {
53
123
// let's make sure we piped all the input and outputs
54
124
self . cmd . stdin ( Stdio :: piped ( ) ) ;
55
125
self . cmd . stdout ( Stdio :: piped ( ) ) ;
@@ -71,20 +141,6 @@ impl Command {
71
141
}
72
142
}
73
143
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
-
88
144
/// Represents the result of an executed process.
89
145
/// The various `assert_` helper methods should preferably be used for
90
146
/// checking the contents of stdout/stderr.
@@ -93,14 +149,17 @@ pub struct CompletedProcess {
93
149
}
94
150
95
151
impl CompletedProcess {
152
+ #[ must_use]
96
153
pub fn stdout_utf8 ( & self ) -> String {
97
154
String :: from_utf8 ( self . output . stdout . clone ( ) ) . expect ( "stdout is not valid UTF-8" )
98
155
}
99
156
157
+ #[ must_use]
100
158
pub fn stderr_utf8 ( & self ) -> String {
101
159
String :: from_utf8 ( self . output . stderr . clone ( ) ) . expect ( "stderr is not valid UTF-8" )
102
160
}
103
161
162
+ #[ must_use]
104
163
pub fn status ( & self ) -> ExitStatus {
105
164
self . output . status
106
165
}
0 commit comments