use std::time::Duration;
use calloop::EventLoop;
use calloop::channel::Event as ChannelEvent;
use tablero::producer::{ProducerBridge, from_fn};
use tablero::render::Bounds;
use tablero::sysmon::{cpu_usage, parse_cpu_times, parse_mem_usage};
use tablero::widget::{Dashboard, Msg, SystemStats, SystemWidget};
type Sample = (&'static str, &'static str);
struct Harness {
dashboard: Dashboard,
labels: Vec<String>,
redraws: usize,
}
impl Harness {
fn new() -> Self {
Self {
dashboard: Dashboard::new(vec![Box::new(SystemWidget::new(Bounds::new(
0, 0, 320, 32,
)))]),
labels: Vec::new(),
redraws: 0,
}
}
}
fn pump_until(
event_loop: &mut EventLoop<Harness>,
harness: &mut Harness,
done: impl Fn(&Harness) -> bool,
) {
let mut waited = Duration::ZERO;
let step = Duration::from_millis(20);
while !done(harness) && waited < Duration::from_secs(5) {
event_loop
.dispatch(step, harness)
.expect("loop dispatch succeeds");
waited += step;
}
}
fn stats_between(prev: Sample, cur: Sample) -> SystemStats {
let prev_cpu = parse_cpu_times(prev.0).expect("valid prev /proc/stat");
let cur_cpu = parse_cpu_times(cur.0).expect("valid cur /proc/stat");
let cpu = cpu_usage(prev_cpu, cur_cpu);
let mem = parse_mem_usage(cur.1).expect("valid /proc/meminfo");
SystemStats::new(cpu, mem)
}
fn drive(samples: Vec<SystemStats>) -> (Vec<String>, usize) {
let mut harness = Harness::new();
let mut event_loop: EventLoop<Harness> = EventLoop::try_new().expect("event loop");
let handle = event_loop.handle();
let (bridge, channel) = ProducerBridge::new().expect("runtime starts");
let expected = samples.len();
handle
.insert_source(channel, |event, _, h: &mut Harness| {
if let ChannelEvent::Msg(msg) = event {
if h.dashboard.update(&msg) {
h.redraws += 1;
}
if let Msg::System(stats) = &msg {
h.labels.push(stats.label());
}
}
})
.expect("channel registers");
bridge.spawn(from_fn("fake-sysmon", move |tx| async move {
for stats in samples {
tx.send(Msg::System(stats))?;
}
Ok(())
}));
pump_until(&mut event_loop, &mut harness, |h| {
h.labels.len() >= expected
});
drop(bridge);
(harness.labels, harness.redraws)
}
const STAT_A: &str = "cpu 100 0 50 800 30 0 20 0 0 0\ncpu0 50 0 25 400 15 0 10\n";
const STAT_B: &str = "cpu 150 0 70 825 30 0 25 0 0 0\ncpu0 75 0 35 412 15 0 12\n";
const MEMINFO: &str = "MemTotal: 16000000 kB\nMemAvailable: 8000000 kB\n";
#[test]
fn sampled_procfs_text_reaches_rendered_widget_state() {
let stats = stats_between((STAT_A, MEMINFO), (STAT_B, MEMINFO));
let (labels, redraws) = drive(vec![stats]);
assert_eq!(labels, vec!["CPU 75% MEM 50%".to_string()]);
assert_eq!(redraws, 1, "the first sample paints once");
}
#[test]
fn a_changed_sample_repaints_an_unchanged_one_does_not() {
let first = stats_between((STAT_A, MEMINFO), (STAT_B, MEMINFO)); let same = SystemStats::new(75.0, 50.0);
let moved = SystemStats::new(75.0, 63.0);
let (labels, redraws) = drive(vec![first, same, moved]);
assert_eq!(
labels,
vec![
"CPU 75% MEM 50%".to_string(),
"CPU 75% MEM 50%".to_string(),
"CPU 75% MEM 63%".to_string(),
]
);
assert_eq!(redraws, 2, "only visible changes repaint");
}