mdbook_validator/
command.rs1use anyhow::{Context, Result};
7use std::io::Write;
8use std::process::{Command, Output, Stdio};
9
10pub trait CommandRunner: Send + Sync {
15 fn run_script(
27 &self,
28 script_path: &str,
29 stdin_content: &str,
30 env_vars: &[(&str, &str)],
31 ) -> Result<Output>;
32}
33
34#[derive(Debug, Default, Clone, Copy)]
38pub struct RealCommandRunner;
39
40impl CommandRunner for RealCommandRunner {
41 fn run_script(
42 &self,
43 script_path: &str,
44 stdin_content: &str,
45 env_vars: &[(&str, &str)],
46 ) -> Result<Output> {
47 let mut cmd = Command::new("bash");
48 cmd.arg(script_path)
49 .stdin(Stdio::piped())
50 .stdout(Stdio::piped())
51 .stderr(Stdio::piped());
52
53 for (key, value) in env_vars {
55 cmd.env(*key, *value);
56 }
57
58 let mut child = cmd
59 .spawn()
60 .with_context(|| format!("Failed to spawn validator: {script_path}"))?;
61
62 if let Some(mut stdin) = child.stdin.take() {
64 stdin
65 .write_all(stdin_content.as_bytes())
66 .context("Failed to write to validator stdin")?;
67 }
68
69 child
70 .wait_with_output()
71 .context("Failed to wait for validator")
72 }
73}
74
75#[cfg(test)]
76mod tests {
77 #![allow(clippy::panic, clippy::expect_used, clippy::unwrap_used)]
78
79 use super::*;
80
81 #[test]
82 fn test_real_command_runner_default() {
83 let runner = RealCommandRunner;
84 let _ = runner;
86 }
87
88 #[test]
89 fn test_real_command_runner_clone() {
90 let runner = RealCommandRunner;
91 let cloned = runner;
92 let _ = cloned;
93 }
94
95 #[test]
96 fn test_run_script_success() {
97 let runner = RealCommandRunner;
98 let result = runner.run_script("tests/fixtures/echo_validator.sh", "", &[]);
100 assert!(result.is_ok());
101 }
102
103 #[test]
104 fn test_run_script_with_stdin() {
105 let runner = RealCommandRunner;
106 let result = runner.run_script("tests/fixtures/echo_validator.sh", "test input", &[]);
108 assert!(result.is_ok());
109 }
110
111 #[test]
112 fn test_run_script_with_env_vars() {
113 let runner = RealCommandRunner;
114 let result = runner.run_script(
116 "tests/fixtures/echo_validator.sh",
117 "{}",
118 &[("VALIDATOR_ASSERTIONS", "rows >= 1")],
119 );
120 assert!(result.is_ok());
121 let output = result.expect("run_script should succeed");
122 let stdout = String::from_utf8_lossy(&output.stdout);
123 assert!(
124 stdout.contains("rows >= 1"),
125 "Expected 'rows >= 1' in stdout: {stdout}"
126 );
127 }
128
129 #[test]
130 fn test_run_script_nonexistent_script() {
131 let runner = RealCommandRunner;
132 let result = runner.run_script("/nonexistent/script.sh", "", &[]);
134 assert!(result.is_ok()); let output = result.expect("run_script should succeed");
136 assert!(!output.status.success()); }
138}