summary refs log tree commit diff
path: root/sys_util/src/timerfd.rs
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 /sys_util/src/timerfd.rs
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>
Diffstat (limited to 'sys_util/src/timerfd.rs')
-rw-r--r--sys_util/src/timerfd.rs128
1 files changed, 127 insertions, 1 deletions
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);
+    }
 }