use deferred_map::{DefaultKey, Key};
use crate::config::{BatchConfig, WheelConfig};
use crate::task::{CallbackWrapper, TaskId, TimerTask, TimerTaskWithCompletionNotifier};
use crate::wheel::Wheel;
use std::time::Duration;
#[test]
fn test_postpone_single_task() {
let mut wheel = Wheel::new(WheelConfig::default(), BatchConfig::default());
let callback = CallbackWrapper::new(|| async {});
let task = TimerTask::new_oneshot(Duration::from_millis(100), Some(callback));
let (task_with_notifier, _completion_rx) =
TimerTaskWithCompletionNotifier::from_timer_task(task);
let handle = wheel.allocate_handle();
let task_id = handle.task_id();
wheel.insert(handle, task_with_notifier);
let postponed = wheel.postpone(task_id, Duration::from_millis(200), None);
assert!(postponed);
assert!(!wheel.is_empty());
for _ in 0..10 {
let expired = wheel.advance();
assert!(expired.is_empty());
}
let mut triggered = false;
for _ in 0..10 {
let expired = wheel.advance();
if !expired.is_empty() {
assert_eq!(expired.len(), 1);
assert_eq!(expired[0].id, task_id);
triggered = true;
break;
}
}
assert!(triggered);
}
#[test]
fn test_postpone_with_new_callback() {
let mut wheel = Wheel::new(WheelConfig::default(), BatchConfig::default());
let old_callback = CallbackWrapper::new(|| async {});
let task = TimerTask::new_oneshot(Duration::from_millis(100), Some(old_callback.clone()));
let (task_with_notifier, _completion_rx) =
TimerTaskWithCompletionNotifier::from_timer_task(task);
let handle = wheel.allocate_handle();
let task_id = handle.task_id();
wheel.insert(handle, task_with_notifier);
let new_callback = CallbackWrapper::new(|| async {});
let postponed = wheel.postpone(task_id, Duration::from_millis(50), Some(new_callback));
assert!(postponed);
let mut triggered = false;
for i in 0..5 {
let expired = wheel.advance();
if !expired.is_empty() {
assert_eq!(
expired.len(),
1,
"On the {}th advance, there should be 1 task triggered",
i + 1
);
assert_eq!(expired[0].id, task_id);
triggered = true;
break;
}
}
assert!(triggered, "Task should be triggered within 5 ticks"); }
#[test]
fn test_postpone_nonexistent_task() {
let mut wheel = Wheel::new(WheelConfig::default(), BatchConfig::default());
let handle = wheel.allocate_handle();
let valid_key = handle.task_id().key();
#[cfg(debug_assertions)]
let map_id = valid_key.map_id();
let fake_key = DefaultKey::from_parts(
u32::MAX,
deferred_map::Generation::MIN,
#[cfg(debug_assertions)]
map_id,
);
let fake_task_id = TaskId::from_key(fake_key);
let postponed = wheel.postpone(fake_task_id, Duration::from_millis(100), None);
assert!(!postponed);
}
#[test]
fn test_postpone_batch() {
let mut wheel = Wheel::new(WheelConfig::default(), BatchConfig::default());
let mut task_ids = Vec::new();
for _ in 0..5 {
let callback = CallbackWrapper::new(|| async {});
let task = TimerTask::new_oneshot(Duration::from_millis(50), Some(callback));
let (task_with_notifier, _completion_rx) =
TimerTaskWithCompletionNotifier::from_timer_task(task);
let handle = wheel.allocate_handle();
let task_id = handle.task_id();
wheel.insert(handle, task_with_notifier);
task_ids.push(task_id);
}
let updates: Vec<_> = task_ids
.iter()
.map(|&id| (id, Duration::from_millis(150)))
.collect();
let postponed_count = wheel.postpone_batch(updates);
assert_eq!(postponed_count, 5);
for _ in 0..5 {
let expired = wheel.advance();
assert!(
expired.is_empty(),
"The first 5 ticks should not have tasks triggered"
);
}
let mut total_triggered = 0;
for _ in 0..10 {
let expired = wheel.advance();
total_triggered += expired.len();
}
assert_eq!(
total_triggered, 5,
"There should be 5 tasks triggered on the 15th tick"
); }
#[test]
fn test_postpone_batch_partial() {
let mut wheel = Wheel::new(WheelConfig::default(), BatchConfig::default());
let mut task_ids = Vec::new();
for _ in 0..10 {
let callback = CallbackWrapper::new(|| async {});
let task = TimerTask::new_oneshot(Duration::from_millis(50), Some(callback));
let (task_with_notifier, _completion_rx) =
TimerTaskWithCompletionNotifier::from_timer_task(task);
let handle = wheel.allocate_handle();
let task_id = handle.task_id();
wheel.insert(handle, task_with_notifier);
task_ids.push(task_id);
}
#[cfg(debug_assertions)]
let map_id = task_ids[0].key().map_id();
let fake_key = DefaultKey::from_parts(
u32::MAX,
deferred_map::Generation::MIN,
#[cfg(debug_assertions)]
map_id,
);
let fake_task_id = TaskId::from_key(fake_key);
let mut updates: Vec<_> = task_ids[0..5]
.iter()
.map(|&id| (id, Duration::from_millis(150)))
.collect();
updates.push((fake_task_id, Duration::from_millis(150)));
let postponed_count = wheel.postpone_batch(updates);
assert_eq!(
postponed_count, 5,
"There should be 5 tasks successfully postponed (fake_task_id failed)"
);
let mut triggered_at_50ms = 0;
for _ in 0..5 {
let expired = wheel.advance();
triggered_at_50ms += expired.len();
}
assert_eq!(
triggered_at_50ms, 5,
"There should be 5 tasks that were not postponed triggered on the 5th tick"
);
let mut triggered_at_150ms = 0;
for _ in 0..10 {
let expired = wheel.advance();
triggered_at_150ms += expired.len();
}
assert_eq!(
triggered_at_150ms, 5,
"There should be 5 tasks that were postponed triggered on the 15th tick"
); }
#[test]
fn test_postpone_same_task_multiple_times() {
let mut wheel = Wheel::new(WheelConfig::default(), BatchConfig::default());
let callback = CallbackWrapper::new(|| async {});
let task = TimerTask::new_oneshot(Duration::from_millis(100), Some(callback));
let (task_with_notifier, _completion_rx) =
TimerTaskWithCompletionNotifier::from_timer_task(task);
let handle = wheel.allocate_handle();
let task_id = handle.task_id();
wheel.insert(handle, task_with_notifier);
let postponed = wheel.postpone(task_id, Duration::from_millis(200), None);
assert!(postponed, "First postpone should succeed");
let postponed = wheel.postpone(task_id, Duration::from_millis(300), None);
assert!(postponed, "Second postpone should succeed");
let postponed = wheel.postpone(task_id, Duration::from_millis(50), None);
assert!(postponed, "Third postpone should succeed");
let mut triggered = false;
for _ in 0..5 {
let expired = wheel.advance();
if !expired.is_empty() {
assert_eq!(expired.len(), 1);
assert_eq!(expired[0].id, task_id);
triggered = true;
break;
}
}
assert!(
triggered,
"Task should be triggered at the last postpone time"
); }
#[test]
fn test_cancel_after_postpone() {
let mut wheel = Wheel::new(WheelConfig::default(), BatchConfig::default());
let callback = CallbackWrapper::new(|| async {});
let task = TimerTask::new_oneshot(Duration::from_millis(100), Some(callback));
let (task_with_notifier, _completion_rx) =
TimerTaskWithCompletionNotifier::from_timer_task(task);
let handle = wheel.allocate_handle();
let task_id = handle.task_id();
wheel.insert(handle, task_with_notifier);
let postponed = wheel.postpone(task_id, Duration::from_millis(200), None);
assert!(postponed, "Postpone should succeed");
let cancelled = wheel.cancel(task_id);
assert!(cancelled, "Cancel should succeed");
for _ in 0..20 {
let expired = wheel.advance();
assert!(expired.is_empty(), "Cancelled task should not trigger"); }
assert!(wheel.is_empty(), "Wheel should be empty"); }
#[test]
fn test_cross_layer_postpone() {
let config = WheelConfig::default();
let mut wheel = Wheel::new(config, BatchConfig::default());
let callback = CallbackWrapper::new(|| async {});
let task = TimerTask::new_oneshot(Duration::from_millis(100), Some(callback));
let (task_with_notifier, _rx) = TimerTaskWithCompletionNotifier::from_timer_task(task);
let handle = wheel.allocate_handle();
let task_id = handle.task_id();
wheel.insert(handle, task_with_notifier);
assert_eq!(wheel.task_index.get(task_id.key()).unwrap().level, 0);
assert!(wheel.postpone(task_id, Duration::from_secs(10), None));
assert_eq!(wheel.task_index.get(task_id.key()).unwrap().level, 1);
assert!(wheel.postpone(task_id, Duration::from_millis(200), None));
assert_eq!(wheel.task_index.get(task_id.key()).unwrap().level, 0);
}