//! `Observer` callbacks must actually fire. //! //! `on_slice_processed` (formerly `on_batch_processed`) was declared on the //! trait and never called from anywhere, so implementors wired up a callback //! that could not run. These tests exist so that cannot silently recur. use std::sync::{Arc, Mutex}; use trueskill_tt::{History, Observer}; /// `History` takes its observer by value and never hands it back, so a test /// that wants to read what was recorded shares the storage rather than the /// observer: the handles are cloned, the buffers are not. #[derive(Clone, Default)] struct Recorder { iterations: Arc>>, slices: Arc>>, converged: Arc>>, } impl Observer for Recorder { fn on_iteration_end(&self, iter: usize, _max_step: (f64, f64)) { self.iterations.lock().unwrap().push(iter); } fn on_slice_processed(&self, time: &i64, slice_idx: usize, n_events: usize) { self.slices .lock() .unwrap() .push((*time, slice_idx, n_events)); } fn on_converged(&self, iters: usize, _final_step: (f64, f64), converged: bool) { self.converged.lock().unwrap().push((iters, converged)); } } #[test] fn every_observer_callback_fires() { let recorder = Recorder::default(); let mut h = History::builder().observer(recorder.clone()).build(); h.record_winner(&"a", &"b", 1).unwrap(); h.record_winner(&"b", &"c", 2).unwrap(); h.record_winner(&"c", &"a", 3).unwrap(); h.converge().unwrap(); assert!( !recorder.iterations.lock().unwrap().is_empty(), "on_iteration_end never fired" ); assert!( !recorder.converged.lock().unwrap().is_empty(), "on_converged never fired" ); assert!( !recorder.slices.lock().unwrap().is_empty(), "on_slice_processed never fired — the defect this test exists for" ); } #[test] fn slice_callbacks_report_the_slice_they_swept() { let recorder = Recorder::default(); let mut h = History::builder().observer(recorder.clone()).build(); h.record_winner(&"a", &"b", 10).unwrap(); h.record_winner(&"a", &"b", 20).unwrap(); h.converge().unwrap(); let slices = recorder.slices.lock().unwrap(); // Only the times actually in the history, and each with its own events. for &(time, idx, events) in slices.iter() { assert!(time == 10 || time == 20, "unexpected slice time {time}"); assert!(idx < 2, "slice index {idx} out of range"); assert_eq!(events, 1, "each slice holds exactly one event"); } // Both slices must be reported, not just one end of the sweep. assert!( slices.iter().any(|&(t, ..)| t == 10), "slice 10 never reported" ); assert!( slices.iter().any(|&(t, ..)| t == 20), "slice 20 never reported" ); } #[test] fn a_single_slice_history_still_reports_its_sweep() { let recorder = Recorder::default(); let mut h = History::builder().observer(recorder.clone()).build(); h.record_winner(&"a", &"b", 1).unwrap(); h.converge().unwrap(); let slices = recorder.slices.lock().unwrap(); assert!( !slices.is_empty(), "the single-slice path must report its sweep too" ); assert!(slices.iter().all(|&(t, idx, _)| t == 1 && idx == 0)); }