use std::sync::Arc;
use std::sync::atomic::{AtomicBool, AtomicUsize, Ordering};
use std::thread;
use std::time::Duration;
use hackshell::taskpool::{TaskOptions, TaskPool};
#[test]
fn test_spawn_and_execute_task() {
let pool = TaskPool::default();
let executed = Arc::new(AtomicBool::new(false));
let executed_clone = executed.clone();
pool.spawn("test_task", TaskOptions::default(), move |run| {
while run.load(Ordering::Relaxed) {
executed_clone.store(true, Ordering::Relaxed);
break;
}
None
});
thread::sleep(Duration::from_millis(50));
assert!(executed.load(Ordering::Relaxed));
}
#[test]
fn test_task_metadata() {
let pool = TaskPool::default();
let task_name = "metadata_test";
pool.spawn(task_name, TaskOptions::default(), |run| {
while run.load(Ordering::Relaxed) {
thread::sleep(Duration::from_millis(10));
}
None
});
let tasks = pool.get_all();
assert_eq!(tasks.len(), 1);
let task = &tasks[0];
assert_eq!(task.name, task_name);
assert!(task.started <= chrono::Utc::now());
pool.remove(task_name).unwrap();
}
#[test]
fn test_remove_task() {
let pool = TaskPool::default();
let still_running = Arc::new(AtomicBool::new(true));
let still_running_clone = still_running.clone();
pool.spawn("removable_task", TaskOptions::default(), move |run| {
while run.load(Ordering::Relaxed) {
thread::sleep(Duration::from_millis(10));
}
still_running_clone.store(false, Ordering::Relaxed);
None
});
thread::sleep(Duration::from_millis(50));
assert!(still_running.load(Ordering::Relaxed));
assert!(pool.remove("removable_task").is_ok());
thread::sleep(Duration::from_millis(100));
assert!(!still_running.load(Ordering::Relaxed));
let tasks = pool.get_all();
assert_eq!(tasks.len(), 0);
}
#[test]
fn test_remove_nonexistent_task() {
let pool = TaskPool::default();
assert!(pool.remove("nonexistent").is_err());
}
#[test]
fn test_join_for_task() {
let pool = TaskPool::default();
let completed = Arc::new(AtomicBool::new(false));
let completed_clone = completed.clone();
pool.spawn("join_task", TaskOptions::default(), move |_run| {
thread::sleep(Duration::from_millis(100));
completed_clone.store(true, Ordering::Relaxed);
None
});
assert!(pool.join("join_task").is_ok());
assert!(completed.load(Ordering::Relaxed));
let tasks = pool.get_all();
assert_eq!(tasks.len(), 0);
}
#[test]
fn test_join_for_nonexistent_task() {
let pool = TaskPool::default();
assert!(pool.join("nonexistent").is_ok());
}
#[test]
fn test_spawn_with_same_name_kills_previous() {
let pool = TaskPool::default();
let first_task_running = Arc::new(AtomicBool::new(true));
let first_task_running_clone = first_task_running.clone();
let second_task_started = Arc::new(AtomicBool::new(false));
let second_task_started_clone = second_task_started.clone();
pool.spawn("duplicate_name", TaskOptions::default(), move |run| {
while run.load(Ordering::Relaxed) {
thread::sleep(Duration::from_millis(10));
}
first_task_running_clone.store(false, Ordering::Relaxed);
None
});
thread::sleep(Duration::from_millis(50));
assert!(first_task_running.load(Ordering::Relaxed));
pool.spawn("duplicate_name", TaskOptions::default(), move |_run| {
second_task_started_clone.store(true, Ordering::Relaxed);
thread::sleep(Duration::from_millis(50));
None
});
thread::sleep(Duration::from_millis(100));
assert!(!first_task_running.load(Ordering::Relaxed));
assert!(second_task_started.load(Ordering::Relaxed));
}
#[test]
fn test_multiple_tasks() {
let pool = TaskPool::default();
let counter = Arc::new(AtomicUsize::new(0));
for i in 0..5 {
let counter_clone = counter.clone();
pool.spawn(
&format!("task_{}", i),
TaskOptions::default(),
move |_run| {
counter_clone.fetch_add(1, Ordering::Relaxed);
thread::sleep(Duration::from_millis(50));
None
},
);
}
thread::sleep(Duration::from_millis(100));
assert_eq!(counter.load(Ordering::Relaxed), 5);
let tasks = pool.get_all();
assert_eq!(tasks.len(), 0);
}
#[test]
fn test_auto_removal_on_completion() {
let pool = TaskPool::default();
pool.spawn("auto_remove", TaskOptions::default(), |_run| {
None
});
thread::sleep(Duration::from_millis(100));
let tasks = pool.get_all();
assert_eq!(tasks.len(), 0);
}
#[test]
fn test_clone_pool() {
let pool1 = TaskPool::default();
let pool2 = pool1.clone();
pool1.spawn("task_from_pool1", TaskOptions::default(), |_run| {
thread::sleep(Duration::from_millis(100));
None
});
let tasks = pool2.get_all();
assert_eq!(tasks.len(), 1);
assert_eq!(tasks[0].name, "task_from_pool1");
assert!(pool2.remove("task_from_pool1").is_ok());
assert_eq!(pool1.get_all().len(), 0);
assert_eq!(pool2.get_all().len(), 0);
}
#[cfg(feature = "async")]
mod async_tests {
use super::*;
use tokio;
#[tokio::test]
async fn test_spawn_async_task() {
let pool = TaskPool::default();
let executed = Arc::new(AtomicBool::new(false));
let executed_clone = executed.clone();
pool.spawn_async("async_task", TaskOptions::default(), async move {
tokio::time::sleep(Duration::from_millis(50)).await;
executed_clone.store(true, Ordering::Relaxed);
None
});
tokio::time::sleep(Duration::from_millis(100)).await;
assert!(executed.load(Ordering::Relaxed));
}
#[tokio::test]
async fn test_async_task_metadata() {
let pool = TaskPool::default();
pool.spawn_async("async_metadata_test", TaskOptions::default(), async {
tokio::time::sleep(Duration::from_millis(100)).await;
None
});
let tasks = pool.get_all();
assert_eq!(tasks.len(), 1);
assert_eq!(tasks[0].name, "async_metadata_test");
pool.remove("async_metadata_test").unwrap();
}
#[tokio::test]
async fn test_kill_async_task() {
let pool = TaskPool::default();
let still_running = Arc::new(AtomicBool::new(true));
let still_running_clone = still_running.clone();
pool.spawn_async("killable_async", TaskOptions::default(), async move {
tokio::time::sleep(Duration::from_secs(10)).await;
still_running_clone.store(false, Ordering::Relaxed);
None
});
tokio::time::sleep(Duration::from_millis(50)).await;
assert!(still_running.load(Ordering::Relaxed));
assert!(pool.remove("killable_async").is_ok());
tokio::time::sleep(Duration::from_millis(100)).await;
assert!(still_running.load(Ordering::Relaxed));
}
#[tokio::test]
async fn test_join_async_task() {
let pool = TaskPool::default();
let completed = Arc::new(AtomicBool::new(false));
let completed_clone = completed.clone();
pool.spawn_async("join_async", TaskOptions::default(), async move {
tokio::time::sleep(Duration::from_millis(100)).await;
completed_clone.store(true, Ordering::Relaxed);
None
});
assert!(pool.join_async("join_async").await.is_ok());
assert!(completed.load(Ordering::Relaxed));
}
#[tokio::test]
async fn test_async_auto_removal() {
let pool = TaskPool::default();
pool.spawn_async("async_auto_remove", TaskOptions::default(), async {
tokio::time::sleep(Duration::from_millis(50)).await;
None
});
tokio::time::sleep(Duration::from_millis(100)).await;
let tasks = pool.get_all();
assert_eq!(tasks.len(), 0);
}
#[tokio::test]
async fn test_mixed_sync_and_async_tasks() {
let pool = TaskPool::default();
let sync_counter = Arc::new(AtomicUsize::new(0));
let async_counter = Arc::new(AtomicUsize::new(0));
let sync_counter_clone = sync_counter.clone();
pool.spawn("sync_task", TaskOptions::default(), move |_run| {
sync_counter_clone.fetch_add(1, Ordering::Relaxed);
None
});
let async_counter_clone = async_counter.clone();
pool.spawn_async("async_task", TaskOptions::default(), async move {
async_counter_clone.fetch_add(1, Ordering::Relaxed);
None
});
assert!(pool.join("sync_task").is_ok());
assert!(pool.join_async("async_task").await.is_ok());
assert_eq!(sync_counter.load(Ordering::Relaxed), 1);
assert_eq!(async_counter.load(Ordering::Relaxed), 1);
assert_eq!(pool.get_all().len(), 0);
}
#[tokio::test]
async fn test_drop_kills_async_tasks() {
let task_aborted = Arc::new(AtomicBool::new(false));
let task_aborted_clone = task_aborted.clone();
{
let pool = TaskPool::default();
pool.spawn_async("long_async", TaskOptions::default(), async move {
tokio::time::sleep(Duration::from_secs(10)).await;
task_aborted_clone.store(true, Ordering::Relaxed);
None
});
tokio::time::sleep(Duration::from_millis(50)).await;
}
tokio::time::sleep(Duration::from_millis(100)).await;
assert!(!task_aborted.load(Ordering::Relaxed));
}
#[tokio::test]
async fn test_drop_kills_mixed_tasks() {
let sync_stopped = Arc::new(AtomicBool::new(false));
let sync_stopped_clone = sync_stopped.clone();
let async_completed = Arc::new(AtomicBool::new(false));
let async_completed_clone = async_completed.clone();
{
let pool = TaskPool::default();
pool.spawn("sync_task", TaskOptions::default(), move |run| {
while run.load(Ordering::Relaxed) {
thread::sleep(Duration::from_millis(10));
}
sync_stopped_clone.store(true, Ordering::Relaxed);
None
});
pool.spawn_async("async_task", TaskOptions::default(), async move {
tokio::time::sleep(Duration::from_secs(10)).await;
async_completed_clone.store(true, Ordering::Relaxed);
None
});
tokio::time::sleep(Duration::from_millis(50)).await;
}
tokio::time::sleep(Duration::from_millis(100)).await;
assert!(sync_stopped.load(Ordering::Relaxed));
assert!(!async_completed.load(Ordering::Relaxed));
}
}
#[test]
fn test_concurrent_access() {
let pool = TaskPool::default();
let barrier = Arc::new(std::sync::Barrier::new(3));
let pool1 = pool.clone();
let barrier1 = barrier.clone();
let handle1 = thread::spawn(move || {
barrier1.wait();
for i in 0..10 {
pool1.spawn(
&format!("thread1_task_{}", i),
TaskOptions::default(),
|_run| {
thread::sleep(Duration::from_millis(10));
None
},
);
}
});
let pool2 = pool.clone();
let barrier2 = barrier.clone();
let handle2 = thread::spawn(move || {
barrier2.wait();
for i in 0..10 {
pool2.spawn(
&format!("thread2_task_{}", i),
TaskOptions::default(),
|_run| {
thread::sleep(Duration::from_millis(10));
None
},
);
}
});
barrier.wait();
handle1.join().unwrap();
handle2.join().unwrap();
thread::sleep(Duration::from_millis(100));
let tasks = pool.get_all();
assert!(tasks.len() == 0);
}
#[test]
fn test_drop_kills_running_tasks() {
let task_stopped = Arc::new(AtomicBool::new(false));
let task_stopped_clone = task_stopped.clone();
{
let pool = TaskPool::default();
pool.spawn("long_running", TaskOptions::default(), move |run| {
while run.load(Ordering::Relaxed) {
thread::sleep(Duration::from_millis(10));
}
task_stopped_clone.store(true, Ordering::Relaxed);
None
});
thread::sleep(Duration::from_millis(50));
assert!(!task_stopped.load(Ordering::Relaxed));
}
thread::sleep(Duration::from_millis(100));
assert!(task_stopped.load(Ordering::Relaxed));
}
#[test]
fn test_drop_kills_multiple_tasks() {
let stopped_count = Arc::new(AtomicUsize::new(0));
{
let pool = TaskPool::default();
for i in 0..5 {
let stopped_count_clone = stopped_count.clone();
pool.spawn(&format!("task_{}", i), TaskOptions::default(), move |run| {
while run.load(Ordering::Relaxed) {
thread::sleep(Duration::from_millis(10));
}
stopped_count_clone.fetch_add(1, Ordering::Relaxed);
None
});
}
thread::sleep(Duration::from_millis(50));
assert_eq!(stopped_count.load(Ordering::Relaxed), 0);
}
thread::sleep(Duration::from_millis(100));
assert_eq!(stopped_count.load(Ordering::Relaxed), 5);
}
#[test]
fn test_drop_only_when_last_clone_dropped() {
let task_stopped = Arc::new(AtomicBool::new(false));
let task_stopped_clone = task_stopped.clone();
let pool1 = TaskPool::default();
let pool2 = pool1.clone();
pool1.spawn("shared_task", TaskOptions::default(), move |run| {
while run.load(Ordering::Relaxed) {
thread::sleep(Duration::from_millis(10));
}
task_stopped_clone.store(true, Ordering::Relaxed);
None
});
thread::sleep(Duration::from_millis(50));
drop(pool1);
thread::sleep(Duration::from_millis(50));
assert!(!task_stopped.load(Ordering::Relaxed));
drop(pool2);
thread::sleep(Duration::from_millis(100));
assert!(task_stopped.load(Ordering::Relaxed));
}
#[test]
fn test_hidden_task_not_in_default_listing() {
let pool = TaskPool::default();
pool.spawn("visible", TaskOptions::default(), |_run| {
thread::sleep(Duration::from_millis(100));
None
});
pool.spawn(
"hidden",
TaskOptions {
hidden: true,
..Default::default()
},
|_run| {
thread::sleep(Duration::from_millis(100));
None
},
);
let tasks = pool.get_all();
assert_eq!(tasks.len(), 1);
assert_eq!(tasks[0].name, "visible");
let all_tasks = pool.get_all_filtered(true);
assert_eq!(all_tasks.len(), 2);
pool.kill_all();
}
#[test]
fn test_hidden_task_metadata() {
let pool = TaskPool::default();
pool.spawn("visible", TaskOptions::default(), |_run| {
thread::sleep(Duration::from_millis(100));
None
});
pool.spawn(
"hidden",
TaskOptions {
hidden: true,
..Default::default()
},
|_run| {
thread::sleep(Duration::from_millis(100));
None
},
);
let all_tasks = pool.get_all_filtered(true);
let visible = all_tasks.iter().find(|t| t.name == "visible").unwrap();
let hidden = all_tasks.iter().find(|t| t.name == "hidden").unwrap();
assert!(!visible.hidden);
assert!(hidden.hidden);
pool.kill_all();
}
#[test]
fn test_protected_task_cannot_be_terminated_via_command() {
use hackshell::{Hackshell, error::HackshellError};
let shell = Hackshell::new("> ").unwrap();
shell.spawn(
"protected_task",
TaskOptions {
protected: true,
..Default::default()
},
|run| {
while run.load(Ordering::Relaxed) {
thread::sleep(Duration::from_millis(10));
}
None
},
);
thread::sleep(Duration::from_millis(50));
let result = shell.feed_line("task --terminate protected_task");
assert!(result.is_err());
assert!(matches!(
result.unwrap_err(),
HackshellError::TaskIsProtected
));
let tasks = shell.get_tasks();
assert_eq!(tasks.len(), 1);
assert_eq!(tasks[0].name, "protected_task");
assert!(shell.terminate("protected_task").is_ok());
thread::sleep(Duration::from_millis(50));
let tasks = shell.get_tasks();
assert!(tasks.is_empty());
}