use super::{
AdoptError, LOCK_FD_ENV, adopt, interpret_cloexec, interpret_flock, set_fd_flags, take_lease,
};
use crate::prompt::inbox::{inbox_dir, try_acquire};
use std::ffi::OsStr;
use std::fs::File;
use std::io;
use std::os::fd::{FromRawFd, IntoRawFd, RawFd};
use std::path::Path;
use tempfile::TempDir;
fn leaked_dir_fd(dir: &Path) -> RawFd {
File::open(dir).unwrap().into_raw_fd()
}
fn cloexec_of(fd: RawFd) -> bool {
let flags = unsafe { libc::fcntl(fd, libc::F_GETFD) };
assert!(flags != -1, "fcntl F_GETFD failed");
flags & libc::FD_CLOEXEC != 0
}
#[test]
fn adopt_validates_and_restores_cloexec() {
let ws = TempDir::new().unwrap();
let dir = inbox_dir(ws.path(), "a1");
std::fs::create_dir_all(&dir).unwrap();
let fd = leaked_dir_fd(&dir);
set_fd_flags(fd, 0).unwrap();
let lock = adopt(OsStr::new(&fd.to_string()), &dir)
.unwrap()
.expect("adopted");
assert!(cloexec_of(lock.as_raw_fd()));
}
#[test]
fn adopt_declines_a_non_numeric_env_value() {
let ws = TempDir::new().unwrap();
let err = adopt(OsStr::new("not-a-number"), ws.path()).unwrap_err();
assert!(matches!(err, AdoptError::Parse(_)), "{err}");
}
#[test]
fn adopt_declines_a_closed_fd() {
let ws = TempDir::new().unwrap();
let err = adopt(OsStr::new("999999"), ws.path()).unwrap_err();
assert!(matches!(err, AdoptError::Fstat { .. }), "{err}");
}
#[test]
fn adopt_declines_when_the_inbox_is_unreadable() {
let ws = TempDir::new().unwrap();
let fd = leaked_dir_fd(ws.path());
let missing = ws.path().join("no-such-inbox");
let err = adopt(OsStr::new(&fd.to_string()), &missing).unwrap_err();
assert!(matches!(err, AdoptError::InboxStat { .. }), "{err}");
assert!(unsafe { libc::fcntl(fd, libc::F_GETFD) } != -1);
drop(unsafe { File::from_raw_fd(fd) });
}
#[test]
fn adopt_declines_a_mismatched_fd_without_stealing_it() {
let ws = TempDir::new().unwrap();
let other = ws.path().join("other");
let dir = inbox_dir(ws.path(), "a1");
std::fs::create_dir_all(&other).unwrap();
std::fs::create_dir_all(&dir).unwrap();
let fd = leaked_dir_fd(&other);
let err = adopt(OsStr::new(&fd.to_string()), &dir).unwrap_err();
assert!(matches!(err, AdoptError::Mismatch { .. }), "{err}");
assert!(unsafe { libc::fcntl(fd, libc::F_GETFD) } != -1);
drop(unsafe { File::from_raw_fd(fd) });
}
#[test]
fn adopt_resolves_a_contended_lease_to_already_driven() {
let ws = TempDir::new().unwrap();
let dir = inbox_dir(ws.path(), "a1");
let _held = try_acquire(&dir).unwrap().expect("free");
let fd = leaked_dir_fd(&dir);
let out = adopt(OsStr::new(&fd.to_string()), &dir).unwrap();
assert!(out.is_none());
}
#[test]
fn take_lease_acquires_when_no_env_is_published() {
let ws = TempDir::new().unwrap();
let dir = inbox_dir(ws.path(), "a1");
let lock = take_lease(None, &dir).unwrap().expect("acquired");
drop(lock);
}
#[test]
fn take_lease_surfaces_an_acquire_failure() {
let ws = TempDir::new().unwrap();
std::fs::create_dir_all(ws.path().join("inbox")).unwrap();
let broken = inbox_dir(ws.path(), "a1");
std::fs::write(&broken, b"not a dir").unwrap();
let err = take_lease(None, &broken).unwrap_err();
assert!(matches!(err, super::LeaseError::Acquire(_)), "{err}");
}
#[test]
fn take_lease_adopts_when_the_env_is_published() {
let ws = TempDir::new().unwrap();
let dir = inbox_dir(ws.path(), "a1");
std::fs::create_dir_all(&dir).unwrap();
let fd = leaked_dir_fd(&dir);
let lock = take_lease(Some(OsStr::new(&fd.to_string())), &dir)
.unwrap()
.expect("adopted");
assert_eq!(lock.as_raw_fd(), fd);
}
#[test]
fn successor_command_publishes_an_inheritable_fd() {
let ws = TempDir::new().unwrap();
let dir = inbox_dir(ws.path(), "a1");
let lease = try_acquire(&dir).unwrap().expect("free");
let fd = lease.as_raw_fd();
let cmd =
super::successor_command(Path::new("/usr/bin/lernie"), ws.path(), "a1", lease).unwrap();
assert!(!cloexec_of(fd));
let args: Vec<_> = cmd.get_args().map(|a| a.to_os_string()).collect();
assert_eq!(args[0], "advance");
assert_eq!(args[2], "a1");
let published = cmd
.get_envs()
.find(|(k, _)| *k == OsStr::new(LOCK_FD_ENV))
.and_then(|(_, v)| v.map(|v| v.to_os_string()))
.expect("LERNIE_LOCK_FD published");
assert_eq!(published, OsStr::new(&fd.to_string()));
drop(unsafe { File::from_raw_fd(fd) });
}
#[test]
fn set_fd_flags_surfaces_a_bad_fd() {
let err = set_fd_flags(999999, 0).unwrap_err();
assert_eq!(err.raw_os_error(), Some(libc::EBADF));
}
#[test]
fn interpret_helpers_map_errors_to_their_adopt_arms() {
let e = interpret_cloexec(7, Err(io::Error::other("x"))).unwrap_err();
assert!(matches!(e, AdoptError::Cloexec { fd: 7, .. }), "{e}");
let e = interpret_flock(9, Err(io::Error::other("y"))).unwrap_err();
assert!(matches!(e, AdoptError::Flock { fd: 9, .. }), "{e}");
assert!(interpret_cloexec(7, Ok(())).is_ok());
}