use anyhow::{Context, Result};
use std::cell::RefCell;
use std::process::Command;
type Runner = dyn Fn(&[&str]) -> Result<(String, String, bool)>;
thread_local! {
static OVERRIDE: RefCell<Option<Box<Runner>>> = const { RefCell::new(None) };
}
pub fn run_nix_command(args: &[&str]) -> Result<(String, String, bool)> {
let mocked = OVERRIDE.with(|cell| cell.borrow().as_ref().map(|f| f(args)));
if let Some(out) = mocked {
return out;
}
let output = Command::new("nix")
.args(args)
.output()
.context("Failed to execute nix command")?;
let stdout = String::from_utf8(output.stdout)?;
let stderr = String::from_utf8(output.stderr)?;
let success = output.status.success();
Ok((stdout, stderr, success))
}
pub fn check_nix_available() -> bool {
if OVERRIDE.with(|cell| cell.borrow().is_some()) {
return true;
}
Command::new("nix").arg("--version").output().is_ok()
}
#[cfg(test)]
pub fn with_nix_runner<F, R>(runner: F, body: impl FnOnce() -> R) -> R
where
F: Fn(&[&str]) -> Result<(String, String, bool)> + 'static,
{
OVERRIDE.with(|c| *c.borrow_mut() = Some(Box::new(runner)));
let result = body();
OVERRIDE.with(|c| *c.borrow_mut() = None);
result
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn runner_override_intercepts_calls() {
let result = with_nix_runner(
|args| {
assert_eq!(args, &["search", "nixpkgs", "ripgrep"]);
Ok(("hit\n".into(), String::new(), true))
},
|| run_nix_command(&["search", "nixpkgs", "ripgrep"]).unwrap(),
);
assert_eq!(result, ("hit\n".to_string(), String::new(), true));
}
#[test]
fn runner_override_makes_nix_appear_available() {
with_nix_runner(
|_| Ok((String::new(), String::new(), true)),
|| assert!(check_nix_available()),
);
}
#[test]
fn runner_override_clears_after_body() {
with_nix_runner(|_| Ok((String::new(), String::new(), true)), || {});
OVERRIDE.with(|c| assert!(c.borrow().is_none()));
}
}