use super::*;
use std::cell::RefCell;
use std::process::ExitStatus;
#[cfg(unix)]
pub fn exit_status(code: i32) -> ExitStatus {
use std::os::unix::process::ExitStatusExt;
ExitStatusExt::from_raw(code << 8)
}
#[cfg(windows)]
pub fn exit_status(code: i32) -> ExitStatus {
use std::os::windows::process::ExitStatusExt;
ExitStatusExt::from_raw(code as u32)
}
pub fn output_with_status(code: i32) -> Output {
Output {
status: exit_status(code),
stdout: Vec::new(),
stderr: Vec::new(),
}
}
pub fn output_with_stderr(code: i32, stderr: &str) -> Output {
Output {
status: exit_status(code),
stdout: Vec::new(),
stderr: stderr.as_bytes().to_vec(),
}
}
pub fn test_toolchain(channel: &str) -> Toolchain {
Toolchain {
channel: channel.to_owned(),
workspace_root: Utf8PathBuf::from("."),
}
}
pub(crate) struct CapturingCommandRunner {
calls: RefCell<Vec<(String, Vec<String>)>>,
output: Output,
}
impl CapturingCommandRunner {
pub(crate) fn new(output: Output) -> Self {
Self {
calls: RefCell::new(Vec::new()),
output,
}
}
pub(crate) fn recorded_calls(&self) -> Vec<(String, Vec<String>)> {
self.calls.borrow().clone()
}
}
impl CommandRunner for CapturingCommandRunner {
fn run(&self, program: &str, args: &[&str]) -> std::io::Result<Output> {
self.calls.borrow_mut().push((
program.to_owned(),
args.iter().map(|arg| (*arg).to_owned()).collect(),
));
Ok(self.output.clone())
}
}
pub struct RustupExpectation<'a> {
pub exit_code: i32,
pub stderr: Option<&'a str>,
}
fn expect_rustup_command<F>(
runner: &mut MockCommandRunner,
seq: &mut mockall::Sequence,
expectation: RustupExpectation<'_>,
matcher: F,
) where
F: Fn(&str, &[&str]) -> bool + Send + 'static,
{
let stderr = expectation.stderr.map(str::to_owned);
let exit_code = expectation.exit_code;
runner
.expect_run()
.withf(matcher)
.times(1)
.in_sequence(seq)
.returning(move |_, _| {
let output = match stderr.as_deref() {
Some(message) => output_with_stderr(exit_code, message),
None => output_with_status(exit_code),
};
Ok(output)
});
}
pub struct ToolchainInstallExpectation<'a> {
pub channel: &'a str,
pub exit_code: i32,
pub stderr: Option<&'a str>,
}
pub fn expect_rustc_version(
runner: &mut MockCommandRunner,
seq: &mut mockall::Sequence,
channel: &str,
exit_code: i32,
) {
let channel = channel.to_owned();
runner
.expect_run()
.withf(move |program, args| {
program == "rustup"
&& args.len() == 4
&& args[0] == "run"
&& args[1] == channel
&& args[2] == "rustc"
&& args[3] == "--version"
})
.times(1)
.in_sequence(seq)
.returning(move |_, _| Ok(output_with_status(exit_code)));
}
pub fn expect_toolchain_install(
runner: &mut MockCommandRunner,
seq: &mut mockall::Sequence,
expectation: ToolchainInstallExpectation<'_>,
) {
let channel = expectation.channel.to_owned();
expect_rustup_command(
runner,
seq,
RustupExpectation {
exit_code: expectation.exit_code,
stderr: expectation.stderr,
},
move |program, args| {
program == "rustup"
&& args.len() == 3
&& args[0] == "toolchain"
&& args[1] == "install"
&& args[2] == channel
},
);
}
pub fn assert_install_fails_with<F, I, E>(
toolchain: Toolchain,
setup_mocks: F,
install: I,
error_matcher: E,
) where
F: FnOnce(&mut MockCommandRunner, &mut mockall::Sequence),
I: FnOnce(&Toolchain, &MockCommandRunner) -> Result<ToolchainInstallStatus>,
E: FnOnce(InstallerError),
{
let mut runner = MockCommandRunner::new();
let mut seq = mockall::Sequence::new();
setup_mocks(&mut runner, &mut seq);
let err = install(&toolchain, &runner).expect_err("expected installation failure");
error_matcher(err);
}
pub fn matches_multi_component_add(
channel: &str,
components: &[&str],
) -> impl Fn(&str, &[&str]) -> bool + use<> {
let channel = channel.to_owned();
let components: Vec<String> = components.iter().map(|s| (*s).to_owned()).collect();
move |program, args| {
let Some(actual_components) = args.get(4..) else {
return false;
};
program == "rustup"
&& args[0] == "component"
&& args[1] == "add"
&& args[2] == "--toolchain"
&& args[3] == channel
&& actual_components.len() == components.len()
&& actual_components
.iter()
.zip(&components)
.all(|(a, b)| *a == b)
}
}