use super::*;
use crate::test_support::{fixture, remove};
use std::sync::Mutex;
fn index_in(name: &str) -> (PathBuf, PathBuf) {
let root: PathBuf = fixture(name);
let git_dir: PathBuf = root.join("git-dir");
std::fs::create_dir(&git_dir).expect("directory");
let index: PathBuf = git_dir.join("index");
std::fs::write(&index, "old index").expect("index");
return (root, index);
}
#[test]
fn the_lock_is_exclusive_and_its_release_installs_the_new_index() {
let (root, index) = index_in("index-lock-release");
let lock = acquire(&index, 7, Duration::from_secs(1), &|_| {}).expect("taken");
assert_eq!(std::fs::read_to_string(lock_path_of(&index)).expect("marker"), "cli-git-service 7\n");
let built: PathBuf = index.with_file_name("index.built");
std::fs::write(&built, "new index").expect("built");
lock.release_with(&built).expect("released");
assert_eq!(std::fs::read_to_string(&index).expect("index"), "new index");
assert!(!lock_path_of(&index).exists() && !built.exists());
remove(root.as_path());
}
#[test]
fn the_release_renames_the_built_index_into_place_before_the_lock_goes() {
use std::os::unix::fs::MetadataExt;
let (root, index) = index_in("index-lock-rename");
let lock = acquire(&index, 8, Duration::from_secs(1), &|_| {}).expect("taken");
let built: PathBuf = index.with_file_name("index.cli-git-8");
std::fs::write(&built, "new index").expect("built");
let built_inode: u64 = std::fs::metadata(&built).expect("built").ino();
lock.release_with(&built).expect("released");
assert_eq!(std::fs::metadata(&index).expect("index").ino(), built_inode, "installed by rename, not by a copy");
assert_eq!(std::fs::read_to_string(&index).expect("index"), "new index");
assert!(!lock_path_of(&index).exists() && !built.exists());
remove(root.as_path());
}
#[test]
fn a_staging_file_a_kill_left_behind_is_overwritten_and_never_left() {
let (root, index) = index_in("index-lock-staged");
let staged: PathBuf = staged_path_of(&lock_path_of(&index));
std::fs::write(&staged, "cli-git-service 41\npartial").expect("a killed attempt's staging file");
let lock = acquire(&index, 42, Duration::from_secs(1), &|_| {}).expect("taken");
assert_eq!(std::fs::read_to_string(lock_path_of(&index)).expect("marker"), "cli-git-service 42\n");
assert!(!staged.exists(), "the staging file is gone once the lock exists");
drop(lock);
std::fs::write(&staged, "left by another kill").expect("again");
let held = try_acquire(&index, 43).expect("an attempt without a wait takes it too");
assert_eq!(std::fs::read_to_string(lock_path_of(&index)).expect("marker"), "cli-git-service 43\n");
assert!(!staged.exists());
drop(held);
remove(root.as_path());
}
#[test]
fn a_lock_that_is_not_released_leaves_the_index_as_it_was() {
let (root, index) = index_in("index-lock-drop");
drop(acquire(&index, 1, Duration::from_secs(1), &|_| {}).expect("taken"));
assert!(!lock_path_of(&index).exists());
assert_eq!(std::fs::read_to_string(&index).expect("index"), "old index");
remove(root.as_path());
}
#[test]
fn a_wait_ends_when_the_foreign_holder_lets_go() {
let (root, index) = index_in("index-lock-wait");
std::fs::write(lock_path_of(&index), "foreign").expect("foreign lock");
let foreign: PathBuf = lock_path_of(&index);
let release = std::thread::spawn(move || {
std::thread::sleep(Duration::from_millis(400));
std::fs::remove_file(foreign).expect("the foreign holder lets go");
});
let heard: Mutex<Vec<Announce>> = Mutex::new(Vec::new());
let began: Instant = Instant::now();
let lock = acquire(&index, 2, Duration::from_secs(30), &|note| { heard.lock().expect("lock").push(note); }).expect("taken after the wait");
let waited: Duration = began.elapsed();
release.join().expect("thread");
assert!(waited >= Duration::from_millis(300) && waited < NOTICE_EVERY, "{waited:?}");
assert_eq!(std::fs::read_to_string(lock_path_of(&index)).expect("marker"), "cli-git-service 2\n");
drop(lock);
remove(root.as_path());
}
#[test]
fn a_holder_that_outlives_the_limit_ends_the_wait_with_nothing_changed() {
let (root, index) = index_in("index-lock-limit");
std::fs::write(lock_path_of(&index), "foreign").expect("foreign lock");
let heard: Mutex<Vec<Announce>> = Mutex::new(Vec::new());
let began: Instant = Instant::now();
let failure = acquire(&index, 3, Duration::from_millis(500), &|note| { heard.lock().expect("lock").push(note); }).expect_err("refused");
assert_eq!(failure.code, FailureCode::IndexInUse);
assert!(began.elapsed() >= Duration::from_millis(500) && began.elapsed() < Duration::from_secs(10), "{:?}", began.elapsed());
assert_eq!(std::fs::read_to_string(lock_path_of(&index)).expect("foreign lock stands"), "foreign");
assert_eq!(std::fs::read_to_string(&index).expect("index"), "old index");
assert!(heard.lock().expect("lock").iter().any(|note| { return matches!(note, Announce::Holder(_)); }), "the holder was announced");
remove(root.as_path());
}
#[test]
fn a_dead_service_instances_lock_does_not_block_the_next_landing() {
let (root, index) = index_in("index-lock-dead-instance");
std::fs::write(lock_path_of(&index), "cli-git-service 99\n").expect("a killed service's lock");
let began: Instant = Instant::now();
let lock = acquire(&index, 100, Duration::from_secs(30), &|_| {}).expect("taken at once");
assert!(began.elapsed() < Duration::from_secs(5), "{:?}", began.elapsed());
assert_eq!(std::fs::read_to_string(lock_path_of(&index)).expect("marker"), "cli-git-service 100\n");
drop(lock);
std::fs::write(lock_path_of(&index), "cli-git-service 7\n").expect("again");
assert!(try_acquire(&index, 101).is_some(), "an attempt without a wait removes it too");
remove(root.as_path());
}
#[test]
fn a_stale_lock_is_removed_only_when_it_is_the_services_own() {
let (root, index) = index_in("index-lock-stale");
std::fs::write(lock_path_of(&index), "cli-git-service 5\n").expect("ours");
remove_stale(&index, 6);
assert!(lock_path_of(&index).exists(), "another batch's marker is left");
remove_stale(&index, 5);
assert!(!lock_path_of(&index).exists(), "its own marker is removed");
std::fs::write(lock_path_of(&index), "DIRCbinary").expect("foreign");
remove_stale(&index, 5);
assert!(lock_path_of(&index).exists(), "a foreign lock is never touched");
remove(root.as_path());
}