armdb 0.1.10

sharded bitcask key-value storage optimized for NVMe
Documentation
use std::sync::Arc;
use std::thread;

use armdb::{FixedConfig, FixedMap, FixedTree};
use tempfile::tempdir;

fn test_config() -> FixedConfig {
    FixedConfig {
        shard_count: 4,
        grow_step: 64,
        ..FixedConfig::test()
    }
}

// ---------------------------------------------------------------------------
// FixedTree concurrent writes — 8 threads, disjoint key ranges
// ---------------------------------------------------------------------------

#[test]
fn test_fixed_tree_concurrent_writes() {
    let dir = tempdir().expect("failed to create tempdir");
    let tree = Arc::new(
        FixedTree::<[u8; 8], 8>::open(dir.path(), test_config())
            .expect("failed to open fixed tree"),
    );

    let mut handles = Vec::new();

    for t in 0..8u64 {
        let tree = Arc::clone(&tree);
        let handle = thread::spawn(move || {
            for i in 0..100u64 {
                let key = (t * 1000 + i).to_be_bytes();
                let value = (t * 1000 + i).to_be_bytes();
                tree.put(&key, &value).expect("put failed");
            }
        });
        handles.push(handle);
    }

    for handle in handles {
        handle.join().expect("thread panicked");
    }

    assert_eq!(tree.len(), 800, "expected 800 keys from 8 threads x 100");

    // Verify all keys are readable with correct values
    for t in 0..8u64 {
        for i in 0..100u64 {
            let id = t * 1000 + i;
            let key = id.to_be_bytes();
            let expected = id.to_be_bytes();
            let got = tree
                .get(&key)
                .unwrap_or_else(|| panic!("key {} not found", id));
            assert_eq!(got, expected, "value mismatch for key {}", id);
        }
    }
}

// ---------------------------------------------------------------------------
// FixedTree concurrent read + write — pre-populate, then mixed readers/writers
// ---------------------------------------------------------------------------

#[test]
fn test_fixed_tree_concurrent_read_write() {
    let dir = tempdir().expect("failed to create tempdir");
    let tree = Arc::new(
        FixedTree::<[u8; 8], 8>::open(dir.path(), test_config())
            .expect("failed to open fixed tree"),
    );

    // Pre-populate 100 keys
    for i in 0..100u64 {
        let key = i.to_be_bytes();
        let value = (i * 7).to_be_bytes();
        tree.put(&key, &value).expect("pre-populate put failed");
    }

    let mut handles = Vec::new();

    for t in 0..4u64 {
        let tree = Arc::clone(&tree);
        if t % 2 == 0 {
            // Even threads: overwrite existing keys with updated values
            let handle = thread::spawn(move || {
                for i in 0..100u64 {
                    let key = i.to_be_bytes();
                    let value = (i * 7 + t + 1).to_be_bytes();
                    tree.put(&key, &value).expect("writer put failed");
                }
            });
            handles.push(handle);
        } else {
            // Odd threads: read keys and verify values are consistent
            let handle = thread::spawn(move || {
                for _ in 0..500u64 {
                    for i in 0..100u64 {
                        let key = i.to_be_bytes();
                        // Value may be the original or an updated one — just verify it exists
                        let _val = tree.get(&key);
                    }
                }
            });
            handles.push(handle);
        }
    }

    for handle in handles {
        handle.join().expect("thread panicked");
    }

    // Count should still be 100 — no new keys were added
    assert_eq!(tree.len(), 100, "overwrites should not change len");
}

// ---------------------------------------------------------------------------
// FixedMap concurrent writes — 8 threads, disjoint key ranges
// ---------------------------------------------------------------------------

#[test]
fn test_fixed_map_concurrent_writes() {
    let dir = tempdir().expect("failed to create tempdir");
    let map = Arc::new(
        FixedMap::<[u8; 8], 8>::open(dir.path(), test_config()).expect("failed to open fixed map"),
    );

    let mut handles = Vec::new();

    for t in 0..8u64 {
        let map = Arc::clone(&map);
        let handle = thread::spawn(move || {
            for i in 0..100u64 {
                let key = (t * 1000 + i).to_be_bytes();
                let value = (t * 1000 + i).to_be_bytes();
                map.put(&key, &value).expect("put failed");
            }
        });
        handles.push(handle);
    }

    for handle in handles {
        handle.join().expect("thread panicked");
    }

    assert_eq!(map.len(), 800, "expected 800 keys from 8 threads x 100");

    // Verify all keys are readable with correct values
    for t in 0..8u64 {
        for i in 0..100u64 {
            let id = t * 1000 + i;
            let key = id.to_be_bytes();
            let expected = id.to_be_bytes();
            let got = map
                .get(&key)
                .unwrap_or_else(|| panic!("key {} not found", id));
            assert_eq!(got, expected, "value mismatch for key {}", id);
        }
    }
}

// ---------------------------------------------------------------------------
// FixedTree stress overwrite — 8 threads all hammer the same key
// ---------------------------------------------------------------------------

#[test]
fn test_fixed_tree_stress_overwrite() {
    let dir = tempdir().expect("failed to create tempdir");
    let tree = Arc::new(
        FixedTree::<[u8; 8], 8>::open(dir.path(), test_config())
            .expect("failed to open fixed tree"),
    );

    let key = 42u64.to_be_bytes();

    let mut handles = Vec::new();

    for t in 0..8u64 {
        let tree = Arc::clone(&tree);
        let handle = thread::spawn(move || {
            for i in 0..1000u64 {
                let value = (t * 1000 + i).to_be_bytes();
                tree.put(&key, &value).expect("put failed");
            }
        });
        handles.push(handle);
    }

    for handle in handles {
        handle.join().expect("thread panicked");
    }

    assert_eq!(tree.len(), 1, "all overwrites on same key, len must be 1");

    // The key must exist and hold a valid value (one of the written values)
    let val = tree
        .get(&key)
        .expect("key should exist after stress overwrites");
    let stored = u64::from_be_bytes(val);
    // The stored value should be in the range of values we wrote
    assert!(
        stored < 8 * 1000,
        "stored value {stored} outside expected range 0..8000"
    );
}