use std::cell::Cell;
use std::rc::Rc;
use auralis_signal::{batch, Memo, Signal};
use auralis_task::{
init_flush_scheduler, init_time_source, reset_executor_for_test, set_deferred, Priority,
ScheduleFlush, TaskScope, TimeSource,
};
struct SyncScheduler;
impl ScheduleFlush for SyncScheduler {
fn schedule(&self, callback: Box<dyn FnOnce()>) {
callback();
}
}
struct TestClock {
now: Cell<u64>,
}
impl TestClock {
fn new(initial: u64) -> Self {
Self {
now: Cell::new(initial),
}
}
}
impl TimeSource for TestClock {
fn now_ms(&self) -> u64 {
self.now.get()
}
}
fn setup() -> Rc<TestClock> {
reset_executor_for_test();
let sched: Rc<dyn ScheduleFlush> = Rc::new(SyncScheduler);
init_flush_scheduler(sched);
let clock = Rc::new(TestClock::new(0));
init_time_source(clock.clone());
clock
}
#[test]
fn signal_change_wakes_spawned_task() {
setup();
let sig = Signal::new(0);
let executed = Rc::new(Cell::new(false));
let s = sig.clone();
let e = Rc::clone(&executed);
let scope = TaskScope::new();
scope.spawn(async move {
s.changed().await;
e.set(true);
});
sig.set(42);
assert!(executed.get());
}
#[test]
fn scope_drop_cancels_awaiting_task() {
setup();
let sig = Signal::new(0);
let dropped = Rc::new(Cell::new(false));
let d = Rc::clone(&dropped);
struct Guard(Rc<Cell<bool>>);
impl Drop for Guard {
fn drop(&mut self) {
self.0.set(true);
}
}
{
let _scope = TaskScope::new();
let s = sig.clone();
_scope.spawn(async move {
let _g = Guard(d);
s.changed().await;
unreachable!();
});
}
assert!(dropped.get());
}
#[test]
fn batch_multiple_sets_wakes_once() {
setup();
let sig = Signal::new(0);
let count = Rc::new(Cell::new(0u32));
let c = Rc::clone(&count);
let scope = TaskScope::new();
let s = sig.clone();
scope.spawn(async move {
loop {
s.changed().await;
c.set(c.get() + 1);
}
});
batch(|| {
sig.set(1);
sig.set(2);
sig.set(3);
});
assert_eq!(count.get(), 1);
}
#[test]
fn memo_change_wakes_task() {
setup();
let a = Signal::new(2);
let b = Signal::new(3);
let a2 = a.clone();
let b2 = b.clone();
let sum = Memo::new(move || a2.read() + b2.read());
let executed = Rc::new(Cell::new(false));
let e = Rc::clone(&executed);
let scope = TaskScope::new();
let s = sum.clone();
scope.spawn(async move {
s.changed().await;
e.set(true);
});
a.set(10);
assert!(executed.get());
}
#[test]
fn set_deferred_does_not_panic_in_task_drop() {
setup();
let sig = Signal::new(0);
{
let scope = TaskScope::new();
let s = sig.clone();
scope.spawn(async move {
s.changed().await;
});
set_deferred(&sig, 42);
}
assert_eq!(sig.read(), 42);
}
#[test]
fn high_priority_task_spawns_and_runs() {
setup();
let executed = Rc::new(Cell::new(false));
let e = Rc::clone(&executed);
let scope = TaskScope::new();
scope.spawn_with_priority(Priority::High, async move {
e.set(true);
});
assert!(executed.get());
}
#[test]
fn deep_scope_tree_no_leak_on_batch_cancel() {
setup();
let sig = Signal::new(0);
let alive_count = Rc::new(Cell::new(0u32));
{
let root = TaskScope::new();
let mut current = root.clone();
for _ in 0..50 {
let child = TaskScope::new_child(¤t);
let s = sig.clone();
let a = Rc::clone(&alive_count);
a.set(a.get() + 1);
child.spawn(async move {
let _guard = IncrementOnDrop(Rc::clone(&a));
s.changed().await;
});
current = child;
}
}
assert_eq!(alive_count.get(), 0);
}
struct IncrementOnDrop(Rc<Cell<u32>>);
impl Drop for IncrementOnDrop {
fn drop(&mut self) {
self.0.set(self.0.get().saturating_sub(1));
}
}