alef 0.81.0

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

/// Regression test for the C10 sync-versions ordering bug.
///
/// `[crates.e2e.registry.packages.*].version` is a real generation input: it feeds
/// registry-mode test_apps content via `E2eConfig::effective_package_for` (merged on top of
/// `[e2e.packages]` when rendering `test_apps/<lang>/...` manifests), so it is correctly folded
/// into `compute_inputs_hash`'s `alef.toml` fingerprint — unlike the `[workspace] alef_version`
/// pin, which `strip_alef_version_pin` deliberately excludes because nothing branches on it.
///
/// Because it is a real input, the bumped version must already be rendered into
/// `test_apps/rust/Cargo.toml`'s own body *before* `sync_versions` stamps `alef:hash:` into
/// the files it just rewrote (`finalize_hashes`, called from the `finalize_paths` block).
/// Before the fix, `sync_registry_package_versions` (which performs the `alef.toml` write
/// that `effective_package_for` later reads to render the bumped body) ran *after* the
/// `finalize_hashes` call for a differently-ordered predecessor of this stamp recipe, so a
/// file `sync_versions` rewrote in the same run could be stamped against content that was
/// about to change again — stale from the moment it was written.
///
/// The per-file `alef:hash:` stamp no longer folds `inputs_hash` in at all (see
/// `core::hash::compute_file_hash`'s doc), so this test now pins the narrower, still-real
/// invariant that recipe change left standing: the embedded hash must cover the file's own
/// **final** on-disk bytes, not an intermediate pre-rewrite body. It asserts the `alef:hash:`
/// line embedded in a file `sync_versions` rewrote in this same run
/// (`test_apps/rust/Cargo.toml`, rewritten by `sync_rust_test_app_version`) matches a fresh
/// content-only hash of that file's actual final bytes. ~keep
#[test]
fn sync_versions_stamps_rewritten_files_fresh_against_post_bump_alef_toml() {
    use crate::core::config::NewAlefConfig;

    let _guard = CWD_LOCK.lock().unwrap_or_else(|e| e.into_inner());
    let original_cwd = std::env::current_dir().expect("cwd");

    let tmp = tempfile::tempdir().expect("tempdir");
    let root = tmp.path();

    std::fs::write(
        root.join("Cargo.toml"),
        "[package]\nname = \"mylib\"\nversion = \"1.3.1\"\nedition = \"2024\"\n",
    )
    .expect("write Cargo.toml");

    // Registry-mode harness: `<e2e.registry.output>/rust/Cargo.toml` (default "test_apps").
    // Pre-seeded with an alef marker and a placeholder hash so `sync_rust_test_app_version`'s
    // rewrite lands in `updated` and reaches the `finalize_hashes` block under test.
    let test_apps_rust_dir = root.join("test_apps/rust");
    std::fs::create_dir_all(&test_apps_rust_dir).expect("mkdir test_apps/rust");
    std::fs::write(
        test_apps_rust_dir.join("Cargo.toml"),
        concat!(
            "# This file is auto-generated by alef — DO NOT EDIT.\n",
            "# alef:hash:deadbeef\n",
            "# To regenerate: alef generate\n",
            "# To verify freshness: alef verify\n",
            "\n",
            "[workspace]\n",
            "\n",
            "[package]\n",
            "name = \"mylib-e2e-rust\"\n",
            "version = \"1.3.1\"\n",
            "edition = \"2024\"\n",
            "license = \"MIT\"\n",
            "publish = false\n",
            "\n",
            "[dependencies]\n",
            "mylib = { version = \"1.3.1\" }\n",
        ),
    )
    .expect("write test_apps/rust/Cargo.toml");

    // `[crates.e2e.registry.packages.python].version` is the hashed input this run bumps mid-way
    // through `sync_versions`, via `sync_registry_package_versions`.
    let alef_toml = concat!(
        "[workspace]\n",
        "languages = [\"rust\"]\n\n",
        "[[crates]]\n",
        "name = \"mylib\"\n",
        "sources = []\n",
        "version_from = \"Cargo.toml\"\n\n",
        "[crates.e2e]\n",
        "languages = []\n\n",
        "[crates.e2e.call]\n",
        "module = \"mylib\"\n",
        "function = \"hello\"\n\n",
        "[crates.e2e.registry.packages.python]\n",
        "name = \"mylib\"\n",
        "version = \"0.0.0\"\n",
    );
    let alef_toml_path = root.join("alef.toml");
    std::fs::write(&alef_toml_path, alef_toml).expect("write alef.toml");

    let cfg: NewAlefConfig = toml::from_str(alef_toml).expect("parse alef.toml");
    let mut resolved = cfg.resolve().expect("resolve config");
    let resolved_cfg = resolved.remove(0);

    std::env::set_current_dir(root).expect("set_current_dir");
    // no_regen = true: isolates the bug to the first `finalize_hashes` block over `updated` —
    // `regenerate_test_apps_after_sync`/`regenerate_scaffold_after_sync` (gated behind
    // `no_regen = false`) already re-read `alef.toml` fresh after the registry bump and were
    // never the buggy path.
    let sync_result = sync_versions(&resolved_cfg, &alef_toml_path, Some("patch"), true, true, None);
    let _ = std::env::set_current_dir(&original_cwd);
    sync_result.expect("sync_versions ok");

    // The per-file stamp no longer depends on generation inputs at all (see
    // `core::hash::compute_file_hash`'s doc) -- what this test still pins down is that
    // `sync_versions` finalizes the hash line *after* it finishes rewriting the dependency
    // version, not before, so the embedded value matches the file's actual final bytes.
    let rewritten = std::fs::read_to_string(test_apps_rust_dir.join("Cargo.toml"))
        .expect("read test_apps/rust/Cargo.toml after sync_versions");
    let embedded_hash = crate::core::hash::extract_hash(&rewritten)
        .expect("test_apps/rust/Cargo.toml must carry an alef:hash: line after sync_versions");
    let stripped = crate::core::hash::strip_hash_line(&rewritten);
    let expected_hash = crate::core::hash::compute_file_hash(&stripped);

    assert_eq!(
        embedded_hash, expected_hash,
        "test_apps/rust/Cargo.toml's embedded alef:hash: must cover the file's own final, \
         post-registry-bump bytes — it was rewritten by this very sync_versions run and must \
         not be stamped against stale (pre-bump) content. embedded={embedded_hash} \
         expected={expected_hash}\n\nfile content:\n{rewritten}"
    );
}