alef 0.84.0

Opinionated polyglot binding generator for Rust libraries
Documentation
use super::*;
use crate::core::backend::GeneratedFile;
use std::path::PathBuf;

fn generated(path: &str, content: &str) -> GeneratedFile {
    GeneratedFile {
        path: PathBuf::from(path),
        content: content.to_string(),
        generated_header: true,
    }
}

#[test]
fn reconciliation_replaces_only_alef_owned_generated_manifests() {
    let directory = tempfile::tempdir().expect("create temp directory");
    let managed_path = directory.path().join("crates/sample-ffi/Cargo.toml");
    let handwritten_path = directory.path().join("crates/manual/Cargo.toml");
    let source_path = directory.path().join("crates/sample-ffi/src/lib.rs");
    std::fs::create_dir_all(managed_path.parent().expect("managed parent")).expect("create managed directory");
    std::fs::create_dir_all(handwritten_path.parent().expect("handwritten parent"))
        .expect("create handwritten directory");
    std::fs::create_dir_all(source_path.parent().expect("source parent")).expect("create source directory");
    std::fs::write(
        &managed_path,
        "# This file is auto-generated by alef — DO NOT EDIT.\n# alef:hash:old\n[dependencies]\ncustom = \"1\"\n",
    )
    .expect("write managed manifest");
    let handwritten = "[dependencies]\ncustom = \"1\"\n";
    std::fs::write(&handwritten_path, handwritten).expect("write handwritten manifest");
    std::fs::write(&source_path, "pub fn existing() {}\n").expect("write generated source");

    let files = vec![
        generated(
            "crates/sample-ffi/Cargo.toml",
            "[dependencies]\nserde = { version = \"1\", features = [\"derive\"] }\n",
        ),
        generated(
            "crates/manual/Cargo.toml",
            "[dependencies]\nserde = { version = \"1\", features = [\"derive\"] }\n",
        ),
        generated("crates/sample-ffi/src/lib.rs", "pub fn replaced() {}\n"),
    ];

    let (report, lock_freshness_error) =
        reconcile_managed_scaffold_manifests(&files, directory.path(), None).expect("reconcile manifests");
    assert!(
        lock_freshness_error.is_none(),
        "no Cargo.lock exists beside this fixture's manifest, so nothing should be checked: \
         {lock_freshness_error:?}"
    );
    let managed = std::fs::read_to_string(&managed_path).expect("read managed manifest");

    assert!(
        managed.contains("serde"),
        "a pristine-clone manifest with Alef's standard header must receive generated dependencies"
    );
    assert!(
        !managed.contains("custom"),
        "managed manifest must match generated content"
    );
    assert_eq!(
        std::fs::read_to_string(&handwritten_path).expect("read handwritten manifest"),
        handwritten,
        "an unmarked manifest must not be claimed or modified",
    );
    assert_eq!(
        std::fs::read_to_string(&source_path).expect("read generated source"),
        "pub fn existing() {}\n",
        "manifest reconciliation must not widen into source regeneration",
    );
    assert_eq!(report.changed_paths, std::collections::HashSet::from([managed_path]));
}

#[test]
fn reconciliation_creates_missing_generated_manifest() {
    let directory = tempfile::tempdir().expect("create temp directory");
    let manifest = generated("crates/sample-ffi/Cargo.toml", "[dependencies]\nserde = \"1\"\n");

    reconcile_managed_scaffold_manifests(&[manifest], directory.path(), None).expect("reconcile manifest");

    let content =
        std::fs::read_to_string(directory.path().join("crates/sample-ffi/Cargo.toml")).expect("read created manifest");
    assert!(
        content.contains("auto-generated by alef"),
        "new managed manifest must carry Alef's generated header"
    );
    assert!(
        content.contains("serde"),
        "new managed manifest must include generated dependencies"
    );
}

/// The regression this task exists for: `alef generate` rewrites a workspace-excluded native
/// extension manifest (an Elixir/Ruby/R `Cargo.toml`, `generated_header: true`, never a root
/// workspace member) to require a NEW version of a third-party registry dependency, the sibling
/// `Cargo.lock` still pins the OLD version, and -- because the fake dependency below cannot
/// possibly resolve, offline or online -- the relock attempt fails exactly like it did in the
/// field when the registry was unreachable or the cache was cold. `reconcile_managed_scaffold_manifests`
/// must now report that failure back to its caller instead of writing the new manifest, leaving
/// the stale lock behind, and returning as if nothing happened.
mod relock_still_stale_hard_fails {
    use super::*;

    const NATIVE_MANIFEST: &str = "packages/elixir/native/fixture_nif/Cargo.toml";
    const NATIVE_LOCK: &str = "packages/elixir/native/fixture_nif/Cargo.lock";
    const NATIVE_LIB: &str = "packages/elixir/native/fixture_nif/src/lib.rs";
    const NONEXISTENT_DEPENDENCY: &str = "alef-fixture-nonexistent-dependency";

    fn native_manifest(requirement: &str) -> GeneratedFile {
        generated(
            NATIVE_MANIFEST,
            &format!(
                "[workspace]\n\n[package]\nname = \"fixture_nif\"\nversion = \"0.1.0\"\nedition = \"2024\"\n\n\
                 [lib]\ncrate-type = [\"cdylib\"]\n\n\
                 [dependencies]\n{NONEXISTENT_DEPENDENCY} = \"{requirement}\"\n"
            ),
        )
    }

    fn seed_native_crate(base: &std::path::Path, old_requirement: &str, locked_version: &str) {
        let manifest_path = base.join(NATIVE_MANIFEST);
        std::fs::create_dir_all(manifest_path.parent().expect("native manifest parent")).expect("mkdir native");
        let seed = native_manifest(old_requirement);
        std::fs::write(
            &manifest_path,
            format!(
                "# This file is auto-generated by alef — DO NOT EDIT.\n# alef:hash:old\n{}",
                seed.content
            ),
        )
        .expect("seed prior-generation manifest");
        let lib_path = base.join(NATIVE_LIB);
        std::fs::create_dir_all(lib_path.parent().expect("lib parent")).expect("mkdir src");
        std::fs::write(&lib_path, "// placeholder\n").expect("seed native lib.rs");
        std::fs::write(
            base.join(NATIVE_LOCK),
            format!(
                "version = 3\n\n\
                 [[package]]\nname = \"fixture_nif\"\nversion = \"0.1.0\"\n\n\
                 [[package]]\nname = \"{NONEXISTENT_DEPENDENCY}\"\nversion = \"{locked_version}\"\nsource = \
                 \"registry+https://github.com/rust-lang/crates.io-index\"\n"
            ),
        )
        .expect("seed stale Cargo.lock");
    }

    #[test]
    fn reconcile_fails_when_the_relocked_lock_is_still_stale() {
        let directory = tempfile::tempdir().expect("create temp directory");
        let base = directory.path();
        seed_native_crate(base, "1.4.2", "1.4.2");

        let files = vec![native_manifest("1.5.0")];
        let (report, lock_freshness_error) =
            reconcile_managed_scaffold_manifests(&files, base, Some("9.9.9")).expect("write itself must succeed");

        assert!(
            report.changed_paths.contains(&base.join(NATIVE_MANIFEST)),
            "the manifest write must still be reported as changed: {:?}",
            report.changed_paths
        );
        let manifest = std::fs::read_to_string(base.join(NATIVE_MANIFEST)).expect("read regenerated manifest");
        assert!(
            manifest.contains("1.5.0"),
            "the manifest must carry the new requirement even though the relock failed: {manifest}"
        );

        let error = lock_freshness_error
            .expect("a lock that cannot resolve the new requirement, offline or online, must be reported");
        let message = format!("{error:#}");
        assert!(message.contains(NONEXISTENT_DEPENDENCY), "got: {message}");
        assert!(message.contains("1.5.0"), "got: {message}");
        assert!(message.contains("1.4.2"), "got: {message}");

        let lock = std::fs::read_to_string(base.join(NATIVE_LOCK)).expect("read lock");
        assert!(
            lock.contains("1.4.2"),
            "the lock must genuinely still be stale (not merely reported so) for this assertion to mean \
             anything: {lock}"
        );
    }

    /// ~keep The incident this check exists for had THREE stale locks in one tree. A
    /// first-failure return would relock one, name one, and leave the rest untouched -- so an
    /// operator fixes one, re-runs a full generate, meets the next, and pays for the same round
    /// trip once per stale lock, having been told each time that there was a single problem. A
    /// partial report that reads like a complete one is the defect class this whole check
    /// removes, so every changed manifest must be relocked and every failure named at once.
    #[test]
    fn every_stale_lock_is_relocked_and_named_not_just_the_first() {
        const SECOND_MANIFEST: &str = "packages/ruby/ext/fixture_rb/native/Cargo.toml";
        const SECOND_LOCK: &str = "packages/ruby/ext/fixture_rb/native/Cargo.lock";
        const SECOND_LIB: &str = "packages/ruby/ext/fixture_rb/native/src/lib.rs";

        let directory = tempfile::tempdir().expect("create temp directory");
        let base = directory.path();
        seed_native_crate(base, "1.4.2", "1.4.2");

        let second = |requirement: &str| {
            generated(
                SECOND_MANIFEST,
                &format!(
                    "[workspace]\n\n[package]\nname = \"fixture_rb\"\nversion = \"0.1.0\"\nedition = \"2024\"\n\n\
                     [lib]\ncrate-type = [\"cdylib\"]\n\n\
                     [dependencies]\n{NONEXISTENT_DEPENDENCY} = \"{requirement}\"\n"
                ),
            )
        };
        let second_path = base.join(SECOND_MANIFEST);
        std::fs::create_dir_all(second_path.parent().expect("second parent")).expect("mkdir second");
        std::fs::write(
            &second_path,
            format!(
                "# This file is auto-generated by alef — DO NOT EDIT.\n# alef:hash:old\n{}",
                second("2.0.0").content
            ),
        )
        .expect("seed second manifest");
        let second_lib = base.join(SECOND_LIB);
        std::fs::create_dir_all(second_lib.parent().expect("second lib parent")).expect("mkdir second src");
        std::fs::write(&second_lib, "// placeholder\n").expect("seed second lib.rs");
        std::fs::write(
            base.join(SECOND_LOCK),
            format!(
                "version = 3\n\n\
                 [[package]]\nname = \"fixture_rb\"\nversion = \"0.1.0\"\n\n\
                 [[package]]\nname = \"{NONEXISTENT_DEPENDENCY}\"\nversion = \"2.0.0\"\nsource = \
                 \"registry+https://github.com/rust-lang/crates.io-index\"\n"
            ),
        )
        .expect("seed second stale lock");

        let files = vec![native_manifest("1.5.0"), second("2.1.0")];
        let (_, lock_freshness_error) =
            reconcile_managed_scaffold_manifests(&files, base, None).expect("write itself must succeed");

        let error = lock_freshness_error.expect("two stale locks must be reported, not swallowed");
        let message = format!("{error:#}");
        assert!(
            message.contains("packages/elixir/native/fixture_nif"),
            "the FIRST stale lock must be named: {message}"
        );
        assert!(
            message.contains("packages/ruby/ext/fixture_rb/native"),
            "the SECOND stale lock must be named too -- naming only the first is the bug this test \
             exists for: {message}"
        );
        assert!(
            message.contains("2 generated lockfile(s)"),
            "the report must state how many are unsatisfied: {message}"
        );
    }

    /// Control: an unrelated dependency drift with no bearing on the crate's own pending release
    /// must fail regardless of `canonical` -- the exemption must not blanket-suppress every
    /// finding just because a resolved version happens to be on hand.
    #[test]
    fn reconcile_fails_even_with_no_canonical_version_supplied() {
        let directory = tempfile::tempdir().expect("create temp directory");
        let base = directory.path();
        seed_native_crate(base, "1.4.2", "1.4.2");

        let files = vec![native_manifest("1.5.0")];
        let (_, lock_freshness_error) =
            reconcile_managed_scaffold_manifests(&files, base, None).expect("write itself must succeed");

        assert!(
            lock_freshness_error.is_some(),
            "an unrelated third-party drift must fail even without a canonical version on hand"
        );
    }
}