@@ -15,34 +15,81 @@ use crate::{
1515/// This is a custom command wrapper that simplifies working with commands and makes it easier to
1616/// ensure that we check the exit status of executed processes.
1717///
18- /// # A [`Command`] must be executed
18+ /// # A [`Command`] must be executed exactly once
1919///
2020/// A [`Command`] is armed by a [`DropBomb`] on construction to enforce that it will be executed. If
2121/// a [`Command`] is constructed but never executed, the drop bomb will explode and cause the test
2222/// to panic. Execution methods [`run`] and [`run_fail`] will defuse the drop bomb. A test
2323/// containing constructed but never executed commands is dangerous because it can give a false
2424/// sense of confidence.
2525///
26+ /// Each [`Command`] invocation can also only be executed once, because we want to enforce
27+ /// `std{in,out,err}` config via [`std::process::Stdio`] but [`std::process::Stdio`] is not
28+ /// cloneable.
29+ ///
30+ /// In this sense, [`Command`] exhibits linear type semantics but enforced at run-time.
31+ ///
2632/// [`run`]: Self::run
2733/// [`run_fail`]: Self::run_fail
2834/// [`run_unchecked`]: Self::run_unchecked
2935#[ derive( Debug ) ]
3036pub struct Command {
3137 cmd : StdCommand ,
32- stdin : Option < Box < [ u8 ] > > ,
38+ // Convience for providing a quick stdin buffer.
39+ stdin_buf : Option < Box < [ u8 ] > > ,
40+
41+ // Configurations for child process's std{in,out,err} handles.
42+ stdin : Option < Stdio > ,
43+ stdout : Option < Stdio > ,
44+ stderr : Option < Stdio > ,
45+
46+ // Emulate linear type semantics.
3347 drop_bomb : DropBomb ,
48+ already_executed : bool ,
3449}
3550
3651impl Command {
3752 #[ track_caller]
3853 pub fn new < P : AsRef < OsStr > > ( program : P ) -> Self {
3954 let program = program. as_ref ( ) ;
40- Self { cmd : StdCommand :: new ( program) , stdin : None , drop_bomb : DropBomb :: arm ( program) }
55+ Self {
56+ cmd : StdCommand :: new ( program) ,
57+ stdin_buf : None ,
58+ drop_bomb : DropBomb :: arm ( program) ,
59+ stdin : None ,
60+ stdout : None ,
61+ stderr : None ,
62+ already_executed : false ,
63+ }
4164 }
4265
43- /// Specify a stdin input
44- pub fn stdin < I : AsRef < [ u8 ] > > ( & mut self , input : I ) -> & mut Self {
45- self . stdin = Some ( input. as_ref ( ) . to_vec ( ) . into_boxed_slice ( ) ) ;
66+ /// Specify a stdin input buffer. This is a convenience helper,
67+ pub fn stdin_buf < I : AsRef < [ u8 ] > > ( & mut self , input : I ) -> & mut Self {
68+ self . stdin_buf = Some ( input. as_ref ( ) . to_vec ( ) . into_boxed_slice ( ) ) ;
69+ self
70+ }
71+
72+ /// Configuration for the child process’s standard input (stdin) handle.
73+ ///
74+ /// See [`std::process::Command::stdin`].
75+ pub fn stdin < T : Into < Stdio > > ( & mut self , cfg : T ) -> & mut Self {
76+ self . stdin = Some ( cfg. into ( ) ) ;
77+ self
78+ }
79+
80+ /// Configuration for the child process’s standard output (stdout) handle.
81+ ///
82+ /// See [`std::process::Command::stdout`].
83+ pub fn stdout < T : Into < Stdio > > ( & mut self , cfg : T ) -> & mut Self {
84+ self . stdout = Some ( cfg. into ( ) ) ;
85+ self
86+ }
87+
88+ /// Configuration for the child process’s standard error (stderr) handle.
89+ ///
90+ /// See [`std::process::Command::stderr`].
91+ pub fn stderr < T : Into < Stdio > > ( & mut self , cfg : T ) -> & mut Self {
92+ self . stderr = Some ( cfg. into ( ) ) ;
4693 self
4794 }
4895
@@ -105,6 +152,8 @@ impl Command {
105152 }
106153
107154 /// Run the constructed command and assert that it is successfully run.
155+ ///
156+ /// By default, std{in,out,err} are [`Stdio::piped()`].
108157 #[ track_caller]
109158 pub fn run ( & mut self ) -> CompletedProcess {
110159 let output = self . command_output ( ) ;
@@ -115,6 +164,8 @@ impl Command {
115164 }
116165
117166 /// Run the constructed command and assert that it does not successfully run.
167+ ///
168+ /// By default, std{in,out,err} are [`Stdio::piped()`].
118169 #[ track_caller]
119170 pub fn run_fail ( & mut self ) -> CompletedProcess {
120171 let output = self . command_output ( ) ;
@@ -124,24 +175,30 @@ impl Command {
124175 output
125176 }
126177
127- /// Run the command but do not check its exit status.
128- /// Only use if you explicitly don't care about the exit status.
129- /// Prefer to use [`Self::run`] and [`Self::run_fail`]
130- /// whenever possible.
178+ /// Run the command but do not check its exit status. Only use if you explicitly don't care
179+ /// about the exit status.
180+ ///
181+ /// Prefer to use [`Self::run`] and [`Self::run_fail`] whenever possible.
131182 #[ track_caller]
132183 pub fn run_unchecked ( & mut self ) -> CompletedProcess {
133184 self . command_output ( )
134185 }
135186
136187 #[ track_caller]
137188 fn command_output ( & mut self ) -> CompletedProcess {
189+ if self . already_executed {
190+ panic ! ( "command was already executed" ) ;
191+ } else {
192+ self . already_executed = true ;
193+ }
194+
138195 self . drop_bomb . defuse ( ) ;
139196 // let's make sure we piped all the input and outputs
140- self . cmd . stdin ( Stdio :: piped ( ) ) ;
141- self . cmd . stdout ( Stdio :: piped ( ) ) ;
142- self . cmd . stderr ( Stdio :: piped ( ) ) ;
197+ self . cmd . stdin ( self . stdin . take ( ) . unwrap_or ( Stdio :: piped ( ) ) ) ;
198+ self . cmd . stdout ( self . stdout . take ( ) . unwrap_or ( Stdio :: piped ( ) ) ) ;
199+ self . cmd . stderr ( self . stderr . take ( ) . unwrap_or ( Stdio :: piped ( ) ) ) ;
143200
144- let output = if let Some ( input) = & self . stdin {
201+ let output = if let Some ( input) = & self . stdin_buf {
145202 let mut child = self . cmd . spawn ( ) . unwrap ( ) ;
146203
147204 {
0 commit comments