use std::sync::Arc;
use std::sync::atomic::{AtomicUsize, Ordering};
use std::thread;
use std::time::Duration;
use tempfile::TempDir;
use crate::brain::tools::path_lock::{acquire, contention_notice};
#[test]
fn an_uncontended_write_gets_the_lock() {
let dir = TempDir::new().unwrap();
let target = dir.path().join("only-writer.txt");
std::fs::write(&target, "x").unwrap();
let lock = acquire(&target).expect("a lock is always returned");
assert!(lock.is_held(), "nobody else is writing");
}
#[test]
fn a_second_writer_is_told_it_did_not_get_the_lock() {
let dir = TempDir::new().unwrap();
let target = dir.path().join("contended.txt");
std::fs::write(&target, "x").unwrap();
let first = acquire(&target).expect("first lock");
assert!(first.is_held());
let second = acquire(&target).expect("second lock is still returned");
assert!(
!second.is_held(),
"the second writer must know it did not hold the lock"
);
}
#[test]
fn releasing_lets_the_next_writer_in() {
let dir = TempDir::new().unwrap();
let target = dir.path().join("sequential.txt");
std::fs::write(&target, "x").unwrap();
{
let first = acquire(&target).expect("first lock");
assert!(first.is_held());
}
let second = acquire(&target).expect("second lock");
assert!(second.is_held(), "the lock was released with the guard");
}
#[test]
fn a_holder_that_panics_still_releases() {
let dir = TempDir::new().unwrap();
let target = dir.path().join("panicky.txt");
std::fs::write(&target, "x").unwrap();
let path = target.clone();
let _ = std::panic::catch_unwind(move || {
let _lock = acquire(&path).expect("lock");
panic!("writer died mid-write");
});
let after = acquire(&target).expect("lock");
assert!(after.is_held(), "the dead writer's lock is gone");
}
#[test]
fn separate_paths_never_contend() {
let dir = TempDir::new().unwrap();
let a = dir.path().join("a.txt");
let b = dir.path().join("b.txt");
std::fs::write(&a, "a").unwrap();
std::fs::write(&b, "b").unwrap();
let lock_a = acquire(&a).expect("lock a");
let lock_b = acquire(&b).expect("lock b");
assert!(lock_a.is_held() && lock_b.is_held());
}
#[test]
fn concurrent_writers_take_the_lock_one_at_a_time() {
let dir = TempDir::new().unwrap();
let target = dir.path().join("shared.txt");
std::fs::write(&target, "x").unwrap();
let inside = Arc::new(AtomicUsize::new(0));
let overlaps = Arc::new(AtomicUsize::new(0));
let mut handles = Vec::new();
for _ in 0..4 {
let target = target.clone();
let inside = Arc::clone(&inside);
let overlaps = Arc::clone(&overlaps);
handles.push(thread::spawn(move || {
let lock = acquire(&target).expect("lock");
if lock.is_held() {
if inside.fetch_add(1, Ordering::SeqCst) != 0 {
overlaps.fetch_add(1, Ordering::SeqCst);
}
thread::sleep(Duration::from_millis(20));
inside.fetch_sub(1, Ordering::SeqCst);
}
}));
}
for h in handles {
h.join().expect("thread");
}
assert_eq!(
overlaps.load(Ordering::SeqCst),
0,
"two holders were inside the lock at once"
);
}
#[test]
fn the_notice_names_the_file_and_what_it_means() {
let notice = contention_notice(std::path::Path::new("/tmp/thing.rs"));
assert!(notice.contains("/tmp/thing.rs"));
assert!(notice.contains("re-read"), "got: {notice}");
}