use super::*;
use crate::test_support::{fixture, remove};
use crate::worktree_copy_journal::ensure_journal_root;
use std::time::Duration;
fn lock_of(common: &Path) -> PathBuf {
return ensure_journal_root(common)
.expect("root")
.join(SETTLEMENT_LOCK_NAME);
}
fn publish(lock: &Path, text: &str) {
std::fs::create_dir_all(lock).expect("lock directory");
crate::private_storage::write_private_file(
lock.join(OWNER_RECORD_NAME).as_path(),
text.as_bytes(),
)
.expect("owner record");
}
const DEAD_OWNER: &str = "{\"leaseToken\":\"dead-token\",\"ownerBirthIdentity\":\"no-such-birth\",\"ownerPid\":2147483646,\"schemaVersion\":1}\n";
#[test]
fn owner_record_round_trips_with_incumbent_spelling() {
let owner: LockOwner = LockOwner {
lease_token: String::from("token"),
owner_pid: 5,
owner_birth_identity: String::from("linux:9"),
};
let text: String = encode_owner(&owner);
assert_eq!(
text,
"{\"leaseToken\":\"token\",\"ownerBirthIdentity\":\"linux:9\",\"ownerPid\":5,\"schemaVersion\":1}\n"
);
assert_eq!(parse_owner(text.as_str()), Ok(owner));
}
#[test]
fn invalid_owner_records_are_unproven() {
assert_eq!(
parse_owner("{\"schemaVersion\":2}"),
Err(String::from("missing or mistyped owner fields"))
);
assert_eq!(
parse_owner(
"{\"schemaVersion\":1,\"leaseToken\":\"\",\"ownerPid\":1,\"ownerBirthIdentity\":\"x\"}"
),
Err(String::from("missing or mistyped owner fields"))
);
assert_eq!(
parse_owner(
"{\"schemaVersion\":1,\"leaseToken\":\"t\",\"ownerPid\":0,\"ownerBirthIdentity\":\"x\"}"
),
Err(String::from("missing or mistyped owner fields"))
);
assert!(
parse_owner("not json")
.expect_err("not json")
.starts_with("not JSON (")
);
}
#[cfg(unix)]
#[test]
fn free_lock_is_acquired_and_released() {
let root: PathBuf = fixture("lock-free");
let common: PathBuf = root.join("common");
std::fs::create_dir(common.as_path()).expect("common");
let lease: WorktreeCopyLease = try_acquire_worktree_copy_lock(common.as_path())
.expect("attempt")
.expect("free");
let lock: PathBuf = lock_of(common.as_path());
let published: String = std::fs::read_to_string(lock.join(OWNER_RECORD_NAME)).expect("record");
let owner: LockOwner = parse_owner(published.as_str()).expect("valid owner");
assert_eq!(owner.owner_pid, i64::from(std::process::id()));
assert_eq!(owner.lease_token, lease.lease_token());
lease.release().expect("released");
assert!(!lock.exists());
remove(root.as_path());
}
#[cfg(unix)]
#[test]
fn live_holder_keeps_the_lock() {
let root: PathBuf = fixture("lock-live");
let common: PathBuf = root.join("common");
std::fs::create_dir(common.as_path()).expect("common");
let first: WorktreeCopyLease = try_acquire_worktree_copy_lock(common.as_path())
.expect("attempt")
.expect("free");
assert!(
try_acquire_worktree_copy_lock(common.as_path())
.expect("attempt")
.is_none()
);
first.release().expect("released");
remove(root.as_path());
}
#[cfg(unix)]
#[test]
fn dead_owner_lock_is_retired_and_taken() {
let root: PathBuf = fixture("lock-dead");
let common: PathBuf = root.join("common");
std::fs::create_dir(common.as_path()).expect("common");
publish(lock_of(common.as_path()).as_path(), DEAD_OWNER);
let lease: WorktreeCopyLease = try_acquire_worktree_copy_lock(common.as_path())
.expect("attempt")
.expect("taken from the dead owner");
assert_ne!(lease.lease_token(), "dead-token");
lease.release().expect("released");
remove(root.as_path());
}
#[cfg(unix)]
#[test]
fn unproven_owner_keeps_the_lock_in_place() {
let root: PathBuf = fixture("lock-unproven");
let common: PathBuf = root.join("common");
std::fs::create_dir(common.as_path()).expect("common");
let lock: PathBuf = lock_of(common.as_path());
publish(lock.as_path(), "garbage");
assert!(
try_acquire_worktree_copy_lock(common.as_path())
.expect("attempt")
.is_none()
);
assert!(lock.join(OWNER_RECORD_NAME).exists());
remove(root.as_path());
}
#[cfg(unix)]
#[test]
fn unproven_owner_fails_the_blocking_wait_and_keeps_lock() {
let root: PathBuf = fixture("lock-unproven-wait");
let common: PathBuf = root.join("common");
std::fs::create_dir(common.as_path()).expect("common");
let lock: PathBuf = lock_of(common.as_path());
publish(lock.as_path(), "garbage");
let mut stderr: Vec<u8> = Vec::new();
let failure: WorktreeCopyError =
acquire_worktree_copy_lock(common.as_path(), &mut stderr).expect_err("timeout");
assert!(
failure.0.contains("stayed in place for 1000 ms"),
"{}",
failure.0
);
assert!(
failure.0.contains("holds an invalid owner record"),
"{}",
failure.0
);
assert!(lock.join(OWNER_RECORD_NAME).exists());
assert!(stderr.is_empty());
remove(root.as_path());
}
#[cfg(unix)]
#[test]
fn replaced_lock_release_fails_and_keeps_replacement() {
let root: PathBuf = fixture("lock-replaced");
let common: PathBuf = root.join("common");
std::fs::create_dir(common.as_path()).expect("common");
let lease: WorktreeCopyLease = try_acquire_worktree_copy_lock(common.as_path())
.expect("attempt")
.expect("free");
let lock: PathBuf = lock_of(common.as_path());
std::fs::remove_dir_all(lock.as_path()).expect("drop ours");
publish(lock.as_path(), DEAD_OWNER);
let failure: WorktreeCopyError = lease.release().expect_err("changed");
assert_eq!(
failure.0,
format!(
"cli-git: worktree-copy lock ownership changed: {}.",
quote(lock.to_string_lossy().as_ref())
)
);
assert!(lock.join(OWNER_RECORD_NAME).exists());
remove(root.as_path());
}
#[cfg(unix)]
#[test]
fn inherited_lease_requires_the_published_live_token() {
let root: PathBuf = fixture("lock-inherited");
let common: PathBuf = root.join("common");
std::fs::create_dir(common.as_path()).expect("common");
let lease: WorktreeCopyLease = try_acquire_worktree_copy_lock(common.as_path())
.expect("attempt")
.expect("free");
assert!(validate_inherited_lease(common.as_path(), lease.lease_token()).expect("valid"));
assert!(!validate_inherited_lease(common.as_path(), "other").expect("other token"));
assert!(!validate_inherited_lease(common.as_path(), "").expect("empty token"));
lease.release().expect("released");
remove(root.as_path());
}
#[cfg(unix)]
#[test]
fn waiter_names_a_live_holder_and_takes_the_lock_after_release() {
let root: PathBuf = fixture("lock-waiter");
let common: PathBuf = root.join("common");
std::fs::create_dir(common.as_path()).expect("common");
let held: WorktreeCopyLease = try_acquire_worktree_copy_lock(common.as_path())
.expect("attempt")
.expect("free");
let holder_pid: i64 = i64::from(std::process::id());
let releaser = std::thread::spawn(move || {
std::thread::sleep(Duration::from_millis(150));
return held.release();
});
let mut stderr: Vec<u8> = Vec::new();
let lease: WorktreeCopyLease =
acquire_worktree_copy_lock(common.as_path(), &mut stderr).expect("taken after release");
releaser.join().expect("releaser").expect("released");
let line: String = String::from_utf8(stderr).expect("utf8");
assert_eq!(
line.matches("cli-git: waiting for PID ").count(),
1,
"{line}"
);
assert!(
line.contains(&format!("waiting for PID {holder_pid},")),
"{line}"
);
assert!(
line.contains("proven by its live process identity"),
"{line}"
);
lease.release().expect("released");
remove(root.as_path());
}