summary refs log tree commit diff
diff options
context:
space:
mode:
authorMiriam Zimmerman <mutexlox@google.com>2019-01-15 16:22:07 -0800
committerchrome-bot <chrome-bot@chromium.org>2019-01-24 00:51:49 -0800
commitd3144f7a52608d5295dd5bf8edc4f3d76ae4ad82 (patch)
tree9003b21dca3304b4663d73c288fb8b180eff151a
parent39f93db2a4434d7c4126005024cc2f085af3bb43 (diff)
downloadcrosvm-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.rs84
-rw-r--r--sys_util/src/lib.rs2
-rw-r--r--sys_util/src/timerfd.rs128
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);
+    }
 }