injectable_time/
injectable_time.rs

1// Copyright 2020 The Fuchsia Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style license that can be
3// found in the LICENSE file.
4
5use fuchsia_sync::Mutex;
6use std::sync::Arc;
7
8/// TimeSource provides the current time in nanoseconds since the Unix epoch.
9/// A `&'a dyn TimeSource` can be injected into a data structure.
10/// TimeSource is implemented by UtcInstant for wall-clock system time, and
11/// FakeTime for a clock that is explicitly set by testing code.
12pub trait TimeSource: std::fmt::Debug {
13    fn now(&self) -> i64;
14}
15
16/// FakeTime instances return the last value that was configured by testing code via `set_ticks()`
17///  or `add_ticks()`. Upon initialization, they return 0.
18#[derive(Clone, Debug)]
19pub struct FakeTime {
20    time: Arc<Mutex<i64>>,
21}
22
23impl TimeSource for FakeTime {
24    fn now(&self) -> i64 {
25        *self.time.lock()
26    }
27}
28
29impl FakeTime {
30    pub fn new() -> FakeTime {
31        FakeTime { time: Arc::new(Mutex::new(0)) }
32    }
33
34    pub fn set_ticks(&self, now: i64) {
35        *self.time.lock() = now;
36    }
37
38    pub fn add_ticks(&self, ticks: i64) {
39        *self.time.lock() += ticks;
40    }
41}
42
43/// IncrementingFakeTime automatically increments itself by `increment_by`
44/// before each call to `self.now()`.
45#[derive(Debug)]
46pub struct IncrementingFakeTime {
47    time: FakeTime,
48    increment_by: std::time::Duration,
49}
50
51impl TimeSource for IncrementingFakeTime {
52    fn now(&self) -> i64 {
53        let now = self.time.now();
54        self.time.add_ticks(self.increment_by.as_nanos() as i64);
55        now
56    }
57}
58
59impl IncrementingFakeTime {
60    pub fn new(start_time: i64, increment_by: std::time::Duration) -> Self {
61        let time = FakeTime::new();
62        time.set_ticks(start_time);
63        Self { time, increment_by }
64    }
65}
66
67/// UtcInstant instances return the Rust system clock value each time now() is called.
68#[derive(Debug)]
69pub struct UtcInstant {}
70
71impl UtcInstant {
72    pub fn new() -> UtcInstant {
73        UtcInstant {}
74    }
75}
76
77impl TimeSource for UtcInstant {
78    fn now(&self) -> i64 {
79        if cfg!(target_arch = "wasm32") {
80            // TODO(https://fxbug.dev/42143658): Remove this when WASM avoids calling this method.
81            0i64
82        } else {
83            let now_utc = chrono::prelude::Utc::now(); // Consider using SystemTime::now()?
84            now_utc.timestamp() * 1_000_000_000 + now_utc.timestamp_subsec_nanos() as i64
85        }
86    }
87}
88
89/// MonotonicInstant instances provide a monotonic clock.
90/// On Fuchsia, MonotonicInstant uses zx::MonotonicInstant::get().
91#[derive(Debug)]
92pub struct MonotonicInstant {
93    #[cfg(not(target_os = "fuchsia"))]
94    starting_time: std::time::Instant,
95}
96
97impl MonotonicInstant {
98    pub fn new() -> MonotonicInstant {
99        #[cfg(target_os = "fuchsia")]
100        let time = MonotonicInstant {};
101        #[cfg(not(target_os = "fuchsia"))]
102        let time = MonotonicInstant { starting_time: std::time::Instant::now() };
103
104        time
105    }
106}
107
108impl TimeSource for MonotonicInstant {
109    fn now(&self) -> i64 {
110        #[cfg(target_os = "fuchsia")]
111        let now = zx::MonotonicInstant::get().into_nanos();
112        #[cfg(not(target_os = "fuchsia"))]
113        let now = (std::time::Instant::now() - self.starting_time).as_nanos() as i64;
114
115        now
116    }
117}
118
119/// BootInstant instances provide a monotonic clock.
120/// On Fuchsia, BootInstant uses zx::BootInstant::get().
121#[derive(Debug)]
122pub struct BootInstant {
123    #[cfg(not(target_os = "fuchsia"))]
124    starting_time: std::time::Instant,
125}
126
127impl BootInstant {
128    pub fn new() -> BootInstant {
129        #[cfg(target_os = "fuchsia")]
130        let time = BootInstant {};
131        #[cfg(not(target_os = "fuchsia"))]
132        let time = BootInstant { starting_time: std::time::Instant::now() };
133
134        time
135    }
136}
137
138impl TimeSource for BootInstant {
139    fn now(&self) -> i64 {
140        #[cfg(target_os = "fuchsia")]
141        let now = zx::BootInstant::get().into_nanos();
142        #[cfg(not(target_os = "fuchsia"))]
143        let now = (std::time::Instant::now() - self.starting_time).as_nanos() as i64;
144
145        now
146    }
147}
148
149#[cfg(test)]
150mod test {
151
152    use super::*;
153
154    struct TimeHolder<'a> {
155        time_source: &'a dyn TimeSource,
156    }
157
158    impl<'a> TimeHolder<'a> {
159        fn new(time_source: &'a dyn TimeSource) -> TimeHolder<'_> {
160            TimeHolder { time_source }
161        }
162
163        fn now(&self) -> i64 {
164            self.time_source.now()
165        }
166    }
167
168    #[test]
169    fn test_system_time() {
170        let time_source = UtcInstant::new();
171        let time_holder = TimeHolder::new(&time_source);
172        let first_time = time_holder.now();
173        // Make sure the system time is ticking. If not, this will hang until the test times out.
174        while time_holder.now() == first_time {}
175    }
176
177    #[test]
178    fn test_monotonic_time() {
179        let time_source = MonotonicInstant::new();
180        let time_holder = TimeHolder::new(&time_source);
181        let first_time = time_holder.now();
182        // Make sure the monotonic time is ticking. If not, this will hang until the test times out.
183        while time_holder.now() == first_time {}
184    }
185
186    #[test]
187    fn test_boot_time() {
188        let time_source = BootInstant::new();
189        let time_holder = TimeHolder::new(&time_source);
190        let first_time = time_holder.now();
191        // Make sure the monotonic time is ticking. If not, this will hang until the test times out.
192        while time_holder.now() == first_time {}
193    }
194
195    #[test]
196    fn test_fake_time() {
197        let time_source = FakeTime::new();
198        let time_holder = TimeHolder::new(&time_source);
199
200        // Fake time is 0 on initialization.
201        let time_0 = time_holder.now();
202        time_source.set_ticks(1000);
203        let time_1000 = time_holder.now();
204        // Fake time does not auto-increment.
205        let time_1000_2 = time_holder.now();
206        // Fake time can go backward.
207        time_source.set_ticks(500);
208        let time_500 = time_holder.now();
209        // add_ticks() works.
210        time_source.add_ticks(123);
211        let time_623 = time_holder.now();
212        // add_ticks() can take a negative value
213        time_source.add_ticks(-23);
214        let time_600 = time_holder.now();
215
216        assert_eq!(time_0, 0);
217        assert_eq!(time_1000, 1000);
218        assert_eq!(time_1000_2, 1000);
219        assert_eq!(time_500, 500);
220        assert_eq!(time_623, 623);
221        assert_eq!(time_600, 600);
222    }
223
224    #[test]
225    fn test_incrementing_fake_time() {
226        let duration = std::time::Duration::from_nanos(1000);
227        let timer = IncrementingFakeTime::new(0, duration);
228
229        assert_eq!(0, timer.now());
230        assert_eq!((1 * duration).as_nanos() as i64, timer.now());
231        assert_eq!((2 * duration).as_nanos() as i64, timer.now());
232    }
233}