Skip to content

Commit 32d8129

Browse files
committed
Add tests.
1 parent 9a6ea24 commit 32d8129

8 files changed

Lines changed: 280 additions & 86 deletions

File tree

src/actor.rs

Lines changed: 63 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -151,20 +151,20 @@ where
151151

152152
pub fn is_started(&mut self) -> bool {
153153
let started_alive = self.started_alive.lock().unwrap();
154-
let &(ref started, _) = &*started_alive;
154+
let (started, _) = &*started_alive;
155155
started.load(Ordering::SeqCst)
156156
}
157157

158158
pub fn is_alive(&mut self) -> bool {
159159
let started_alive = self.started_alive.lock().unwrap();
160-
let &(_, ref alive) = &*started_alive;
160+
let (_, alive) = &*started_alive;
161161
alive.load(Ordering::SeqCst)
162162
}
163163

164164
pub fn stop(&mut self) {
165165
{
166166
let started_alive = self.started_alive.lock().unwrap();
167-
let &(ref started, ref alive) = &*started_alive;
167+
let (started, alive) = &*started_alive;
168168

169169
if !started.load(Ordering::SeqCst) {
170170
return;
@@ -174,7 +174,6 @@ where
174174
}
175175
alive.store(false, Ordering::SeqCst);
176176
}
177-
178177
// NOTE: Kill thread <- OS depending
179178
// let mut join_handle = self.join_handle.lock().unwrap();
180179
// join_handle
@@ -193,7 +192,7 @@ where
193192
pub fn start(&mut self) {
194193
{
195194
let started_alive = self.started_alive.lock().unwrap();
196-
let &(ref started, ref alive) = &*started_alive;
195+
let (started, alive) = &*started_alive;
197196

198197
if started.load(Ordering::SeqCst) {
199198
return;
@@ -212,7 +211,7 @@ where
212211
self.join_handle = Arc::new(Mutex::new(Some(thread::spawn(move || {
213212
while {
214213
let started_alive = started_alive_thread.lock().unwrap();
215-
let &(_, ref alive) = &*started_alive;
214+
let (_, alive) = &*started_alive;
216215

217216
alive.load(Ordering::SeqCst)
218217
} {
@@ -225,7 +224,7 @@ where
225224
}
226225
None => {
227226
let started_alive = started_alive_thread.lock().unwrap();
228-
let &(_, ref alive) = &*started_alive;
227+
let (_, alive) = &*started_alive;
229228

230229
alive.store(false, Ordering::SeqCst);
231230
}
@@ -317,7 +316,7 @@ where
317316

318317
#[test]
319318
fn test_actor_common() {
320-
use std::time::Duration;
319+
use std::time::{Duration, Instant};
321320

322321
use super::common::LinkedListAsync;
323322

@@ -407,15 +406,32 @@ fn test_actor_common() {
407406
// Send Shutdown
408407
root_handle.send(Value::Shutdown);
409408

410-
thread::sleep(Duration::from_millis(10));
409+
// result_string receives the children-ids vec once the root handles Shutdown;
410+
// the 3 child actors push 6 ints across their own threads. Wait deterministically
411+
// rather than racing a fixed sleep (the 5s ceilings only bound a genuine hang).
412+
let ids_deadline = Instant::now() + Duration::from_secs(5);
413+
let ids = loop {
414+
if let Some(v) = result_string.pop_front() {
415+
break Some(v);
416+
}
417+
if Instant::now() >= ids_deadline {
418+
break None;
419+
}
420+
thread::yield_now();
421+
};
411422
// 3 children Actors
412-
assert_eq!(3, result_string.pop_front().unwrap().len());
423+
assert_eq!(Some(3), ids.map(|ids| ids.len()));
413424

414425
let mut v = Vec::<Option<i32>>::new();
415-
for _ in 1..7 {
416-
let i = result_i32.pop_front();
417-
println!("Actor {:?}", i);
418-
v.push(i);
426+
let v_deadline = Instant::now() + Duration::from_secs(5);
427+
while v.len() < 6 {
428+
if let Some(i) = result_i32.pop_front() {
429+
v.push(Some(i));
430+
} else if Instant::now() >= v_deadline {
431+
break;
432+
} else {
433+
thread::yield_now();
434+
}
419435
}
420436
v.sort();
421437
assert_eq!(
@@ -433,7 +449,7 @@ fn test_actor_common() {
433449

434450
#[test]
435451
fn test_actor_ask() {
436-
use std::time::Duration;
452+
use std::time::{Duration, Instant};
437453

438454
use super::common::LinkedListAsync;
439455

@@ -466,37 +482,47 @@ fn test_actor_ask() {
466482
let mut root_handle = root.get_handle();
467483
root.start();
468484

469-
// LinkedListAsync<i32>
485+
// LinkedListAsync<i32> exposes only a non-blocking pop_front(), so wait-poll
486+
// until the actor has pushed each value. This replaces a fixed sleep with a
487+
// deterministic wait; the deadline only guards against a genuine hang.
488+
let wait_pop = |q: &LinkedListAsync<i32>| -> Option<i32> {
489+
let deadline = Instant::now() + Duration::from_secs(5);
490+
loop {
491+
if let Some(v) = q.pop_front() {
492+
return Some(v);
493+
}
494+
if Instant::now() >= deadline {
495+
return None;
496+
}
497+
thread::yield_now();
498+
}
499+
};
500+
470501
let result_i32 = LinkedListAsync::<i32>::new();
471502
root_handle.send(Value::AskIntByLinkedListAsync((1, result_i32.clone())));
472503
root_handle.send(Value::AskIntByLinkedListAsync((2, result_i32.clone())));
473504
root_handle.send(Value::AskIntByLinkedListAsync((3, result_i32.clone())));
474-
thread::sleep(Duration::from_millis(5));
475-
let i = result_i32.pop_front();
476-
assert_eq!(Some(10), i);
477-
let i = result_i32.pop_front();
478-
assert_eq!(Some(20), i);
479-
let i = result_i32.pop_front();
480-
assert_eq!(Some(30), i);
481-
482-
// BlockingQueue<i32>
505+
assert_eq!(Some(10), wait_pop(&result_i32));
506+
assert_eq!(Some(20), wait_pop(&result_i32));
507+
assert_eq!(Some(30), wait_pop(&result_i32));
508+
509+
// BlockingQueue<i32>: take() with a timeout blocks until the actor offers,
510+
// so the data path is deterministic under load. The generous 5s ceiling only
511+
// bounds a genuine hang rather than racing a fixed sleep.
483512
let mut result_i32 = BlockingQueue::<i32>::new();
484-
result_i32.timeout = Some(Duration::from_millis(1));
513+
result_i32.timeout = Some(Duration::from_secs(5));
485514
root_handle.send(Value::AskIntByBlockingQueue((4, result_i32.clone())));
486515
root_handle.send(Value::AskIntByBlockingQueue((5, result_i32.clone())));
487516
root_handle.send(Value::AskIntByBlockingQueue((6, result_i32.clone())));
488-
thread::sleep(Duration::from_millis(5));
489-
let i = result_i32.take();
490-
assert_eq!(Some(40), i);
491-
let i = result_i32.take();
492-
assert_eq!(Some(50), i);
493-
let i = result_i32.take();
494-
assert_eq!(Some(60), i);
495-
496-
// Timeout case:
517+
assert_eq!(Some(40), result_i32.take());
518+
assert_eq!(Some(50), result_i32.take());
519+
assert_eq!(Some(60), result_i32.take());
520+
521+
// Timeout case: the actor returns early for negatives (never offers), so a
522+
// short timeout makes take() return None deterministically.
523+
result_i32.timeout = Some(Duration::from_millis(1));
497524
root_handle.send(Value::AskIntByBlockingQueue((-1, result_i32.clone())));
498-
let i = result_i32.take();
499-
assert_eq!(None, i);
525+
assert_eq!(None, result_i32.take());
500526
}
501527

502528
#[test]

src/common.rs

Lines changed: 41 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ use std::marker::PhantomData;
99
use std::sync::{Arc, Mutex};
1010
use std::thread;
1111
use std::time::{SystemTime, UNIX_EPOCH};
12+
use std::sync::atomic::{AtomicUsize, Ordering};
1213

1314
#[cfg(feature = "for_futures")]
1415
use futures::executor::ThreadPool;
@@ -19,10 +20,7 @@ use std::mem;
1920
#[cfg(feature = "for_futures")]
2021
use std::pin::Pin;
2122
#[cfg(feature = "for_futures")]
22-
use std::sync::{
23-
atomic::{AtomicBool, Ordering},
24-
Once,
25-
};
23+
use std::sync::{atomic::AtomicBool, Once};
2624
#[cfg(feature = "for_futures")]
2725
use std::task::{Context, Poll, Waker};
2826

@@ -247,15 +245,6 @@ where
247245
}
248246
return Poll::Ready(None);
249247
}
250-
251-
fn size_hint(&self) -> (usize, Option<usize>) {
252-
// Check alive
253-
let alive = self.alive.lock().unwrap();
254-
if alive.load(Ordering::SeqCst) {
255-
return (0, Some(0));
256-
}
257-
return (0, None);
258-
}
259248
}
260249

261250
impl<T> Default for LinkedListAsync<Arc<T>> {
@@ -356,12 +345,19 @@ pub trait UniqueId<T> {
356345
fn get_id(&self) -> T;
357346
}
358347

348+
static ID_COUNTER: AtomicUsize = AtomicUsize::new(0);
349+
359350
pub fn generate_id() -> String {
360351
let since_the_epoch = SystemTime::now()
361352
.duration_since(UNIX_EPOCH)
362353
.expect("Time went backwards");
363354

364-
format!("{:?}{:?}", thread::current().id(), since_the_epoch)
355+
// A process-wide monotonic counter guarantees uniqueness even for
356+
// same-thread, same-tick, back-to-back calls: SystemTime resolution is
357+
// coarse, so thread id + timestamp alone collide (see UniqueId).
358+
let seq = ID_COUNTER.fetch_add(1, Ordering::Relaxed);
359+
360+
format!("{:?}{:?}-{}", thread::current().id(), since_the_epoch, seq)
365361
}
366362

367363
/**
@@ -620,16 +616,20 @@ fn test_common_get_mut() {
620616

621617
#[test]
622618
fn test_common_generate_id_non_empty_and_changes() {
623-
use std::thread;
624-
use std::time::Duration;
619+
use std::collections::HashSet;
625620

626621
let a = generate_id();
627622
assert_eq!(false, a.is_empty());
628623

629-
// Ids embed the elapsed time since epoch; after a delay a fresh id differs.
630-
thread::sleep(Duration::from_millis(2));
631-
let b = generate_id();
632-
assert_eq!(true, a != b);
624+
// UniqueId contract: ids must be unique even for tight, same-thread,
625+
// same-tick, back-to-back calls (SystemTime resolution alone is too
626+
// coarse to guarantee this; a monotonic counter does).
627+
let n = 10_000;
628+
let mut seen = HashSet::new();
629+
for _ in 0..n {
630+
assert_eq!(true, seen.insert(generate_id()));
631+
}
632+
assert_eq!(n, seen.len());
633633
}
634634

635635
#[test]
@@ -646,6 +646,22 @@ fn test_common_linked_list_async_fifo() {
646646
assert_eq!(None, list.pop_front());
647647
}
648648

649+
#[cfg(feature = "for_futures")]
650+
#[test]
651+
fn test_common_linked_list_async_size_hint_never_lies() {
652+
use futures::stream::Stream;
653+
654+
// size_hint's upper bound is a promise. A live stream can still receive
655+
// items via push_back, so it must NOT report Some(0) ("finished") — that
656+
// lets Stream combinators drop pending items. A closed stream must not be
657+
// reported as falsely unbounded either. (0, None) is correct for both.
658+
let mut list = LinkedListAsync::<i32>::new();
659+
assert_eq!((0, None), Stream::size_hint(&list));
660+
661+
list.close_stream();
662+
assert_eq!((0, None), Stream::size_hint(&list));
663+
}
664+
649665
#[test]
650666
fn test_common_linked_list_async_clone_shares_state() {
651667
// Clones share the same underlying list (Arc<Mutex<..>> inside).
@@ -730,14 +746,14 @@ fn test_common_subscription_func_clone_equal_same_id() {
730746

731747
#[test]
732748
fn test_common_subscription_func_distinct_ids() {
733-
use std::thread;
734-
use std::time::Duration;
735-
749+
// Back-to-back construction (no sleep): independently-constructed
750+
// subscriptions must have distinct ids, which is what makes PartialEq
751+
// and Publisher::delete_observer correct. (The reliable regression
752+
// guard for the underlying same-tick collision is the tight-loop
753+
// test on generate_id itself.)
736754
let a = SubscriptionFunc::new(|_x: Arc<i32>| {});
737-
thread::sleep(Duration::from_millis(2));
738755
let b = SubscriptionFunc::new(|_x: Arc<i32>| {});
739756

740-
// Independently constructed subscriptions are not equal.
741757
assert_eq!(true, a.get_id() != b.get_id());
742758
assert_eq!(false, a == b);
743759
}

0 commit comments

Comments
 (0)