blob: 7b18ab338c7d5e435fb04733e76bd2e0aa034e7c [file] [log] [blame]
// 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());
}
}