use super::commit::{remove_settled, wrapped_commit};
use super::support::{
Fixture, fixture, git, repository,
};
use std::ffi::OsStr;
use std::fs::File;
use std::io::{BufRead, BufReader};
use std::path::{Path, PathBuf};
use std::process::{Child, Command, Stdio};
const NOTICE_SECONDS: &str = "1";
pub(super) struct Watched {
pub(super) code: Option<i32>,
pub(super) stderr: String,
}
fn edited_repository(fixture: &Fixture) -> PathBuf {
let repo: PathBuf = repository(fixture, OsStr::new("repo"));
std::fs::write(repo.join("a.txt"), b"one\n").expect("a");
git(fixture, repo.as_path(), &["add", "--", "a.txt"]);
git(fixture, repo.as_path(), &["commit", "--quiet", "--message=base"]);
std::fs::write(repo.join("a.txt"), b"one\ntwo\n").expect("edit a");
git(fixture, repo.as_path(), &["config", "maintenance.autoDetach", "false"]);
return repo;
}
fn waiting_commit(fixture: &Fixture, repo: &Path) -> Command {
super::support::give_service_environment(fixture, "CLI_GIT_WAIT_NOTICE_SECONDS", NOTICE_SECONDS);
let mut command: Command = wrapped_commit(fixture, repo, &["commit", "--message=waiter", "a.txt"]);
command.env("CLI_GIT_WAIT_NOTICE_SECONDS", NOTICE_SECONDS);
return command;
}
fn lines_with<'text>(stderr: &'text str, needle: &str) -> Vec<&'text str> {
return stderr.lines().filter(|line| { return line.contains(needle); }).collect();
}
pub(super) fn watch_notices(command: &mut Command, needle: &str, count: usize, on_count: impl FnOnce()) -> Watched {
let mut child: Child = command
.stdin(Stdio::null())
.stdout(Stdio::null())
.stderr(Stdio::piped())
.spawn()
.expect("the commit starts");
let reader: BufReader<std::process::ChildStderr> = BufReader::new(child.stderr.take().expect("piped"));
let mut stderr: String = String::new();
let mut seen: usize = 0;
let mut action: Option<_> = Some(on_count);
for read in reader.lines() {
let Ok(line) = read else {
break;
};
stderr.push_str(line.as_str());
stderr.push('\n');
if line.contains(needle) {
seen += 1;
if seen >= count && let Some(act) = action.take() {
act();
}
}
}
let status = child.wait().expect("the commit ends");
return Watched { code: status.code(), stderr };
}
#[test]
fn a_commit_waiting_for_a_live_index_lock_holder_repeats_its_notice() {
let fixture: Fixture = fixture("wait-notice-index-live");
let repo: PathBuf = edited_repository(&fixture);
let lock_path: PathBuf = repo.join(".git/index.lock");
let lock: File = File::create(&lock_path).expect("index lock");
let needle: String = format!("waiting for PID {}", std::process::id());
let watched: Watched = watch_notices(
&mut waiting_commit(&fixture, repo.as_path()),
needle.as_str(),
2,
move || {
drop(lock);
std::fs::remove_file(&lock_path).expect("the holder lets go");
},
);
let notices: Vec<&str> = lines_with(watched.stderr.as_str(), needle.as_str());
assert!(notices.len() >= 2, "a first notice and a repeat: {}", watched.stderr);
assert!(notices.iter().all(|line| { return line.contains("for as long as that process holds the lock"); }), "{}", watched.stderr);
assert!(!watched.stderr.contains("giving up"), "{}", watched.stderr);
assert_eq!(watched.code, Some(0), "{}", watched.stderr);
remove_settled(&fixture);
}
#[test]
fn a_commit_waits_on_a_live_index_lock_holder_with_no_bound() {
let fixture: Fixture = fixture("wait-notice-index-live-no-bound");
let repo: PathBuf = edited_repository(&fixture);
let lock_path: PathBuf = repo.join(".git/index.lock");
let lock: File = File::create(&lock_path).expect("index lock");
let needle: String = format!("waiting for PID {}", std::process::id());
let watched: Watched = watch_notices(&mut waiting_commit(&fixture, repo.as_path()), needle.as_str(), LONG_WAIT_NOTICES, move || {
drop(lock);
std::fs::remove_file(&lock_path).expect("the holder lets go");
});
assert!(lines_with(watched.stderr.as_str(), needle.as_str()).len() >= LONG_WAIT_NOTICES, "{}", watched.stderr);
assert!(!watched.stderr.contains("giving up") && !watched.stderr.contains("is using this repository's index"), "{}", watched.stderr);
assert_eq!(watched.code, Some(0), "the commit landed once the holder let go: {}", watched.stderr);
remove_settled(&fixture);
}
const LONG_WAIT_NOTICES: usize = 4;
#[test]
fn a_commit_waiting_for_an_unproven_index_lock_lands_when_it_goes() {
let fixture: Fixture = fixture("wait-notice-index-unproven");
let repo: PathBuf = edited_repository(&fixture);
let lock_path: PathBuf = repo.join(".git/index.lock");
std::fs::write(&lock_path, b"").expect("index lock nobody holds");
let needle: &str = "cannot be proven alive";
let watched: Watched = watch_notices(
&mut waiting_commit(&fixture, repo.as_path()),
needle,
1,
move || { std::fs::remove_file(&lock_path).expect("the stale lock is removed"); },
);
assert!(!lines_with(watched.stderr.as_str(), needle).is_empty(), "{}", watched.stderr);
assert_eq!(watched.code, Some(0), "{}", watched.stderr);
remove_settled(&fixture);
}
#[test]
fn a_commit_waits_on_an_unproven_index_lock_and_says_what_it_knows() {
let fixture: Fixture = fixture("wait-notice-index-unproven-no-bound");
let repo: PathBuf = edited_repository(&fixture);
let lock_path: PathBuf = repo.join(".git/index.lock");
std::fs::write(&lock_path, b"").expect("index lock nobody holds");
let needle: &str = "cannot be proven alive";
let watched: Watched = watch_notices(&mut waiting_commit(&fixture, repo.as_path()), needle, LONG_WAIT_NOTICES, {
let released: PathBuf = lock_path.clone();
move || { std::fs::remove_file(&released).expect("the lock goes"); }
});
let notices: Vec<&str> = lines_with(watched.stderr.as_str(), needle);
assert!(notices.len() >= LONG_WAIT_NOTICES, "{}", watched.stderr);
assert!(notices.iter().all(|line| { return line.contains("no Git lock PID file") && line.contains("has the lock open"); }), "what is known is said: {}", watched.stderr);
assert!(watched.stderr.contains(format!("removing the file {}", lock_path.display()).as_str()), "the way out is named: {}", watched.stderr);
assert!(!watched.stderr.contains("was not made") && !watched.stderr.contains("stops waiting"), "{}", watched.stderr);
assert_eq!(watched.code, Some(0), "the commit landed once the lock went: {}", watched.stderr);
remove_settled(&fixture);
}