use std::sync::Arc;
use std::sync::atomic::{AtomicU64, Ordering};
use std::time::Duration;
use minitimer::MiniTimer;
use minitimer::task::TaskBuilder;
mod common;
use common::CounterTask;
#[tokio::test]
async fn test_advance_task_by_duration() {
let counter = Arc::new(AtomicU64::new(0));
let timer = MiniTimer::new();
let task = TaskBuilder::new(10)
.with_frequency_repeated_by_seconds(60)
.spawn_async(CounterTask::new(counter.clone()))
.unwrap();
timer.add_task(task).unwrap();
tokio::time::sleep(Duration::from_millis(50)).await;
let initial_status = timer.task_status(10).expect("Task should exist");
assert!(
initial_status.time_to_next_run > 55 && initial_status.time_to_next_run <= 60,
"Initial time_to_next_run should be around 60 seconds, got {}",
initial_status.time_to_next_run
);
timer
.advance_task(10, Some(Duration::from_secs(30)), true)
.unwrap();
assert!(
timer.contains_task(10),
"Task should still exist after advance"
);
let status_after_advance = timer
.task_status(10)
.expect("Task should exist after advance");
assert!(
status_after_advance.time_to_next_run > 25 && status_after_advance.time_to_next_run <= 35,
"time_to_next_run should be reduced by ~30 seconds (expected 25-35, got {})",
status_after_advance.time_to_next_run
);
}
#[tokio::test]
async fn test_advance_task_trigger_immediately() {
let counter = Arc::new(AtomicU64::new(0));
let timer = MiniTimer::new();
let task = TaskBuilder::new(11)
.with_frequency_repeated_by_seconds(60)
.spawn_async(CounterTask::new(counter.clone()))
.unwrap();
timer.add_task(task).unwrap();
timer.tick().await;
timer.tick().await;
timer.tick().await;
timer.tick().await;
timer.tick().await;
timer.advance_task(11, None, true).unwrap();
timer.tick().await;
tokio::time::sleep(Duration::from_millis(100)).await;
let count = counter.load(Ordering::SeqCst);
println!("Counter after 1 tick + sleep: {}", count);
let final_count = counter.load(Ordering::SeqCst);
assert!(
final_count >= 1,
"Task should execute after manual tick when triggered immediately, executed {} times",
final_count
);
}
#[tokio::test]
async fn test_advance_nonexistent_task() {
let timer = MiniTimer::new();
let result = timer.advance_task(999, Some(Duration::from_secs(30)), true);
assert!(result.is_err(), "Should return error for non-existent task");
}
#[tokio::test]
async fn test_advance_task_exceed_wait_time() {
let counter = Arc::new(AtomicU64::new(0));
let timer = MiniTimer::new();
let task = TaskBuilder::new(12)
.with_frequency_repeated_by_seconds(60)
.spawn_async(CounterTask::new(counter.clone()))
.unwrap();
timer.add_task(task).unwrap();
tokio::time::sleep(Duration::from_millis(50)).await;
let _initial_status = timer.task_status(12).expect("Task should exist");
timer
.advance_task(12, Some(Duration::from_secs(120)), true)
.unwrap();
assert!(
timer.contains_task(12),
"Task should still exist after advance beyond wait"
);
let status_after = timer
.task_status(12)
.expect("Task should exist after advance");
assert!(
status_after.time_to_next_run <= 1,
"Task should be scheduled for immediate execution when advancing beyond wait time, got time_to_next_run={}",
status_after.time_to_next_run
);
assert_eq!(
format!("{:?}", status_after.wheel_type),
"Second",
"Task should be in Second wheel for immediate execution"
);
}
#[tokio::test]
async fn test_advance_task_reset_frequency_behavior() {
let counter = Arc::new(AtomicU64::new(0));
let timer = MiniTimer::new();
let task = TaskBuilder::new(13)
.with_frequency_repeated_by_seconds(60)
.spawn_async(CounterTask::new(counter.clone()))
.unwrap();
timer.add_task(task).unwrap();
assert!(timer.contains_task(13), "Task should exist before advance");
tokio::time::sleep(Duration::from_millis(50)).await;
let initial_status = timer.task_status(13).expect("Task should exist");
let _initial_cascade_guide = initial_status.cascade_guide;
timer
.advance_task(13, Some(Duration::from_secs(30)), false)
.unwrap();
assert!(
timer.contains_task(13),
"Task should exist after advance with reset_frequency=false"
);
let status_after = timer
.task_status(13)
.expect("Task should exist after advance");
assert!(
status_after.time_to_next_run > 25 && status_after.time_to_next_run <= 35,
"time_to_next_run should be ~30 seconds less than initial (got {})",
status_after.time_to_next_run
);
assert!(
status_after.cascade_guide.sec < 60,
"Task should have valid second position"
);
}
#[tokio::test]
async fn test_advance_task_zero_duration() {
let counter = Arc::new(AtomicU64::new(0));
let timer = MiniTimer::new();
let task = TaskBuilder::new(14)
.with_frequency_repeated_by_seconds(60)
.spawn_async(CounterTask::new(counter.clone()))
.unwrap();
timer.add_task(task).unwrap();
tokio::time::sleep(Duration::from_millis(50)).await;
let initial_status = timer.task_status(14).expect("Task should exist");
let initial_time_to_next = initial_status.time_to_next_run;
timer
.advance_task(14, Some(Duration::from_secs(0)), true)
.unwrap();
let status_after = timer
.task_status(14)
.expect("Task should exist after advance");
let time_diff = initial_time_to_next.abs_diff(status_after.time_to_next_run);
assert!(
time_diff <= 2,
"Zero duration advance should not significantly change time_to_next_run (diff={}, initial={}, after={})",
time_diff,
initial_time_to_next,
status_after.time_to_next_run
);
timer.tick().await;
let count = counter.load(Ordering::SeqCst);
assert_eq!(
count, 0,
"Task should NOT execute with zero duration advance, executed {} times",
count
);
}
#[tokio::test]
async fn test_advance_task_once_triggers_immediately() {
let counter = Arc::new(AtomicU64::new(0));
let timer = MiniTimer::new();
let task = TaskBuilder::new(15)
.with_frequency_once_by_seconds(60)
.spawn_async(CounterTask::new(counter.clone()))
.unwrap();
timer.add_task(task).unwrap();
tokio::time::sleep(Duration::from_millis(50)).await;
let initial_status = timer.task_status(15).expect("Task should exist");
assert!(
initial_status.time_to_next_run > 55 && initial_status.time_to_next_run <= 60,
"Initial time_to_next_run should be around 60 seconds"
);
timer.advance_task(15, None, true).unwrap();
assert!(
timer.contains_task(15),
"Task should still exist after trigger"
);
let status_after = timer
.task_status(15)
.expect("Task should exist after trigger");
assert!(
status_after.time_to_next_run <= 1,
"Once task should be scheduled for immediate execution, got time_to_next_run={}",
status_after.time_to_next_run
);
assert_eq!(
format!("{:?}", status_after.wheel_type),
"Second",
"Task should be in Second wheel for immediate execution"
);
}
#[tokio::test]
async fn test_advance_task_once_removed_after_execution() {
let counter = Arc::new(AtomicU64::new(0));
let timer = MiniTimer::new();
let task = TaskBuilder::new(16)
.with_frequency_once_by_seconds(60)
.spawn_async(CounterTask::new(counter.clone()))
.unwrap();
timer.add_task(task).unwrap();
assert!(timer.contains_task(16), "Task should exist before advance");
tokio::time::sleep(Duration::from_millis(50)).await;
let initial_status = timer.task_status(16).expect("Task should exist");
assert!(
initial_status.running_records.is_empty(),
"Task should have no running records before execution"
);
timer.advance_task(16, None, true).unwrap();
assert!(
timer.contains_task(16),
"Task should still exist immediately after trigger"
);
let status_after_trigger = timer
.task_status(16)
.expect("Task should exist after trigger");
assert!(
status_after_trigger.time_to_next_run <= 1,
"Task should be scheduled for immediate execution"
);
tokio::time::sleep(Duration::from_millis(200)).await;
}
#[tokio::test]
async fn test_advance_task_reset_frequency_true() {
let counter = Arc::new(AtomicU64::new(0));
let timer = MiniTimer::new();
let task = TaskBuilder::new(17)
.with_frequency_repeated_by_seconds(60)
.spawn_async(CounterTask::new(counter.clone()))
.unwrap();
timer.add_task(task).unwrap();
tokio::time::sleep(Duration::from_millis(50)).await;
let initial_status = timer.task_status(17).expect("Task should exist");
assert!(
initial_status.time_to_next_run > 55 && initial_status.time_to_next_run <= 60,
"Initial time_to_next_run should be around 60 seconds, got {}",
initial_status.time_to_next_run
);
timer.advance_task(17, None, true).unwrap();
let status_after_trigger = timer
.task_status(17)
.expect("Task should exist after trigger");
assert!(
status_after_trigger.time_to_next_run <= 1,
"Task should be scheduled for immediate execution after trigger with reset, got {}",
status_after_trigger.time_to_next_run
);
timer.tick().await;
tokio::time::sleep(Duration::from_millis(100)).await;
let count_after_first = counter.load(Ordering::SeqCst);
assert!(
count_after_first >= 1,
"Task should have executed after trigger, count={}",
count_after_first
);
tokio::time::sleep(Duration::from_millis(50)).await;
let status_after_exec = timer.task_status(17).expect("Task should still exist");
assert!(
status_after_exec.time_to_next_run > 55 && status_after_exec.time_to_next_run <= 60,
"Next execution should be ~60 seconds after reset (got {})",
status_after_exec.time_to_next_run
);
}
#[tokio::test]
async fn test_advance_task_reset_frequency_multi_round() {
let counter = Arc::new(AtomicU64::new(0));
let timer = MiniTimer::new();
let task = TaskBuilder::new(18)
.with_frequency_repeated_by_seconds(60)
.spawn_async(CounterTask::new(counter.clone()))
.unwrap();
timer.add_task(task).unwrap();
tokio::time::sleep(Duration::from_millis(50)).await;
timer.advance_task(18, None, true).unwrap();
timer.tick().await;
tokio::time::sleep(Duration::from_millis(100)).await;
tokio::time::sleep(Duration::from_millis(50)).await;
let count_after_first = counter.load(Ordering::SeqCst);
assert!(
count_after_first >= 1,
"Task should have executed after first trigger"
);
let status_after_first = timer.task_status(18).expect("Task should exist");
let first_reset_next_run = status_after_first.time_to_next_run;
assert!(
first_reset_next_run > 55 && first_reset_next_run <= 60,
"After first reset, next execution should be ~60 seconds, got {}",
first_reset_next_run
);
timer.advance_task(18, None, true).unwrap();
timer.tick().await;
tokio::time::sleep(Duration::from_millis(100)).await;
tokio::time::sleep(Duration::from_millis(50)).await;
let count_after_second = counter.load(Ordering::SeqCst);
assert!(
count_after_second >= 2,
"Task should have executed after second trigger, count={}",
count_after_second
);
let status_after_second = timer.task_status(18).expect("Task should exist");
let second_reset_next_run = status_after_second.time_to_next_run;
assert!(
second_reset_next_run > 55 && second_reset_next_run <= 60,
"After second reset, next execution should be ~60 seconds, got {}",
second_reset_next_run
);
timer.advance_task(18, None, true).unwrap();
timer.tick().await;
tokio::time::sleep(Duration::from_millis(100)).await;
tokio::time::sleep(Duration::from_millis(50)).await;
let count_after_third = counter.load(Ordering::SeqCst);
assert!(
count_after_third >= 3,
"Task should have executed after third trigger, count={}",
count_after_third
);
let status_after_third = timer.task_status(18).expect("Task should exist");
let third_reset_next_run = status_after_third.time_to_next_run;
assert!(
third_reset_next_run > 55 && third_reset_next_run <= 60,
"After third reset, next execution should be ~60 seconds, got {}",
third_reset_next_run
);
}