lightweight_command_runner/
command_runner.rs1crate::ix!();
3
4pub trait CommandRunner: Send + Sync {
5
6 fn run_command(&self, cmd: tokio::process::Command)
7 -> tokio::task::JoinHandle<Result<std::process::Output, io::Error>>;
8}
9
10pub struct DefaultCommandRunner;
11
12impl CommandRunner for DefaultCommandRunner {
13
14 fn run_command(&self, cmd: tokio::process::Command)
15 -> tokio::task::JoinHandle<Result<std::process::Output, io::Error>>
16 {
17 tokio::spawn(async move {
18 let mut cmd = cmd;
19 cmd.output().await
20 })
21 }
22}
23
24#[cfg(test)]
25mod test_command_runner {
26 use super::*;
27
28 use tokio::process::Command;
29 use tokio::runtime::Runtime;
30
31 fn create_command_runner() -> DefaultCommandRunner {
39 DefaultCommandRunner
40 }
41
42 #[test]
45 fn test_run_command_successfully() {
46 let rt = Runtime::new().expect("Failed to create tokio runtime");
47 rt.block_on(async {
48 let runner = create_command_runner();
49
50 let cmd = if cfg!(windows) {
51 let mut c = Command::new("cmd");
52 c.arg("/C").arg("echo hello");
53 c
54 } else {
55 let mut c = Command::new("echo");
56 c.arg("hello");
57 c
58 };
59
60 let handle = runner.run_command(cmd);
61 let output_result = handle.await.expect("JoinHandle panicked");
62 assert!(
63 output_result.is_ok(),
64 "Expected successful result from echo command"
65 );
66 let output = output_result.unwrap();
67 assert!(
68 output.status.success(),
69 "Expected exit code 0 from echo command"
70 );
71 });
72 }
73
74 #[test]
76 fn test_run_command_non_existent() {
77 let rt = Runtime::new().expect("Failed to create tokio runtime");
78 rt.block_on(async {
79 let runner = create_command_runner();
80
81 let cmd = if cfg!(windows) {
83 Command::new("thisCommandDefinitelyShouldNotExistOnWindows")
85 } else {
86 Command::new("thisCommandDefinitelyShouldNotExistOnUnix")
88 };
89
90 let handle = runner.run_command(cmd);
91 let output_result = handle.await.expect("JoinHandle panicked");
92 assert!(output_result.is_err() || !output_result.as_ref().unwrap().status.success(),
93 "Expected an error or a failing exit code for non-existent command"
94 );
95 });
96 }
97
98 #[test]
100 fn test_run_command_stdout_capture() {
101 let rt = Runtime::new().expect("Failed to create tokio runtime");
102 rt.block_on(async {
103 let runner = create_command_runner();
104
105 let cmd = if cfg!(windows) {
106 let mut c = Command::new("cmd");
107 c.arg("/C").arg("echo capture_this_stdout");
108 c
109 } else {
110 let mut c = Command::new("echo");
111 c.arg("capture_this_stdout");
112 c
113 };
114
115 let handle = runner.run_command(cmd);
116 let output_result = handle.await.expect("JoinHandle panicked");
117 let output = match output_result {
118 Ok(o) => o,
119 Err(e) => panic!("Failed to run echo command: {e}"),
120 };
121 assert!(
122 output.status.success(),
123 "Expected exit code 0 from echo command"
124 );
125
126 let stdout_str = String::from_utf8_lossy(&output.stdout);
128 assert!(
129 stdout_str.contains("capture_this_stdout"),
130 "Expected stdout to contain 'capture_this_stdout', got: {stdout_str}"
131 );
132 });
133 }
134
135 #[test]
138 fn test_run_command_stderr_capture() {
139 let rt = Runtime::new().expect("Failed to create tokio runtime");
140 rt.block_on(async {
141 let runner = create_command_runner();
142
143 let cmd = if cfg!(windows) {
146 let mut c = Command::new("cmd");
147 c.arg("/C").arg("dir thisDirectoryDoesNotExist");
148 c
149 } else {
150 let mut c = Command::new("ls");
151 c.arg("thisDirectoryDoesNotExist");
152 c
153 };
154
155 let handle = runner.run_command(cmd);
156 let output_result = handle.await.expect("JoinHandle panicked");
157 let output = match output_result {
158 Ok(o) => o,
159 Err(e) => panic!("Failed to run 'ls/dir' command: {e}"),
160 };
161
162 assert!(
163 !output.status.success(),
164 "Expected a non-zero exit code for listing a non-existent directory"
165 );
166 let stderr_str = String::from_utf8_lossy(&output.stderr);
167 assert!(
168 !stderr_str.is_empty(),
169 "Expected non-empty stderr for listing a non-existent directory"
170 );
171 });
172 }
173
174 #[cfg(unix)]
176 #[test]
177 fn test_make_exit_status_unix() {
178 let status_success = make_exit_status(0);
180 assert!(status_success.success());
181
182 let status_error = make_exit_status(256);
184 assert!(!status_error.success());
185 assert_eq!(status_error.code(), Some(1), "Expected exit code 1");
186 }
187}