named_timer/lib.rs
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160
// Copyright 2021 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_async::{self as fasync, TimeoutExt};
use std::future::Future;
use std::os::raw::c_char;
use zx::sys::zx_time_t;
/// A version of the fidl `DeadlineId` containing unowned data.
#[derive(Clone, Copy)]
pub struct DeadlineId<'a> {
component_id: &'a str,
code: &'a str,
}
impl<'a> Into<fidl_fuchsia_testing_deadline::DeadlineId> for DeadlineId<'a> {
fn into(self) -> fidl_fuchsia_testing_deadline::DeadlineId {
fidl_fuchsia_testing_deadline::DeadlineId {
component_id: self.component_id.to_string(),
code: self.code.to_string(),
}
}
}
impl<'a> DeadlineId<'a> {
/// Create a new deadline identifier.
pub const fn new(component_id: &'a str, code: &'a str) -> Self {
Self { component_id, code }
}
}
extern "C" {
fn create_named_deadline(
component: *const c_char,
component_len: usize,
code: *const c_char,
code_len: usize,
duration: zx_time_t,
out: *mut zx_time_t,
) -> bool;
}
fn create_named_deadline_rust(
deadline: &DeadlineId<'_>,
duration: zx::MonotonicDuration,
) -> fasync::MonotonicInstant {
let mut time: zx_time_t = 0;
let time_valid = unsafe {
create_named_deadline(
deadline.component_id.as_ptr() as *const c_char,
deadline.component_id.len(),
deadline.code.as_ptr() as *const c_char,
deadline.code.len(),
duration.into_nanos(),
&mut time,
)
};
match time_valid {
true => zx::MonotonicInstant::from_nanos(time).into(),
false => fasync::MonotonicInstant::now() + duration,
}
}
/// A timer with an associated name.
/// This timer is intended to be used in conjunction with the fake-clock library. Under normal
/// execution, the timer behaves the same as a regular [`fuchsia_async::Timer`]. When run in an
/// integration test with the fake-clock library linked in, the creation of the timer and
/// the expiration of the timer are reported to the fake-clock service. The integration test may
/// register interest in these events to stop time when they occur.
pub struct NamedTimer;
impl NamedTimer {
/// Create a new `NamedTimer` that will expire `duration` in the future.
/// In an integration test, the `SET` event is reported immediately when this method is called,
/// and `EXPIRED` is reported after `duration` elapses. Note `EXPIRED` is still reported even
/// if the timer is dropped before `duration` elapses.
pub fn new(id: &DeadlineId<'_>, duration: zx::MonotonicDuration) -> fasync::Timer {
let deadline = create_named_deadline_rust(id, duration);
fasync::Timer::new(deadline)
}
}
/// An extension trait that allows setting a timeout with an associated name.
/// The timeout is intended to be used in conjunction with the fake-clock library. Under normal
/// execution, this behaves identically to [`fuchsia_async::TimeoutExt`].
/// When run in an integration test with the fake-clock library linked in, the creation of the
/// timer and the expiration of the timer are reported to the fake-clock service. The integration
/// test may register interest in these events to stop time when they occur.
pub trait NamedTimeoutExt: Future + Sized {
/// Wraps the future in a timeout, calling `on_timeout` when the timeout occurs.
/// In an integration test, the `SET` event is reported immediately when this method is called,
/// and `EXPIRED` is reported after `duration` elapses. Note `EXPIRED` is still reported even
/// if `on_timeout` is not run.
fn on_timeout_named<OT>(
self,
id: &DeadlineId<'_>,
duration: zx::MonotonicDuration,
on_timeout: OT,
) -> fasync::OnTimeout<Self, OT>
where
OT: FnOnce() -> Self::Output,
{
let deadline = create_named_deadline_rust(id, duration);
self.on_timeout(deadline, on_timeout)
}
}
impl<F: Future + Sized> NamedTimeoutExt for F {}
#[cfg(test)]
mod test {
use super::*;
use core::task::Poll;
use std::pin::pin;
// When the fake-clock library is not linked in, these timers should behave identical to
// fasync::Timer. These tests verify that the fake time utilities provided by
// fasync::TestExecutor continue to work when fake-clock is NOT linked in. Behavior with
// fake-clock linked in is verified by integration tests in fake-clock/examples.
const ONE_HOUR: zx::MonotonicDuration = zx::MonotonicDuration::from_hours(1);
const DEADLINE_ID: DeadlineId<'static> = DeadlineId::new("component", "code");
#[test]
fn test_timer() {
let mut executor = fasync::TestExecutor::new_with_fake_time();
let start_time = executor.now();
let mut timer = pin!(NamedTimer::new(&DEADLINE_ID, ONE_HOUR));
assert!(executor.run_until_stalled(&mut timer).is_pending());
executor.set_fake_time(start_time + ONE_HOUR);
assert_eq!(executor.wake_next_timer(), Some(start_time + ONE_HOUR));
assert!(executor.run_until_stalled(&mut timer).is_ready());
}
#[test]
fn test_timeout_not_invoked() {
let mut executor = fasync::TestExecutor::new_with_fake_time();
let mut ready_future =
pin!(futures::future::ready("ready")
.on_timeout_named(&DEADLINE_ID, ONE_HOUR, || "timeout"));
assert_eq!(executor.run_until_stalled(&mut ready_future), Poll::Ready("ready"));
}
#[test]
fn test_timeout_invoked() {
let mut executor = fasync::TestExecutor::new_with_fake_time();
let start_time = executor.now();
let mut stalled_future =
pin!(futures::future::pending().on_timeout_named(&DEADLINE_ID, ONE_HOUR, || "timeout"));
assert!(executor.run_until_stalled(&mut stalled_future).is_pending());
executor.set_fake_time(start_time + ONE_HOUR);
assert_eq!(executor.wake_next_timer(), Some(start_time + ONE_HOUR));
assert_eq!(executor.run_until_stalled(&mut stalled_future), Poll::Ready("timeout"));
}
}