fastskip 0.1.1

Lock-free arena-backed skip list memtable for LSM-tree storage engines
Documentation
use fastskip::ConcurrentSkipList;

#[test]
fn test_insert_10k_sequential() {
    let sl = ConcurrentSkipList::new();
    for i in 0..10_000 {
        let key = format!("key_{:06}", i);
        let val = format!("val_{:06}", i);
        assert!(sl.insert(key.as_bytes(), val.as_bytes()));
    }

    for i in 0..10_000 {
        let key = format!("key_{:06}", i);
        let expected = format!("val_{:06}", i);
        match sl.get(key.as_bytes()) {
            Some((v, false)) => assert_eq!(v, expected.as_bytes(), "mismatch at key {}", i),
            other => panic!("expected value for key {}, got {:?}", i, other),
        }
    }

    assert_eq!(sl.len(), 10_000);
}

#[test]
fn test_insert_10k_random() {
    use std::collections::HashSet;

    let sl = ConcurrentSkipList::new();
    let mut keys = HashSet::new();

    for i in 0u64..10_000 {
        let k = (i.wrapping_mul(2654435761) % 10_000) as u32;
        let key = format!("key_{:06}", k);
        let val = format!("val_{:06}", k);
        if keys.insert(k) {
            assert!(sl.insert(key.as_bytes(), val.as_bytes()));
        } else {
            assert!(!sl.insert(key.as_bytes(), val.as_bytes()));
        }
    }

    for &k in &keys {
        let key = format!("key_{:06}", k);
        let expected = format!("val_{:06}", k);
        match sl.get(key.as_bytes()) {
            Some((v, false)) => assert_eq!(v, expected.as_bytes()),
            other => panic!("expected value for key {}, got {:?}", k, other),
        }
    }
}

#[test]
fn test_delete_half() {
    let sl = ConcurrentSkipList::new();

    for i in 0..1000 {
        let key = format!("key_{:04}", i);
        let val = format!("val_{:04}", i);
        sl.insert(key.as_bytes(), val.as_bytes());
    }

    for i in (0..1000).step_by(2) {
        let key = format!("key_{:04}", i);
        assert!(sl.delete(key.as_bytes()));
    }

    for i in 0..1000 {
        let key = format!("key_{:04}", i);
        if i % 2 == 0 {
            match sl.get(key.as_bytes()) {
                Some((_, true)) => {}
                other => panic!("expected tombstone for key {}, got {:?}", i, other),
            }
            assert_eq!(sl.get_live(key.as_bytes()), None);
        } else {
            let expected = format!("val_{:04}", i);
            match sl.get(key.as_bytes()) {
                Some((v, false)) => assert_eq!(v, expected.as_bytes()),
                other => panic!("expected live value for key {}, got {:?}", i, other),
            }
        }
    }
}

#[test]
fn test_snapshot_consistency() {
    let sl = ConcurrentSkipList::new();
    for i in 0..100 {
        let key = format!("key_{:04}", i);
        let val = format!("val_{:04}", i);
        sl.insert(key.as_bytes(), val.as_bytes());
    }

    let snap = sl.snapshot();
    let entries: Vec<_> = snap.iter().collect();
    assert_eq!(entries.len(), 100);

    for (i, entry) in entries.iter().enumerate() {
        let expected_key = format!("key_{:04}", i);
        assert_eq!(entry.key, expected_key.as_bytes());
    }
}

#[test]
fn test_get_live_returns_none_for_missing() {
    let sl = ConcurrentSkipList::new();
    assert_eq!(sl.get_live(b"nonexistent"), None);
    assert_eq!(sl.get(b"nonexistent"), None);
}

#[test]
fn test_duplicate_insert() {
    let sl = ConcurrentSkipList::new();
    assert!(sl.insert(b"key", b"val1"));
    assert!(!sl.insert(b"key", b"val2"));

    match sl.get(b"key") {
        Some((v, false)) => assert_eq!(v, b"val1"),
        other => panic!("expected val1, got {:?}", other),
    }
}

#[test]
fn test_memory_usage() {
    let sl = ConcurrentSkipList::new();
    let initial = sl.memory_usage();

    for i in 0..100 {
        let key = format!("key_{:04}", i);
        let val = format!("val_{:04}", i);
        sl.insert(key.as_bytes(), val.as_bytes());
    }

    let after = sl.memory_usage();
    assert!(after > initial, "memory should increase after inserts");
}