use super::*;
use crate::test_support::{fixture, remove};
use std::path::PathBuf;
const QUICK: Cadence = Cadence { grace: Duration::from_millis(20), interval: Duration::from_millis(20) };
fn index_in(root: &Path) -> PathBuf {
return root.join("index");
}
fn marked(seq: u64) -> String {
return format!("{LOCK_MARKER}{seq}\n");
}
#[test]
fn the_holder_is_told_by_the_mark() {
let root: PathBuf = fixture("index-wait-holder");
let lock: PathBuf = lock_path_of(index_in(root.as_path()).as_path());
assert_eq!(holder_of(lock.as_path()), Holder::Nobody);
std::fs::write(&lock, marked(7)).expect("lock");
assert_eq!(holder_of(lock.as_path()), Holder::Service);
std::fs::write(&lock, "").expect("lock");
assert_eq!(holder_of(lock.as_path()), Holder::Foreign, "an empty lock is a program that has not written its index yet");
std::fs::write(&lock, "DIRC\0\0\0\u{2}").expect("lock");
assert_eq!(holder_of(lock.as_path()), Holder::Foreign);
std::fs::write(&lock, &LOCK_MARKER.as_bytes()[..LOCK_MARKER.len() / 2]).expect("lock");
assert_eq!(holder_of(lock.as_path()), Holder::Foreign, "half a mark is no mark");
remove(root.as_path());
}
#[test]
fn a_mark_that_arrives_a_moment_late_is_still_the_services() {
let root: PathBuf = fixture("index-wait-late");
let lock: PathBuf = lock_path_of(index_in(root.as_path()).as_path());
std::fs::write(&lock, "").expect("lock");
let writer: PathBuf = lock.clone();
let late = std::thread::spawn(move || {
std::thread::sleep(EMPTY_PAUSE);
std::fs::write(writer, marked(1)).expect("mark");
});
assert_eq!(holder_of(lock.as_path()), Holder::Service);
late.join().expect("writer");
remove(root.as_path());
}
#[test]
fn nothing_is_waited_for_when_nobody_or_a_foreign_program_holds_the_index() {
let root: PathBuf = fixture("index-wait-none");
let index: PathBuf = index_in(root.as_path());
let mut out: Vec<u8> = Vec::new();
assert_eq!(wait_for_service_hold(index.as_path(), "add", Duration::from_secs(5), QUICK, &mut out), Waited::NotHeld);
std::fs::write(lock_path_of(index.as_path()), b"not the service\n").expect("lock");
assert_eq!(wait_for_service_hold(index.as_path(), "add", Duration::from_secs(5), QUICK, &mut out), Waited::NotHeld);
assert!(out.is_empty(), "{}", String::from_utf8_lossy(&out));
remove(root.as_path());
}
#[test]
fn the_services_hold_is_waited_for_until_it_is_released() {
let root: PathBuf = fixture("index-wait-release");
let index: PathBuf = index_in(root.as_path());
let lock: PathBuf = lock_path_of(index.as_path());
std::fs::write(&lock, marked(3)).expect("lock");
let released: PathBuf = lock.clone();
let releaser = std::thread::spawn(move || {
std::thread::sleep(Duration::from_millis(150));
std::fs::remove_file(released).expect("release");
});
let began: Instant = Instant::now();
let mut out: Vec<u8> = Vec::new();
let outcome: Waited = wait_for_service_hold(index.as_path(), "rebase", Duration::from_secs(30), QUICK, &mut out);
releaser.join().expect("releaser");
assert_eq!(outcome, Waited::Released);
assert!(began.elapsed() < Duration::from_secs(5), "the release woke the wait: {:?}", began.elapsed());
let text: String = String::from_utf8_lossy(&out).into_owned();
assert!(text.contains("git rebase") && text.contains("commit service"), "{text}");
assert!(!text.contains("index.lock") && !text.contains("lock"), "no internal name reaches the person: {text}");
remove(root.as_path());
}
#[test]
fn a_hold_that_outlasts_the_limit_ends_the_wait_and_stays() {
let root: PathBuf = fixture("index-wait-limit");
let index: PathBuf = index_in(root.as_path());
let lock: PathBuf = lock_path_of(index.as_path());
std::fs::write(&lock, marked(9)).expect("lock");
let mut out: Vec<u8> = Vec::new();
assert_eq!(wait_for_service_hold(index.as_path(), "add", Duration::from_millis(100), QUICK, &mut out), Waited::GaveUp);
let text: String = String::from_utf8_lossy(&out).into_owned();
assert!(text.contains("still finishing a commit") && text.contains("git add runs now"), "{text}");
assert!(lock.exists(), "the hold is not the command's to remove");
remove(root.as_path());
}
#[test]
fn a_hold_older_than_any_landing_is_not_waited_for() {
let root: PathBuf = fixture("index-wait-stale");
let index: PathBuf = index_in(root.as_path());
let lock: PathBuf = lock_path_of(index.as_path());
std::fs::write(&lock, marked(5)).expect("lock");
let long_ago: std::time::SystemTime = std::time::SystemTime::now() - HOLD_LIMIT - Duration::from_secs(60);
std::fs::File::options().write(true).open(&lock).expect("open").set_modified(long_ago).expect("age");
let began: Instant = Instant::now();
let mut out: Vec<u8> = Vec::new();
assert_eq!(wait_for_service_hold(index.as_path(), "add", Duration::from_secs(60), QUICK, &mut out), Waited::Stale);
assert!(began.elapsed() < Duration::from_secs(5), "{:?}", began.elapsed());
assert!(String::from_utf8_lossy(&out).contains("does not wait for it"), "{}", String::from_utf8_lossy(&out));
assert!(lock.exists());
remove(root.as_path());
}
#[test]
fn a_hold_released_within_the_grace_prints_nothing() {
let root: PathBuf = fixture("index-wait-grace");
let index: PathBuf = index_in(root.as_path());
let lock: PathBuf = lock_path_of(index.as_path());
std::fs::write(&lock, marked(4)).expect("lock");
let released: PathBuf = lock.clone();
let releaser = std::thread::spawn(move || {
std::thread::sleep(Duration::from_millis(20));
std::fs::remove_file(released).expect("release");
});
let mut out: Vec<u8> = Vec::new();
let slow: Cadence = Cadence { grace: Duration::from_secs(30), interval: Duration::from_secs(30) };
assert_eq!(wait_for_service_hold(index.as_path(), "add", Duration::from_secs(60), slow, &mut out), Waited::Released);
releaser.join().expect("releaser");
assert!(out.is_empty(), "{}", String::from_utf8_lossy(&out));
remove(root.as_path());
}
#[test]
fn a_watch_that_ended_is_set_again_and_the_release_that_follows_is_seen() {
let root: PathBuf = fixture("index-wait-watch-ended");
let index: PathBuf = index_in(root.as_path());
let lock: PathBuf = lock_path_of(index.as_path());
std::fs::write(&lock, marked(6)).expect("lock");
let decoy: PathBuf = root.join("decoy");
std::fs::create_dir(&decoy).expect("decoy directory");
let set: std::cell::Cell<u32> = std::cell::Cell::new(0);
let set_watch = |directory: &Path| -> Option<Watch> {
set.set(set.get() + 1);
if set.get() > 1 {
return Watch::on(directory);
}
let ending: Option<Watch> = Watch::on(decoy.as_path());
std::fs::remove_dir(&decoy).expect("the watched directory goes");
return ending;
};
let released: PathBuf = lock.clone();
let releaser = std::thread::spawn(move || {
std::thread::sleep(Duration::from_millis(300));
std::fs::remove_file(released).expect("release");
});
let began: Instant = Instant::now();
let mut out: Vec<u8> = Vec::new();
let slow: Cadence = Cadence { grace: Duration::from_secs(30), interval: Duration::from_secs(30) };
let outcome: Waited = wait_with_watches(index.as_path(), "add", Duration::from_secs(60), slow, &mut out, &set_watch);
releaser.join().expect("releaser");
assert_eq!(outcome, Waited::Released);
assert_eq!(set.get(), 2, "a new watch was set once the first had ended");
assert!(began.elapsed() < Duration::from_secs(5), "the release woke the wait: {:?}", began.elapsed());
remove(root.as_path());
}