use crate::core::GitError;
#[derive(Debug, Clone)]
pub struct CommandOutput {
pub code: i32,
pub stdout: String,
pub stderr: String,
}
pub trait CommandRunner: Send + Sync {
fn run(&self, args: &[&str]) -> Result<CommandOutput, GitError>;
}
pub struct RealRunner;
impl CommandRunner for RealRunner {
fn run(&self, args: &[&str]) -> Result<CommandOutput, GitError> {
let git = which::which("git").map_err(|e| GitError::NotFound(e.to_string()))?;
let output = std::process::Command::new(git)
.args(args)
.output()
.map_err(|e| GitError::Other(e.to_string()))?;
Ok(CommandOutput {
code: output.status.code().unwrap_or(-1),
stdout: String::from_utf8_lossy(&output.stdout).into_owned(),
stderr: String::from_utf8_lossy(&output.stderr).into_owned(),
})
}
}
#[cfg(test)]
pub use stub::StubRunner;
#[cfg(test)]
mod stub {
use std::sync::Mutex;
use crate::core::GitError;
use super::{CommandOutput, CommandRunner};
struct CmdStub {
pattern: regex::Regex,
code: i32,
stdout: String,
matched: bool,
}
#[derive(Default)]
pub struct StubRunner {
stubs: Mutex<Vec<CmdStub>>,
}
impl StubRunner {
#[must_use]
pub fn new() -> Self {
Self::default()
}
pub fn register(&self, pattern: &str, code: i32, stdout: &str) {
self.stubs.lock().expect("stubs poisoned").push(CmdStub {
pattern: regex::Regex::new(pattern).expect("invalid stub regex"),
code,
stdout: stdout.to_owned(),
matched: false,
});
}
}
impl CommandRunner for StubRunner {
fn run(&self, args: &[&str]) -> Result<CommandOutput, GitError> {
let line = format!("git {}", args.join(" "));
let mut stubs = self.stubs.lock().expect("stubs poisoned");
for stub in stubs.iter_mut() {
if !stub.matched && stub.pattern.is_match(&line) {
stub.matched = true;
return Ok(CommandOutput {
code: stub.code,
stdout: stub.stdout.clone(),
stderr: String::new(),
});
}
}
panic!("StubRunner: no stub matched command line: {line:?}");
}
}
impl Drop for StubRunner {
fn drop(&mut self) {
if std::thread::panicking() {
return;
}
let stubs = self.stubs.lock().expect("stubs poisoned");
let unmatched: Vec<&str> = stubs
.iter()
.filter(|s| !s.matched)
.map(|s| s.pattern.as_str())
.collect();
assert!(
unmatched.is_empty(),
"StubRunner: these git stubs were never matched: {unmatched:?}"
);
}
}
}