kawauso_process/execution/mod.rs
1//! The result of one run of an external command
2//!
3//! A run produces one value that describes what happened: how the command
4//! ended, what it wrote to each of its streams, and how long it took. This
5//! module holds that value and the capture of one stream.
6//!
7//! A command that ends without success is not a failure of the run. The exit
8//! status is part of the result, and the caller decides what it means.
9
10pub mod capture;
11
12use std::process::ExitStatus;
13use std::time::Duration;
14
15pub use self::capture::Capture;
16use crate::error::RequireSuccessError;
17use crate::invocation::Invocation;
18use crate::process_id::ProcessId;
19
20/// The result of one run of an external command
21///
22/// The value holds the command that ran, the exit status of the command, what
23/// the command wrote to its standard output and to its standard error, and
24/// the time that the run took. It also holds the identifier that the
25/// operating system gave the command. The two streams stay apart, because a
26/// program separates its result from its diagnostics.
27///
28/// A status that is not a success is data. The check mode of a formatter ends
29/// without success when it finds a file to format, and that is the answer
30/// that the caller asked for. A caller that must not accept such a status
31/// calls [`require_success`][require-success].
32///
33/// # Examples
34///
35/// ```no_run
36/// use kawauso_process::Invocation;
37///
38/// # #[tokio::main(flavor = "current_thread")]
39/// # async fn main() -> Result<(), Box<dyn std::error::Error>> {
40/// let execution = Invocation::new("git").arg("status").run().await?;
41///
42/// println!("{}", execution.stdout());
43/// # Ok(())
44/// # }
45/// ```
46///
47/// [require-success]: Execution::require_success
48#[derive(Clone, Eq, PartialEq, Debug)]
49pub struct Execution {
50 /// The command that ran
51 ///
52 /// The result carries it so that a log line and an error message can name
53 /// the command without the caller holding it a second time.
54 invocation: Invocation,
55
56 /// The identifier that the operating system gave the command
57 ///
58 /// `None` when no command produced the result, which is the case for a
59 /// result that a caller built.
60 id: Option<ProcessId>,
61
62 /// The status with which the command ended
63 status: ExitStatus,
64
65 /// What the command wrote to its standard output
66 stdout: Capture,
67
68 /// What the command wrote to its standard error
69 stderr: Capture,
70
71 /// The time that the run took
72 duration: Duration,
73}
74
75impl Execution {
76 /// Creates the result of a run
77 ///
78 /// A run builds this value, and a caller builds one where a test stands
79 /// in for a command that no one starts. The identifier is the one that
80 /// the operating system gave the command, and a result that no command
81 /// produced carries none.
82 pub fn new(
83 invocation: Invocation,
84 id: Option<ProcessId>,
85 status: ExitStatus,
86 stdout: Capture,
87 stderr: Capture,
88 duration: Duration,
89 ) -> Self {
90 Self {
91 invocation,
92 id,
93 status,
94 stdout,
95 stderr,
96 duration,
97 }
98 }
99
100 /// Returns the time that the run took
101 ///
102 /// The time covers the whole run, from the start of the program to the
103 /// end of the capture. A caller that reports a slow step, or that logs
104 /// how long a tool ran, reads it here instead of measuring the call.
105 // process[impl run.duration]
106 pub fn duration(&self) -> Duration {
107 self.duration
108 }
109
110 /// Returns the identifier that the operating system gave the command
111 ///
112 /// An application that reports which program ran, or that writes the
113 /// identifier to a log, reads it here. The value is `None` when no run
114 /// produced the result, such as a result that a test built.
115 ///
116 /// The command of a result has ended, and the operating system can give
117 /// the identifier to another command. The value therefore names the
118 /// command that ran. It does not name a command that runs now.
119 // process[impl run.identity]
120 pub fn id(&self) -> Option<ProcessId> {
121 self.id
122 }
123
124 /// Returns the command that ran
125 pub fn invocation(&self) -> &Invocation {
126 &self.invocation
127 }
128
129 /// Returns the result of a run that ended with success
130 ///
131 /// A caller that must not accept a command that failed makes the check
132 /// with this method instead of reading the status itself. The result
133 /// travels on, so a caller can take the output of the command from it.
134 ///
135 /// # Errors
136 ///
137 /// Returns [`UnsuccessfulCommand`][unsuccessful] when the command ended
138 /// without success. The error names the command, the status, and what the
139 /// command wrote to its standard error, which is where a program states
140 /// why it stopped.
141 ///
142 /// # Examples
143 ///
144 /// ```no_run
145 /// use kawauso_process::Invocation;
146 ///
147 /// # #[tokio::main(flavor = "current_thread")]
148 /// # async fn main() -> Result<(), Box<dyn std::error::Error>> {
149 /// let execution = Invocation::new("git")
150 /// .arg("rev-parse")
151 /// .arg("HEAD")
152 /// .run()
153 /// .await?
154 /// .require_success()?;
155 ///
156 /// let commit = execution.stdout().to_string_lossy().trim().to_owned();
157 /// # Ok(())
158 /// # }
159 /// ```
160 ///
161 /// [unsuccessful]: RequireSuccessError::UnsuccessfulCommand
162 // process[impl run.success]
163 pub fn require_success(self) -> Result<Self, RequireSuccessError> {
164 if self.status.success() {
165 Ok(self)
166 } else {
167 Err(RequireSuccessError::UnsuccessfulCommand {
168 invocation: Box::new(self.invocation),
169 status: self.status,
170 stderr: self.stderr,
171 })
172 }
173 }
174
175 /// Returns the status with which the command ended
176 ///
177 /// A status that is not a success is a normal result. A command that ran
178 /// and failed reports its failure here, and the run itself returns an
179 /// error only when the command could not run at all.
180 // process[impl run.exit]
181 pub fn status(&self) -> ExitStatus {
182 self.status
183 }
184
185 /// Returns what the command wrote to its standard error
186 // process[impl run.output]
187 pub fn stderr(&self) -> &Capture {
188 &self.stderr
189 }
190
191 /// Returns what the command wrote to its standard output
192 // process[impl run.output]
193 pub fn stdout(&self) -> &Capture {
194 &self.stdout
195 }
196}
197
198#[cfg(test)]
199mod tests {
200 // An assertion in a test panics by design. A `# Panics` section on every
201 // test would repeat that and give the reader no information.
202 #![allow(clippy::missing_panics_doc)]
203
204 use super::*;
205
206 // A caller keeps the result of a run in a report that another thread
207 // reads. This test holds the type to the auto traits that make this
208 // possible, because a private field of a later version could take them
209 // away without a word from the compiler.
210 #[test]
211 fn execution_is_send_and_sync() {
212 fn assert_send_and_sync<T: Send + Sync>() {}
213
214 assert_send_and_sync::<Execution>();
215 }
216}