1use crate::utils::PathExt;
2use std::borrow::Cow;
3use std::ffi::{OsStr, OsString};
4use std::path::{Path, PathBuf};
5use std::process::{Child, ExitStatus};
6
7pub struct Command {
8 program: OsString,
10 program_args: Vec<OsString>,
12 current_dir: PathBuf,
14 child: Option<Child>,
16 stdout: Vec<u8>,
18 stderr: Vec<u8>,
20 status: ExitStatus,
22 expected_success: Option<bool>,
24 expected_failure: Option<bool>,
26 expected_status: Option<i32>,
28 expected_stdout: Option<Vec<u8>>,
30 expected_stderr: Option<Vec<u8>>,
32}
33
34impl Command {
35 pub fn new(program: impl AsRef<OsStr>, caller: &str, dir: &str) -> Self {
36 let dir = Path::new(dir);
37 let caller = Path::new(caller).parent().expect("failed to retrieve parent directory");
38 let current_dir = match dir.rem(caller) {
39 None => caller.into(),
40 Some(path_buf) => {
41 if path_buf.components().count() == 0 {
42 PathBuf::from(".")
43 } else {
44 path_buf
45 }
46 }
47 };
48 Self {
49 program: program.as_ref().into(),
50 program_args: vec![],
51 current_dir,
52 child: None,
53 stdout: vec![],
54 stderr: vec![],
55 status: ExitStatus::default(),
56 expected_success: None,
57 expected_failure: None,
58 expected_status: None,
59 expected_stdout: None,
60 expected_stderr: None,
61 }
62 }
63
64 pub fn arg(mut self, arg: impl AsRef<OsStr>) -> Self {
65 self.program_args.push(arg.as_ref().into());
66 self
67 }
68
69 pub fn success(mut self) -> Self {
70 self.expected_success = Some(true);
71 self.expected_failure = None;
72 self
73 }
74
75 pub fn failure(mut self) -> Self {
76 self.expected_failure = Some(true);
77 self.expected_success = None;
78 self
79 }
80
81 pub fn code(mut self, code: i32) -> Self {
82 self.expected_status = Some(code);
83 self
84 }
85
86 pub fn stdout(mut self, bytes: impl AsRef<[u8]>) -> Self {
87 self.expected_stdout = Some(bytes.as_ref().to_vec());
88 self
89 }
90
91 pub fn stderr(mut self, bytes: impl AsRef<[u8]>) -> Self {
92 self.expected_stderr = Some(bytes.as_ref().to_vec());
93 self
94 }
95
96 pub fn spawn(&mut self) {
97 let mut command = std::process::Command::new(self.program.clone());
98 self.child = Some(
99 command
100 .args(self.program_args.clone())
101 .current_dir(self.current_dir.clone())
102 .stdout(std::process::Stdio::piped())
103 .stderr(std::process::Stdio::piped())
104 .spawn()
105 .expect("failed to spawn requested command"),
106 );
107 }
108
109 pub fn wait(&mut self) {
110 if let Some(child) = self.child.take() {
111 let output = child
112 .wait_with_output()
113 .expect("failed during waiting for a child process");
114 self.stdout = output.stdout;
115 self.stderr = output.stderr;
116 self.status = output.status;
117 self.assert();
118 }
119 }
120
121 pub fn execute(&mut self) {
122 self.spawn();
123 self.wait();
124 }
125
126 pub fn stop(&mut self) {
127 if let Some(child) = &mut self.child {
128 child.kill().expect("failed to force a child process to stop");
129 }
130 }
131
132 pub fn get_program(&self) -> &OsStr {
133 &self.program
134 }
135
136 pub fn get_current_dir(&self) -> &Path {
137 &self.current_dir
138 }
139
140 pub fn get_stdout(&'_ self) -> Cow<'_, str> {
141 String::from_utf8_lossy(&self.stdout)
142 }
143
144 pub fn get_stderr(&'_ self) -> Cow<'_, str> {
145 String::from_utf8_lossy(&self.stderr)
146 }
147
148 pub fn get_stdout_raw(&self) -> &[u8] {
149 &self.stdout
150 }
151
152 pub fn get_stderr_raw(&self) -> &[u8] {
153 &self.stderr
154 }
155
156 pub fn get_status(&self) -> ExitStatus {
157 self.status
158 }
159
160 fn assert(&self) {
162 if let Some(true) = self.expected_success {
163 assert!(self.status.success(), "expected success");
164 }
165 if let Some(true) = self.expected_failure {
166 assert!(!self.status.success(), "expected failure");
167 }
168 if let Some(expected) = self.expected_status {
169 let actual = self.status.code().expect("failed to retrieve status code");
170 assert_eq!(
171 expected, actual,
172 "\nexpected status code: {}\n actual status code: {}",
173 expected, actual
174 );
175 }
176 if let Some(expected) = self.expected_stdout.as_ref() {
177 let actual = self.get_stdout_raw();
178 assert_eq!(
179 expected, actual,
180 "\nexpected stdout: {:?}\n actual stdout: {:?}",
181 expected, actual
182 )
183 }
184 if let Some(expected) = self.expected_stderr.as_ref() {
185 let actual = self.get_stderr_raw();
186 assert_eq!(
187 expected, actual,
188 "\nexpected stderr: {:?}\n actual stderr: {:?}",
189 expected, actual
190 )
191 }
192 }
193}