#![allow(clippy::indexing_slicing)]
use prikk_error::Result;
use super::{PidLiveness, clear_lock, find_held_lock, list_held_locks};
use crate::RepositoryLayout;
use crate::layout::LockableContainer;
use crate::lock::{ActiveLock, RefLock, acquire_container_locks};
use crate::test_support::unique_temp_dir;
#[test]
fn a_fresh_repository_holds_no_locks() -> Result<()> {
let root = unique_temp_dir("unlock-empty");
let layout = RepositoryLayout::init(root.clone())?;
assert!(list_held_locks(&layout)?.is_empty());
let _ = std::fs::remove_dir_all(root);
Ok(())
}
#[test]
fn every_held_lock_kind_is_enumerated_with_its_own_pid_live() -> Result<()> {
let root = unique_temp_dir("unlock-enumerate-all-kinds");
let layout = RepositoryLayout::init(root.clone())?;
let active = ActiveLock::acquire(&layout)?;
let ref_lock = RefLock::acquire(&layout, "heads/main")?;
let container_lock = acquire_container_locks(&layout, &[LockableContainer::TrustPolicy])?;
let locks = list_held_locks(&layout)?;
assert_eq!(locks.len(), 3);
for lock in &locks {
assert_eq!(lock.recorded_pid, Some(std::process::id()));
assert_eq!(lock.liveness, PidLiveness::AppearsRunning);
}
let kinds: Vec<&str> = locks.iter().map(|lock| lock.kind.as_str()).collect();
assert!(kinds.contains(&"active"));
assert!(kinds.contains(&"ref"));
assert!(kinds.contains(&"container:trust-policy"));
drop(active);
drop(ref_lock);
drop(container_lock);
let _ = std::fs::remove_dir_all(root);
Ok(())
}
#[test]
fn a_lock_recording_a_nonexistent_pid_is_reported_as_not_appearing_to_run() -> Result<()> {
let root = unique_temp_dir("unlock-dead-pid");
let layout = RepositoryLayout::init(root.clone())?;
let path = layout.default_active_lock_path();
std::fs::write(&path, "pid=999999\nkind=active\nnote=test\n")?;
let locks = list_held_locks(&layout)?;
assert_eq!(locks.len(), 1);
assert_eq!(locks[0].recorded_pid, Some(999_999));
assert_eq!(locks[0].liveness, PidLiveness::DoesNotAppearRunning);
let _ = std::fs::remove_dir_all(root);
Ok(())
}
#[test]
fn clear_lock_removes_the_named_lock_and_it_becomes_reacquirable() -> Result<()> {
let root = unique_temp_dir("unlock-clear");
let layout = RepositoryLayout::init(root.clone())?;
let active = ActiveLock::acquire(&layout)?;
let path = active.path().to_path_buf();
std::mem::forget(active);
assert!(ActiveLock::acquire(&layout).is_err());
clear_lock(&layout, &path)?;
assert!(list_held_locks(&layout)?.is_empty());
assert!(ActiveLock::acquire(&layout).is_ok());
let _ = std::fs::remove_dir_all(root);
Ok(())
}
#[test]
fn a_malformed_lock_body_reports_unknown_liveness() -> Result<()> {
let root = unique_temp_dir("unlock-malformed-body");
let layout = RepositoryLayout::init(root.clone())?;
let path = layout.default_active_lock_path();
std::fs::write(&path, "not a lock body at all")?;
let locks = list_held_locks(&layout)?;
assert_eq!(locks.len(), 1);
assert_eq!(locks[0].recorded_pid, None);
assert_eq!(locks[0].liveness, PidLiveness::Unknown);
let _ = std::fs::remove_dir_all(root);
Ok(())
}
#[test]
fn a_wedged_container_lock_blocks_compaction_until_unlock_clears_it() -> Result<()> {
let root = unique_temp_dir("unlock-wedged-container-lock");
let layout = RepositoryLayout::init(root.clone())?;
let held = acquire_container_locks(&layout, &[LockableContainer::RefPointerIndex])?;
let path = layout.lockable_container_lock_path(LockableContainer::RefPointerIndex);
std::mem::forget(held);
assert!(crate::compact_ref_pointer_index(&layout).is_err());
assert!(crate::plan_compact_ref_pointer_index(&layout).is_err());
let locks = list_held_locks(&layout)?;
assert_eq!(locks.len(), 1);
assert_eq!(locks[0].path, path);
assert_eq!(locks[0].kind, "container:ref-pointer-index");
clear_lock(&layout, &path)?;
assert!(list_held_locks(&layout)?.is_empty());
let report = crate::compact_ref_pointer_index(&layout)?;
assert_eq!(report.entries_before, 0);
let _ = std::fs::remove_dir_all(root);
Ok(())
}
#[cfg(any(target_os = "linux", target_os = "macos"))]
#[test]
fn find_held_lock_matches_a_lock_reached_through_a_symlinked_route() -> Result<()> {
let real_root = unique_temp_dir("unlock-symlink-real");
let layout = RepositoryLayout::init(real_root.clone())?;
let active = ActiveLock::acquire(&layout)?;
let symlink_root = unique_temp_dir("unlock-symlink-link");
std::fs::remove_dir(&symlink_root)?;
std::os::unix::fs::symlink(&real_root, &symlink_root)?;
let symlinked_target = symlink_root.join(".prikk/active/default/active.lock");
assert_ne!(symlinked_target, active.path());
let locks = list_held_locks(&layout)?;
let found = find_held_lock(&locks, &symlinked_target);
assert!(
found.is_some(),
"a lock reached through a symlinked route must still match the canonical `HeldLock` \
`list_held_locks` reports"
);
drop(active);
let _ = std::fs::remove_file(&symlink_root);
let _ = std::fs::remove_dir_all(real_root);
Ok(())
}
#[test]
fn find_held_lock_returns_none_for_a_target_that_does_not_exist() -> Result<()> {
let root = unique_temp_dir("unlock-bogus-target");
let layout = RepositoryLayout::init(root.clone())?;
let _active = ActiveLock::acquire(&layout)?;
let locks = list_held_locks(&layout)?;
let bogus = root.join("this-path-was-never-created").join("active.lock");
assert!(find_held_lock(&locks, &bogus).is_none());
let _ = std::fs::remove_dir_all(root);
Ok(())
}