blob: 4d8cabb7415e8624357830c6f96fc59f56367dda [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 {
super::*,
async_utils::event::Event,
fidl::endpoints::*,
fidl_fuchsia_bluetooth_component::{LifecycleMarker, LifecycleRequest, LifecycleState},
fidl_fuchsia_io::{DirectoryMarker, DirectoryRequest},
fidl_fuchsia_sys::{
ComponentControllerRequestStream, LaunchInfo, LauncherMarker, LauncherProxy,
},
futures::{select, FutureExt, StreamExt},
std::future::Future,
};
/// Based on the expected_role, assert that exactly the expected components are contained in
/// `child_components`.
///
/// Params:
/// $expected_urls: impl Into<&[&str]>
/// $child_components: impl Into<&[ChildComponent>
#[macro_export]
macro_rules! assert_expected_components {
($expected_urls:expr, $child_components:expr) => {
assert_eq!($child_components.len(), $expected_urls.len());
for c in $child_components {
assert!(!c.terminated());
assert!($expected_urls.contains(&c.url()));
}
};
}
/// Represents a `ChildComponent` that is spawned by the mock `LauncherProxy`. It can be used to
/// terminate a component and determine termination status, is identified by the url used to request
/// the component, and contains the logic to execute the component as a Future.
pub struct ChildComponent {
termination: Event,
launch_info: LaunchInfo,
controller: Option<ComponentControllerRequestStream>,
}
impl ChildComponent {
pub fn new(
launch_info: LaunchInfo,
controller: ComponentControllerRequestStream,
) -> ChildComponent {
ChildComponent { termination: Event::new(), launch_info, controller: Some(controller) }
}
/// Send signal that child component should be terminated.
pub fn terminate(&self) {
self.termination.signal();
}
/// Returns a bool representing whether the component has been terminated.
pub fn terminated(&self) -> bool {
self.termination.signaled()
}
/// Returns the url used to create the component
pub fn url(&self) -> &str {
&self.launch_info.url
}
/// Handle service directory requests. Implements the Lifecycle protocol and responds with
/// `LifecycleState::Ready` for all requests.
async fn lifecycle_always_ready(server: impl Into<ServerEnd<LifecycleMarker>>) {
let mut stream = server.into().into_stream().unwrap();
while let Some(request) = stream.next().await {
if let Ok(LifecycleRequest::GetState { responder }) = request {
let _ = responder.send(LifecycleState::Ready);
} else {
unimplemented!();
}
}
}
async fn service_directory_handler(svc_dir: impl Into<ServerEnd<DirectoryMarker>>) {
let mut stream = svc_dir.into().into_stream().unwrap();
while let Some(request) = stream.next().await {
match request {
Ok(DirectoryRequest::Open { object, path, .. })
if path == LifecycleMarker::NAME =>
{
fasync::Task::spawn(Self::lifecycle_always_ready(object.into_channel()))
.detach();
}
_ => unimplemented!("No other request type expected"),
}
}
}
async fn component_controller_handler(mut stream: ComponentControllerRequestStream) {
while let Some(request) = stream.next().await {
match request {
Ok(fidl_fuchsia_sys::ComponentControllerRequest::Kill { control_handle }) => {
let _ = control_handle
.send_on_terminated(0, fidl_fuchsia_sys::TerminationReason::Exited);
}
_ => unimplemented!("Unhandled control request"),
}
}
}
/// Returns a `Future` that lives until explicitly terminated via
/// `ComponentControllerRequest::Kill` or the `ChildComponent::terminate` method.
/// It supports the `fuchsia.bluetooth.component.Lifecycle` FIDL protocol.
pub fn execute(&mut self) -> impl Future<Output = ()> + 'static {
let stream = self.controller.take().expect("cannot execute multiple times");
let svc_dir = self.launch_info.directory_request.take().unwrap();
let termination = self.termination.clone();
async move {
select! {
_ = Self::component_controller_handler(stream).fuse() => (),
_ = Self::service_directory_handler(svc_dir).fuse() => (),
_ = termination.wait() => (),
};
termination.signal();
}
}
}
/// A `Launcher` provides a test with a way of accessesing `ChildComponent` objects that have been
/// created by the associated `LauncherProxy`. All
pub struct Launcher {
launched_components: mpsc::UnboundedReceiver<ChildComponent>,
}
impl Launcher {
/// Wait for and collect the next `n` `ChildComponents` spawned.
pub async fn next_n(&mut self, n: usize) -> Vec<ChildComponent> {
(&mut self.launched_components).take(n).collect().await
}
}
/// Create a new `Launcher` and `LauncherProxy`. The proxy handles requests by spawning
/// components as new async tasks and passing a `ChildComponent` object to the associated
/// `Launcher`.
pub fn mock_launcher() -> (Launcher, LauncherProxy) {
let (tx, rx) = mpsc::unbounded();
let launcher = Launcher { launched_components: rx };
let (proxy, stream) = fidl::endpoints::create_proxy_and_stream::<LauncherMarker>().unwrap();
fasync::Task::spawn(stream.for_each_concurrent(None, move |request| {
let mut tx = tx.clone();
async move {
let fidl_fuchsia_sys::LauncherRequest::CreateComponent {
launch_info, controller, ..
} = request.expect("Error in receiving LauncherRequest");
let controller_request_stream = controller.unwrap().into_stream().unwrap();
let mut child = ChildComponent::new(launch_info, controller_request_stream);
let execute = child.execute();
let _ = tx.send(child).await;
execute.await;
}
}))
.detach();
(launcher, proxy)
}