#[cfg(any(test, feature = "test-support"))]
use crate::deps::CommandExecutor;
use crate::dirs::BaseDirs;
#[cfg(any(test, feature = "test-support"))]
use crate::error::InstallerError;
use crate::error::Result;
use std::cell::RefCell;
use std::collections::VecDeque;
use std::path::PathBuf;
use std::process::{ExitStatus, Output};
#[cfg(unix)]
pub fn exit_status(code: i32) -> ExitStatus {
use std::os::unix::process::ExitStatusExt;
ExitStatus::from_raw(code << 8)
}
#[cfg(windows)]
pub fn exit_status(code: i32) -> ExitStatus {
use std::os::windows::process::ExitStatusExt;
ExitStatus::from_raw(code as u32)
}
pub fn success_output() -> Output {
Output {
status: exit_status(0),
stdout: Vec::new(),
stderr: Vec::new(),
}
}
pub fn failure_output(stderr: impl AsRef<str>) -> Output {
let stderr = stderr.as_ref();
Output {
status: exit_status(1),
stdout: Vec::new(),
stderr: stderr.as_bytes().to_vec(),
}
}
#[derive(Debug, Clone, Default)]
pub struct StubDirs {
pub bin_dir: Option<PathBuf>,
}
impl BaseDirs for StubDirs {
fn home_dir(&self) -> Option<PathBuf> {
None
}
fn bin_dir(&self) -> Option<PathBuf> {
self.bin_dir.clone()
}
fn whitaker_data_dir(&self) -> Option<PathBuf> {
None
}
}
#[cfg(any(test, feature = "test-support"))]
pub fn sha256_hex(data: &[u8]) -> String {
use sha2::{Digest, Sha256};
format!("{:x}", Sha256::digest(data))
}
#[cfg(any(test, feature = "test-support"))]
pub fn prebuilt_manifest_json(
toolchain: impl AsRef<str>,
target: impl AsRef<str>,
sha256: impl AsRef<str>,
) -> String {
let toolchain = toolchain.as_ref();
let target = target.as_ref();
let sha256 = sha256.as_ref();
format!(
concat!(
r#"{{"git_sha":"abc1234","schema_version":1,"#,
r#""toolchain":"{toolchain}","#,
r#""target":"{target}","#,
r#""generated_at":"2026-02-03T00:00:00Z","#,
r#""files":["libwhitaker_suite.so"],"#,
r#""sha256":"{sha256}"}}"#,
),
toolchain = toolchain,
target = target,
sha256 = sha256,
)
}
#[derive(Debug)]
pub struct ExpectedCall {
pub cmd: &'static str,
pub args: Vec<&'static str>,
pub result: Result<Output>,
}
#[derive(Debug)]
pub struct StubExecutor {
expected: RefCell<VecDeque<ExpectedCall>>,
}
impl StubExecutor {
pub fn new(expected: Vec<ExpectedCall>) -> Self {
Self {
expected: RefCell::new(expected.into()),
}
}
pub fn assert_finished(&self) {
assert!(
self.expected.borrow().is_empty(),
"expected no further command invocations"
);
}
}
#[cfg(any(test, feature = "test-support"))]
impl CommandExecutor for StubExecutor {
fn run(&self, cmd: &str, args: &[&str]) -> Result<Output> {
let mut expected = self.expected.borrow_mut();
let call = expected
.pop_front()
.ok_or_else(|| InstallerError::StubMismatch {
message: format!("unexpected command invocation: {cmd} {}", args.join(" ")),
})?;
if call.cmd != cmd {
return Err(InstallerError::StubMismatch {
message: format!("command mismatch: expected {}, got {cmd}", call.cmd),
});
}
if call.args.as_slice() != args {
return Err(InstallerError::StubMismatch {
message: format!(
"argument mismatch for {cmd}: expected {:?}, got {args:?}",
call.args
),
});
}
call.result
}
}
#[cfg(any(test, feature = "test-support"))]
pub mod dependency_binary_helpers;
#[cfg(test)]
mod dependency_binary_helpers_tests;