armdb 0.7.0

sharded bitcask key-value storage optimized for NVMe
Documentation
//! The headline contract: two *processes* cannot open one directory.
//!
//! An in-process test exercises the same kernel path (flock binds to the open
//! file description, not the process), but it cannot prove the cross-process
//! contract — so this one re-executes the test binary as a child. The child
//! runs the same `#[test]` and picks its role from the env var.
#![cfg(feature = "armour")]

use std::path::Path;
use std::process::Command;

use armdb::armour::Db;
use armdb::{Config, ConstTree, DbError};
use tempfile::tempdir;

const ROOT_ENV: &str = "ARMDB_TEST_LOCK_CHILD_ROOT";
const COLLECTION_ENV: &str = "ARMDB_TEST_LOCK_CHILD_COLLECTION";

/// Re-run this very test binary, asking it to execute only `test_name` with the
/// child role selected. A zero exit means the child observed `DbError::Locked`.
fn run_child(test_name: &str, env_key: &str, dir: &Path) {
    let exe = std::env::current_exe().expect("current_exe");
    let out = Command::new(exe)
        .args(["--exact", test_name, "--nocapture"])
        .env(env_key, dir)
        .output()
        .expect("spawn child process");

    let stdout = String::from_utf8_lossy(&out.stdout);
    let stderr = String::from_utf8_lossy(&out.stderr);

    assert!(
        out.status.success(),
        "child did not observe the lock (status {:?})\n--- stdout ---\n{stdout}\n--- stderr ---\n{stderr}",
        out.status.code(),
    );

    // `test_name` is a hand-copied literal, and libtest exits 0 when a filter
    // matches nothing ("running 0 tests"). Without this the whole test decays
    // into a silent no-op the moment the `#[test]` fn is renamed.
    assert!(
        stdout.contains("1 passed"),
        "child ran no test — `{test_name}` no longer names a #[test] fn in this binary\
         \n--- stdout ---\n{stdout}\n--- stderr ---\n{stderr}",
    );
}

#[test]
fn root_lock_holds_across_processes() {
    if let Ok(dir) = std::env::var(ROOT_ENV) {
        // Child role: the parent holds this root.
        let res = Db::open_test(&dir);
        match res {
            Ok(_) => panic!("child must not open a root locked by the parent"),
            Err(e) => assert!(matches!(e, DbError::Locked { .. }), "got {e:?}"),
        }
        return;
    }

    let dir = tempdir().unwrap();
    let _held = Db::open_test(dir.path()).expect("parent opens the root");
    run_child("root_lock_holds_across_processes", ROOT_ENV, dir.path());
}

#[test]
fn collection_lock_holds_across_processes() {
    if let Ok(dir) = std::env::var(COLLECTION_ENV) {
        // Child role: the parent holds this collection.
        let res = ConstTree::<[u8; 8], 8>::open(&dir, Config::test());
        match res {
            Ok(_) => panic!("child must not open a collection locked by the parent"),
            Err(e) => assert!(matches!(e, DbError::Locked { .. }), "got {e:?}"),
        }
        return;
    }

    let dir = tempdir().unwrap();
    let _held = ConstTree::<[u8; 8], 8>::open(dir.path(), Config::test())
        .expect("parent opens the collection");
    run_child(
        "collection_lock_holds_across_processes",
        COLLECTION_ENV,
        dir.path(),
    );
}