use super::{super::types::RemoteUpdate, *};
use glam::Vec3;
use indicatrix_net::{
client::Accumulator,
messages::{Done, ErrorMsg, FrameHeader, Progress, Stats},
radiance,
};
fn current_request(
request_id: u32,
width: u32,
height: u32,
) -> (CurrentRequest, Arc<Mutex<Vec<RemoteUpdate>>>) {
let mut acc = Accumulator::new(width, height);
acc.begin_request(request_id);
let updates = Arc::new(Mutex::new(Vec::new()));
let updates_for_closure = Arc::clone(&updates);
let on_update = Box::new(move |u: RemoteUpdate| {
updates_for_closure
.lock()
.unwrap_or_else(PoisonError::into_inner)
.push(u);
});
(
CurrentRequest {
request_id,
accumulator: Arc::new(Mutex::new(acc)),
on_update,
},
updates,
)
}
#[test]
fn route_event_with_no_current_request_is_a_harmless_no_op() {
let mut current: Option<CurrentRequest> = None;
let event = StreamEvent::Progress(Progress {
request_id: 1,
samples_done: 5,
});
route_event(&mut current, &event, None); assert!(current.is_none());
}
#[test]
fn route_event_applies_a_matching_frame_and_reports_it() {
let (cur, updates) = current_request(1, 2, 2);
let mut current = Some(cur);
let bytes = radiance::encode(&[Vec3::splat(1.0); 4]);
let header = FrameHeader::for_payload(1, 0, 3, &bytes);
route_event(&mut current, &StreamEvent::Frame(header), Some(&bytes));
assert!(current.is_some(), "a FRAME is never terminal");
assert!(matches!(
updates.lock().unwrap()[..],
[RemoteUpdate::Frame {
request_id: 1,
samples_done: 3
}]
));
}
#[test]
fn route_event_drops_a_stale_frame_for_a_superseded_request() {
let (cur, updates) = current_request(2, 2, 2); let mut current = Some(cur);
let bytes = radiance::encode(&[Vec3::splat(9.0); 4]);
let stale_header = FrameHeader::for_payload(1, 0, 3, &bytes);
route_event(
&mut current,
&StreamEvent::Frame(stale_header),
Some(&bytes),
);
assert!(
current.is_some(),
"the current (id 2) request must be untouched by a stale id-1 reply"
);
assert!(
updates.lock().unwrap().is_empty(),
"a stale reply for a superseded request must never reach on_update"
);
}
#[test]
fn route_event_clears_current_on_done() {
let (cur, updates) = current_request(7, 1, 1);
let mut current = Some(cur);
route_event(
&mut current,
&StreamEvent::Done(Done {
request_id: 7,
cancelled: false,
stats: Stats {
samples_done: 10,
requested_cadence_ms: 0,
effective_cadence_ms: 0,
},
}),
None,
);
assert!(current.is_none(), "DONE must end this request's epoch");
assert!(matches!(
updates.lock().unwrap()[..],
[RemoteUpdate::Done {
request_id: 7,
cancelled: false
}]
));
}
#[test]
fn route_event_drops_a_stale_done_without_clearing_the_real_current_request() {
let (cur, updates) = current_request(2, 1, 1);
let mut current = Some(cur);
route_event(
&mut current,
&StreamEvent::Done(Done {
request_id: 1, cancelled: true,
stats: Stats {
samples_done: 1,
requested_cadence_ms: 0,
effective_cadence_ms: 0,
},
}),
None,
);
assert!(
current.is_some(),
"a stale DONE for a superseded request must never end the CURRENT one"
);
assert!(updates.lock().unwrap().is_empty());
}
#[test]
fn route_event_worker_error_clears_current_regardless_of_epoch() {
let (cur, updates) = current_request(3, 1, 1);
let mut current = Some(cur);
route_event(
&mut current,
&StreamEvent::Error(ErrorMsg {
code: 9,
message: "boom".to_string(),
}),
None,
);
assert!(current.is_none(), "a WorkerError always ends the request");
assert!(matches!(
&updates.lock().unwrap()[..],
[RemoteUpdate::Failed { request_id: 3, message }] if message == "boom"
));
}
#[test]
fn check_liveness_is_a_no_op_when_nothing_is_current() {
let mut current: Option<CurrentRequest> = None;
let long_ago = Instant::now()
.checked_sub(Duration::from_millis(500))
.expect("500ms in the past is always representable");
assert!(!check_liveness(
&mut current,
long_ago,
Duration::from_millis(10)
));
assert!(current.is_none());
}
#[test]
fn check_liveness_is_a_no_op_before_the_deadline() {
let (cur, updates) = current_request(4, 1, 1);
let mut current = Some(cur);
assert!(!check_liveness(
&mut current,
Instant::now(),
Duration::from_secs(30)
));
assert!(current.is_some());
assert!(updates.lock().unwrap().is_empty());
}
#[test]
fn check_liveness_fails_the_current_request_once_the_deadline_has_passed() {
let (cur, updates) = current_request(5, 1, 1);
let mut current = Some(cur);
let timeout = Duration::from_millis(20);
let last_event = Instant::now()
.checked_sub(Duration::from_millis(50))
.expect("50ms in the past is always representable");
assert!(check_liveness(&mut current, last_event, timeout));
assert!(
current.is_none(),
"a timed-out request must be dropped so the next dispatch reconnects"
);
assert!(matches!(
&updates.lock().unwrap()[..],
[RemoteUpdate::Failed { request_id: 5, message }] if message.contains("silent")
));
}
#[test]
fn check_liveness_never_trips_while_last_event_keeps_being_reset() {
let (cur, updates) = current_request(6, 1, 1);
let mut current = Some(cur);
let timeout = Duration::from_millis(30);
for _ in 0..5 {
thread::sleep(Duration::from_millis(10)); let last_event = Instant::now(); assert!(!check_liveness(&mut current, last_event, timeout));
}
assert!(current.is_some());
assert!(updates.lock().unwrap().is_empty());
}
#[test]
fn liveness_deadline_grants_the_first_event_grace_before_any_event_has_arrived() {
let liveness_timeout = Duration::from_secs(8);
assert_eq!(
liveness_deadline(false, liveness_timeout),
FIRST_EVENT_TIMEOUT,
"the wait for a request's very first event must use the longer grace, not the \
steady-state deadline"
);
}
#[test]
fn liveness_deadline_switches_to_the_tighter_steady_state_timeout_once_seen() {
let liveness_timeout = Duration::from_millis(123); assert_eq!(
liveness_deadline(true, liveness_timeout),
liveness_timeout,
"once a request has produced at least one event, every wait after it must use \
the caller's own (heartbeat-derived) liveness_timeout, not the first-event grace"
);
}
#[test]
fn first_event_timeout_is_strictly_longer_than_liveness_timeout() {
assert!(FIRST_EVENT_TIMEOUT > LIVENESS_TIMEOUT);
}
#[test]
fn check_liveness_ignores_a_long_busy_phase_that_ends_with_a_fresh_event() {
let (cur, updates) = current_request(8, 1, 1);
let mut current = Some(cur);
let timeout = Duration::from_millis(20);
let stale_last_event = Instant::now()
.checked_sub(Duration::from_secs(5))
.expect("5s in the past is always representable");
let fresh_last_event = Instant::now();
assert!(!check_liveness(&mut current, fresh_last_event, timeout));
assert!(
current.is_some(),
"an event arriving right after a long busy phase must never be treated as the \
worker having gone silent"
);
assert!(updates.lock().unwrap().is_empty());
assert!(check_liveness(&mut current, stale_last_event, timeout));
assert!(current.is_none());
}