blob: 1ee6ce56f547ab9ccea794544bfc2f75f79dfc9c [file] [log] [blame]
// Copyright 2019 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 {
anyhow::{format_err, Error},
fidl::endpoints::RequestStream,
fuchsia_async::{DurationExt, TimeoutExt},
fuchsia_zircon::Duration,
futures::{TryStream, TryStreamExt},
};
/// Represents the status of an expectation over a FIDL request stream.
pub(crate) enum Status<T> {
Pending,
Satisfied(T),
}
/// Asynchronously applies handler `H` to each FIDL request of type `R` received from stream `S`
/// until the handler is satisfied or the provided timeout expires.
pub(crate) async fn expect_call<H, T, R, S>(
stream: &mut S,
timeout: Duration,
mut handler: H,
) -> Result<T, Error>
where
H: FnMut(R) -> Result<Status<T>, Error>,
S: RequestStream + TryStream<Ok = R, Error = fidl::Error>,
{
async {
while let Some(msg) = stream.try_next().await? {
match handler(msg)? {
Status::Satisfied(t) => {
return Ok(t);
}
Status::Pending => (),
}
}
Err(format_err!("Stream closed before expectation was satisifed"))
}
.on_timeout(timeout.after_now(), || {
Err(format_err!("timed out before expectation was satisfied"))
})
.await
}
#[cfg(test)]
mod tests {
use super::*;
use {
fidl::endpoints::create_proxy_and_stream,
fidl_fuchsia_bluetooth::DeviceClass,
fidl_fuchsia_bluetooth_sys::{AccessMarker, AccessRequest, AccessRequestStream},
fuchsia_zircon::DurationNum,
};
// This is a mock handler that does the following for the purposes of the unit tests below:
// - Return success if Access.SetLocalName is called with the given `expected_name`;
// - Return an error if Access.SetLocalName is called with a name that is different from
// `expected_name`;
// - Continue expecting a message if any other FIDL message is received.
async fn expect_set_local_name(
mut stream: AccessRequestStream,
expected_name: String,
) -> Result<(), Error> {
expect_call(&mut stream, 500.millis(), move |req| match req {
AccessRequest::SetLocalName { name, control_handle: _ } => {
if name == expected_name {
Ok(Status::Satisfied(()))
} else {
Err(format_err!("received incorrect name"))
}
}
_ => Ok(Status::Pending),
})
.await
}
#[fuchsia_async::run_until_stalled(test)]
async fn test_satisfied() {
let (proxy, stream) =
create_proxy_and_stream::<AccessMarker>().expect("failed to create proxy");
let name = "TEST".to_string();
let _ = proxy.set_local_name(&name);
let mock_result = expect_set_local_name(stream, name).await;
assert!(mock_result.is_ok());
}
#[fuchsia_async::run_until_stalled(test)]
async fn test_unsatisfied_due_to_mismatch() {
let (proxy, stream) =
create_proxy_and_stream::<AccessMarker>().expect("failed to create proxy");
let expected_name = "TEST".to_string();
let wrong_name = "💩".to_string();
let _ = proxy.set_local_name(&wrong_name);
let mock_result = expect_set_local_name(stream, expected_name).await;
assert!(mock_result.is_err());
}
#[fuchsia_async::run_singlethreaded(test)]
async fn test_timeout_without_any_message() {
let (_proxy, stream) =
create_proxy_and_stream::<AccessMarker>().expect("failed to create proxy");
let expected_name = "TEST".to_string();
let result = expect_set_local_name(stream, expected_name).await;
assert!(result.is_err());
}
#[fuchsia_async::run_singlethreaded(test)]
async fn test_timeout_after_unexpected_message() {
let (proxy, stream) =
create_proxy_and_stream::<AccessMarker>().expect("failed to create proxy");
let expected_name = "TEST".to_string();
let _ = proxy.set_device_class(&DeviceClass { value: 0 });
let mock_result = expect_set_local_name(stream, expected_name).await;
assert!(mock_result.is_err());
}
#[fuchsia_async::run_until_stalled(test)]
async fn test_error_after_handle_closure() {
let (proxy, stream) =
create_proxy_and_stream::<AccessMarker>().expect("failed to create proxy");
let expected_name = "TEST".to_string();
drop(proxy);
let result = expect_set_local_name(stream, expected_name).await;
assert!(result.is_err());
}
#[fuchsia_async::run_until_stalled(test)]
async fn test_satisfied_with_expected_message_after_unexpected_message() {
let (proxy, stream) =
create_proxy_and_stream::<AccessMarker>().expect("failed to create proxy");
let expected_name = "TEST".to_string();
let _ = proxy.set_device_class(&DeviceClass { value: 0 });
let _ = proxy.set_local_name(&expected_name);
let mock_result = expect_set_local_name(stream, expected_name.clone()).await;
assert!(mock_result.is_ok());
}
}