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 /sys_util/src/timerfd.rs | |
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>
Diffstat (limited to 'sys_util/src/timerfd.rs')
-rw-r--r-- | sys_util/src/timerfd.rs | 128 |
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); + } } |