1use crate::context::Host;
6
7pub struct Clock<'a> {
9 host: &'a dyn Host,
10}
11
12impl<'a> Clock<'a> {
13 #[must_use]
14 pub(crate) fn new(host: &'a dyn Host) -> Self {
15 Self { host }
16 }
17 #[must_use]
19 pub fn now(&self) -> u64 {
20 self.host.monotonic_nanos()
21 }
22 pub fn start(&self) -> Timer<'a> {
24 Timer {
25 host: self.host,
26 start: self.host.monotonic_nanos(),
27 }
28 }
29 pub fn sleep_ms(&self, ms: u64) {
31 self.host.sleep_nanos(ms.saturating_mul(1_000_000));
32 }
33}
34
35#[must_use = "a timer measures nothing until `.elapsed_ms()` is read"]
38pub struct Timer<'a> {
39 host: &'a dyn Host,
40 start: u64,
41}
42
43impl Timer<'_> {
44 #[must_use]
46 pub fn elapsed_ms(&self) -> u64 {
47 self.host.monotonic_nanos().saturating_sub(self.start) / 1_000_000
48 }
49}
50
51#[cfg(test)]
52mod tests {
53 use crate::Context;
54 use crate::testing::MockHost;
55
56 #[test]
57 fn timer_elapsed_ms_from_scripted_clock() {
58 let host = MockHost::new().with_clock(&[0, 5_000_000]);
60 let ctx = Context::new(&host, "/w".into(), "s".into());
61 let t = ctx.clock().start();
62 assert_eq!(t.elapsed_ms(), 5);
63 }
64
65 #[test]
66 fn now_passes_through_and_saturates_non_increasing() {
67 let host = MockHost::new().with_clock(&[10, 3]); let ctx = Context::new(&host, "/w".into(), "s".into());
69 let t = ctx.clock().start(); assert_eq!(t.elapsed_ms(), 0); }
72
73 #[test]
74 fn empty_clock_script_reads_zero() {
75 let host = MockHost::new();
76 let ctx = Context::new(&host, "/w".into(), "s".into());
77 assert_eq!(ctx.clock().now(), 0);
78 }
79
80 #[test]
81 fn sleep_ms_converts_to_nanos_and_records() {
82 let host = MockHost::new();
83 let ctx = Context::new(&host, "/w".into(), "s".into());
84 ctx.clock().sleep_ms(5);
85 ctx.clock().sleep_ms(0);
86 assert_eq!(host.recorded_sleeps(), vec![5_000_000, 0]);
87 }
88}