| // Copyright 2020 The Fuchsia Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| use fuchsia_sync::Mutex; |
| #[cfg(target_os = "fuchsia")] |
| use fuchsia_zircon as zx; |
| use std::sync::Arc; |
| |
| /// TimeSource provides the current time in nanoseconds since the Unix epoch. |
| /// A `&'a dyn TimeSource` can be injected into a data structure. |
| /// TimeSource is implemented by UtcTime for wall-clock system time, and |
| /// FakeTime for a clock that is explicitly set by testing code. |
| pub trait TimeSource: std::fmt::Debug { |
| fn now(&self) -> i64; |
| } |
| |
| /// FakeTime instances return the last value that was configured by testing code via `set_ticks()` |
| /// or `add_ticks()`. Upon initialization, they return 0. |
| #[derive(Clone, Debug)] |
| pub struct FakeTime { |
| time: Arc<Mutex<i64>>, |
| } |
| |
| impl TimeSource for FakeTime { |
| fn now(&self) -> i64 { |
| *self.time.lock() |
| } |
| } |
| |
| impl FakeTime { |
| pub fn new() -> FakeTime { |
| FakeTime { time: Arc::new(Mutex::new(0)) } |
| } |
| |
| pub fn set_ticks(&self, now: i64) { |
| *self.time.lock() = now; |
| } |
| |
| pub fn add_ticks(&self, ticks: i64) { |
| *self.time.lock() += ticks; |
| } |
| } |
| |
| /// IncrementingFakeTime automatically increments itself by `increment_by` |
| /// before each call to `self.now()`. |
| #[derive(Debug)] |
| pub struct IncrementingFakeTime { |
| time: FakeTime, |
| increment_by: std::time::Duration, |
| } |
| |
| impl TimeSource for IncrementingFakeTime { |
| fn now(&self) -> i64 { |
| let now = self.time.now(); |
| self.time.add_ticks(self.increment_by.as_nanos() as i64); |
| now |
| } |
| } |
| |
| impl IncrementingFakeTime { |
| pub fn new(start_time: i64, increment_by: std::time::Duration) -> Self { |
| let time = FakeTime::new(); |
| time.set_ticks(start_time); |
| Self { time, increment_by } |
| } |
| } |
| |
| /// UtcTime instances return the Rust system clock value each time now() is called. |
| #[derive(Debug)] |
| pub struct UtcTime {} |
| |
| impl UtcTime { |
| pub fn new() -> UtcTime { |
| UtcTime {} |
| } |
| } |
| |
| impl TimeSource for UtcTime { |
| fn now(&self) -> i64 { |
| if cfg!(target_arch = "wasm32") { |
| // TODO(https://fxbug.dev/42143658): Remove this when WASM avoids calling this method. |
| 0i64 |
| } else { |
| let now_utc = chrono::prelude::Utc::now(); // Consider using SystemTime::now()? |
| now_utc.timestamp() * 1_000_000_000 + now_utc.timestamp_subsec_nanos() as i64 |
| } |
| } |
| } |
| |
| /// MonotonicTime instances provide a monotonic clock. |
| /// On Fuchsia, MonotonicTime uses fuchsia_zircon::Time::get_monotonic(). |
| #[derive(Debug)] |
| pub struct MonotonicTime { |
| #[cfg(not(target_os = "fuchsia"))] |
| starting_time: std::time::Instant, |
| } |
| |
| impl MonotonicTime { |
| pub fn new() -> MonotonicTime { |
| #[cfg(target_os = "fuchsia")] |
| let time = MonotonicTime {}; |
| #[cfg(not(target_os = "fuchsia"))] |
| let time = MonotonicTime { starting_time: std::time::Instant::now() }; |
| |
| time |
| } |
| } |
| |
| impl TimeSource for MonotonicTime { |
| fn now(&self) -> i64 { |
| #[cfg(target_os = "fuchsia")] |
| let now = zx::Time::get_monotonic().into_nanos(); |
| #[cfg(not(target_os = "fuchsia"))] |
| let now = (std::time::Instant::now() - self.starting_time).as_nanos() as i64; |
| |
| now |
| } |
| } |
| |
| #[cfg(test)] |
| mod test { |
| |
| use super::*; |
| |
| struct TimeHolder<'a> { |
| time_source: &'a dyn TimeSource, |
| } |
| |
| impl<'a> TimeHolder<'a> { |
| fn new(time_source: &'a dyn TimeSource) -> TimeHolder<'_> { |
| TimeHolder { time_source } |
| } |
| |
| fn now(&self) -> i64 { |
| self.time_source.now() |
| } |
| } |
| |
| #[test] |
| fn test_system_time() { |
| let time_source = UtcTime::new(); |
| let time_holder = TimeHolder::new(&time_source); |
| let first_time = time_holder.now(); |
| // Make sure the system time is ticking. If not, this will hang until the test times out. |
| while time_holder.now() == first_time {} |
| } |
| |
| #[test] |
| fn test_monotonic_time() { |
| let time_source = MonotonicTime::new(); |
| let time_holder = TimeHolder::new(&time_source); |
| let first_time = time_holder.now(); |
| // Make sure the monotonic time is ticking. If not, this will hang until the test times out. |
| while time_holder.now() == first_time {} |
| } |
| |
| #[test] |
| fn test_fake_time() { |
| let time_source = FakeTime::new(); |
| let time_holder = TimeHolder::new(&time_source); |
| |
| // Fake time is 0 on initialization. |
| let time_0 = time_holder.now(); |
| time_source.set_ticks(1000); |
| let time_1000 = time_holder.now(); |
| // Fake time does not auto-increment. |
| let time_1000_2 = time_holder.now(); |
| // Fake time can go backward. |
| time_source.set_ticks(500); |
| let time_500 = time_holder.now(); |
| // add_ticks() works. |
| time_source.add_ticks(123); |
| let time_623 = time_holder.now(); |
| // add_ticks() can take a negative value |
| time_source.add_ticks(-23); |
| let time_600 = time_holder.now(); |
| |
| assert_eq!(time_0, 0); |
| assert_eq!(time_1000, 1000); |
| assert_eq!(time_1000_2, 1000); |
| assert_eq!(time_500, 500); |
| assert_eq!(time_623, 623); |
| assert_eq!(time_600, 600); |
| } |
| |
| #[test] |
| fn test_incrementing_fake_time() { |
| let duration = std::time::Duration::from_nanos(1000); |
| let timer = IncrementingFakeTime::new(0, duration); |
| |
| assert_eq!(0, timer.now()); |
| assert_eq!((1 * duration).as_nanos() as i64, timer.now()); |
| assert_eq!((2 * duration).as_nanos() as i64, timer.now()); |
| } |
| } |