bt_fidl_mocks/
expect.rs

1// Copyright 2019 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 anyhow::{format_err, Error};
6use fidl::endpoints::RequestStream;
7use fuchsia_async::{DurationExt, TimeoutExt};
8use futures::{TryStream, TryStreamExt};
9use zx::MonotonicDuration;
10
11/// Represents the status of an expectation over a FIDL request stream.
12pub(crate) enum Status<T> {
13    Pending,
14    Satisfied(T),
15}
16
17/// Asynchronously applies handler `H` to each FIDL request of type `R` received from stream `S`
18/// until the handler is satisfied or the provided timeout expires.
19pub(crate) async fn expect_call<H, T, R, S>(
20    stream: &mut S,
21    timeout: MonotonicDuration,
22    mut handler: H,
23) -> Result<T, Error>
24where
25    H: FnMut(R) -> Result<Status<T>, Error>,
26    S: RequestStream + TryStream<Ok = R, Error = fidl::Error>,
27{
28    async {
29        while let Some(msg) = stream.try_next().await? {
30            match handler(msg)? {
31                Status::Satisfied(t) => {
32                    return Ok(t);
33                }
34                Status::Pending => (),
35            }
36        }
37        Err(format_err!("Stream closed before expectation was satisifed"))
38    }
39    .on_timeout(timeout.after_now(), || {
40        Err(format_err!("timed out before expectation was satisfied"))
41    })
42    .await
43}
44
45#[cfg(test)]
46mod tests {
47    use super::*;
48    use fidl::endpoints::create_proxy_and_stream;
49    use fidl_fuchsia_bluetooth::DeviceClass;
50    use fidl_fuchsia_bluetooth_sys::{AccessMarker, AccessRequest, AccessRequestStream};
51
52    // This is a mock handler that does the following for the purposes of the unit tests below:
53    // - Return success if Access.SetLocalName is called with the given `expected_name`;
54    // - Return an error if Access.SetLocalName is called with a name that is different from
55    //   `expected_name`;
56    // - Continue expecting a message if any other FIDL message is received.
57    async fn expect_set_local_name(
58        mut stream: AccessRequestStream,
59        expected_name: String,
60    ) -> Result<(), Error> {
61        expect_call(&mut stream, zx::MonotonicDuration::from_millis(500), move |req| match req {
62            AccessRequest::SetLocalName { name, control_handle: _ } => {
63                if name == expected_name {
64                    Ok(Status::Satisfied(()))
65                } else {
66                    Err(format_err!("received incorrect name"))
67                }
68            }
69            _ => Ok(Status::Pending),
70        })
71        .await
72    }
73
74    #[fuchsia_async::run_until_stalled(test)]
75    async fn test_satisfied() {
76        let (proxy, stream) = create_proxy_and_stream::<AccessMarker>();
77        let name = "TEST".to_string();
78
79        let _ = proxy.set_local_name(&name);
80        let mock_result = expect_set_local_name(stream, name).await;
81        assert!(mock_result.is_ok());
82    }
83
84    #[fuchsia_async::run_until_stalled(test)]
85    async fn test_unsatisfied_due_to_mismatch() {
86        let (proxy, stream) = create_proxy_and_stream::<AccessMarker>();
87        let expected_name = "TEST".to_string();
88        let wrong_name = "💩".to_string();
89
90        let _ = proxy.set_local_name(&wrong_name);
91        let mock_result = expect_set_local_name(stream, expected_name).await;
92        assert!(mock_result.is_err());
93    }
94
95    #[fuchsia_async::run_singlethreaded(test)]
96    async fn test_timeout_without_any_message() {
97        let (_proxy, stream) = create_proxy_and_stream::<AccessMarker>();
98        let expected_name = "TEST".to_string();
99        let result = expect_set_local_name(stream, expected_name).await;
100        assert!(result.is_err());
101    }
102
103    #[fuchsia_async::run_singlethreaded(test)]
104    async fn test_timeout_after_unexpected_message() {
105        let (proxy, stream) = create_proxy_and_stream::<AccessMarker>();
106        let expected_name = "TEST".to_string();
107
108        let _ = proxy.set_device_class(&DeviceClass { value: 0 });
109        let mock_result = expect_set_local_name(stream, expected_name).await;
110        assert!(mock_result.is_err());
111    }
112
113    #[fuchsia_async::run_until_stalled(test)]
114    async fn test_error_after_handle_closure() {
115        let (proxy, stream) = create_proxy_and_stream::<AccessMarker>();
116        let expected_name = "TEST".to_string();
117
118        drop(proxy);
119        let result = expect_set_local_name(stream, expected_name).await;
120        assert!(result.is_err());
121    }
122
123    #[fuchsia_async::run_until_stalled(test)]
124    async fn test_satisfied_with_expected_message_after_unexpected_message() {
125        let (proxy, stream) = create_proxy_and_stream::<AccessMarker>();
126        let expected_name = "TEST".to_string();
127
128        let _ = proxy.set_device_class(&DeviceClass { value: 0 });
129        let _ = proxy.set_local_name(&expected_name);
130        let mock_result = expect_set_local_name(stream, expected_name.clone()).await;
131        assert!(mock_result.is_ok());
132    }
133}