alef 0.110.2

Opinionated polyglot binding generator for Rust libraries
Documentation
use super::*;

pub(super) const NIF_CARGO_LOCK_SAMPLE: &str = r#"# This file is automatically @generated by Cargo.
# It is not intended for manual editing.
version = 4

[[package]]
name = "example_core"
version = "0.15.6-rc.2"
dependencies = [
 "serde",
]

[[package]]
name = "example_nif"
version = "0.15.6-rc.2"
dependencies = [
 "example_core",
 "rustler",
]

[[package]]
name = "serde"
version = "1.0.219"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5f0e2c6ed6606019b4e29e69dbaba95b11854410e5347d525002456dbbb786b6"
"#;

fn nif_versions() -> std::collections::HashMap<String, String> {
    [
        ("example_core".to_string(), "0.15.6-rc.3".to_string()),
        ("example_nif".to_string(), "0.15.6-rc.3".to_string()),
    ]
    .into()
}

#[test]
fn sync_cargo_lock_path_versions_bumps_only_sourceless_entries() {
    let out = sync_cargo_lock_path_versions(NIF_CARGO_LOCK_SAMPLE, &nif_versions()).expect("lock updated");
    assert!(
        out.contains("version = 4"),
        "lock format version line must be preserved:\n{out}"
    );
    assert_eq!(
        out.matches("version = \"0.15.6-rc.3\"").count(),
        2,
        "both local crates must be bumped:\n{out}"
    );
    assert!(!out.contains("0.15.6-rc.2"), "old version must be gone:\n{out}");
    assert!(
        out.contains("version = \"1.0.219\""),
        "registry dep version must not change:\n{out}"
    );
    assert!(
        out.contains("source = \"registry+https://github.com/rust-lang/crates.io-index\""),
        "registry source line must be preserved:\n{out}"
    );
}

#[test]
fn sync_cargo_lock_path_versions_is_idempotent() {
    let first = sync_cargo_lock_path_versions(NIF_CARGO_LOCK_SAMPLE, &nif_versions()).unwrap();
    assert!(
        sync_cargo_lock_path_versions(&first, &nif_versions()).is_none(),
        "second call with same version must return None"
    );
}

#[test]
fn sync_cargo_lock_path_versions_preserves_no_trailing_newline() {
    let no_newline = NIF_CARGO_LOCK_SAMPLE.trim_end_matches('\n');
    let out = sync_cargo_lock_path_versions(no_newline, &nif_versions()).unwrap();
    assert!(
        !out.ends_with('\n'),
        "absence of trailing newline must be preserved:\n{out:?}"
    );
}

#[test]
fn sync_cargo_lock_path_versions_all_registry_returns_none() {
    let content = "version = 4\n\n[[package]]\nname = \"serde\"\nversion = \"1.0.219\"\nsource = \"registry+x\"\n";
    assert!(sync_cargo_lock_path_versions(content, &nif_versions()).is_none());
}

#[test]
fn sync_cargo_lock_path_versions_preserves_independently_versioned_vendor() {
    let content = format!("{NIF_CARGO_LOCK_SAMPLE}\n[[package]]\nname = \"browser-driver\"\nversion = \"0.9.2\"\n");
    let out = sync_cargo_lock_path_versions(&content, &nif_versions()).expect("core bumped");
    let parsed: toml::Value = toml::from_str(&out).expect("valid lock");
    let driver = parsed["package"]
        .as_array()
        .unwrap()
        .iter()
        .find(|package| package["name"].as_str() == Some("browser-driver"))
        .unwrap();
    assert_eq!(driver["version"].as_str(), Some("0.9.2"));
}

#[test]
fn sync_elixir_nif_cargo_locks_uses_each_local_manifest_version() {
    use crate::core::config::NewAlefConfig;
    use crate::test_support::{CwdGuard, git_add, git_init, write_file};
    let tmp = tempfile::tempdir().expect("workspace");
    let root = tmp.path();
    write_file(
        root,
        "Cargo.toml",
        "[workspace]\nmembers = []\n[workspace.package]\nversion = \"1.10.3\"\n",
    );
    for (path, name, version) in [
        ("crates/core/Cargo.toml", "example_core", "1.10.3"),
        ("packages/elixir/native/example_nif/Cargo.toml", "example_nif", "1.10.3"),
        ("crates/helper/Cargo.toml", "helper", "2.7.0"),
        ("vendor/browser/Cargo.toml", "browser-driver", "0.9.2"),
    ] {
        write_file(
            root,
            path,
            &format!("[package]\nname = \"{name}\"\nversion = \"{version}\"\n"),
        );
    }
    let content = format!(
        "{NIF_CARGO_LOCK_SAMPLE}\n[[package]]\nname = \"browser-driver\"\nversion = \"0.9.2\"\n\n[[package]]\nname = \"helper\"\nversion = \"2.6.0\"\n"
    );
    let lock = write_file(root, "packages/elixir/native/example_nif/Cargo.lock", &content);
    let cfg: NewAlefConfig =
        toml::from_str("[workspace]\nlanguages = [\"elixir\"]\n[[crates]]\nname = \"example\"\nsources = []\n")
            .expect("config");
    let mut config = cfg.resolve().expect("resolved").remove(0);
    config.workspace_root = Some(root.to_path_buf());
    git_init(root);
    git_add(
        root,
        &["Cargo.toml", "crates/core/Cargo.toml", "crates/helper/Cargo.toml"],
    );
    let _cwd = CwdGuard::enter(root);
    let writable = crate::cli::git::IgnoreFilter::for_root(root);
    let mut updated = Vec::new();
    sync_elixir_nif_cargo_locks(&config, &writable, "1.10.3", &mut updated).expect("sync lock");
    let first = std::fs::read_to_string(&lock).expect("lock");
    let document: toml::Value = toml::from_str(&first).expect("valid lock");
    let versions: std::collections::HashMap<_, _> = document["package"]
        .as_array()
        .expect("packages")
        .iter()
        .map(|package| {
            (
                package["name"].as_str().expect("name"),
                package["version"].as_str().expect("version"),
            )
        })
        .collect();
    assert_eq!(versions["example_core"], "1.10.3");
    assert_eq!(versions["example_nif"], "1.10.3");
    assert_eq!(versions["helper"], "2.7.0");
    assert_eq!(versions["browser-driver"], "0.9.2");
    assert_eq!(versions["serde"], "1.0.219");
    assert_eq!(updated.len(), 1);
    sync_elixir_nif_cargo_locks(&config, &writable, "1.10.3", &mut updated).expect("second sync");
    assert_eq!(std::fs::read_to_string(lock).expect("second lock"), first);
    assert_eq!(updated.len(), 1);
}

#[test]
fn inherited_local_versions_resolve_the_owning_workspace() {
    for (label, member, nested, expected) in [
        ("root", "version.workspace = true\n", "", Some("1.10.3")),
        (
            "nested",
            "version.workspace = true\n",
            "[workspace]\n[workspace.package]\nversion = \"2.7.0\"\n",
            Some("2.7.0"),
        ),
        (
            "explicit",
            "version.workspace = true\nworkspace = \"../../independent\"\n",
            "[workspace]\n",
            Some("2.7.0"),
        ),
        (
            "explicit_root",
            "version.workspace = true\nworkspace = \"../..\"\n",
            "[workspace]\n[workspace.package]\nversion = \"2.7.0\"\n",
            Some("1.10.3"),
        ),
        ("unresolved", "version.workspace = true\n", "[workspace]\n", None),
        ("literal", "version = \"2.7.0\"\n", "", Some("2.7.0")),
    ] {
        let tmp = tempfile::tempdir().expect("workspace");
        write_inherited_version_fixture(tmp.path(), member, nested);
        let versions = crate::cli::commands::version_manifests::local_cargo_package_versions(tmp.path(), "1.10.3");
        assert_eq!(
            versions.len(),
            usize::from(expected.is_some()),
            "{label}: discovery count"
        );
        assert_eq!(versions.get("helper").map(String::as_str), expected, "{label}");
        let lock = "version = 4\n\n[[package]]\nname = \"helper\"\nversion = \"2.6.0\"\n";
        let updated = sync_cargo_lock_path_versions(lock, &versions);
        if let Some(expected) = expected {
            let updated = updated.expect("resolved version updates lock");
            let document: toml::Value = toml::from_str(&updated).expect("valid lock");
            assert_eq!(document["package"][0]["version"].as_str(), Some(expected), "{label}");
            assert!(
                sync_cargo_lock_path_versions(&updated, &versions).is_none(),
                "{label}: idempotent"
            );
        } else {
            assert!(updated.is_none(), "{label}: unresolved inheritance preserves lock");
        }
    }
}

fn write_inherited_version_fixture(root: &std::path::Path, member: &str, nested: &str) {
    use crate::test_support::write_file;
    write_file(
        root,
        "Cargo.toml",
        "[workspace]\n[workspace.package]\nversion = \"1.10.3\"\n",
    );
    write_file(root, "helper/Cargo.toml", nested);
    write_file(
        root,
        "helper/member/Cargo.toml",
        &format!("[package]\nname = \"helper\"\n{member}"),
    );
    write_file(
        root,
        "independent/Cargo.toml",
        "[workspace]\n[workspace.package]\nversion = \"2.7.0\"\n",
    );
}