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