use ralph::lock;
use std::fs;
use tempfile::TempDir;
fn get_task_owner_files(lock_dir: &std::path::Path) -> Vec<std::path::PathBuf> {
if !lock_dir.exists() {
return vec![];
}
fs::read_dir(lock_dir)
.expect("read lock dir")
.filter_map(|e| e.ok())
.filter(|e| {
e.file_name()
.to_str()
.map(|name| name.starts_with("owner_task_"))
.unwrap_or(false)
})
.map(|e| e.path())
.collect()
}
#[test]
fn task_lock_can_be_acquired_when_supervisor_holds_lock() {
let temp = TempDir::new().expect("create temp dir");
let repo_root = temp.path();
let ralph_dir = repo_root.join(".ralph");
std::fs::create_dir_all(&ralph_dir).expect("create .ralph dir");
let lock_dir = lock::queue_lock_dir(repo_root);
let supervisor_lock =
lock::acquire_dir_lock(&lock_dir, "run one", false).expect("supervisor lock");
assert!(lock_dir.exists());
assert!(lock_dir.join("owner").exists());
let task_lock = lock::acquire_dir_lock(&lock_dir, "task", false).expect("task lock");
assert!(lock_dir.exists());
assert!(lock_dir.join("owner").exists());
let task_owner_files = get_task_owner_files(&lock_dir);
assert_eq!(
task_owner_files.len(),
1,
"Expected exactly one task owner file"
);
drop(task_lock);
assert!(lock_dir.exists());
assert!(lock_dir.join("owner").exists());
assert!(
get_task_owner_files(&lock_dir).is_empty(),
"Task owner files should be cleaned up"
);
drop(supervisor_lock);
assert!(!lock_dir.exists());
}
#[test]
fn non_task_lock_still_fails_when_supervisor_holds_lock() {
let temp = TempDir::new().expect("create temp dir");
let repo_root = temp.path();
let ralph_dir = repo_root.join(".ralph");
std::fs::create_dir_all(&ralph_dir).expect("create .ralph dir");
let lock_dir = lock::queue_lock_dir(repo_root);
let _supervisor_lock =
lock::acquire_dir_lock(&lock_dir, "run one", false).expect("supervisor lock");
assert!(lock_dir.exists());
assert!(lock_dir.join("owner").exists());
let result = lock::acquire_dir_lock(&lock_dir, "task edit", false);
assert!(result.is_err());
let err_msg = result.unwrap_err().to_string();
assert!(err_msg.contains("Queue lock already held"));
assert!(err_msg.contains("run one"));
}
#[test]
fn task_lock_cleans_up_directory_when_no_supervisor() {
let temp = TempDir::new().expect("create temp dir");
let repo_root = temp.path();
let ralph_dir = repo_root.join(".ralph");
std::fs::create_dir_all(&ralph_dir).expect("create .ralph dir");
let lock_dir = lock::queue_lock_dir(repo_root);
{
let _task_lock = lock::acquire_dir_lock(&lock_dir, "task", false).expect("task lock");
assert!(lock_dir.exists());
let task_owner_files = get_task_owner_files(&lock_dir);
assert_eq!(task_owner_files.len(), 1);
assert!(!lock_dir.join("owner").exists());
}
assert!(
get_task_owner_files(&lock_dir).is_empty(),
"Task owner files should be cleaned up"
);
assert!(!lock_dir.exists());
}
#[test]
fn multiple_task_locks_in_same_process_have_unique_sidecars() {
let temp = TempDir::new().expect("create temp dir");
let repo_root = temp.path();
let ralph_dir = repo_root.join(".ralph");
std::fs::create_dir_all(&ralph_dir).expect("create .ralph dir");
let lock_dir = lock::queue_lock_dir(repo_root);
let supervisor_lock =
lock::acquire_dir_lock(&lock_dir, "run loop", false).expect("supervisor lock");
assert!(lock_dir.join("owner").exists());
let task_lock1 = lock::acquire_dir_lock(&lock_dir, "task", false).expect("task lock 1");
let task_lock2 = lock::acquire_dir_lock(&lock_dir, "task", false).expect("task lock 2");
let task_lock3 = lock::acquire_dir_lock(&lock_dir, "task", false).expect("task lock 3");
let task_owner_files = get_task_owner_files(&lock_dir);
assert_eq!(
task_owner_files.len(),
3,
"Expected three unique task owner files, found: {:?}",
task_owner_files
);
assert!(lock_dir.join("owner").exists());
drop(task_lock3);
assert!(lock_dir.exists());
assert!(lock_dir.join("owner").exists());
assert_eq!(
get_task_owner_files(&lock_dir).len(),
2,
"Expected two task owner files remaining"
);
drop(task_lock2);
assert!(lock_dir.exists());
assert!(lock_dir.join("owner").exists());
assert_eq!(
get_task_owner_files(&lock_dir).len(),
1,
"Expected one task owner file remaining"
);
drop(task_lock1);
assert!(lock_dir.exists());
assert!(lock_dir.join("owner").exists());
assert!(
get_task_owner_files(&lock_dir).is_empty(),
"All task owner files should be cleaned up"
);
drop(supervisor_lock);
assert!(!lock_dir.exists());
}
#[test]
fn concurrent_task_locks_release_independently() {
let temp = TempDir::new().expect("create temp dir");
let repo_root = temp.path();
let ralph_dir = repo_root.join(".ralph");
std::fs::create_dir_all(&ralph_dir).expect("create .ralph dir");
let lock_dir = lock::queue_lock_dir(repo_root);
let supervisor_lock =
lock::acquire_dir_lock(&lock_dir, "run loop", false).expect("supervisor lock");
assert!(lock_dir.exists());
let task_lock1 = lock::acquire_dir_lock(&lock_dir, "task", false).expect("task lock 1");
let task_lock2 = lock::acquire_dir_lock(&lock_dir, "task", false).expect("task lock 2");
let owner_files_before: Vec<_> = get_task_owner_files(&lock_dir);
assert_eq!(owner_files_before.len(), 2);
drop(task_lock1);
let owner_files_after = get_task_owner_files(&lock_dir);
assert_eq!(
owner_files_after.len(),
1,
"Expected one task owner file to remain"
);
assert_ne!(
owner_files_before, owner_files_after,
"The remaining file should be different from the set before"
);
assert!(lock_dir.exists());
assert!(lock_dir.join("owner").exists());
drop(task_lock2);
drop(supervisor_lock);
assert!(!lock_dir.exists());
}
#[test]
fn task_lock_handles_supervisor_drop_before_task() {
let temp = TempDir::new().expect("create temp dir");
let repo_root = temp.path();
let ralph_dir = repo_root.join(".ralph");
std::fs::create_dir_all(&ralph_dir).expect("create .ralph dir");
let lock_dir = lock::queue_lock_dir(repo_root);
let supervisor_lock =
lock::acquire_dir_lock(&lock_dir, "run one", false).expect("supervisor lock");
let task_lock = lock::acquire_dir_lock(&lock_dir, "task", false).expect("task lock");
assert!(lock_dir.join("owner").exists());
assert_eq!(get_task_owner_files(&lock_dir).len(), 1);
drop(supervisor_lock);
assert!(lock_dir.exists());
assert!(
!lock_dir.join("owner").exists(),
"Main owner file should be gone"
);
assert_eq!(
get_task_owner_files(&lock_dir).len(),
1,
"Task owner should remain"
);
drop(task_lock);
assert!(!lock_dir.exists());
}