use std::collections::BTreeMap;
use std::path::{Component, Path, PathBuf};
use crate::config::schema::{CheckSpec, InstallSpec};
use crate::error::ForgeError;
use crate::paths::{app_home, managed_bin_dir};
#[derive(Debug, Clone, PartialEq, Eq)]
pub(crate) struct CommandInvocation {
pub(crate) program: String,
pub(crate) args: Vec<String>,
pub(crate) env: BTreeMap<String, String>,
pub(crate) current_dir: Option<PathBuf>,
pub(crate) clear_env: bool,
pub(crate) null_stdin: bool,
pub(crate) timeout_secs: Option<u64>,
pub(crate) inactivity_timeout_secs: Option<u64>,
pub(crate) idempotent: bool,
pub(crate) success_codes: Vec<i32>,
pub(crate) stdout_contains: Option<String>,
}
impl CommandInvocation {
fn new(program: impl Into<String>, args: Vec<String>) -> Self {
Self {
program: program.into(),
args,
env: BTreeMap::new(),
current_dir: None,
clear_env: false,
null_stdin: false,
timeout_secs: None,
inactivity_timeout_secs: None,
idempotent: false,
success_codes: vec![0],
stdout_contains: None,
}
}
}
pub(crate) fn rustup_bootstrap_operation(executable: &Path) -> BackendOperation {
let mut invocation = CommandInvocation::new(
executable.display().to_string(),
vec![
"-y".into(),
"--no-modify-path".into(),
"--default-toolchain".into(),
"none".into(),
],
);
invocation.timeout_secs = Some(300);
BackendOperation::Command(invocation)
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub(crate) enum BackendOperation {
Command(CommandInvocation),
PathExists {
path: PathBuf,
},
Archive {
url: String,
sha256: String,
target: PathBuf,
},
Shell {
command: String,
timeout_secs: Option<u64>,
inactivity_timeout_secs: Option<u64>,
},
}
#[derive(Debug, Clone)]
pub(crate) struct BackendContext<'a> {
pub(crate) staging: &'a Path,
pub(crate) target: &'a Path,
pub(crate) cargo_jobs: usize,
pub(crate) allow_insecure_hosts: &'a [String],
pub(crate) apt_source: Option<&'a Path>,
pub(crate) apt_lists: Option<&'a Path>,
}
pub(crate) fn install_operations(
install: &InstallSpec,
context: &BackendContext<'_>,
) -> Result<Vec<BackendOperation>, ForgeError> {
let operations = match install {
InstallSpec::Apt(apt) => {
let mut operations = Vec::new();
if apt.update {
operations.push(BackendOperation::Command(apt_command(
vec!["update".into()],
context.apt_source,
context.apt_lists,
)));
}
let mut args = vec!["install".into(), "-y".into(), "--".into()];
args.extend(apt.packages.iter().cloned());
operations.push(BackendOperation::Command(apt_command(
args,
context.apt_source,
context.apt_lists,
)));
operations
}
InstallSpec::Brew(brew) => {
let mut args = vec!["install".into(), "--formula".into()];
args.extend(brew.formulae.iter().cloned());
let mut invocation = CommandInvocation::new("brew", args);
invocation
.env
.insert("HOMEBREW_NO_AUTO_UPDATE".into(), "1".into());
vec![BackendOperation::Command(invocation)]
}
InstallSpec::Cargo(cargo) => {
if cargo.version.trim().is_empty() || cargo.version.contains(['*', '^', '~', '>', '<'])
{
return Err(ForgeError::Config(format!(
"Cargo managed build must use a pinned version: {}",
cargo.version
)));
}
let mut args = vec![
cargo
.toolchain
.as_ref()
.map(|toolchain| format!("+{toolchain}"))
.unwrap_or_default(),
"install".into(),
cargo.crate_name.clone(),
"--version".into(),
cargo.version.clone(),
"--root".into(),
context.staging.display().to_string(),
"--target-dir".into(),
context.target.display().to_string(),
"--profile".into(),
cargo.profile.clone(),
];
if cargo.toolchain.is_none() {
args.remove(0);
}
if cargo.locked {
args.push("--locked".into());
}
if !cargo.features.is_empty() {
args.extend(["--features".into(), cargo.features.join(",")]);
}
if let Some(target) = &cargo.target {
args.extend(["--target".into(), target.clone()]);
}
if let Some(source) = &cargo.source {
if source.starts_with("git+") || source.starts_with("https://") {
let url = source.strip_prefix("git+").unwrap_or(source);
args.extend(["--git".into(), url.into()]);
if let Some(revision) = &cargo.revision {
args.extend(["--rev".into(), revision.clone()]);
}
} else if let Some(index) = source.strip_prefix("index+") {
args.extend(["--index".into(), index.into()]);
} else {
args.extend(["--registry".into(), source.clone()]);
}
}
for bin in &cargo.bins {
args.extend(["--bin".into(), bin.clone()]);
}
let mut invocation = CommandInvocation::new("cargo", args);
invocation.clear_env = true;
for name in [
"PATH",
"HOME",
"USERPROFILE",
"SYSTEMROOT",
"TEMP",
"TMP",
"RUSTUP_HOME",
]
.into_iter()
.chain(cargo.build_env_allow.iter().map(String::as_str))
{
if let Ok(value) = std::env::var(name) {
invocation.env.insert(name.to_string(), value);
}
}
invocation.env.insert(
"CARGO_BUILD_JOBS".into(),
context.cargo_jobs.max(1).to_string(),
);
invocation.idempotent = true;
vec![BackendOperation::Command(invocation)]
}
InstallSpec::Rustup(rustup) => {
let mut operations = Vec::new();
if let Some(toolchain) = &rustup.toolchain {
operations.push(command("rustup", ["toolchain", "install", toolchain]));
if rustup.default {
operations.push(command("rustup", ["default", toolchain]));
}
}
for component in &rustup.components {
let mut args = vec!["component", "add", component.as_str()];
if let Some(toolchain) = &rustup.toolchain {
args.extend(["--toolchain", toolchain]);
}
operations.push(command("rustup", args));
}
operations
}
InstallSpec::Npm(npm) => {
let package = format!("{}@{}", npm.package, npm.version);
let mut args = vec!["install".into(), "--global".into()];
if let Some(registry) = &npm.source {
args.extend(["--registry".into(), registry.clone()]);
}
args.extend(["--".into(), package]);
vec![BackendOperation::Command(CommandInvocation::new(
"npm", args,
))]
}
InstallSpec::Pip(pip) => {
let environment = expand_app_home(&pip.environment);
let python = if cfg!(windows) {
environment.join("Scripts").join("python.exe")
} else {
environment.join("bin").join("python")
};
let mut install_args = vec![
"-m".into(),
"pip".into(),
"install".into(),
"--disable-pip-version-check".into(),
];
if let Some(index) = &pip.index {
install_args.extend(["--index-url".into(), index.clone()]);
}
for host in context.allow_insecure_hosts {
install_args.extend(["--trusted-host".into(), host.clone()]);
}
install_args.extend(["--".into(), format!("{}=={}", pip.package, pip.version)]);
vec![
BackendOperation::Command(CommandInvocation::new(
&pip.python,
vec![
"-m".into(),
"venv".into(),
environment.display().to_string(),
],
)),
BackendOperation::Command(CommandInvocation::new(
python.display().to_string(),
install_args,
)),
]
}
InstallSpec::UvTool(uv) => {
let mut args = vec!["tool".into(), "install".into()];
if uv.force {
args.push("--force".into());
}
if let Some(index) = &uv.index {
args.extend(["--default-index".into(), index.clone()]);
}
for host in context.allow_insecure_hosts {
args.extend(["--allow-insecure-host".into(), host.clone()]);
}
args.push(uv.package.clone());
let mut invocation = CommandInvocation::new("uv", args);
invocation.env.insert(
"UV_TOOL_BIN_DIR".into(),
managed_bin_dir().display().to_string(),
);
vec![BackendOperation::Command(invocation)]
}
InstallSpec::Winget(winget) => {
let mut args = vec![
"install".into(),
"--exact".into(),
"--id".into(),
winget.package.clone(),
"--accept-package-agreements".into(),
"--accept-source-agreements".into(),
];
args.extend(winget.arguments.iter().cloned());
vec![BackendOperation::Command(CommandInvocation::new(
"winget", args,
))]
}
InstallSpec::Archive(archive) => vec![BackendOperation::Archive {
url: archive.url.clone(),
sha256: archive.sha256.clone(),
target: archive.target.clone(),
}],
InstallSpec::Git(git) => {
vec![
BackendOperation::Command(CommandInvocation::new(
"git",
vec![
"clone".into(),
"--filter=blob:none".into(),
"--no-checkout".into(),
"--".into(),
git.url.clone(),
context.staging.display().to_string(),
],
)),
command_at(
"git",
["checkout", "--detach", &git.revision],
context.staging,
),
]
}
InstallSpec::Shell(shell) => vec![BackendOperation::Shell {
command: shell.command.clone(),
timeout_secs: shell.timeout_secs,
inactivity_timeout_secs: shell.inactivity_timeout_secs,
}],
};
Ok(operations)
}
fn expand_app_home(path: &Path) -> PathBuf {
let mut components = path.components();
if matches!(components.next(), Some(Component::Normal(value)) if value == "$BOT_FORGE_HOME") {
return components.fold(app_home(), |base, component| {
base.join(component.as_os_str())
});
}
path.to_path_buf()
}
fn apt_command(
mut args: Vec<String>,
source: Option<&Path>,
lists: Option<&Path>,
) -> CommandInvocation {
if let Some(source) = source {
args.splice(
0..0,
[
"-o".into(),
format!("Dir::Etc::sourcelist={}", source.display()),
"-o".into(),
"Dir::Etc::sourceparts=-".into(),
],
);
}
if let Some(lists) = lists {
args.splice(
0..0,
[
"-o".into(),
format!("Dir::State::lists={}", lists.display()),
"-o".into(),
"APT::Get::List-Cleanup=0".into(),
],
);
}
let mut invocation = CommandInvocation::new("apt-get", args);
invocation.null_stdin = true;
invocation
.env
.insert("DEBIAN_FRONTEND".into(), "noninteractive".into());
invocation.env.insert("NEEDRESTART_MODE".into(), "a".into());
invocation
.env
.insert("APT_LISTCHANGES_FRONTEND".into(), "none".into());
invocation
}
pub(crate) fn verification_operations(
check: &CheckSpec,
artifact: Option<&Path>,
) -> Vec<BackendOperation> {
match check {
CheckSpec::Command {
program,
args,
timeout_secs,
stdout_contains,
..
} => {
let program = artifact
.map(|root| root.join(program).display().to_string())
.unwrap_or_else(|| program.clone());
let mut invocation = CommandInvocation::new(program, args.clone());
invocation.timeout_secs = *timeout_secs;
invocation.stdout_contains = stdout_contains.clone();
if let CheckSpec::Command { success_codes, .. } = check {
invocation.success_codes = success_codes.clone();
}
vec![BackendOperation::Command(invocation)]
}
CheckSpec::Shell {
command,
timeout_secs,
} => vec![BackendOperation::Shell {
command: command.clone(),
timeout_secs: *timeout_secs,
inactivity_timeout_secs: None,
}],
CheckSpec::Path { path } => vec![BackendOperation::PathExists { path: path.clone() }],
CheckSpec::All { checks } => checks
.iter()
.flat_map(|check| verification_operations(check, artifact))
.collect(),
}
}
fn command<'a>(program: &str, args: impl IntoIterator<Item = &'a str>) -> BackendOperation {
BackendOperation::Command(CommandInvocation::new(
program,
args.into_iter().map(str::to_string).collect(),
))
}
fn command_at<'a>(
program: &str,
args: impl IntoIterator<Item = &'a str>,
current_dir: &Path,
) -> BackendOperation {
let mut invocation =
CommandInvocation::new(program, args.into_iter().map(str::to_string).collect());
invocation.current_dir = Some(current_dir.to_path_buf());
BackendOperation::Command(invocation)
}
#[cfg(test)]
mod tests {
use std::path::Path;
use crate::backends::typed::{
BackendContext, BackendOperation, CheckSpec, InstallSpec, install_operations,
rustup_bootstrap_operation, verification_operations,
};
use crate::config::schema::{
AptInstall, BrewInstall, CargoInstall, NpmInstall, PipInstall, RustupInstall, UvToolInstall,
};
use crate::paths::managed_bin_dir;
fn context() -> BackendContext<'static> {
BackendContext {
staging: Path::new("/staging"),
target: Path::new("/target"),
cargo_jobs: 3,
allow_insecure_hosts: &[],
apt_source: None,
apt_lists: None,
}
}
fn context_with_insecure_hosts(hosts: &[String]) -> BackendContext<'_> {
BackendContext {
allow_insecure_hosts: hosts,
apt_source: None,
apt_lists: None,
..context()
}
}
#[test]
fn package_arguments_are_not_shell_joined() {
let operations = install_operations(
&InstallSpec::Apt(AptInstall {
packages: vec!["demo;touch /tmp/pwned".into()],
update: false,
}),
&context(),
)
.unwrap();
let BackendOperation::Command(command) = &operations[0] else {
panic!("expected command")
};
assert_eq!(command.program, "apt-get");
assert_eq!(command.args.last().unwrap(), "demo;touch /tmp/pwned");
assert!(command.null_stdin);
assert_eq!(
command.env.get("DEBIAN_FRONTEND").map(String::as_str),
Some("noninteractive")
);
assert_eq!(
command.env.get("NEEDRESTART_MODE").map(String::as_str),
Some("a")
);
assert_eq!(
command
.env
.get("APT_LISTCHANGES_FRONTEND")
.map(String::as_str),
Some("none")
);
}
#[test]
fn apt_update_is_noninteractive() {
let operations = install_operations(
&InstallSpec::Apt(AptInstall {
packages: vec!["cmake".into()],
update: true,
}),
&context(),
)
.unwrap();
let BackendOperation::Command(command) = &operations[0] else {
panic!("expected command")
};
assert_eq!(command.args, ["update"]);
assert!(command.null_stdin);
assert_eq!(
command.env.get("DEBIAN_FRONTEND").map(String::as_str),
Some("noninteractive")
);
}
#[test]
fn apt_source_override_is_applied_to_update_and_install() {
let mut context = context();
context.apt_source = Some(Path::new("/etc/apt/sources.list.d/bot-forge.sources"));
context.apt_lists = Some(Path::new("/var/lib/bot-forge/apt/lists"));
let operations = install_operations(
&InstallSpec::Apt(AptInstall {
packages: vec!["cmake".into()],
update: true,
}),
&context,
)
.unwrap();
for operation in operations {
let BackendOperation::Command(command) = operation else {
panic!("expected command")
};
assert!(command.args.windows(2).any(|args| {
args == [
"-o",
"Dir::Etc::sourcelist=/etc/apt/sources.list.d/bot-forge.sources",
]
}));
assert!(
command
.args
.windows(2)
.any(|args| args == ["-o", "Dir::Etc::sourceparts=-"])
);
assert!(
command.args.windows(2).any(|args| {
args == ["-o", "Dir::State::lists=/var/lib/bot-forge/apt/lists"]
})
);
}
}
#[test]
fn brew_formulae_are_typed_arguments_without_auto_update() {
let operations = install_operations(
&InstallSpec::Brew(BrewInstall {
formulae: vec!["node".into(), "openssl@3".into()],
}),
&context(),
)
.unwrap();
let BackendOperation::Command(command) = &operations[0] else {
panic!("expected command")
};
assert_eq!(command.program, "brew");
assert_eq!(command.args, ["install", "--formula", "node", "openssl@3"]);
assert_eq!(
command
.env
.get("HOMEBREW_NO_AUTO_UPDATE")
.map(String::as_str),
Some("1")
);
}
#[test]
fn npm_version_is_one_literal_argument() {
let operations = install_operations(
&InstallSpec::Npm(NpmInstall {
package: "demo".into(),
version: "1.2.3".into(),
source: None,
}),
&context(),
)
.unwrap();
let BackendOperation::Command(command) = &operations[0] else {
panic!("expected command")
};
assert_eq!(command.args, ["install", "--global", "--", "demo@1.2.3"]);
}
#[test]
fn npm_registry_is_an_explicit_argument() {
let operations = install_operations(
&InstallSpec::Npm(NpmInstall {
package: "demo".into(),
version: "1.2.3".into(),
source: Some("https://packages.example/npm".into()),
}),
&context(),
)
.unwrap();
let BackendOperation::Command(command) = &operations[0] else {
panic!("expected command")
};
assert_eq!(
command.args,
[
"install",
"--global",
"--registry",
"https://packages.example/npm",
"--",
"demo@1.2.3"
]
);
}
#[test]
fn pip_creates_a_managed_environment_before_installing() {
let operations = install_operations(
&InstallSpec::Pip(PipInstall {
package: "uv".into(),
version: "0.12.3".into(),
python: "python3".into(),
environment: "$BOT_FORGE_HOME/python-tools/uv".into(),
index: None,
}),
&context(),
)
.unwrap();
let BackendOperation::Command(venv) = &operations[0] else {
panic!("expected venv command")
};
assert_eq!(venv.program, "python3");
assert_eq!(&venv.args[..2], ["-m", "venv"]);
let BackendOperation::Command(pip) = &operations[1] else {
panic!("expected pip command")
};
assert!(pip.program.ends_with(if cfg!(windows) {
"Scripts\\python.exe"
} else {
"bin/python"
}));
assert_eq!(
pip.args,
[
"-m",
"pip",
"install",
"--disable-pip-version-check",
"--",
"uv==0.12.3"
]
);
}
#[test]
fn uv_tool_preserves_the_requested_git_install_arguments() {
let operations = install_operations(
&InstallSpec::UvTool(UvToolInstall {
package: "git+https://gitcode.com/xuanwu/project-brain.git".into(),
bins: vec!["project-brain".into()],
force: true,
index: None,
}),
&context(),
)
.unwrap();
let BackendOperation::Command(command) = &operations[0] else {
panic!("expected uv command")
};
assert_eq!(command.program, "uv");
assert_eq!(
command.args,
[
"tool",
"install",
"--force",
"git+https://gitcode.com/xuanwu/project-brain.git"
]
);
assert_eq!(
command.env.get("UV_TOOL_BIN_DIR"),
Some(&managed_bin_dir().display().to_string())
);
}
#[test]
fn uv_tool_forwards_component_insecure_hosts_as_separate_arguments() {
let hosts = vec![
"mirror.example.test".to_string(),
"redirect.example.test:8080".to_string(),
];
let operations = install_operations(
&InstallSpec::UvTool(UvToolInstall {
package: "https://mirror.example.test/project-brain.whl".into(),
bins: vec!["project-brain".into()],
force: true,
index: Some("https://mirror.example.test/pypi/simple".into()),
}),
&context_with_insecure_hosts(&hosts),
)
.unwrap();
let BackendOperation::Command(command) = &operations[0] else {
panic!("expected uv command")
};
assert_eq!(
command.args,
[
"tool",
"install",
"--force",
"--default-index",
"https://mirror.example.test/pypi/simple",
"--allow-insecure-host",
"mirror.example.test",
"--allow-insecure-host",
"redirect.example.test:8080",
"https://mirror.example.test/project-brain.whl",
]
);
}
#[test]
fn uv_tool_forwards_wildcard_to_allow_all_insecure_http_hosts() {
let hosts = vec!["*".to_string()];
let operations = install_operations(
&InstallSpec::UvTool(UvToolInstall {
package: "https://mirror.example.test/project-brain.whl".into(),
bins: vec!["project-brain".into()],
force: true,
index: Some("https://mirror.example.test/pypi/simple".into()),
}),
&context_with_insecure_hosts(&hosts),
)
.unwrap();
let BackendOperation::Command(command) = &operations[0] else {
panic!("expected uv command")
};
assert!(
command
.args
.windows(2)
.any(|args| args == ["--allow-insecure-host", "*"])
);
}
#[test]
fn pip_forwards_component_insecure_hosts_as_trusted_hosts() {
let hosts = vec![
"mirror.example.test".to_string(),
"redirect.example.test:8080".to_string(),
];
let operations = install_operations(
&InstallSpec::Pip(PipInstall {
package: "uv".into(),
version: "0.12.3".into(),
python: "python3".into(),
environment: "$BOT_FORGE_HOME/python-tools/uv".into(),
index: Some("https://mirror.example.test/pypi/simple".into()),
}),
&context_with_insecure_hosts(&hosts),
)
.unwrap();
let BackendOperation::Command(command) = &operations[1] else {
panic!("expected pip command")
};
assert!(
command
.args
.windows(2)
.any(|args| { args == ["--trusted-host", "mirror.example.test"] })
);
assert!(
command
.args
.windows(2)
.any(|args| { args == ["--trusted-host", "redirect.example.test:8080"] })
);
}
#[test]
fn pip_forwards_wildcard_as_a_trusted_host() {
let hosts = vec!["*".to_string()];
let operations = install_operations(
&InstallSpec::Pip(PipInstall {
package: "uv".into(),
version: "0.12.3".into(),
python: "python3".into(),
environment: "$BOT_FORGE_HOME/python-tools/uv".into(),
index: Some("https://mirror.example.test/pypi/simple".into()),
}),
&context_with_insecure_hosts(&hosts),
)
.unwrap();
let BackendOperation::Command(command) = &operations[1] else {
panic!("expected pip command")
};
assert!(
command
.args
.windows(2)
.any(|args| args == ["--trusted-host", "*"])
);
}
#[test]
fn rustup_components_target_the_selected_toolchain() {
let operations = install_operations(
&InstallSpec::Rustup(RustupInstall {
toolchain: Some("nightly".into()),
toolchain_ref: None,
components: vec!["miri".into()],
default: false,
bootstrap: None,
}),
&context(),
)
.unwrap();
let BackendOperation::Command(component) = &operations[1] else {
panic!("expected component command")
};
assert_eq!(
component.args,
["component", "add", "miri", "--toolchain", "nightly"]
);
}
#[test]
fn rustup_bootstrap_is_noninteractive_and_does_not_modify_path() {
let BackendOperation::Command(command) =
rustup_bootstrap_operation(Path::new("/staging/rustup-init"))
else {
panic!("expected bootstrap command")
};
assert_eq!(command.program, "/staging/rustup-init");
assert_eq!(
command.args,
["-y", "--no-modify-path", "--default-toolchain", "none"]
);
assert_eq!(command.timeout_secs, Some(300));
}
#[test]
fn composite_checks_preserve_order() {
let operations = verification_operations(
&CheckSpec::All {
checks: vec![
CheckSpec::Command {
program: "first".into(),
args: vec!["--version".into()],
stdout_contains: None,
success_codes: vec![0],
timeout_secs: Some(1),
},
CheckSpec::Command {
program: "second".into(),
args: Vec::new(),
stdout_contains: None,
success_codes: vec![0],
timeout_secs: Some(1),
},
],
},
None,
);
let programs = operations
.iter()
.map(|operation| match operation {
BackendOperation::Command(command) => command.program.as_str(),
_ => panic!("expected commands"),
})
.collect::<Vec<_>>();
assert_eq!(programs, ["first", "second"]);
}
#[test]
fn cargo_install_is_isolated_pinned_and_environment_allowlisted() {
let operation = install_operations(
&InstallSpec::Cargo(CargoInstall {
crate_name: "demo".into(),
version: "=1.2.3".into(),
source: Some("git+https://example.invalid/demo".into()),
revision: Some("0123456789abcdef".into()),
locked: true,
features: vec!["json".into()],
bins: vec!["demo".into()],
target: None,
toolchain: Some("stable".into()),
profile: "release".into(),
build_env_allow: Vec::new(),
}),
&context(),
)
.unwrap()
.remove(0);
let BackendOperation::Command(command) = operation else {
panic!("expected command")
};
assert_eq!(command.program, "cargo");
for expected in [
"+stable",
"--root",
"/staging",
"--target-dir",
"/target",
"--git",
"https://example.invalid/demo",
"--rev",
"0123456789abcdef",
"--locked",
] {
assert!(command.args.iter().any(|argument| argument == expected));
}
assert!(command.clear_env);
assert_eq!(command.env["CARGO_BUILD_JOBS"], "3");
}
#[test]
fn cargo_registry_url_uses_explicit_index() {
let operation = install_operations(
&InstallSpec::Cargo(CargoInstall {
crate_name: "demo".into(),
version: "=1.2.3".into(),
source: Some("index+https://packages.example/cargo/index".into()),
revision: None,
locked: true,
features: Vec::new(),
bins: vec!["demo".into()],
target: None,
toolchain: None,
profile: "release".into(),
build_env_allow: Vec::new(),
}),
&context(),
)
.unwrap()
.remove(0);
let BackendOperation::Command(command) = operation else {
unreachable!()
};
let index = command
.args
.iter()
.position(|arg| arg == "--index")
.unwrap();
assert_eq!(
command.args[index + 1],
"https://packages.example/cargo/index"
);
}
#[test]
fn typed_check_preserves_success_codes_and_timeout() {
let operation = verification_operations(
&CheckSpec::Command {
program: "demo".into(),
args: vec!["--check".into()],
stdout_contains: None,
success_codes: vec![0, 2],
timeout_secs: Some(3),
},
None,
)
.remove(0);
let BackendOperation::Command(command) = operation else {
unreachable!()
};
assert_eq!(command.success_codes, [0, 2]);
assert_eq!(command.timeout_secs, Some(3));
}
}