use std::sync::Mutex;
use std::time::Duration;
use crate::backend::{BoxFuture, Invocation, LocalBackend, Output};
use crate::exec::ExecError;
#[derive(Debug, Clone)]
pub enum Reply {
Ran(Output),
Unavailable,
TimedOut { printed: String },
}
impl Reply {
pub fn ok(stdout: impl Into<String>) -> Self {
Self::Ran(Output {
exit_code: Some(0),
stdout: stdout.into().into_bytes(),
stderr: String::new(),
})
}
pub fn timed_out() -> Self {
Self::TimedOut {
printed: String::new(),
}
}
pub fn hung_after(printed: impl Into<String>) -> Self {
Self::TimedOut {
printed: printed.into(),
}
}
pub fn failed(exit_code: i32, stderr: impl Into<String>) -> Self {
Self::Ran(Output {
exit_code: Some(exit_code),
stdout: Vec::new(),
stderr: stderr.into(),
})
}
}
#[derive(Debug)]
pub struct StubBackend {
rules: Vec<(Vec<String>, Reply)>,
fallback: Reply,
calls: Mutex<Vec<Invocation>>,
}
impl StubBackend {
pub fn always(reply: Reply) -> Self {
Self {
rules: Vec::new(),
fallback: reply,
calls: Mutex::new(Vec::new()),
}
}
pub fn ok(stdout: impl Into<String>) -> Self {
Self::always(Reply::ok(stdout))
}
pub fn failure(exit_code: i32, stderr: impl Into<String>) -> Self {
Self::always(Reply::failed(exit_code, stderr))
}
pub fn missing() -> Self {
Self::always(Reply::Unavailable)
}
#[must_use]
pub fn on<I, S>(mut self, prefix: I, reply: Reply) -> Self
where
I: IntoIterator<Item = S>,
S: Into<String>,
{
self.rules
.push((prefix.into_iter().map(Into::into).collect(), reply));
self
}
pub fn calls(&self) -> Vec<Invocation> {
self.calls.lock().map(|c| c.clone()).unwrap_or_default()
}
pub fn argv(&self) -> Vec<Vec<String>> {
self.calls().into_iter().map(|c| c.args).collect()
}
fn reply_for(&self, args: &[String]) -> Reply {
self.rules
.iter()
.find(|(prefix, _)| args.starts_with(prefix))
.map_or_else(|| self.fallback.clone(), |(_, reply)| reply.clone())
}
}
impl LocalBackend for StubBackend {
fn run<'a>(&'a self, invocation: Invocation) -> BoxFuture<'a, Result<Output, ExecError>> {
let reply = self.reply_for(&invocation.args);
let display = invocation.display();
if let Ok(mut calls) = self.calls.lock() {
calls.push(invocation);
}
Box::pin(async move {
match reply {
Reply::Ran(output) => Ok(output),
Reply::Unavailable => Err(ExecError::BinaryNotFound {
searched: vec!["a stub backend with no binary".to_owned()],
}),
Reply::TimedOut { printed } => Err(ExecError::Timeout {
command: display,
timeout: Duration::from_secs(30),
printed,
}),
}
})
}
}