| // 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 { |
| anyhow::{format_err, Context as _, Error}, |
| fidl::endpoints::{ |
| create_request_stream, ClientEnd, ControlHandle, DiscoverableProtocolMarker, |
| ProtocolMarker, RequestStream, ServerEnd, ServiceMarker, ServiceProxy, |
| }, |
| fidl_fuchsia_component as fcomponent, fidl_fuchsia_component_runner as fcrunner, |
| fidl_fuchsia_component_test as ftest, fidl_fuchsia_data as fdata, fidl_fuchsia_io as fio, |
| fidl_fuchsia_process as fprocess, fuchsia_async as fasync, |
| fuchsia_component::DEFAULT_SERVICE_INSTANCE, |
| fuchsia_zircon as zx, |
| futures::{channel::oneshot, future::BoxFuture, lock::Mutex, select, FutureExt, TryStreamExt}, |
| runner::get_value as get_dictionary_value, |
| std::{collections::HashMap, sync::Arc}, |
| tracing::*, |
| vfs::execution_scope::ExecutionScope, |
| }; |
| |
| /// The handles from the framework over which the local component should interact with other |
| /// components. |
| pub struct LocalComponentHandles { |
| namespace: HashMap<String, fio::DirectoryProxy>, |
| numbered_handles: HashMap<u32, zx::Handle>, |
| |
| stop_notifier: Arc<Mutex<Option<oneshot::Sender<()>>>>, |
| |
| /// The outgoing directory handle for a local component. This can be used to run a ServiceFs |
| /// for the component. |
| pub outgoing_dir: ServerEnd<fio::DirectoryMarker>, |
| } |
| |
| impl LocalComponentHandles { |
| fn new( |
| fidl_namespace: Vec<fcrunner::ComponentNamespaceEntry>, |
| fidl_numbered_handles: Vec<fprocess::HandleInfo>, |
| outgoing_dir: ServerEnd<fio::DirectoryMarker>, |
| ) -> Result<(Self, Arc<Mutex<Option<oneshot::Sender<()>>>>), Error> { |
| let stop_notifier = Arc::new(Mutex::new(None)); |
| let mut namespace = HashMap::new(); |
| for namespace_entry in fidl_namespace { |
| namespace.insert( |
| namespace_entry.path.ok_or(format_err!("namespace entry missing path"))?, |
| namespace_entry |
| .directory |
| .ok_or(format_err!("namespace entry missing directory handle"))? |
| .into_proxy() |
| .expect("failed to convert handle to proxy"), |
| ); |
| } |
| let numbered_handles = |
| fidl_numbered_handles.into_iter().map(|h| (h.id, h.handle)).collect::<HashMap<_, _>>(); |
| Ok(( |
| Self { |
| namespace, |
| numbered_handles, |
| outgoing_dir, |
| stop_notifier: stop_notifier.clone(), |
| }, |
| stop_notifier, |
| )) |
| } |
| |
| pub fn take_numbered_handle(&mut self, id: u32) -> Option<zx::Handle> { |
| self.numbered_handles.remove(&id) |
| } |
| |
| pub fn numbered_handles(&self) -> &HashMap<u32, zx::Handle> { |
| &self.numbered_handles |
| } |
| |
| /// Registers a new stop notifier for this component. If this function is called, then realm |
| /// builder will deliver a message on the returned oneshot when component manager asks for this |
| /// component to stop. It is then this component's responsibility to exit. If it takes too long |
| /// to exit (the default is 5 seconds) then it will be killed. |
| /// |
| /// If this function is not called, then the component is immediately killed when component |
| /// manager asks for it to be stopped. Killing the component is performed by dropping the |
| /// underlying future, effectively cancelling all pending work. |
| /// |
| /// If this function is called more than once on a single local component, then it will panic. |
| pub async fn register_stop_notifier(&self) -> oneshot::Receiver<()> { |
| let mut stop_notifier_guard = self.stop_notifier.lock().await; |
| if stop_notifier_guard.is_some() { |
| panic!("cannot register multiple stop handlers for a single local component"); |
| } |
| let (sender, receiver) = oneshot::channel(); |
| *stop_notifier_guard = Some(sender); |
| receiver |
| } |
| |
| /// Connects to a FIDL protocol and returns a proxy to that protocol. |
| pub fn connect_to_protocol<P: DiscoverableProtocolMarker>(&self) -> Result<P::Proxy, Error> { |
| self.connect_to_named_protocol::<P>(P::PROTOCOL_NAME) |
| } |
| |
| /// Connects to a FIDL protocol with the given name and returns a proxy to that protocol. |
| pub fn connect_to_named_protocol<P: ProtocolMarker>( |
| &self, |
| name: &str, |
| ) -> Result<P::Proxy, Error> { |
| let svc_dir_proxy = self |
| .namespace |
| .get(&"/svc".to_string()) |
| .ok_or(format_err!("the component's namespace doesn't have a /svc directory"))?; |
| fuchsia_component::client::connect_to_named_protocol_at_dir_root::<P>(svc_dir_proxy, name) |
| } |
| |
| /// Opens a FIDL service as a directory, which holds instances of the service. |
| pub fn open_service<S: ServiceMarker>(&self) -> Result<fio::DirectoryProxy, Error> { |
| self.open_named_service(S::SERVICE_NAME) |
| } |
| |
| /// Opens a FIDL service with the given name as a directory, which holds instances of the |
| /// service. |
| pub fn open_named_service(&self, name: &str) -> Result<fio::DirectoryProxy, Error> { |
| let svc_dir_proxy = self |
| .namespace |
| .get("/svc") |
| .ok_or(format_err!("the component's namespace doesn't have a /svc directory"))?; |
| fuchsia_fs::directory::open_directory_no_describe( |
| &svc_dir_proxy, |
| name, |
| fuchsia_fs::OpenFlags::empty(), |
| ) |
| .map_err(Into::into) |
| } |
| |
| /// Connect to the "default" instance of a FIDL service in the `/svc` directory of |
| /// the local component's root namespace. |
| pub fn connect_to_service<S: ServiceMarker>(&self) -> Result<S::Proxy, Error> { |
| self.connect_to_service_instance::<S>(DEFAULT_SERVICE_INSTANCE) |
| } |
| |
| /// Connect to an instance of a FIDL service in the `/svc` directory of |
| /// the local components's root namespace. |
| /// `instance` is a path of one or more components. |
| pub fn connect_to_service_instance<S: ServiceMarker>( |
| &self, |
| instance_name: &str, |
| ) -> Result<S::Proxy, Error> { |
| self.connect_to_named_service_instance::<S>(S::SERVICE_NAME, instance_name) |
| } |
| |
| /// Connect to an instance of a FIDL service with the given name in the `/svc` directory of |
| /// the local components's root namespace. |
| /// `instance` is a path of one or more components. |
| pub fn connect_to_named_service_instance<S: ServiceMarker>( |
| &self, |
| service_name: &str, |
| instance_name: &str, |
| ) -> Result<S::Proxy, Error> { |
| let service_dir = self.open_named_service(service_name)?; |
| let directory_proxy = fuchsia_fs::directory::open_directory_no_describe( |
| &service_dir, |
| instance_name, |
| fuchsia_fs::OpenFlags::empty(), |
| )?; |
| Ok(S::Proxy::from_member_opener(Box::new( |
| fuchsia_component::client::ServiceInstanceDirectory(directory_proxy), |
| ))) |
| } |
| |
| /// Clones a directory from the local component's namespace. |
| /// |
| /// Note that this function only works on exact matches from the namespace. For example if the |
| /// namespace had a `data` entry in it, and the caller wished to open the subdirectory at |
| /// `data/assets`, then this function should be called with the argument `data` and the |
| /// returned `DirectoryProxy` would then be used to open the subdirectory `assets`. In this |
| /// scenario, passing `data/assets` in its entirety to this function would fail. |
| /// |
| /// ``` |
| /// let data_dir = handles.clone_from_namespace("data")?; |
| /// let assets_dir = |
| /// fuchsia_fs::directory::open_directory_no_describe(&data_dir, "assets", ...)?; |
| /// ``` |
| pub fn clone_from_namespace(&self, directory_name: &str) -> Result<fio::DirectoryProxy, Error> { |
| let dir_proxy = self.namespace.get(&format!("/{}", directory_name)).ok_or(format_err!( |
| "the local component's namespace doesn't have a /{} directory", |
| directory_name |
| ))?; |
| fuchsia_fs::directory::clone_no_describe(&dir_proxy, None).context("clone") |
| } |
| } |
| |
| type LocalComponentImplementations = HashMap< |
| String, |
| Arc< |
| dyn Fn(LocalComponentHandles) -> BoxFuture<'static, Result<(), Error>> |
| + Sync |
| + Send |
| + 'static, |
| >, |
| >; |
| |
| #[derive(Clone, Debug)] |
| pub struct LocalComponentRunnerBuilder { |
| local_component_implementations: Arc<Mutex<Option<LocalComponentImplementations>>>, |
| } |
| |
| impl LocalComponentRunnerBuilder { |
| pub fn new() -> Self { |
| Self { local_component_implementations: Arc::new(Mutex::new(Some(HashMap::new()))) } |
| } |
| |
| pub(crate) async fn register_local_component<I>( |
| &self, |
| name: String, |
| implementation: I, |
| ) -> Result<(), ftest::RealmBuilderError> |
| where |
| I: Fn(LocalComponentHandles) -> BoxFuture<'static, Result<(), Error>> |
| + Sync |
| + Send |
| + 'static, |
| { |
| self.local_component_implementations |
| .lock() |
| .await |
| .as_mut() |
| .ok_or(ftest::RealmBuilderError::BuildAlreadyCalled)? |
| .insert(name, Arc::new(implementation)); |
| Ok(()) |
| } |
| |
| pub(crate) async fn build( |
| self, |
| ) -> Result< |
| (ClientEnd<fcrunner::ComponentRunnerMarker>, fasync::Task<()>), |
| ftest::RealmBuilderError, |
| > { |
| let local_component_implementations = self |
| .local_component_implementations |
| .lock() |
| .await |
| .take() |
| .ok_or(ftest::RealmBuilderError::BuildAlreadyCalled)?; |
| let (runner_client_end, runner_request_stream) = |
| create_request_stream::<fcrunner::ComponentRunnerMarker>() |
| .expect("failed to create channel pair"); |
| let runner = LocalComponentRunner::new(local_component_implementations); |
| let runner_task = fasync::Task::spawn(async move { |
| if let Err(e) = runner.handle_stream(runner_request_stream).await { |
| error!("failed to run local component runner: {:?}", e); |
| } |
| }); |
| |
| Ok((runner_client_end, runner_task)) |
| } |
| } |
| |
| pub struct LocalComponentRunner { |
| execution_scope: ExecutionScope, |
| local_component_implementations: HashMap< |
| String, |
| Arc< |
| dyn Fn(LocalComponentHandles) -> BoxFuture<'static, Result<(), Error>> |
| + Sync |
| + Send |
| + 'static, |
| >, |
| >, |
| } |
| |
| impl Drop for LocalComponentRunner { |
| fn drop(&mut self) { |
| self.execution_scope.shutdown(); |
| } |
| } |
| |
| impl LocalComponentRunner { |
| fn new( |
| local_component_implementations: HashMap< |
| String, |
| Arc< |
| dyn Fn(LocalComponentHandles) -> BoxFuture<'static, Result<(), Error>> |
| + Sync |
| + Send |
| + 'static, |
| >, |
| >, |
| ) -> Self { |
| Self { local_component_implementations, execution_scope: ExecutionScope::new() } |
| } |
| |
| async fn handle_stream( |
| &self, |
| mut runner_request_stream: fcrunner::ComponentRunnerRequestStream, |
| ) -> Result<(), Error> { |
| while let Some(req) = runner_request_stream.try_next().await? { |
| match req { |
| fcrunner::ComponentRunnerRequest::Start { start_info, controller, .. } => { |
| let program = start_info |
| .program |
| .ok_or(format_err!("program is missing from start_info"))?; |
| let namespace = |
| start_info.ns.ok_or(format_err!("namespace is missing from start_info"))?; |
| let numbered_handles = start_info.numbered_handles.unwrap_or_default(); |
| let outgoing_dir = start_info |
| .outgoing_dir |
| .ok_or(format_err!("outgoing_dir is missing from start_info"))?; |
| let _runtime_dir_server_end: ServerEnd<fio::DirectoryMarker> = start_info |
| .runtime_dir |
| .ok_or(format_err!("runtime_dir is missing from start_info"))?; |
| |
| let local_component_name = extract_local_component_name(program)?; |
| let local_component_implementation = self |
| .local_component_implementations |
| .get(&local_component_name) |
| .ok_or(format_err!("no such local component: {:?}", local_component_name))? |
| .clone(); |
| let (component_handles, stop_notifier) = |
| LocalComponentHandles::new(namespace, numbered_handles, outgoing_dir)?; |
| |
| let mut controller_request_stream = controller.into_stream()?; |
| self.execution_scope.spawn(async move { |
| let mut local_component_implementation_fut = |
| (*local_component_implementation)(component_handles).fuse(); |
| let controller_control_handle = controller_request_stream.control_handle(); |
| let mut controller_request_fut = |
| controller_request_stream.try_next().fuse(); |
| loop { |
| select! { |
| res = local_component_implementation_fut => { |
| let epitaph = match res { |
| Err(e) => { |
| error!( |
| "the local component {:?} returned an error: {:?}", |
| local_component_name, |
| e, |
| ); |
| zx::Status::from_raw(fcomponent::Error::InstanceDied.into_primitive() as i32) |
| } |
| Ok(()) => zx::Status::OK, |
| }; |
| controller_control_handle.shutdown_with_epitaph(epitaph); |
| return; |
| } |
| req_res = controller_request_fut => { |
| match req_res.expect("invalid controller request") { |
| Some(fcrunner::ComponentControllerRequest::Stop { .. }) => { |
| if let Some(stop_notifier) = |
| stop_notifier.lock().await.take() |
| { |
| // If the local component happened to exit the same |
| // moment that the component controller stop |
| // request was received, then the receiver is |
| // already dropped. Let's ignore any errors about |
| // sending this. |
| let _ = stop_notifier.send(()); |
| |
| // Repopulate the `controller_request_fut` field so |
| // that we'll be able to see the `Kill` request. |
| controller_request_fut = controller_request_stream.try_next().fuse(); |
| } else { |
| controller_control_handle.shutdown_with_epitaph( |
| zx::Status::from_raw(fcomponent::Error::InstanceDied.into_primitive() as i32), |
| ); |
| return; |
| } |
| } |
| Some(fcrunner::ComponentControllerRequest::Kill { .. }) => { |
| controller_control_handle.shutdown_with_epitaph( |
| zx::Status::from_raw(fcomponent::Error::InstanceDied.into_primitive() as i32), |
| ); |
| return; |
| } |
| _ => return, |
| } |
| } |
| }; |
| } |
| }); |
| } |
| } |
| } |
| Ok(()) |
| } |
| } |
| |
| fn extract_local_component_name(dict: fdata::Dictionary) -> Result<String, Error> { |
| let entry_value = get_dictionary_value(&dict, ftest::LOCAL_COMPONENT_NAME_KEY) |
| .ok_or(format_err!("program section is missing component name"))?; |
| if let fdata::DictionaryValue::Str(s) = entry_value { |
| return Ok(s.clone()); |
| } else { |
| return Err(format_err!("malformed program section")); |
| } |
| } |
| |
| #[cfg(test)] |
| mod tests { |
| use { |
| super::*, |
| assert_matches::assert_matches, |
| fidl::endpoints::{create_proxy, Proxy as _}, |
| fuchsia_zircon::AsHandleRef, |
| futures::future::pending, |
| }; |
| |
| #[fuchsia::test] |
| async fn runner_builder_correctly_stores_a_function() { |
| let runner_builder = LocalComponentRunnerBuilder::new(); |
| let (sender, receiver) = oneshot::channel(); |
| let sender = Arc::new(Mutex::new(Some(sender))); |
| |
| let component_name = "test".to_string(); |
| |
| runner_builder |
| .register_local_component(component_name.clone(), move |_handles| { |
| let sender = sender.clone(); |
| async move { |
| let sender = sender.lock().await.take().expect("local component invoked twice"); |
| sender.send(()).expect("failed to send"); |
| Ok(()) |
| } |
| .boxed() |
| }) |
| .await |
| .unwrap(); |
| |
| let (_, outgoing_dir) = create_proxy().unwrap(); |
| let handles = LocalComponentHandles { |
| namespace: HashMap::new(), |
| numbered_handles: HashMap::new(), |
| outgoing_dir, |
| stop_notifier: Arc::new(Mutex::new(None)), |
| }; |
| let local_component_implementation = runner_builder |
| .local_component_implementations |
| .lock() |
| .await |
| .as_ref() |
| .unwrap() |
| .get(&component_name) |
| .expect("local component missing from runner builder") |
| .clone(); |
| |
| (*local_component_implementation)(handles) |
| .await |
| .expect("local component implementation failed"); |
| let () = receiver.await.expect("failed to receive"); |
| } |
| |
| struct RunnerAndHandles { |
| _runner_task: fasync::Task<()>, |
| _component_runner_proxy: fcrunner::ComponentRunnerProxy, |
| _runtime_dir_proxy: fio::DirectoryProxy, |
| outgoing_dir_proxy: fio::DirectoryProxy, |
| controller_proxy: fcrunner::ComponentControllerProxy, |
| } |
| |
| async fn build_and_start( |
| runner_builder: LocalComponentRunnerBuilder, |
| component_to_start: String, |
| ) -> RunnerAndHandles { |
| let (component_runner_client_end, runner_task) = runner_builder.build().await.unwrap(); |
| let component_runner_proxy = component_runner_client_end.into_proxy().unwrap(); |
| |
| let (runtime_dir_proxy, runtime_dir_server_end) = create_proxy().unwrap(); |
| let (outgoing_dir_proxy, outgoing_dir_server_end) = create_proxy().unwrap(); |
| let (controller_proxy, controller_server_end) = create_proxy().unwrap(); |
| component_runner_proxy |
| .start( |
| fcrunner::ComponentStartInfo { |
| resolved_url: Some("test://test".to_string()), |
| program: Some(fdata::Dictionary { |
| entries: Some(vec![fdata::DictionaryEntry { |
| key: ftest::LOCAL_COMPONENT_NAME_KEY.to_string(), |
| value: Some(Box::new(fdata::DictionaryValue::Str(component_to_start))), |
| }]), |
| ..Default::default() |
| }), |
| ns: Some(vec![]), |
| outgoing_dir: Some(outgoing_dir_server_end), |
| runtime_dir: Some(runtime_dir_server_end), |
| numbered_handles: Some(vec![]), |
| ..Default::default() |
| }, |
| controller_server_end, |
| ) |
| .expect("failed to send start"); |
| |
| RunnerAndHandles { |
| _runner_task: runner_task, |
| _component_runner_proxy: component_runner_proxy, |
| _runtime_dir_proxy: runtime_dir_proxy, |
| outgoing_dir_proxy, |
| controller_proxy, |
| } |
| } |
| |
| #[fuchsia::test] |
| async fn the_runner_runs_a_component() { |
| let runner_builder = LocalComponentRunnerBuilder::new(); |
| let (sender, receiver) = oneshot::channel(); |
| let sender = Arc::new(Mutex::new(Some(sender))); |
| |
| let component_name = "test".to_string(); |
| |
| runner_builder |
| .register_local_component(component_name.clone(), move |_handles| { |
| let sender = sender.clone(); |
| async move { |
| let sender = sender.lock().await.take().expect("local component invoked twice"); |
| sender.send(()).expect("failed to send"); |
| Ok(()) |
| } |
| .boxed() |
| }) |
| .await |
| .unwrap(); |
| |
| let _runner_and_handles = build_and_start(runner_builder, component_name).await; |
| |
| let () = receiver.await.expect("failed to receive"); |
| } |
| |
| #[fuchsia::test] |
| async fn the_runner_gives_the_component_its_outgoing_dir() { |
| let runner_builder = LocalComponentRunnerBuilder::new(); |
| let (sender, receiver) = oneshot::channel::<ServerEnd<fio::DirectoryMarker>>(); |
| let sender = Arc::new(Mutex::new(Some(sender))); |
| |
| let component_name = "test".to_string(); |
| |
| runner_builder |
| .register_local_component(component_name.clone(), move |handles| { |
| let sender = sender.clone(); |
| async move { |
| let _ = &handles; |
| sender |
| .lock() |
| .await |
| .take() |
| .expect("local component invoked twice") |
| .send(handles.outgoing_dir) |
| .expect("failed to send"); |
| Ok(()) |
| } |
| .boxed() |
| }) |
| .await |
| .unwrap(); |
| |
| let runner_and_handles = build_and_start(runner_builder, component_name.clone()).await; |
| |
| let outgoing_dir_server_end = receiver.await.expect("failed to receive"); |
| |
| assert_eq!( |
| outgoing_dir_server_end |
| .into_channel() |
| .basic_info() |
| .expect("failed to get basic info") |
| .koid, |
| runner_and_handles |
| .outgoing_dir_proxy |
| .into_channel() |
| .expect("failed to convert to channel") |
| .basic_info() |
| .expect("failed to get basic info") |
| .related_koid, |
| ); |
| } |
| |
| #[fuchsia::test] |
| async fn controller_stop_will_stop_a_component() { |
| let runner_builder = LocalComponentRunnerBuilder::new(); |
| let (sender, receiver) = oneshot::channel::<()>(); |
| let sender = Arc::new(Mutex::new(Some(sender))); |
| |
| let component_name = "test".to_string(); |
| |
| runner_builder |
| .register_local_component(component_name.clone(), move |_handles| { |
| let sender = sender.clone(); |
| async move { |
| let _sender = |
| sender.lock().await.take().expect("local component invoked twice"); |
| // Don't use sender, we want to detect when it gets dropped, which causes an error |
| // to appear on receiver. |
| pending().await |
| } |
| .boxed() |
| }) |
| .await |
| .unwrap(); |
| |
| let runner_and_handles = build_and_start(runner_builder, component_name).await; |
| runner_and_handles.controller_proxy.stop().expect("failed to send stop"); |
| |
| assert_eq!(Err(oneshot::Canceled), receiver.await); |
| } |
| |
| #[fuchsia::test] |
| async fn controller_kill_will_kill_a_component() { |
| let runner_builder = LocalComponentRunnerBuilder::new(); |
| let (sender, receiver) = oneshot::channel::<()>(); |
| let sender = Arc::new(Mutex::new(Some(sender))); |
| |
| let component_name = "test".to_string(); |
| |
| runner_builder |
| .register_local_component(component_name.clone(), move |_handles| { |
| let sender = sender.clone(); |
| async move { |
| let _sender = |
| sender.lock().await.take().expect("local component invoked twice"); |
| // Don't use sender, we want to detect when it gets dropped, which causes an error |
| // to appear on receiver. |
| pending().await |
| } |
| .boxed() |
| }) |
| .await |
| .unwrap(); |
| |
| let runner_and_handles = build_and_start(runner_builder, component_name).await; |
| runner_and_handles.controller_proxy.kill().expect("failed to send stop"); |
| |
| assert_eq!(Err(oneshot::Canceled), receiver.await); |
| } |
| |
| #[fuchsia::test] |
| async fn stopping_a_component_calls_the_notifier() { |
| let runner_builder = LocalComponentRunnerBuilder::new(); |
| let (notifier_registered_sender, notifier_registered_receiver) = oneshot::channel::<()>(); |
| let notifier_registered_sender = Arc::new(Mutex::new(Some(notifier_registered_sender))); |
| |
| let (notifier_fired_sender, notifier_fired_receiver) = oneshot::channel::<()>(); |
| let notifier_fired_sender = Arc::new(Mutex::new(Some(notifier_fired_sender))); |
| |
| let component_name = "test".to_string(); |
| |
| runner_builder |
| .register_local_component(component_name.clone(), move |handles| { |
| let notifier_registered_sender = notifier_registered_sender.clone(); |
| let notifier_fired_sender = notifier_fired_sender.clone(); |
| async move { |
| let stop_notifier = handles.register_stop_notifier().await; |
| |
| let sender = notifier_registered_sender |
| .lock() |
| .await |
| .take() |
| .expect("local component invoked twice"); |
| sender.send(()).expect("failed to send that the stop notifier was registered"); |
| |
| stop_notifier.await.expect("failed to wait for stop notification"); |
| |
| let sender = notifier_fired_sender |
| .lock() |
| .await |
| .take() |
| .expect("local component invoked twice"); |
| sender |
| .send(()) |
| .expect("failed to send that the stop notifier received a message"); |
| |
| Ok(()) |
| } |
| .boxed() |
| }) |
| .await |
| .unwrap(); |
| |
| let runner_and_handles = build_and_start(runner_builder, component_name).await; |
| |
| // Wait for the component to have started and registered a stop notifier |
| assert_matches!(notifier_registered_receiver.await, Ok(())); |
| |
| // Ask to stop the component |
| runner_and_handles.controller_proxy.stop().expect("failed to send stop"); |
| |
| // Wait for the component to have received the stop message |
| assert_matches!(notifier_fired_receiver.await, Ok(())); |
| } |
| |
| #[fuchsia::test] |
| async fn dropping_the_runner_will_kill_a_component() { |
| let runner_builder = LocalComponentRunnerBuilder::new(); |
| let (sender, receiver) = oneshot::channel::<()>(); |
| let sender = Arc::new(Mutex::new(Some(sender))); |
| |
| let component_name = "test".to_string(); |
| |
| runner_builder |
| .register_local_component(component_name.clone(), move |_handles| { |
| let sender = sender.clone(); |
| async move { |
| let _sender = |
| sender.lock().await.take().expect("local component invoked twice"); |
| // Don't use sender, we want to detect when it gets dropped, which causes an error |
| // to appear on receiver. |
| pending().await |
| } |
| .boxed() |
| }) |
| .await |
| .unwrap(); |
| |
| let runner_and_handles = build_and_start(runner_builder, component_name).await; |
| drop(runner_and_handles); |
| |
| assert_eq!(Err(oneshot::Canceled), receiver.await); |
| } |
| } |