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
// 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 fuchsia_zircon as zx;
use fuchsia_zircon::sys::zx_time_t;
use std::future::Future;
use std::os::raw::c_char;

/// 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::Duration) -> fasync::Time {
    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::Time::from_nanos(time).into(),
        false => fasync::Time::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::Duration) -> 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::Duration,
        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;
    // 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::Duration = zx::Duration::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 = 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 =
            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 =
            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"));
    }
}