bake-test-rust 0.1.2

Reusable Rust test tasks for Bake
Documentation
// Released under the MIT License.
// Copyright, 2026, by Samuel Williams.

use bake::{Context, Error, Result};
use std::ffi::OsString;
use std::process::Command;

pub(crate) fn executable() -> OsString {
    std::env::var_os("CARGO").unwrap_or_else(|| "cargo".into())
}

pub(crate) fn run(context: &Context, arguments: &[String]) -> Result<()> {
    let status = context.command(executable()).args(arguments).status()?;
    if status.success() {
        Ok(())
    } else {
        Err(Error::new(format!(
            "cargo {} failed: {status}",
            arguments.join(" ")
        )))
    }
}

pub(crate) fn run_in(directory: &std::path::Path, arguments: &[String]) -> Result<()> {
    let status = Command::new(executable())
        .current_dir(directory)
        .args(arguments)
        .status()?;
    if status.success() {
        Ok(())
    } else {
        Err(Error::new(format!(
            "cargo {} failed in {}: {status}",
            arguments.join(" "),
            directory.display()
        )))
    }
}

pub(crate) fn update_in(directory: &std::path::Path) -> Result<()> {
    run_in(directory, &["update".to_owned()])
}

pub(crate) fn metadata_in(directory: &std::path::Path, no_dependencies: bool) -> Result<Vec<u8>> {
    let mut command = Command::new(executable());
    command
        .current_dir(directory)
        .args(["metadata", "--format-version", "1"]);
    if no_dependencies {
        command.arg("--no-deps");
    }

    let output = command.output()?;
    if output.status.success() {
        Ok(output.stdout)
    } else {
        Err(Error::new(format!(
            "cargo metadata failed in {}: {}",
            directory.display(),
            String::from_utf8_lossy(&output.stderr).trim()
        )))
    }
}

#[cfg(test)]
mod tests {
    use super::{metadata_in, update_in};
    use std::fs;
    use std::path::PathBuf;
    use std::time::{SystemTime, UNIX_EPOCH};

    struct TemporaryDirectory(PathBuf);

    impl TemporaryDirectory {
        fn new() -> Self {
            let unique = SystemTime::now()
                .duration_since(UNIX_EPOCH)
                .expect("system time is after the Unix epoch")
                .as_nanos();
            let path = std::env::temp_dir().join(format!(
                "bake-test-rust-cargo-update-{}-{unique}",
                std::process::id()
            ));
            fs::create_dir_all(&path).expect("create temporary project");
            Self(path)
        }

        fn path(&self) -> &std::path::Path {
            &self.0
        }
    }

    impl Drop for TemporaryDirectory {
        fn drop(&mut self) {
            let _ = fs::remove_dir_all(&self.0);
        }
    }

    #[test]
    fn updates_lockfile_to_select_a_new_local_patch_version() {
        let directory = TemporaryDirectory::new();
        let patched_package = directory.path().join("patched");
        fs::create_dir_all(patched_package.join("src")).expect("create patched package");
        fs::write(
            directory.path().join("Cargo.toml"),
            "[package]\nname = \"consumer\"\nversion = \"0.1.0\"\nedition = \"2024\"\n\n[dependencies]\npatched = \"0.1.0\"\n\n[patch.crates-io]\npatched = { path = \"patched\" }\n",
        )
        .expect("write consumer manifest");
        fs::create_dir_all(directory.path().join("src")).expect("create consumer source");
        fs::write(directory.path().join("src/main.rs"), "fn main() {}\n")
            .expect("write consumer source");
        fs::write(
            patched_package.join("Cargo.toml"),
            "[package]\nname = \"patched\"\nversion = \"0.1.0\"\nedition = \"2024\"\n",
        )
        .expect("write patched package manifest");
        fs::write(patched_package.join("src/lib.rs"), "pub fn value() {}\n")
            .expect("write patched package source");

        metadata_in(directory.path(), false).expect("resolve initial local package version");
        let patched_manifest = patched_package.join("Cargo.toml");
        fs::write(
            &patched_manifest,
            "[package]\nname = \"patched\"\nversion = \"0.1.1\"\nedition = \"2024\"\n",
        )
        .expect("bump patched package version");

        update_in(directory.path()).expect("update local patch version in the lockfile");

        let lockfile =
            fs::read_to_string(directory.path().join("Cargo.lock")).expect("read updated lockfile");
        assert!(lockfile.contains("name = \"patched\"\nversion = \"0.1.1\""));
    }
}