cargo-artifact-dependency 0.2.0

Stable crate alternative for cargo artifact dependency.
Documentation
use std::fs;

use crate::utils::{executable_name, sanitize_path_component};
use crate::{ArtifactDependencyBuilder, BuildProfile, Error, find_artifact};
use cargo_install::CargoInstallBuilder;

#[test]
fn builder_defaults_optional_fields() {
    let request = ArtifactDependencyBuilder::default()
        .crate_name("ripgrep")
        .build()
        .unwrap();

    assert_eq!(request.crate_name, "ripgrep");
    assert_eq!(request.version, None);
    assert_eq!(request.bin_name, None);
    assert_eq!(request.profile, BuildProfile::Release);
    assert_eq!(request.target, None);
    assert!(request.locked);
}

#[test]
fn builder_requires_crate_name() {
    let err = ArtifactDependencyBuilder::default().build().unwrap_err();

    assert!(err.to_string().contains("crate_name"));
}

#[test]
fn builder_allows_disabling_locked_installs() {
    let request = ArtifactDependencyBuilder::default()
        .crate_name("ripgrep")
        .locked(false)
        .build()
        .unwrap();

    assert!(!request.locked);
}

#[test]
fn install_builder_contains_expected_arguments() {
    let request = ArtifactDependencyBuilder::default()
        .crate_name("ripgrep")
        .version("^14")
        .bin_name("rg")
        .profile(BuildProfile::Custom("dist".to_string()))
        .target("x86_64-unknown-linux-musl")
        .build()
        .unwrap();

    let builder = CargoInstallBuilder::default()
        .crate_name("ripgrep")
        .version("14.1.0")
        .bin("rg")
        .root("/tmp/install-root")
        .locked(true)
        .target(request.target.as_deref().unwrap())
        .profile("dist");

    let install = builder.build().unwrap();

    let args: Vec<String> = install
        .args()
        .into_iter()
        .map(|arg| arg.to_string_lossy().into_owned())
        .collect();

    assert_eq!(
        args,
        vec![
            "install",
            "--root",
            "/tmp/install-root",
            "--version",
            "14.1.0",
            "--target",
            "x86_64-unknown-linux-musl",
            "--bin",
            "rg",
            "--profile",
            "dist",
            "--locked",
            "ripgrep",
        ]
    );
}

#[test]
fn install_root_is_stable_for_same_dependency() {
    let first = ArtifactDependencyBuilder::default()
        .crate_name("ripgrep")
        .version("14.1.1")
        .bin_name("rg")
        .build()
        .unwrap();
    let second = ArtifactDependencyBuilder::default()
        .crate_name("ripgrep")
        .version("14.1.1")
        .bin_name("rg")
        .build()
        .unwrap();

    assert_eq!(first.install_root(), second.install_root());
}

#[test]
fn install_root_changes_for_different_dependency_settings() {
    let release = ArtifactDependencyBuilder::default()
        .crate_name("ripgrep")
        .version("14.1.1")
        .bin_name("rg")
        .profile(BuildProfile::Release)
        .build()
        .unwrap();
    let debug = ArtifactDependencyBuilder::default()
        .crate_name("ripgrep")
        .version("14.1.1")
        .bin_name("rg")
        .profile(BuildProfile::Debug)
        .build()
        .unwrap();

    assert_ne!(release.install_root(), debug.install_root());
}

#[test]
fn install_root_changes_for_version_bin_target_and_locked() {
    let base = ArtifactDependencyBuilder::default()
        .crate_name("ripgrep")
        .version("14.1.1")
        .bin_name("rg")
        .target("x86_64-unknown-linux-gnu")
        .locked(true)
        .build()
        .unwrap();
    let different_version = ArtifactDependencyBuilder::default()
        .crate_name("ripgrep")
        .version("14.1.0")
        .bin_name("rg")
        .target("x86_64-unknown-linux-gnu")
        .locked(true)
        .build()
        .unwrap();
    let different_bin = ArtifactDependencyBuilder::default()
        .crate_name("ripgrep")
        .version("14.1.1")
        .bin_name("rg-alt")
        .target("x86_64-unknown-linux-gnu")
        .locked(true)
        .build()
        .unwrap();
    let different_target = ArtifactDependencyBuilder::default()
        .crate_name("ripgrep")
        .version("14.1.1")
        .bin_name("rg")
        .target("aarch64-unknown-linux-gnu")
        .locked(true)
        .build()
        .unwrap();
    let different_locked = ArtifactDependencyBuilder::default()
        .crate_name("ripgrep")
        .version("14.1.1")
        .bin_name("rg")
        .target("x86_64-unknown-linux-gnu")
        .locked(false)
        .build()
        .unwrap();

    assert_ne!(base.install_root(), different_version.install_root());
    assert_ne!(base.install_root(), different_bin.install_root());
    assert_ne!(base.install_root(), different_target.install_root());
    assert_ne!(base.install_root(), different_locked.install_root());
}

#[test]
fn install_root_sanitizes_path_components() {
    let request = ArtifactDependencyBuilder::default()
        .crate_name("some/crate")
        .version("^14")
        .bin_name("bin:name")
        .profile(BuildProfile::Custom("release+thin".to_string()))
        .target("x86_64/unknown/linux")
        .build()
        .unwrap();

    let root = request.install_root();
    let root_name = root.file_name().unwrap().to_string_lossy();

    assert_eq!(
        root_name,
        "some_crate___14__bin_name__release_thin__x86_64_unknown_linux__locked"
    );
}

#[test]
fn resolves_existing_artifact_from_stable_install_root_without_network() {
    let request = ArtifactDependencyBuilder::default()
        .crate_name("ripgrep")
        .bin_name("rg")
        .build()
        .unwrap();
    let install_root = request.install_root();
    let bin_dir = install_root.join("bin");
    fs::create_dir_all(&bin_dir).unwrap();
    fs::write(bin_dir.join(executable_name("rg")), "").unwrap();

    let artifact_path = request.resolve().unwrap();

    assert_eq!(artifact_path, bin_dir.join(executable_name("rg")));
}

#[test]
fn finds_named_installed_binary_without_network() {
    let install_root = tempfile::tempdir().unwrap();
    let bin_dir = install_root.path().join("bin");
    fs::create_dir(&bin_dir).unwrap();
    fs::write(bin_dir.join(executable_name("other")), "").unwrap();
    fs::write(bin_dir.join(executable_name("rg")), "").unwrap();

    let artifact_path = find_artifact(install_root.path(), Some("rg")).unwrap();

    assert_eq!(artifact_path, bin_dir.join(executable_name("rg")));
}

#[test]
fn finds_single_installed_binary_without_network() {
    let install_root = tempfile::tempdir().unwrap();
    let bin_dir = install_root.path().join("bin");
    fs::create_dir(&bin_dir).unwrap();
    fs::write(bin_dir.join(executable_name("rg")), "").unwrap();

    let artifact_path = find_artifact(install_root.path(), None).unwrap();

    assert_eq!(artifact_path, bin_dir.join(executable_name("rg")));
}

#[test]
fn rejects_missing_named_binary_without_network() {
    let install_root = tempfile::tempdir().unwrap();
    fs::create_dir(install_root.path().join("bin")).unwrap();

    let err = find_artifact(install_root.path(), Some("rg")).unwrap_err();

    assert!(matches!(err, Error::InvalidArtifactPath { .. }));
}

#[test]
fn rejects_empty_binary_dir_without_network() {
    let install_root = tempfile::tempdir().unwrap();
    fs::create_dir(install_root.path().join("bin")).unwrap();

    let err = find_artifact(install_root.path(), None).unwrap_err();

    assert!(matches!(err, Error::NoInstalledBinaries { .. }));
}

#[test]
fn rejects_ambiguous_binaries_without_network() {
    let install_root = tempfile::tempdir().unwrap();
    let bin_dir = install_root.path().join("bin");
    fs::create_dir(&bin_dir).unwrap();
    fs::write(bin_dir.join(executable_name("rg")), "").unwrap();
    fs::write(bin_dir.join(executable_name("rga")), "").unwrap();

    let err = find_artifact(install_root.path(), None).unwrap_err();

    assert!(matches!(err, Error::AmbiguousInstalledBinaries));
}

#[test]
fn sanitize_path_component_replaces_path_unsafe_characters() {
    assert_eq!(sanitize_path_component("abc-DEF_123.4"), "abc-DEF_123.4");
    assert_eq!(
        sanitize_path_component("^14/bin:name+thin"),
        "_14_bin_name_thin"
    );
}