diff options
author | Miriam Zimmerman <mutexlox@google.com> | 2019-01-15 16:22:07 -0800 |
---|---|---|
committer | chrome-bot <chrome-bot@chromium.org> | 2019-01-24 00:51:49 -0800 |
commit | d3144f7a52608d5295dd5bf8edc4f3d76ae4ad82 (patch) | |
tree | 9003b21dca3304b4663d73c288fb8b180eff151a | |
parent | 39f93db2a4434d7c4126005024cc2f085af3bb43 (diff) | |
download | crosvm-d3144f7a52608d5295dd5bf8edc4f3d76ae4ad82.tar crosvm-d3144f7a52608d5295dd5bf8edc4f3d76ae4ad82.tar.gz crosvm-d3144f7a52608d5295dd5bf8edc4f3d76ae4ad82.tar.bz2 crosvm-d3144f7a52608d5295dd5bf8edc4f3d76ae4ad82.tar.lz crosvm-d3144f7a52608d5295dd5bf8edc4f3d76ae4ad82.tar.xz crosvm-d3144f7a52608d5295dd5bf8edc4f3d76ae4ad82.tar.zst crosvm-d3144f7a52608d5295dd5bf8edc4f3d76ae4ad82.zip |
Add FakeClock and FakeTimerFd for use in tests.
Together, these allow tests to create a FakeTimerFd that they can trigger at a particular point in the test code, without having to rely on sleep()s or other racy methods. BUG=None TEST=Unit tests for FakeTimerFd + dependent CL. Change-Id: I14381272a6d75bebcdedb0a329a017a2131a3482 Reviewed-on: https://chromium-review.googlesource.com/1413830 Commit-Ready: ChromeOS CL Exonerator Bot <chromiumos-cl-exonerator@appspot.gserviceaccount.com> Tested-by: Miriam Zimmerman <mutexlox@chromium.org> Tested-by: kokoro <noreply+kokoro@google.com> Reviewed-by: David Tolnay <dtolnay@chromium.org> Reviewed-by: Dylan Reid <dgreid@chromium.org>
-rw-r--r-- | sys_util/src/clock.rs | 84 | ||||
-rw-r--r-- | sys_util/src/lib.rs | 2 | ||||
-rw-r--r-- | sys_util/src/timerfd.rs | 128 |
3 files changed, 213 insertions, 1 deletions
diff --git a/sys_util/src/clock.rs b/sys_util/src/clock.rs new file mode 100644 index 0000000..5f6ce0c --- /dev/null +++ b/sys_util/src/clock.rs @@ -0,0 +1,84 @@ +// Copyright 2019 The Chromium OS Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +// Utility file to provide a fake clock object representing current time, and a timerfd driven by +// that time. + +use std::time::{Duration, Instant}; +use std::os::unix::io::AsRawFd; +use EventFd; + +#[derive(Debug, Copy, Clone)] +pub struct Clock(Instant); +impl Clock { + pub fn new() -> Self { + Clock(Instant::now()) + } + + pub fn now(&self) -> Self { + Clock(Instant::now()) + } + + pub fn duration_since(&self, earlier: &Self) -> Duration { + self.0.duration_since(earlier.0) + } +} + +const NS_PER_SEC : u64 = 1_000_000_000; +/// A fake clock that can be used in tests to give exact control over the time. +/// For a code example, see the tests in sys_util/src/timerfd.rs. +#[derive(Debug)] +pub struct FakeClock { + ns_since_epoch: u64, + deadlines: Vec<(u64, EventFd)>, +} + +impl FakeClock { + pub fn new() -> Self { + FakeClock { + ns_since_epoch: 1547163599 * NS_PER_SEC, + deadlines: Vec::new(), + } + } + + /// Get the current time, according to this clock. + pub fn now(&self) -> Self { + FakeClock { + ns_since_epoch: self.ns_since_epoch, + deadlines: Vec::new(), + } + } + + /// Get the current time in ns, according to this clock. + pub fn nanos(&self) -> u64 { + self.ns_since_epoch + } + + /// Get the duration since |earlier|, assuming that earlier < self. + pub fn duration_since(&self, earlier: &Self) -> Duration { + let ns_diff = self.ns_since_epoch - earlier.ns_since_epoch; + Duration::new(ns_diff / NS_PER_SEC, (ns_diff % NS_PER_SEC) as u32) + } + + /// Register the event fd for a notification when self's time is |deadline_ns|. + /// Drop any existing events registered to the same raw fd. + pub fn add_event_fd(&mut self, deadline_ns: u64, fd: EventFd) { + self.deadlines.retain(|&(_, ref old_fd)| { + fd.as_raw_fd() != old_fd.as_raw_fd() + }); + self.deadlines.push((deadline_ns, fd)); + } + + pub fn add_ns(&mut self, ns: u64) { + self.ns_since_epoch += ns; + let time = self.ns_since_epoch; + self.deadlines.retain(|&(ns, ref fd)| { + let expired = ns <= time; + if expired { + fd.write(1).unwrap(); + } + !expired + }); + } +} diff --git a/sys_util/src/lib.rs b/sys_util/src/lib.rs index 62cfc9e..cb0374b 100644 --- a/sys_util/src/lib.rs +++ b/sys_util/src/lib.rs @@ -18,6 +18,7 @@ pub mod handle_eintr; pub mod ioctl; #[macro_use] pub mod syslog; +mod clock; mod errno; mod eventfd; mod file_flags; @@ -42,6 +43,7 @@ mod terminal; mod timerfd; mod write_zeroes; +pub use clock::{Clock, FakeClock}; use errno::errno_result; pub use errno::{Error, Result}; pub use eventfd::*; diff --git a/sys_util/src/timerfd.rs b/sys_util/src/timerfd.rs index 5233ab3..8fac9f8 100644 --- a/sys_util/src/timerfd.rs +++ b/sys_util/src/timerfd.rs @@ -7,10 +7,12 @@ use std::mem; use std::os::unix::io::{AsRawFd, FromRawFd, IntoRawFd, RawFd}; use std::ptr; use std::time::Duration; +use std::sync::Arc; +use sync::Mutex; use libc::{self, timerfd_create, timerfd_gettime, timerfd_settime, CLOCK_MONOTONIC, TFD_CLOEXEC}; -use {errno_result, Result}; +use {errno_result, Result, FakeClock, EventFd}; /// A safe wrapper around a Linux timerfd (man 2 timerfd_create). pub struct TimerFd(File); @@ -122,6 +124,93 @@ impl IntoRawFd for TimerFd { } } +/// FakeTimerFd: For use in tests. +pub struct FakeTimerFd { + clock: Arc<Mutex<FakeClock>>, + deadline_ns: Option<u64>, + interval: Option<Duration>, + fd: EventFd, +} + +impl FakeTimerFd { + /// Creates a new fake timerfd. The timer is initally disarmed and must be armed by calling + /// `reset`. + pub fn new(clock: Arc<Mutex<FakeClock>>) -> Self { + FakeTimerFd { + clock, + deadline_ns: None, + interval: None, + fd: EventFd::new().unwrap(), + } + } + + fn duration_to_nanos(d: Duration) -> u64 { + d.as_secs() * 1_000_000_000 + u64::from(d.subsec_nanos()) + } + + /// Sets the timer to expire after `dur`. If `interval` is not `None` it represents + /// the period for repeated expirations after the initial expiration. Otherwise + /// the timer will expire just once. Cancels any existing duration and repeating interval. + pub fn reset(&mut self, dur: Duration, interval: Option<Duration>) -> Result<()> { + let mut guard = self.clock.lock(); + let deadline = guard.nanos() + FakeTimerFd::duration_to_nanos(dur); + self.deadline_ns = Some(deadline); + self.interval = interval; + guard.add_event_fd(deadline, self.fd.try_clone()?); + Ok(()) + } + + /// Waits until the timer expires. The return value represents the number of times the timer + /// has expired since the last time `wait` was called. If the timer has not yet expired once + /// this call will block until it does. + pub fn wait(&mut self) -> Result<u64> { + loop { + self.fd.read()?; + if let Some(ref mut deadline_ns) = self.deadline_ns { + let mut guard = self.clock.lock(); + let now = guard.nanos(); + if now >= *deadline_ns { + let mut expirys = 0; + if let Some(interval) = self.interval { + let interval_ns = FakeTimerFd::duration_to_nanos(interval); + if interval_ns > 0 { + expirys += (now - *deadline_ns) / interval_ns; + *deadline_ns += (expirys + 1) * interval_ns; + guard.add_event_fd(*deadline_ns, self.fd.try_clone()?); + } + } + return Ok(expirys + 1); + } + } + } + } + + /// Returns `true` if the timer is currently armed. + pub fn is_armed(&self) -> Result<bool> { + Ok(self.deadline_ns.is_some()) + } + + /// Disarms the timer. + pub fn clear(&mut self) -> Result<()> { + self.deadline_ns = None; + self.interval = None; + Ok(()) + } +} + +impl AsRawFd for FakeTimerFd { + fn as_raw_fd(&self) -> RawFd { + self.fd.as_raw_fd() + } +} + +impl IntoRawFd for FakeTimerFd { + fn into_raw_fd(self) -> RawFd { + self.fd.into_raw_fd() + } +} + + #[cfg(test)] mod tests { use super::*; @@ -159,4 +248,41 @@ mod tests { let count = tfd.wait().expect("unable to wait for timer"); assert!(count >= 5, "count = {}", count); } + + #[test] + fn fake_one_shot() { + let clock = Arc::new(Mutex::new(FakeClock::new())); + let mut tfd = FakeTimerFd::new(clock.clone()); + assert_eq!(tfd.is_armed().unwrap(), false); + + let dur = Duration::from_nanos(200); + tfd.reset(dur.clone(), None).expect("failed to arm timer"); + + assert_eq!(tfd.is_armed().unwrap(), true); + clock.lock().add_ns(200); + + let count = tfd.wait().expect("unable to wait for timer"); + + assert_eq!(count, 1); + } + + #[test] + fn fake_repeating() { + let clock = Arc::new(Mutex::new(FakeClock::new())); + let mut tfd = FakeTimerFd::new(clock.clone()); + + let dur = Duration::from_nanos(200); + let interval = Duration::from_nanos(100); + tfd.reset(dur.clone(), Some(interval)).expect("failed to arm timer"); + + clock.lock().add_ns(300); + + let mut count = tfd.wait().expect("unable to wait for timer"); + // An expiration from the initial expiry and from 1 repeat. + assert_eq!(count, 2); + + clock.lock().add_ns(300); + count = tfd.wait().expect("unable to wait for timer"); + assert_eq!(count, 3); + } } |