#![allow(clippy::unwrap_used)]
use std::io::ErrorKind;
use super::*;
use pretty_assertions::assert_eq;
fn store() -> tempfile::TempDir {
tempfile::tempdir().unwrap()
}
#[test]
fn a_session_can_take_its_own_lock() {
let dir = store();
let guard = acquire(dir.path(), "s1", LOCK_BUDGET).unwrap();
assert!(guard.is_some());
assert!(guard.unwrap().is_enforced());
}
#[test]
fn a_second_holder_of_the_same_session_is_refused() {
let dir = store();
let _held = acquire(dir.path(), "s1", LOCK_BUDGET).unwrap().unwrap();
let second = acquire(dir.path(), "s1", Duration::from_millis(30)).unwrap();
assert!(
second.is_none(),
"two invocations of one session ran at once"
);
}
#[test]
fn a_different_session_is_not_blocked() {
let dir = store();
let _held = acquire(dir.path(), "s1", LOCK_BUDGET).unwrap().unwrap();
let other = acquire(dir.path(), "s2", Duration::from_millis(30)).unwrap();
assert!(other.is_some(), "an unrelated session was serialized");
}
#[test]
fn dropping_the_guard_releases_it() {
let dir = store();
{
let _held = acquire(dir.path(), "s1", LOCK_BUDGET).unwrap().unwrap();
}
assert!(
acquire(dir.path(), "s1", Duration::from_millis(30))
.unwrap()
.is_some()
);
}
#[test]
#[cfg(unix)]
fn a_lock_dies_with_the_process_holding_it() {
const HOLDER: &str = "FILESNAP_LOCK_HOLDER";
if let Ok(dir) = std::env::var(HOLDER) {
let _held = acquire(Path::new(&dir), "s1", LOCK_BUDGET)
.unwrap()
.unwrap();
std::thread::sleep(Duration::from_secs(60));
return;
}
let dir = store();
let mut holder = std::process::Command::new(std::env::current_exe().unwrap())
.args([
"--exact",
"lock::tests::a_lock_dies_with_the_process_holding_it",
])
.env(HOLDER, dir.path())
.stdout(std::process::Stdio::null())
.stderr(std::process::Stdio::null())
.spawn()
.unwrap();
let deadline = Instant::now() + Duration::from_secs(10);
loop {
assert!(Instant::now() < deadline, "the holder never took the lock");
if acquire(dir.path(), "s1", Duration::from_millis(20))
.unwrap()
.is_none()
{
break;
}
std::thread::sleep(Duration::from_millis(20));
}
holder.kill().unwrap();
holder.wait().unwrap();
assert!(
acquire(dir.path(), "s1", Duration::from_secs(5))
.unwrap()
.is_some(),
"a killed holder wedged the session"
);
}
#[test]
fn the_sentinel_outlives_the_lock() {
let dir = store();
drop(acquire(dir.path(), "s1", LOCK_BUDGET).unwrap());
assert!(dir_in(dir.path()).join("s1.lock").exists());
}
#[test]
fn a_forged_session_id_cannot_place_the_sentinel() {
let dir = store();
for forged in ["../../etc/passwd", "..", "", "my session"] {
assert!(
matches!(
acquire(dir.path(), forged, LOCK_BUDGET),
Err(SnapshotError::InvalidId { .. })
),
"{forged:?}"
);
}
}
#[test]
fn contention_is_recognised_on_both_platforms() {
for platform in [Platform::Unix, Platform::Windows] {
assert_eq!(
classify(ErrorKind::WouldBlock, None, Site::Lock, platform),
Disposition::Contended
);
}
assert_eq!(
classify(
ErrorKind::PermissionDenied,
Some(WINDOWS_SHARING_VIOLATION),
Site::Open,
Platform::Windows
),
Disposition::Contended
);
}
#[test]
fn code_one_means_opposite_things_on_the_two_platforms() {
assert_eq!(
classify(
ErrorKind::PermissionDenied,
Some(1),
Site::Lock,
Platform::Windows
),
Disposition::Unsupported,
"ERROR_INVALID_FUNCTION: this filesystem does not do locks"
);
assert_eq!(
classify(
ErrorKind::PermissionDenied,
Some(1),
Site::Lock,
Platform::Unix
),
Disposition::Fatal,
"EPERM: a real failure, and proceeding unlocked would hide it"
);
}
#[test]
fn a_filesystem_without_locking_is_proceeded_past_rather_than_refused() {
for code in [95, 38, 37, 45] {
assert_eq!(
classify(ErrorKind::Other, Some(code), Site::Lock, Platform::Unix),
Disposition::Unsupported,
"unix code {code}"
);
}
for code in [1, 50] {
assert_eq!(
classify(ErrorKind::Other, Some(code), Site::Lock, Platform::Windows),
Disposition::Unsupported,
"windows code {code}"
);
}
assert_eq!(
classify(ErrorKind::Unsupported, None, Site::Lock, Platform::Unix),
Disposition::Unsupported
);
}
#[test]
fn a_stale_network_handle_is_retried() {
for platform in [Platform::Unix, Platform::Windows] {
assert_eq!(
classify(
ErrorKind::StaleNetworkFileHandle,
None,
Site::Lock,
platform
),
Disposition::Transient
);
assert_eq!(
classify(ErrorKind::Interrupted, None, Site::Lock, platform),
Disposition::Transient
);
}
}
#[test]
fn an_unrecognised_failure_is_fatal_rather_than_ignored() {
assert_eq!(
classify(ErrorKind::NotFound, None, Site::Lock, Platform::Unix),
Disposition::Fatal
);
assert_eq!(
classify(ErrorKind::OutOfMemory, None, Site::Open, Platform::Windows),
Disposition::Fatal
);
}