surelock 0.1.0

Deadlock-free locks for Rust with compile time guarantees, incremental locks, and atomic lock sets.
Documentation
//! Property-based tests using bolero.

#![allow(clippy::unwrap_used, clippy::indexing_slicing)]

use surelock::{key::lock_scope, mutex::Mutex, set::LockSet};

/// `LockId`s are strictly monotonically increasing: for any sequence
/// of N mutexes created in order, each ID is greater than the last.
#[test]
fn lockid_monotonicity() {
    bolero::check!().with_type::<u8>().for_each(|&count| {
        let n = (count as usize).min(64); // cap to avoid OOM
        let mutexes: Vec<Mutex<()>> = (0..n).map(|_| Mutex::new(())).collect();
        for window in mutexes.windows(2) {
            assert!(
                window[0].id() < window[1].id(),
                "LockId must be strictly monotonically increasing"
            );
        }
    });
}

/// For any pair of mutexes provided in any order, `LockSet` acquires
/// both successfully and the guards correspond to the original mutexes.
#[test]
fn lockset_sort_invariant_2tuple() {
    bolero::check!()
        .with_type::<(u32, u32, bool)>()
        .for_each(|&(val_a, val_b, reverse)| {
            let a: Mutex<u32> = Mutex::new(val_a);
            let b: Mutex<u32> = Mutex::new(val_b);

            let set = if reverse {
                LockSet::new((&b, &a))
            } else {
                LockSet::new((&a, &b))
            };

            lock_scope(|key| {
                let ((g1, g2), _key) = key.lock(&set);
                if reverse {
                    assert_eq!(*g1, val_b, "first guard should match b");
                    assert_eq!(*g2, val_a, "second guard should match a");
                } else {
                    assert_eq!(*g1, val_a, "first guard should match a");
                    assert_eq!(*g2, val_b, "second guard should match b");
                }
            });
        });
}

/// For any slice of N mutexes, `LockSet` acquires all of them and
/// the guards contain the correct values (sum is preserved).
#[test]
fn lockset_slice_preserves_values() {
    bolero::check!().with_type::<u8>().for_each(|&count| {
        let n = (count as usize).clamp(1, 16);
        let mutexes: Vec<Mutex<u64>> = (0..n).map(|i| Mutex::new(i as u64)).collect();
        let expected_sum: u64 = (0..n as u64).sum();

        let set = LockSet::new(mutexes.as_slice());
        lock_scope(|key| {
            let (guards, _key) = key.lock(&set);
            let actual_sum: u64 = guards.iter().map(|g| **g).sum();
            assert_eq!(
                actual_sum, expected_sum,
                "sum of locked values must match sum of original values"
            );
        });
    });
}