named_timer/
lib.rs

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