cu29_clock/
lib.rs

1#[cfg(test)]
2#[macro_use]
3extern crate approx;
4use bincode::de::BorrowDecoder;
5use bincode::de::Decoder;
6use bincode::enc::Encoder;
7use bincode::error::{DecodeError, EncodeError};
8use bincode::BorrowDecode;
9use bincode::{Decode, Encode};
10use core::ops::{Add, Sub};
11pub use quanta::Instant;
12use quanta::{Clock, Mock};
13use serde::{Deserialize, Serialize};
14use std::convert::Into;
15use std::fmt::{Display, Formatter};
16use std::ops::{AddAssign, Div, Mul, SubAssign};
17use std::sync::Arc;
18use std::time::Duration;
19
20/// For Robot times, the underlying type is a u64 representing nanoseconds.
21/// It is always positive to simplify the reasoning on the user side.
22#[derive(Copy, Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize, Default)]
23pub struct CuDuration(pub u64);
24
25impl CuDuration {
26    // Lowest value a CuDuration can have.
27    pub const MIN: CuDuration = CuDuration(0u64);
28    // Highest value a CuDuration can have reserving the max value for None.
29    pub const MAX: CuDuration = CuDuration(NONE_VALUE - 1);
30    pub fn max(self, other: CuDuration) -> CuDuration {
31        let Self(lhs) = self;
32        let Self(rhs) = other;
33        CuDuration(lhs.max(rhs))
34    }
35
36    pub fn min(self, other: CuDuration) -> CuDuration {
37        let Self(lhs) = self;
38        let Self(rhs) = other;
39        CuDuration(lhs.min(rhs))
40    }
41
42    pub fn as_nanos(&self) -> u64 {
43        let Self(nanos) = self;
44        *nanos
45    }
46}
47
48/// bridge the API with standard Durations.
49impl From<Duration> for CuDuration {
50    fn from(duration: Duration) -> Self {
51        CuDuration(duration.as_nanos() as u64)
52    }
53}
54
55impl From<CuDuration> for Duration {
56    fn from(val: CuDuration) -> Self {
57        let CuDuration(nanos) = val;
58        Duration::from_nanos(nanos)
59    }
60}
61
62impl From<u64> for CuDuration {
63    fn from(duration: u64) -> Self {
64        CuDuration(duration)
65    }
66}
67
68impl From<CuDuration> for u64 {
69    fn from(val: CuDuration) -> Self {
70        let CuDuration(nanos) = val;
71        nanos
72    }
73}
74
75impl Sub for CuDuration {
76    type Output = Self;
77
78    fn sub(self, rhs: Self) -> Self::Output {
79        let CuDuration(lhs) = self;
80        let CuDuration(rhs) = rhs;
81        CuDuration(lhs - rhs)
82    }
83}
84
85impl Add for CuDuration {
86    type Output = Self;
87
88    fn add(self, rhs: Self) -> Self::Output {
89        let CuDuration(lhs) = self;
90        let CuDuration(rhs) = rhs;
91        CuDuration(lhs + rhs)
92    }
93}
94
95impl AddAssign for CuDuration {
96    fn add_assign(&mut self, rhs: Self) {
97        let CuDuration(lhs) = self;
98        let CuDuration(rhs) = rhs;
99        *lhs += rhs;
100    }
101}
102
103impl SubAssign for CuDuration {
104    fn sub_assign(&mut self, rhs: Self) {
105        let CuDuration(lhs) = self;
106        let CuDuration(rhs) = rhs;
107        *lhs -= rhs;
108    }
109}
110
111// a way to divide a duration by a scalar.
112// useful to compute averages for example.
113impl<T> Div<T> for CuDuration
114where
115    T: Into<u64>,
116{
117    type Output = Self;
118    fn div(self, rhs: T) -> Self {
119        let CuDuration(lhs) = self;
120        CuDuration(lhs / rhs.into())
121    }
122}
123//
124// a way to multiply a duration by a scalar.
125// useful to compute offsets for example.
126// CuDuration * scalar
127impl<T> Mul<T> for CuDuration
128where
129    T: Into<u64>,
130{
131    type Output = CuDuration;
132
133    fn mul(self, rhs: T) -> CuDuration {
134        let CuDuration(lhs) = self;
135        CuDuration(lhs * rhs.into())
136    }
137}
138
139// u64 * CuDuration
140impl Mul<CuDuration> for u64 {
141    type Output = CuDuration;
142
143    fn mul(self, rhs: CuDuration) -> CuDuration {
144        let CuDuration(nanos) = rhs;
145        CuDuration(self * nanos)
146    }
147}
148
149// u32 * CuDuration
150impl Mul<CuDuration> for u32 {
151    type Output = CuDuration;
152
153    fn mul(self, rhs: CuDuration) -> CuDuration {
154        let CuDuration(nanos) = rhs;
155        CuDuration(self as u64 * nanos)
156    }
157}
158
159// i32 * CuDuration
160impl Mul<CuDuration> for i32 {
161    type Output = CuDuration;
162
163    fn mul(self, rhs: CuDuration) -> CuDuration {
164        let CuDuration(nanos) = rhs;
165        CuDuration(self as u64 * nanos)
166    }
167}
168
169impl Encode for CuDuration {
170    fn encode<E: Encoder>(&self, encoder: &mut E) -> Result<(), EncodeError> {
171        let CuDuration(nanos) = self;
172        nanos.encode(encoder)
173    }
174}
175
176impl<Context> Decode<Context> for CuDuration {
177    fn decode<D: Decoder>(decoder: &mut D) -> Result<Self, DecodeError> {
178        Ok(CuDuration(u64::decode(decoder)?))
179    }
180}
181
182impl<'de, Context> BorrowDecode<'de, Context> for CuDuration {
183    fn borrow_decode<D: BorrowDecoder<'de>>(decoder: &mut D) -> Result<Self, DecodeError> {
184        Ok(CuDuration(u64::decode(decoder)?))
185    }
186}
187
188impl Display for CuDuration {
189    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
190        let Self(nanos) = *self;
191        if nanos >= 86_400_000_000_000 {
192            write!(f, "{:.3} d", nanos as f64 / 86_400_000_000_000.0)
193        } else if nanos >= 3_600_000_000_000 {
194            write!(f, "{:.3} h", nanos as f64 / 3_600_000_000_000.0)
195        } else if nanos >= 60_000_000_000 {
196            write!(f, "{:.3} m", nanos as f64 / 60_000_000_000.0)
197        } else if nanos >= 1_000_000_000 {
198            write!(f, "{:.3} s", nanos as f64 / 1_000_000_000.0)
199        } else if nanos >= 1_000_000 {
200            write!(f, "{:.3} ms", nanos as f64 / 1_000_000.0)
201        } else if nanos >= 1_000 {
202            write!(f, "{:.3} µs", nanos as f64 / 1_000.0)
203        } else {
204            write!(f, "{nanos} ns")
205        }
206    }
207}
208
209/// A robot time is just a duration from a fixed point in time.
210pub type CuTime = CuDuration;
211
212/// Homebrewed `Option<CuDuration>` to avoid using 128bits just to represent an Option.
213#[derive(Copy, Clone, Debug, PartialEq, Encode, Decode, Serialize, Deserialize)]
214pub struct OptionCuTime(CuTime);
215
216const NONE_VALUE: u64 = 0xFFFFFFFFFFFFFFFF;
217
218impl OptionCuTime {
219    #[inline]
220    pub fn is_none(&self) -> bool {
221        let Self(CuDuration(nanos)) = self;
222        *nanos == NONE_VALUE
223    }
224
225    #[inline]
226    pub fn none() -> Self {
227        OptionCuTime(CuDuration(NONE_VALUE))
228    }
229
230    #[inline]
231    pub fn unwrap(self) -> CuTime {
232        if self.is_none() {
233            panic!("called `OptionCuTime::unwrap()` on a `None` value");
234        }
235        self.0
236    }
237}
238
239impl Display for OptionCuTime {
240    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
241        if self.is_none() {
242            write!(f, "None")
243        } else {
244            write!(f, "{}", self.0)
245        }
246    }
247}
248
249impl Default for OptionCuTime {
250    fn default() -> Self {
251        Self::none()
252    }
253}
254
255impl From<Option<CuTime>> for OptionCuTime {
256    #[inline]
257    fn from(duration: Option<CuTime>) -> Self {
258        match duration {
259            Some(duration) => OptionCuTime(duration),
260            None => OptionCuTime(CuDuration(NONE_VALUE)),
261        }
262    }
263}
264
265impl From<OptionCuTime> for Option<CuTime> {
266    #[inline]
267    fn from(val: OptionCuTime) -> Self {
268        let OptionCuTime(CuDuration(nanos)) = val;
269        if nanos == NONE_VALUE {
270            None
271        } else {
272            Some(CuDuration(nanos))
273        }
274    }
275}
276
277impl From<CuTime> for OptionCuTime {
278    #[inline]
279    fn from(val: CuTime) -> Self {
280        Some(val).into()
281    }
282}
283
284/// Represents a time range.
285#[derive(Copy, Clone, Debug, Encode, Decode, Serialize, Deserialize, PartialEq)]
286pub struct CuTimeRange {
287    pub start: CuTime,
288    pub end: CuTime,
289}
290
291/// Builds a time range from a slice of CuTime.
292/// This is an O(n) operation.
293impl From<&[CuTime]> for CuTimeRange {
294    fn from(slice: &[CuTime]) -> Self {
295        CuTimeRange {
296            start: *slice.iter().min().expect("Empty slice"),
297            end: *slice.iter().max().expect("Empty slice"),
298        }
299    }
300}
301
302/// Represents a time range with possible undefined start or end or both.
303#[derive(Default, Copy, Clone, Debug, Encode, Decode, Serialize, Deserialize)]
304pub struct PartialCuTimeRange {
305    pub start: OptionCuTime,
306    pub end: OptionCuTime,
307}
308
309impl Display for PartialCuTimeRange {
310    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
311        let start = if self.start.is_none() {
312            "…"
313        } else {
314            &format!("{}", self.start)
315        };
316        let end = if self.end.is_none() {
317            "…"
318        } else {
319            &format!("{}", self.end)
320        };
321        write!(f, "[{start} – {end}]")
322    }
323}
324
325/// The time of validity of a message can be more than one time but can be a time range of Tovs.
326/// For example a sub scan for a lidar, a set of images etc... can have a range of validity.
327#[derive(Default, Clone, Debug, PartialEq, Encode, Decode, Serialize, Deserialize, Copy)]
328pub enum Tov {
329    #[default]
330    None,
331    Time(CuTime),
332    Range(CuTimeRange),
333}
334
335impl Display for Tov {
336    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
337        match self {
338            Tov::None => write!(f, "None"),
339            Tov::Time(t) => write!(f, "{t}"),
340            Tov::Range(r) => write!(f, "[{} – {}]", r.start, r.end),
341        }
342    }
343}
344
345impl From<Option<CuDuration>> for Tov {
346    fn from(duration: Option<CuDuration>) -> Self {
347        match duration {
348            Some(duration) => Tov::Time(duration),
349            None => Tov::None,
350        }
351    }
352}
353
354impl From<CuDuration> for Tov {
355    fn from(duration: CuDuration) -> Self {
356        Tov::Time(duration)
357    }
358}
359
360/// A running Robot clock.
361/// The clock is a monotonic clock that starts at an arbitrary reference time.
362/// It is clone resilient, ie a clone will be the same clock, even when mocked.
363#[derive(Clone, Debug)]
364pub struct RobotClock {
365    inner: Clock,      // This is a wrapper on quanta::Clock today.
366    ref_time: Instant, // The reference instant on which this clock is based.
367}
368
369/// A mock clock that can be controlled by the user.
370#[derive(Debug, Clone)]
371pub struct RobotClockMock(Arc<Mock>); // wraps the Mock from quanta today.
372
373impl RobotClockMock {
374    pub fn increment(&self, amount: Duration) {
375        let Self(mock) = self;
376        mock.increment(amount);
377    }
378
379    /// Decrements the time by the given amount.
380    /// Be careful this brakes the monotonicity of the clock.
381    pub fn decrement(&self, amount: Duration) {
382        let Self(mock) = self;
383        mock.decrement(amount);
384    }
385
386    /// Gets the current value of time.
387    pub fn value(&self) -> u64 {
388        let Self(mock) = self;
389        mock.value()
390    }
391
392    /// A convenient way to get the current time from the mocking side.
393    pub fn now(&self) -> CuTime {
394        let Self(mock) = self;
395        mock.value().into()
396    }
397
398    /// Sets the absolute value of the time.
399    pub fn set_value(&self, value: u64) {
400        let Self(mock) = self;
401        let v = mock.value();
402        // had to work around the quata API here.
403        if v < value {
404            self.increment(Duration::from_nanos(value) - Duration::from_nanos(v));
405        } else {
406            self.decrement(Duration::from_nanos(v) - Duration::from_nanos(value));
407        }
408    }
409}
410
411impl RobotClock {
412    /// Creates a RobotClock using now as its reference time.
413    /// It will start a 0ns incrementing monotonically.
414    pub fn new() -> Self {
415        let clock = Clock::new();
416        let ref_time = clock.now();
417        RobotClock {
418            inner: clock,
419            ref_time,
420        }
421    }
422
423    /// Builds a monotonic clock starting at the given reference time.
424    pub fn from_ref_time(ref_time_ns: u64) -> Self {
425        let clock = Clock::new();
426        let ref_time = clock.now() - Duration::from_nanos(ref_time_ns);
427        RobotClock {
428            inner: Clock::new(),
429            ref_time,
430        }
431    }
432
433    /// Build a fake clock with a reference time of 0.
434    /// The RobotMock interface enables you to control all the clones of the clock given.
435    pub fn mock() -> (Self, RobotClockMock) {
436        let (clock, mock) = Clock::mock();
437        let ref_time = clock.now();
438        (
439            RobotClock {
440                inner: clock,
441                ref_time,
442            },
443            RobotClockMock(mock),
444        )
445    }
446
447    // Now returns the time that passed since the reference time, usually the start time.
448    // It is a monotonically increasing value.
449    #[inline]
450    pub fn now(&self) -> CuTime {
451        // TODO: this could be further optimized to avoid this constant conversion from 2 fields to one under the hood.
452        // Let's say this is the default implementation.
453        (self.inner.now() - self.ref_time).into()
454    }
455
456    // A less precise but quicker time
457    #[inline]
458    pub fn recent(&self) -> CuTime {
459        (self.inner.recent() - self.ref_time).into()
460    }
461}
462
463impl Default for RobotClock {
464    fn default() -> Self {
465        Self::new()
466    }
467}
468
469/// A trait to provide a clock to the runtime.
470pub trait ClockProvider {
471    fn get_clock(&self) -> RobotClock;
472}
473
474#[cfg(test)]
475mod tests {
476    use super::*;
477    #[test]
478    fn test_cuduration_comparison_operators() {
479        let a = CuDuration(100);
480        let b = CuDuration(200);
481
482        assert!(a < b);
483        assert!(b > a);
484        assert_ne!(a, b);
485        assert_eq!(a, CuDuration(100));
486    }
487
488    #[test]
489    fn test_cuduration_arithmetic_operations() {
490        let a = CuDuration(100);
491        let b = CuDuration(50);
492
493        assert_eq!(a + b, CuDuration(150));
494        assert_eq!(a - b, CuDuration(50));
495        assert_eq!(a * 2u32, CuDuration(200));
496        assert_eq!(a / 2u32, CuDuration(50));
497    }
498
499    #[test]
500    fn test_option_cutime_handling() {
501        let some_time = OptionCuTime::from(Some(CuTime::from(100u64)));
502        let none_time = OptionCuTime::none();
503
504        assert!(!some_time.is_none());
505        assert!(none_time.is_none());
506        assert_eq!(some_time.unwrap(), CuTime::from(100u64));
507        assert_eq!(
508            Option::<CuTime>::from(some_time),
509            Some(CuTime::from(100u64))
510        );
511        assert_eq!(Option::<CuTime>::from(none_time), None);
512    }
513
514    #[test]
515    fn test_mock() {
516        let (clock, mock) = RobotClock::mock();
517        assert_eq!(clock.now(), Duration::from_secs(0).into());
518        mock.increment(Duration::from_secs(1));
519        assert_eq!(clock.now(), Duration::from_secs(1).into());
520    }
521
522    #[test]
523    fn test_mock_clone() {
524        let (clock, mock) = RobotClock::mock();
525        assert_eq!(clock.now(), Duration::from_secs(0).into());
526        let clock_clone = clock.clone();
527        mock.increment(Duration::from_secs(1));
528        assert_eq!(clock_clone.now(), Duration::from_secs(1).into());
529    }
530
531    #[test]
532    fn test_robot_clock_synchronization() {
533        // Test that multiple clocks created from the same mock stay synchronized
534        let (clock1, mock) = RobotClock::mock();
535        let clock2 = clock1.clone();
536
537        assert_eq!(clock1.now(), CuDuration(0));
538        assert_eq!(clock2.now(), CuDuration(0));
539
540        mock.increment(Duration::from_secs(5));
541
542        assert_eq!(clock1.now(), Duration::from_secs(5).into());
543        assert_eq!(clock2.now(), Duration::from_secs(5).into());
544    }
545
546    #[test]
547    fn test_from_ref_time() {
548        let tolerance_ms = 10;
549        let clock = RobotClock::from_ref_time(1_000_000_000);
550        assert_relative_eq!(
551            <CuDuration as Into<Duration>>::into(clock.now()).as_millis() as f64,
552            Duration::from_secs(1).as_millis() as f64,
553            epsilon = tolerance_ms as f64
554        );
555    }
556
557    #[test]
558    fn longest_duration() {
559        let maxcu = CuDuration(u64::MAX);
560        let maxd: Duration = maxcu.into();
561        assert_eq!(maxd.as_nanos(), u64::MAX as u128);
562        let s = maxd.as_secs();
563        let y = s / 60 / 60 / 24 / 365;
564        assert!(y >= 584); // 584 years of robot uptime, we should be good.
565    }
566
567    #[test]
568    fn test_some_time_arithmetics() {
569        let a: CuDuration = 10.into();
570        let b: CuDuration = 20.into();
571        let c = a + b;
572        assert_eq!(c.0, 30);
573        let d = b - a;
574        assert_eq!(d.0, 10);
575    }
576
577    #[test]
578    fn test_build_range_from_slice() {
579        let range = CuTimeRange::from(&[20.into(), 10.into(), 30.into()][..]);
580        assert_eq!(range.start, 10.into());
581        assert_eq!(range.end, 30.into());
582    }
583
584    #[test]
585    fn test_time_range_operations() {
586        // Test creating a time range and checking its properties
587        let start = CuTime::from(100u64);
588        let end = CuTime::from(200u64);
589        let range = CuTimeRange { start, end };
590
591        assert_eq!(range.start, start);
592        assert_eq!(range.end, end);
593
594        // Test creating from a slice
595        let times = [
596            CuTime::from(150u64),
597            CuTime::from(120u64),
598            CuTime::from(180u64),
599        ];
600        let range_from_slice = CuTimeRange::from(&times[..]);
601
602        // Range should capture min and max values
603        assert_eq!(range_from_slice.start, CuTime::from(120u64));
604        assert_eq!(range_from_slice.end, CuTime::from(180u64));
605    }
606
607    #[test]
608    fn test_partial_time_range() {
609        // Test creating a partial time range with defined start/end
610        let start = CuTime::from(100u64);
611        let end = CuTime::from(200u64);
612
613        let partial_range = PartialCuTimeRange {
614            start: OptionCuTime::from(start),
615            end: OptionCuTime::from(end),
616        };
617
618        // Test converting to Option
619        let opt_start: Option<CuTime> = partial_range.start.into();
620        let opt_end: Option<CuTime> = partial_range.end.into();
621
622        assert_eq!(opt_start, Some(start));
623        assert_eq!(opt_end, Some(end));
624
625        // Test partial range with undefined values
626        let partial_undefined = PartialCuTimeRange::default();
627        assert!(partial_undefined.start.is_none());
628        assert!(partial_undefined.end.is_none());
629    }
630
631    #[test]
632    fn test_tov_conversions() {
633        // Test different Time of Validity (Tov) variants
634        let time = CuTime::from(100u64);
635
636        // Test conversion from CuTime
637        let tov_time: Tov = time.into();
638        assert!(matches!(tov_time, Tov::Time(_)));
639
640        if let Tov::Time(t) = tov_time {
641            assert_eq!(t, time);
642        }
643
644        // Test conversion from Option<CuTime>
645        let some_time = Some(time);
646        let tov_some: Tov = some_time.into();
647        assert!(matches!(tov_some, Tov::Time(_)));
648
649        let none_time: Option<CuDuration> = None;
650        let tov_none: Tov = none_time.into();
651        assert!(matches!(tov_none, Tov::None));
652
653        // Test range
654        let start = CuTime::from(100u64);
655        let end = CuTime::from(200u64);
656        let range = CuTimeRange { start, end };
657        let tov_range = Tov::Range(range);
658
659        assert!(matches!(tov_range, Tov::Range(_)));
660    }
661
662    #[test]
663    fn test_cuduration_display() {
664        // Test the display implementation for different magnitudes
665        let nano = CuDuration(42);
666        assert_eq!(nano.to_string(), "42 ns");
667
668        let micro = CuDuration(42_000);
669        assert_eq!(micro.to_string(), "42.000 µs");
670
671        let milli = CuDuration(42_000_000);
672        assert_eq!(milli.to_string(), "42.000 ms");
673
674        let sec = CuDuration(1_500_000_000);
675        assert_eq!(sec.to_string(), "1.500 s");
676
677        let min = CuDuration(90_000_000_000);
678        assert_eq!(min.to_string(), "1.500 m");
679
680        let hour = CuDuration(3_600_000_000_000);
681        assert_eq!(hour.to_string(), "1.000 h");
682
683        let day = CuDuration(86_400_000_000_000);
684        assert_eq!(day.to_string(), "1.000 d");
685    }
686
687    #[test]
688    fn test_robot_clock_precision() {
689        // Test that RobotClock::now() and RobotClock::recent() return different values
690        // and that recent() is always <= now()
691        let clock = RobotClock::new();
692
693        // We can't guarantee the exact values, but we can check relationships
694        let recent = clock.recent();
695        let now = clock.now();
696
697        // recent() should be less than or equal to now()
698        assert!(recent <= now);
699
700        // Test precision of from_ref_time
701        let ref_time_ns = 1_000_000_000; // 1 second
702        let clock = RobotClock::from_ref_time(ref_time_ns);
703
704        // Clock should start at approximately ref_time_ns
705        let now = clock.now();
706        let now_ns: u64 = now.into();
707
708        // Allow reasonable tolerance for clock initialization time
709        let tolerance_ns = 50_000_000; // 50ms tolerance
710        assert!(now_ns >= ref_time_ns);
711        assert!(now_ns < ref_time_ns + tolerance_ns);
712    }
713
714    #[test]
715    fn test_mock_clock_advanced_operations() {
716        // Test more complex operations with the mock clock
717        let (clock, mock) = RobotClock::mock();
718
719        // Test initial state
720        assert_eq!(clock.now(), CuDuration(0));
721
722        // Test increment
723        mock.increment(Duration::from_secs(10));
724        assert_eq!(clock.now(), Duration::from_secs(10).into());
725
726        // Test decrement (unusual but supported)
727        mock.decrement(Duration::from_secs(5));
728        assert_eq!(clock.now(), Duration::from_secs(5).into());
729
730        // Test setting absolute value
731        mock.set_value(30_000_000_000); // 30 seconds in ns
732        assert_eq!(clock.now(), Duration::from_secs(30).into());
733
734        // Test that getting the time from the mock directly works
735        assert_eq!(mock.now(), Duration::from_secs(30).into());
736        assert_eq!(mock.value(), 30_000_000_000);
737    }
738
739    #[test]
740    fn test_cuduration_min_max() {
741        // Test MIN and MAX constants
742        assert_eq!(CuDuration::MIN, CuDuration(0));
743
744        // Test min/max methods
745        let a = CuDuration(100);
746        let b = CuDuration(200);
747
748        assert_eq!(a.min(b), a);
749        assert_eq!(a.max(b), b);
750        assert_eq!(b.min(a), a);
751        assert_eq!(b.max(a), b);
752
753        // Edge cases
754        assert_eq!(a.min(a), a);
755        assert_eq!(a.max(a), a);
756
757        // Test with MIN/MAX constants
758        assert_eq!(a.min(CuDuration::MIN), CuDuration::MIN);
759        assert_eq!(a.max(CuDuration::MAX), CuDuration::MAX);
760    }
761
762    #[test]
763    fn test_clock_provider_trait() {
764        // Test implementing the ClockProvider trait
765        struct TestClockProvider {
766            clock: RobotClock,
767        }
768
769        impl ClockProvider for TestClockProvider {
770            fn get_clock(&self) -> RobotClock {
771                self.clock.clone()
772            }
773        }
774
775        // Create a provider with a mock clock
776        let (clock, mock) = RobotClock::mock();
777        let provider = TestClockProvider { clock };
778
779        // Test that provider returns a clock synchronized with the original
780        let provider_clock = provider.get_clock();
781        assert_eq!(provider_clock.now(), CuDuration(0));
782
783        // Advance the mock clock and check that provider's clock also advances
784        mock.increment(Duration::from_secs(5));
785        assert_eq!(provider_clock.now(), Duration::from_secs(5).into());
786    }
787}