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 stdout(mut self, bytes: impl AsRef<[u8]>) -> Self {
88 self.assertions.stdout(bytes);
89 self
90 }
91
92 pub fn stderr(mut self, bytes: impl AsRef<[u8]>) -> Self {
93 self.assertions.stderr(bytes);
94 self
95 }
96
97 pub fn spawn(&mut self) {
98 if self.child.is_some() {
99 panic!("command is already spawned");
100 }
101 let mut command = std::process::Command::new(self.program.clone());
102 let mut child = command
103 .args(self.program_args.clone())
104 .current_dir(self.current_dir.clone())
105 .stdin(std::process::Stdio::piped())
106 .stdout(std::process::Stdio::piped())
107 .stderr(std::process::Stdio::piped())
108 .spawn()
109 .expect("failed to spawn requested command");
110 if let Some(bytes) = &self.stdin {
111 let mut stdin = child.stdin.take().unwrap();
112 stdin.write_all(bytes).unwrap();
113 }
114 self.child = Some(child);
115 }
116
117 pub fn wait(&mut self) {
118 let child = self.child.take().expect("command is not spawned");
119 let output = child.wait_with_output().unwrap();
120 self.stdout = output.stdout;
121 self.stderr = output.stderr;
122 self.status = output.status;
123 self.assertions.assert(self);
124 }
125
126 pub fn execute(&mut self) {
127 self.spawn();
128 self.wait();
129 }
130
131 pub fn stop(&mut self) {
132 if let Some(child) = &mut self.child {
133 child.kill().unwrap();
134 } else {
135 panic!("command is not spawned");
136 }
137 }
138
139 pub fn get_program(&self) -> &OsStr {
140 &self.program
141 }
142
143 pub fn get_current_dir(&self) -> &Path {
144 &self.current_dir
145 }
146
147 pub fn get_stdout(&'_ self) -> Cow<'_, str> {
148 String::from_utf8_lossy(&self.stdout)
149 }
150
151 pub fn get_stderr(&'_ self) -> Cow<'_, str> {
152 String::from_utf8_lossy(&self.stderr)
153 }
154
155 pub fn get_stdout_raw(&self) -> &[u8] {
156 &self.stdout
157 }
158
159 pub fn get_stderr_raw(&self) -> &[u8] {
160 &self.stderr
161 }
162
163 pub fn get_status(&self) -> ExitStatus {
164 self.status
165 }
166}