prolly-map 0.3.0

Content-addressed versioned map storage primitives.
Documentation
use std::fs;
use std::path::{Path, PathBuf};
use std::process::Command;
use std::sync::Arc;
use std::time::{SystemTime, UNIX_EPOCH};

use prolly::{Config, FileNodeStore, Prolly};

fn temp_store_dir(name: &str) -> PathBuf {
    let nanos = SystemTime::now()
        .duration_since(UNIX_EPOCH)
        .map(|duration| duration.as_nanos())
        .unwrap_or_default();
    let path = std::env::temp_dir().join(format!(
        "prolly-inspect-cli-{name}-{}-{nanos}",
        std::process::id()
    ));
    let _ = fs::remove_dir_all(&path);
    path
}

fn config() -> Config {
    Config::builder()
        .min_chunk_size(2)
        .max_chunk_size(4)
        .chunking_factor(2)
        .hash_seed(42)
        .build()
}

fn build_fixture(path: &Path) {
    let store = Arc::new(FileNodeStore::open(path).unwrap());
    let prolly = Prolly::new(store, config());
    let mut main = prolly.create();

    for idx in 0..32 {
        main = prolly
            .put(
                &main,
                format!("k{idx:03}").into_bytes(),
                format!("v{idx:03}").into_bytes(),
            )
            .unwrap();
    }

    let mut feature = main.clone();
    feature = prolly
        .put(&feature, b"k010".to_vec(), b"feature-010".to_vec())
        .unwrap();
    feature = prolly
        .put(&feature, b"k999".to_vec(), b"feature-999".to_vec())
        .unwrap();

    prolly
        .publish_named_root_at_millis(b"main", &main, 1_000)
        .unwrap();
    prolly
        .publish_named_root_at_millis(b"feature", &feature, 2_000)
        .unwrap();
}

fn run_inspect(path: &Path, args: &[&str]) -> String {
    let output = Command::new(env!("CARGO_BIN_EXE_prolly-inspect"))
        .arg(path)
        .args(args)
        .output()
        .unwrap();
    assert!(
        output.status.success(),
        "command failed: stdout={} stderr={}",
        String::from_utf8_lossy(&output.stdout),
        String::from_utf8_lossy(&output.stderr)
    );
    String::from_utf8(output.stdout).unwrap()
}

#[test]
fn inspect_cli_reports_named_roots_stats_walk_changes_and_reachability() {
    let path = temp_store_dir("fixture");
    build_fixture(&path);

    let roots = run_inspect(&path, &["roots"]);
    assert!(roots.contains("roots=2"));
    assert!(roots.contains("name=\"feature\""));
    assert!(roots.contains("name=\"main\""));

    let stats = run_inspect(&path, &["stats", "main"]);
    assert!(stats.contains("root_name=\"main\""));
    assert!(stats.contains("Tree Structure Statistics"));
    assert!(stats.contains("Key-value pairs:    32"));

    let walk = run_inspect(&path, &["walk", "main"]);
    assert!(walk.contains("level"));
    assert!(walk.contains("fill="));

    let compare = run_inspect(&path, &["compare", "main", "feature"]);
    assert!(compare.contains("shared="));
    assert!(compare.contains("right_only="));

    let changed = run_inspect(&path, &["changed", "main", "feature", "--span-size", "1"]);
    assert!(changed.contains("changes=2"));
    assert!(changed.contains("span=1"));
    assert!(changed.contains("changed=1"));
    assert!(changed.contains("added=1"));

    let verify = run_inspect(&path, &["verify", "--all"]);
    assert!(verify.contains("status=ok"));
    assert!(verify.contains("retained_roots=2"));
    assert!(verify.contains("missing_candidates=0"));

    let _ = fs::remove_dir_all(path);
}