alef 0.67.1

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, `sync_versions` must have already written the bumped value to
/// `alef.toml` on disk *before* it re-derives `inputs_hash` and 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 that write) ran *after* the
/// `finalize_hashes` call, so every file `sync_versions` rewrote in the same run was stamped
/// against the pre-bump `inputs_hash` — stale from the moment it was written, because the very
/// next re-derivation of `inputs_hash` (any later `alef verify` or `alef generate`) sees the
/// post-bump `alef.toml` and computes a different value.
///
/// This test reproduces the ordering bug directly: it re-derives `inputs_hash` from the
/// post-run `alef.toml` and Rust sources exactly as `alef verify` does, and 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. ~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);
    let source_hash_paths = resolved_cfg.source_hash_paths();

    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");

    // Re-derive `inputs_hash` exactly as `alef verify` does: from the *current* (post-run)
    // `alef.toml` bytes and the crate's Rust sources.
    let post_run_alef_toml_bytes = crate::cli::cache::read_alef_toml_bytes(&alef_toml_path);
    let sources_hash = crate::cli::cache::sources_hash(&source_hash_paths).expect("compute sources hash");
    let inputs_hash = crate::core::hash::compute_inputs_hash(&sources_hash, &post_run_alef_toml_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(&inputs_hash, &stripped);

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