| // 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.cti |
| |
| use { |
| crate::{ |
| capability::{CapabilityProvider, FrameworkCapability, InternalCapabilityProvider}, |
| model::{ |
| component::{ComponentInstance, WeakComponentInstance}, |
| error::OpenExposedDirError, |
| model::Model, |
| }, |
| }, |
| ::routing::capability_source::InternalCapability, |
| anyhow::Error, |
| async_trait::async_trait, |
| cm_config::RuntimeConfig, |
| cm_rust::FidlIntoNative, |
| cm_types::{Name, OPEN_FLAGS_MAX_POSSIBLE_RIGHTS}, |
| fidl::endpoints::ServerEnd, |
| fidl_fuchsia_component as fcomponent, fidl_fuchsia_component_decl as fdecl, |
| fidl_fuchsia_io as fio, fuchsia_async as fasync, fuchsia_zircon as zx, |
| futures::prelude::*, |
| lazy_static::lazy_static, |
| moniker::{ChildName, ChildNameBase, Moniker}, |
| std::{ |
| cmp, |
| sync::{Arc, Weak}, |
| }, |
| tracing::{debug, error, warn}, |
| vfs::{directory::entry::OpenRequest, path::Path, ToObjectRequest}, |
| }; |
| |
| lazy_static! { |
| static ref REALM_SERVICE: Name = "fuchsia.component.Realm".parse().unwrap(); |
| } |
| |
| struct RealmCapabilityProvider { |
| scope_moniker: Moniker, |
| host: Arc<RealmCapabilityHost>, |
| } |
| |
| impl RealmCapabilityProvider { |
| pub fn new(scope_moniker: Moniker, host: Arc<RealmCapabilityHost>) -> Self { |
| Self { scope_moniker, host } |
| } |
| } |
| |
| #[async_trait] |
| impl InternalCapabilityProvider for RealmCapabilityProvider { |
| async fn open_protocol(self: Box<Self>, server_end: zx::Channel) { |
| let server_end = ServerEnd::<fcomponent::RealmMarker>::new(server_end); |
| // We only need to look up the component matching this scope. |
| // These operations should all work, even if the component is not running. |
| if let Some(model) = self.host.model.upgrade() { |
| if let Ok(component) = model.root().find_and_maybe_resolve(&self.scope_moniker).await { |
| let weak = WeakComponentInstance::new(&component); |
| drop(component); |
| let serve_result = self.host.serve(weak, server_end.into_stream().unwrap()).await; |
| if let Err(error) = serve_result { |
| // TODO: Set an epitaph to indicate this was an unexpected error. |
| warn!(%error, "serve failed"); |
| } |
| } |
| } |
| } |
| } |
| |
| pub struct RealmFrameworkCapability { |
| host: Arc<RealmCapabilityHost>, |
| } |
| |
| pub struct RealmCapabilityHost { |
| model: Weak<Model>, |
| config: Arc<RuntimeConfig>, |
| } |
| |
| impl RealmFrameworkCapability { |
| pub fn new(model: Weak<Model>, config: Arc<RuntimeConfig>) -> Self { |
| Self { host: Arc::new(RealmCapabilityHost::new(model, config)) } |
| } |
| } |
| |
| impl FrameworkCapability for RealmFrameworkCapability { |
| fn matches(&self, capability: &InternalCapability) -> bool { |
| capability.matches_protocol(&REALM_SERVICE) |
| } |
| |
| fn new_provider( |
| &self, |
| scope: WeakComponentInstance, |
| _target: WeakComponentInstance, |
| ) -> Box<dyn CapabilityProvider> { |
| Box::new(RealmCapabilityProvider::new(scope.moniker.clone(), self.host.clone())) |
| } |
| } |
| |
| // `RealmCapabilityHost` is a `Hook` that serves the `Realm` FIDL protocol. |
| impl RealmCapabilityHost { |
| fn new(model: Weak<Model>, config: Arc<RuntimeConfig>) -> Self { |
| Self { model, config } |
| } |
| |
| #[cfg(test)] |
| pub fn new_for_test(model: Weak<Model>, config: Arc<RuntimeConfig>) -> Self { |
| Self::new(model, config) |
| } |
| |
| pub async fn serve( |
| &self, |
| component: WeakComponentInstance, |
| mut stream: fcomponent::RealmRequestStream, |
| ) -> Result<(), fidl::Error> { |
| while let Some(request) = stream.try_next().await? { |
| let method_name = request.method_name(); |
| let result = self.handle_request(request, &component).await; |
| match result { |
| // If the error was PEER_CLOSED then we don't need to log it as a client can |
| // disconnect while we are processing its request. |
| Err(error) if !error.is_closed() => { |
| warn!(%method_name, %error, "Couldn't send Realm response"); |
| } |
| _ => {} |
| } |
| } |
| Ok(()) |
| } |
| |
| async fn handle_request( |
| &self, |
| request: fcomponent::RealmRequest, |
| component: &WeakComponentInstance, |
| ) -> Result<(), fidl::Error> { |
| match request { |
| fcomponent::RealmRequest::CreateChild { responder, collection, decl, args } => { |
| let res = |
| async { Self::create_child(component, collection, decl, args).await }.await; |
| responder.send(res)?; |
| } |
| fcomponent::RealmRequest::DestroyChild { responder, child } => { |
| let res = Self::destroy_child(component, child).await; |
| responder.send(res)?; |
| } |
| fcomponent::RealmRequest::ListChildren { responder, collection, iter } => { |
| let res = Self::list_children( |
| component, |
| self.config.list_children_batch_size, |
| collection, |
| iter, |
| ) |
| .await; |
| responder.send(res)?; |
| } |
| fcomponent::RealmRequest::OpenExposedDir { responder, child, exposed_dir } => { |
| let res = Self::open_exposed_dir(component, child, exposed_dir).await; |
| responder.send(res)?; |
| } |
| } |
| Ok(()) |
| } |
| |
| pub async fn create_child( |
| component: &WeakComponentInstance, |
| collection: fdecl::CollectionRef, |
| child_decl: fdecl::Child, |
| child_args: fcomponent::CreateChildArgs, |
| ) -> Result<(), fcomponent::Error> { |
| let component = component.upgrade().map_err(|_| fcomponent::Error::InstanceDied)?; |
| |
| cm_fidl_validator::validate_dynamic_child(&child_decl).map_err(|error| { |
| warn!(%error, "failed to create dynamic child. child decl is invalid"); |
| fcomponent::Error::InvalidArguments |
| })?; |
| let child_decl = child_decl.fidl_into_native(); |
| |
| component.add_dynamic_child(collection.name.clone(), &child_decl, child_args).await.map_err( |
| |err| { |
| warn!( |
| "Failed to create child \"{}\" in collection \"{}\" of component \"{}\": {}", |
| child_decl.name, collection.name, component.moniker, err |
| ); |
| err.into() |
| }, |
| ) |
| } |
| |
| async fn open_exposed_dir( |
| component: &WeakComponentInstance, |
| child: fdecl::ChildRef, |
| exposed_dir: ServerEnd<fio::DirectoryMarker>, |
| ) -> Result<(), fcomponent::Error> { |
| match Self::get_child(component, child.clone()).await? { |
| Some(child) => { |
| // Resolve child in order to instantiate exposed_dir. |
| child.resolve().await.map_err(|e| { |
| warn!( |
| "resolve failed for child {:?} of component {}: {}", |
| child, component.moniker, e |
| ); |
| return fcomponent::Error::InstanceCannotResolve; |
| })?; |
| // TODO(https://fxbug.dev/42161419): open_exposed does not have a rights input |
| // parameter, so this makes use of the POSIX_[WRITABLE|EXECUTABLE] flags to open a |
| // connection with those rights if available from the parent directory connection |
| // but without failing if not available. |
| let flags = OPEN_FLAGS_MAX_POSSIBLE_RIGHTS | fio::OpenFlags::DIRECTORY; |
| let mut object_request = flags.to_object_request(exposed_dir); |
| child |
| .open_exposed(OpenRequest::new( |
| child.execution_scope.clone(), |
| flags, |
| Path::dot(), |
| &mut object_request, |
| )) |
| .await |
| .map_err(|error| match error { |
| OpenExposedDirError::InstanceDestroyed |
| | OpenExposedDirError::InstanceNotResolved => { |
| fcomponent::Error::InstanceDied |
| } |
| OpenExposedDirError::Open(_) => fcomponent::Error::Internal, |
| })?; |
| } |
| None => { |
| debug!(?child, "open_exposed_dir() failed: instance not found"); |
| return Err(fcomponent::Error::InstanceNotFound); |
| } |
| } |
| Ok(()) |
| } |
| |
| pub async fn destroy_child( |
| component: &WeakComponentInstance, |
| child: fdecl::ChildRef, |
| ) -> Result<(), fcomponent::Error> { |
| let component = component.upgrade().map_err(|_| fcomponent::Error::InstanceDied)?; |
| child.collection.as_ref().ok_or(fcomponent::Error::InvalidArguments)?; |
| let child_moniker = ChildName::try_new(&child.name, child.collection.as_ref()) |
| .map_err(|_| fcomponent::Error::InvalidArguments)?; |
| component.remove_dynamic_child(&child_moniker).await.map_err(|error| { |
| debug!(%error, ?child, "remove_dynamic_child() failed"); |
| error |
| })?; |
| Ok(()) |
| } |
| |
| async fn get_child( |
| parent: &WeakComponentInstance, |
| child: fdecl::ChildRef, |
| ) -> Result<Option<Arc<ComponentInstance>>, fcomponent::Error> { |
| let parent = parent.upgrade().map_err(|_| fcomponent::Error::InstanceDied)?; |
| let state = parent.lock_resolved_state().await.map_err(|error| { |
| debug!(%error, moniker=%parent.moniker, "failed to resolve instance"); |
| fcomponent::Error::InstanceCannotResolve |
| })?; |
| let child_moniker = ChildName::try_new(&child.name, child.collection.as_ref()) |
| .map_err(|_| fcomponent::Error::InvalidArguments)?; |
| Ok(state.get_child(&child_moniker).map(|r| r.clone())) |
| } |
| |
| async fn list_children( |
| component: &WeakComponentInstance, |
| batch_size: usize, |
| collection: fdecl::CollectionRef, |
| iter: ServerEnd<fcomponent::ChildIteratorMarker>, |
| ) -> Result<(), fcomponent::Error> { |
| let component = component.upgrade().map_err(|_| fcomponent::Error::InstanceDied)?; |
| let state = component.lock_resolved_state().await.map_err(|error| { |
| error!(%error, "failed to resolve InstanceState"); |
| fcomponent::Error::Internal |
| })?; |
| let decl = state.decl(); |
| decl.find_collection(&collection.name).ok_or(fcomponent::Error::CollectionNotFound)?; |
| let mut children: Vec<_> = state |
| .children() |
| .filter_map(|(m, _)| match m.collection() { |
| Some(c) => { |
| if c.as_str() == &collection.name { |
| Some(fdecl::ChildRef { |
| name: m.name().to_string(), |
| collection: m.collection().map(|s| s.to_string()), |
| }) |
| } else { |
| None |
| } |
| } |
| _ => None, |
| }) |
| .collect(); |
| children.sort_unstable_by(|a, b| { |
| let a = &a.name; |
| let b = &b.name; |
| if a == b { |
| cmp::Ordering::Equal |
| } else if a < b { |
| cmp::Ordering::Less |
| } else { |
| cmp::Ordering::Greater |
| } |
| }); |
| let stream = iter.into_stream().map_err(|_| fcomponent::Error::AccessDenied)?; |
| fasync::Task::spawn(async move { |
| if let Err(error) = Self::serve_child_iterator(children, stream, batch_size).await { |
| // TODO: Set an epitaph to indicate this was an unexpected error. |
| warn!(%error, "serve_child_iterator failed"); |
| } |
| }) |
| .detach(); |
| Ok(()) |
| } |
| |
| async fn serve_child_iterator( |
| children: Vec<fdecl::ChildRef>, |
| mut stream: fcomponent::ChildIteratorRequestStream, |
| batch_size: usize, |
| ) -> Result<(), Error> { |
| let mut iter = children.chunks(batch_size); |
| while let Some(request) = stream.try_next().await? { |
| match request { |
| fcomponent::ChildIteratorRequest::Next { responder } => { |
| responder.send(iter.next().unwrap_or(&[]))?; |
| } |
| } |
| } |
| Ok(()) |
| } |
| } |
| |
| #[cfg(test)] |
| mod tests { |
| use { |
| super::*, |
| crate::{ |
| builtin_environment::BuiltinEnvironment, |
| model::{ |
| component::StartReason, |
| events::{source::EventSource, stream::EventStream}, |
| hooks::EventType, |
| testing::{mocks::*, out_dir::OutDir, test_helpers::*, test_hook::*}, |
| }, |
| }, |
| assert_matches::assert_matches, |
| cm_rust::{ComponentDecl, ExposeSource}, |
| cm_rust_testing::*, |
| fidl::endpoints, |
| fidl_fidl_examples_routing_echo as echo, fidl_fuchsia_component as fcomponent, |
| fidl_fuchsia_component_decl as fdecl, fidl_fuchsia_io as fio, fuchsia_async as fasync, |
| fuchsia_component::client, |
| futures::lock::Mutex, |
| moniker::MonikerBase, |
| routing_test_helpers::component_decl_with_exposed_binder, |
| std::collections::HashSet, |
| }; |
| |
| struct RealmCapabilityTest { |
| builtin_environment: Option<Arc<Mutex<BuiltinEnvironment>>>, |
| mock_runner: Arc<MockRunner>, |
| component: Option<Arc<ComponentInstance>>, |
| realm_proxy: fcomponent::RealmProxy, |
| hook: Arc<TestHook>, |
| } |
| |
| impl RealmCapabilityTest { |
| async fn new( |
| components: Vec<(&'static str, ComponentDecl)>, |
| component_moniker: Moniker, |
| ) -> Self { |
| // Init model. |
| let config = RuntimeConfig { list_children_batch_size: 2, ..Default::default() }; |
| let hook = Arc::new(TestHook::new()); |
| let TestModelResult { model, builtin_environment, mock_runner, .. } = |
| TestEnvironmentBuilder::new() |
| .set_runtime_config(config) |
| .set_components(components) |
| // Install TestHook at the front so that when we receive an event the hook has |
| // already run so the result is reflected in its printout |
| .set_front_hooks(hook.hooks()) |
| .build() |
| .await; |
| |
| // Look up and start component. |
| let component = model |
| .root() |
| .start_instance(&component_moniker, &StartReason::Eager) |
| .await |
| .expect("failed to start component"); |
| |
| // Host framework service. |
| let (realm_proxy, stream) = |
| endpoints::create_proxy_and_stream::<fcomponent::RealmMarker>().unwrap(); |
| { |
| let component = WeakComponentInstance::from(&component); |
| let realm_capability_host = RealmCapabilityHost::new( |
| Arc::downgrade(&model), |
| model.context().runtime_config().clone(), |
| ); |
| fasync::Task::spawn(async move { |
| realm_capability_host |
| .serve(component, stream) |
| .await |
| .expect("failed serving realm service"); |
| }) |
| .detach(); |
| } |
| Self { |
| builtin_environment: Some(builtin_environment), |
| mock_runner, |
| component: Some(component), |
| realm_proxy, |
| hook, |
| } |
| } |
| |
| fn component(&self) -> &Arc<ComponentInstance> { |
| self.component.as_ref().unwrap() |
| } |
| |
| fn drop_component(&mut self) { |
| self.component = None; |
| self.builtin_environment = None; |
| } |
| |
| async fn new_event_stream(&self, events: Vec<Name>) -> (EventSource, EventStream) { |
| new_event_stream( |
| self.builtin_environment.as_ref().expect("builtin_environment is none").clone(), |
| events, |
| ) |
| .await |
| } |
| } |
| |
| fn child_decl(name: &str) -> fdecl::Child { |
| fdecl::Child { |
| name: Some(name.to_owned()), |
| url: Some(format!("test:///{}", name)), |
| startup: Some(fdecl::StartupMode::Lazy), |
| environment: None, |
| on_terminate: None, |
| ..Default::default() |
| } |
| } |
| |
| #[fuchsia::test] |
| async fn create_dynamic_child() { |
| // Set up model and realm service. |
| let test = RealmCapabilityTest::new( |
| vec![ |
| ("root", ComponentDeclBuilder::new().child_default("system").build()), |
| ( |
| "system", |
| ComponentDeclBuilder::new() |
| .collection(CollectionBuilder::new().name("coll").allow_long_names()) |
| .build(), |
| ), |
| // Eagerly launched so it needs a definition |
| ("b", ComponentDeclBuilder::new().build()), |
| ], |
| vec!["system"].try_into().unwrap(), |
| ) |
| .await; |
| |
| let (_event_source, mut event_stream) = test |
| .new_event_stream(vec![EventType::Discovered.into(), EventType::Started.into()]) |
| .await; |
| |
| // Test that a dynamic child with a long name can also be created. |
| let long_name = &"c".repeat(cm_types::MAX_LONG_NAME_LENGTH); |
| |
| // Create children "a", "b", and "<long_name>" in collection. Expect a Discovered event for |
| // each. |
| let collection_ref = fdecl::CollectionRef { name: "coll".to_string() }; |
| { |
| // Create a child |
| test.realm_proxy |
| .create_child( |
| &collection_ref, |
| &child_decl("a"), |
| fcomponent::CreateChildArgs::default(), |
| ) |
| .await |
| .unwrap() |
| .unwrap(); |
| |
| // Ensure that an event exists for the new child |
| event_stream |
| .wait_until(EventType::Discovered, "system/coll:a".try_into().unwrap()) |
| .await |
| .unwrap(); |
| } |
| { |
| // Create a child (eager) |
| let mut child_decl = child_decl("b"); |
| child_decl.startup = Some(fdecl::StartupMode::Eager); |
| test.realm_proxy |
| .create_child(&collection_ref, &child_decl, fcomponent::CreateChildArgs::default()) |
| .await |
| .unwrap() |
| .unwrap(); |
| |
| // Ensure that an event exists for the new child |
| event_stream |
| .wait_until(EventType::Discovered, "system/coll:b".try_into().unwrap()) |
| .await |
| .unwrap(); |
| event_stream |
| .wait_until(EventType::Started, "system/coll:b".try_into().unwrap()) |
| .await |
| .unwrap(); |
| } |
| { |
| // Create a child (long name) |
| test.realm_proxy |
| .create_child( |
| &collection_ref, |
| &child_decl(long_name), |
| fcomponent::CreateChildArgs::default(), |
| ) |
| .await |
| .unwrap() |
| .unwrap(); |
| |
| // Ensure that an event exists for the new child |
| event_stream |
| .wait_until( |
| EventType::Discovered, |
| format!("system/coll:{long_name}").as_str().try_into().unwrap(), |
| ) |
| .await |
| .unwrap(); |
| } |
| |
| // Verify that the component topology matches expectations. |
| let actual_children = get_live_children(test.component()).await; |
| let mut expected_children: HashSet<ChildName> = HashSet::new(); |
| expected_children.insert("coll:a".try_into().unwrap()); |
| expected_children.insert("coll:b".try_into().unwrap()); |
| expected_children.insert(format!("coll:{}", long_name).as_str().try_into().unwrap()); |
| assert_eq!(actual_children, expected_children); |
| assert_eq!(format!("(system(coll:a,coll:b,coll:{}))", long_name), test.hook.print()); |
| } |
| |
| #[fuchsia::test] |
| async fn create_dynamic_child_errors() { |
| let mut test = RealmCapabilityTest::new( |
| vec![ |
| ("root", ComponentDeclBuilder::new().child_default("system").build()), |
| ( |
| "system", |
| ComponentDeclBuilder::new() |
| .collection_default("coll") |
| .collection( |
| CollectionBuilder::new() |
| .name("pcoll") |
| .durability(fdecl::Durability::Transient) |
| .allow_long_names(), |
| ) |
| .collection( |
| CollectionBuilder::new() |
| .name("dynoff") |
| .allowed_offers(cm_types::AllowedOffers::StaticAndDynamic), |
| ) |
| .build(), |
| ), |
| ], |
| vec!["system"].try_into().unwrap(), |
| ) |
| .await; |
| |
| // Invalid arguments. |
| { |
| let collection_ref = fdecl::CollectionRef { name: "coll".to_string() }; |
| let child_decl = fdecl::Child { |
| name: Some("a".to_string()), |
| url: None, |
| startup: Some(fdecl::StartupMode::Lazy), |
| environment: None, |
| ..Default::default() |
| }; |
| let err = test |
| .realm_proxy |
| .create_child(&collection_ref, &child_decl, fcomponent::CreateChildArgs::default()) |
| .await |
| .expect("fidl call failed") |
| .expect_err("unexpected success"); |
| assert_eq!(err, fcomponent::Error::InvalidArguments); |
| } |
| { |
| let collection_ref = fdecl::CollectionRef { name: "coll".to_string() }; |
| let child_decl = fdecl::Child { |
| name: Some("a".to_string()), |
| url: Some("test:///a".to_string()), |
| startup: Some(fdecl::StartupMode::Lazy), |
| environment: Some("env".to_string()), |
| ..Default::default() |
| }; |
| let err = test |
| .realm_proxy |
| .create_child(&collection_ref, &child_decl, fcomponent::CreateChildArgs::default()) |
| .await |
| .expect("fidl call failed") |
| .expect_err("unexpected success"); |
| assert_eq!(err, fcomponent::Error::InvalidArguments); |
| } |
| |
| // Long dynamic child name violations. |
| { |
| // Name exceeds MAX_NAME_LENGTH when `allow_long_names` is not set. |
| // The FIDL call succeeds but the server responds with an error. |
| let collection_ref = fdecl::CollectionRef { name: "coll".to_string() }; |
| let child_decl = fdecl::Child { |
| name: Some("a".repeat(cm_types::MAX_NAME_LENGTH + 1).to_string()), |
| url: None, |
| startup: Some(fdecl::StartupMode::Lazy), |
| environment: None, |
| ..Default::default() |
| }; |
| let err = test |
| .realm_proxy |
| .create_child(&collection_ref, &child_decl, fcomponent::CreateChildArgs::default()) |
| .await |
| .expect("fidl call failed") |
| .expect_err("unexpected success"); |
| assert_eq!(err, fcomponent::Error::InvalidArguments); |
| |
| // Name length exceeds the MAX_LONG_NAME_LENGTH when `allow_long_names` is set. |
| // In this case the FIDL call fails to encode because the name field |
| // is defined in the FIDL library as `string:MAX_LONG_NAME_LENGTH`. |
| let collection_ref = fdecl::CollectionRef { name: "pcoll".to_string() }; |
| let child_decl = fdecl::Child { |
| name: Some("a".repeat(cm_types::MAX_LONG_NAME_LENGTH + 1).to_string()), |
| url: None, |
| startup: Some(fdecl::StartupMode::Lazy), |
| environment: None, |
| ..Default::default() |
| }; |
| let err = test |
| .realm_proxy |
| .create_child(&collection_ref, &child_decl, fcomponent::CreateChildArgs::default()) |
| .await |
| .expect_err("unexpected success"); |
| // When exceeding the long max name length, the FIDL call itself |
| // fails because the name field is defined as `string:1024`. |
| assert_matches!(err, fidl::Error::StringTooLong { .. }); |
| } |
| |
| // Instance already exists. |
| { |
| let collection_ref = fdecl::CollectionRef { name: "coll".to_string() }; |
| let res = test |
| .realm_proxy |
| .create_child( |
| &collection_ref, |
| &child_decl("a"), |
| fcomponent::CreateChildArgs::default(), |
| ) |
| .await; |
| res.expect("fidl call failed").expect("failed to create child a"); |
| let collection_ref = fdecl::CollectionRef { name: "coll".to_string() }; |
| let err = test |
| .realm_proxy |
| .create_child( |
| &collection_ref, |
| &child_decl("a"), |
| fcomponent::CreateChildArgs::default(), |
| ) |
| .await |
| .expect("fidl call failed") |
| .expect_err("unexpected success"); |
| assert_eq!(err, fcomponent::Error::InstanceAlreadyExists); |
| } |
| |
| // Collection not found. |
| { |
| let collection_ref = fdecl::CollectionRef { name: "nonexistent".to_string() }; |
| let err = test |
| .realm_proxy |
| .create_child( |
| &collection_ref, |
| &child_decl("a"), |
| fcomponent::CreateChildArgs::default(), |
| ) |
| .await |
| .expect("fidl call failed") |
| .expect_err("unexpected success"); |
| assert_eq!(err, fcomponent::Error::CollectionNotFound); |
| } |
| |
| fn sample_offer_from(source: fdecl::Ref) -> fdecl::Offer { |
| fdecl::Offer::Protocol(fdecl::OfferProtocol { |
| source: Some(source), |
| source_name: Some("foo".to_string()), |
| target_name: Some("foo".to_string()), |
| dependency_type: Some(fdecl::DependencyType::Strong), |
| ..Default::default() |
| }) |
| } |
| |
| // Disallowed dynamic offers specified. |
| { |
| let collection_ref = fdecl::CollectionRef { name: "coll".to_string() }; |
| let child_decl = fdecl::Child { |
| name: Some("b".to_string()), |
| url: Some("test:///b".to_string()), |
| startup: Some(fdecl::StartupMode::Lazy), |
| environment: None, |
| ..Default::default() |
| }; |
| let err = test |
| .realm_proxy |
| .create_child( |
| &collection_ref, |
| &child_decl, |
| fcomponent::CreateChildArgs { |
| dynamic_offers: Some(vec![sample_offer_from(fdecl::Ref::Parent( |
| fdecl::ParentRef {}, |
| ))]), |
| ..Default::default() |
| }, |
| ) |
| .await |
| .expect("fidl call failed") |
| .expect_err("unexpected success"); |
| assert_eq!(err, fcomponent::Error::InvalidArguments); |
| } |
| |
| // Malformed dynamic offers specified. |
| { |
| let collection_ref = fdecl::CollectionRef { name: "dynoff".to_string() }; |
| let child_decl = fdecl::Child { |
| name: Some("b".to_string()), |
| url: Some("test:///b".to_string()), |
| startup: Some(fdecl::StartupMode::Lazy), |
| environment: None, |
| ..Default::default() |
| }; |
| let err = test |
| .realm_proxy |
| .create_child( |
| &collection_ref, |
| &child_decl, |
| fcomponent::CreateChildArgs { |
| dynamic_offers: Some(vec![fdecl::Offer::Protocol(fdecl::OfferProtocol { |
| source: Some(fdecl::Ref::Parent(fdecl::ParentRef {})), |
| source_name: Some("foo".to_string()), |
| target_name: Some("foo".to_string()), |
| // Note: has no `dependency_type`. |
| ..Default::default() |
| })]), |
| ..Default::default() |
| }, |
| ) |
| .await |
| .expect("fidl call failed") |
| .expect_err("unexpected success"); |
| assert_eq!(err, fcomponent::Error::InvalidArguments); |
| } |
| |
| // Dynamic offer source is a static component that doesn't exist. |
| { |
| let collection_ref = fdecl::CollectionRef { name: "dynoff".to_string() }; |
| let child_decl = fdecl::Child { |
| name: Some("b".to_string()), |
| url: Some("test:///b".to_string()), |
| startup: Some(fdecl::StartupMode::Lazy), |
| environment: None, |
| ..Default::default() |
| }; |
| let err = test |
| .realm_proxy |
| .create_child( |
| &collection_ref, |
| &child_decl, |
| fcomponent::CreateChildArgs { |
| dynamic_offers: Some(vec![sample_offer_from(fdecl::Ref::Child( |
| fdecl::ChildRef { |
| name: "does_not_exist".to_string(), |
| collection: None, |
| }, |
| ))]), |
| ..Default::default() |
| }, |
| ) |
| .await |
| .expect("fidl call failed") |
| .expect_err("unexpected success"); |
| assert_eq!(err, fcomponent::Error::InvalidArguments); |
| } |
| |
| // Source is a collection that doesn't exist (and using a Service). |
| { |
| let collection_ref = fdecl::CollectionRef { name: "dynoff".to_string() }; |
| let child_decl = fdecl::Child { |
| name: Some("b".to_string()), |
| url: Some("test:///b".to_string()), |
| startup: Some(fdecl::StartupMode::Lazy), |
| environment: None, |
| ..Default::default() |
| }; |
| let err = test |
| .realm_proxy |
| .create_child( |
| &collection_ref, |
| &child_decl, |
| fcomponent::CreateChildArgs { |
| dynamic_offers: Some(vec![fdecl::Offer::Service(fdecl::OfferService { |
| source: Some(fdecl::Ref::Collection(fdecl::CollectionRef { |
| name: "does_not_exist".to_string(), |
| })), |
| source_name: Some("foo".to_string()), |
| target_name: Some("foo".to_string()), |
| ..Default::default() |
| })]), |
| ..Default::default() |
| }, |
| ) |
| .await |
| .expect("fidl call failed") |
| .expect_err("unexpected success"); |
| assert_eq!(err, fcomponent::Error::InvalidArguments); |
| } |
| |
| // Source is a component in the same collection that doesn't exist. |
| { |
| let collection_ref = fdecl::CollectionRef { name: "dynoff".to_string() }; |
| let child_decl = fdecl::Child { |
| name: Some("b".to_string()), |
| url: Some("test:///b".to_string()), |
| startup: Some(fdecl::StartupMode::Lazy), |
| environment: None, |
| ..Default::default() |
| }; |
| let err = test |
| .realm_proxy |
| .create_child( |
| &collection_ref, |
| &child_decl, |
| fcomponent::CreateChildArgs { |
| dynamic_offers: Some(vec![sample_offer_from(fdecl::Ref::Child( |
| fdecl::ChildRef { |
| name: "does_not_exist".to_string(), |
| collection: Some("dynoff".to_string()), |
| }, |
| ))]), |
| ..Default::default() |
| }, |
| ) |
| .await |
| .expect("fidl call failed") |
| .expect_err("unexpected success"); |
| assert_eq!(err, fcomponent::Error::InvalidArguments); |
| } |
| |
| // Source is the component itself... which doesn't exist... yet. |
| { |
| let collection_ref = fdecl::CollectionRef { name: "dynoff".to_string() }; |
| let child_decl = fdecl::Child { |
| name: Some("b".to_string()), |
| url: Some("test:///b".to_string()), |
| startup: Some(fdecl::StartupMode::Lazy), |
| environment: None, |
| ..Default::default() |
| }; |
| let err = test |
| .realm_proxy |
| .create_child( |
| &collection_ref, |
| &child_decl, |
| fcomponent::CreateChildArgs { |
| dynamic_offers: Some(vec![sample_offer_from(fdecl::Ref::Child( |
| fdecl::ChildRef { |
| name: "b".to_string(), |
| collection: Some("dynoff".to_string()), |
| }, |
| ))]), |
| ..Default::default() |
| }, |
| ) |
| .await |
| .expect("fidl call failed") |
| .expect_err("unexpected success"); |
| assert_eq!(err, fcomponent::Error::InvalidArguments); |
| } |
| |
| // Instance died. |
| { |
| test.drop_component(); |
| let collection_ref = fdecl::CollectionRef { name: "coll".to_string() }; |
| let child_decl = fdecl::Child { |
| name: Some("b".to_string()), |
| url: Some("test:///b".to_string()), |
| startup: Some(fdecl::StartupMode::Lazy), |
| environment: None, |
| ..Default::default() |
| }; |
| let err = test |
| .realm_proxy |
| .create_child(&collection_ref, &child_decl, fcomponent::CreateChildArgs::default()) |
| .await |
| .expect("fidl call failed") |
| .expect_err("unexpected success"); |
| assert_eq!(err, fcomponent::Error::InstanceDied); |
| } |
| } |
| |
| #[fuchsia::test] |
| async fn destroy_dynamic_child() { |
| // Set up model and realm service. |
| let test = RealmCapabilityTest::new( |
| vec![ |
| ("root", ComponentDeclBuilder::new().child_default("system").build()), |
| ("system", ComponentDeclBuilder::new().collection_default("coll").build()), |
| ("a", component_decl_with_exposed_binder()), |
| ("b", component_decl_with_exposed_binder()), |
| ], |
| vec!["system"].try_into().unwrap(), |
| ) |
| .await; |
| |
| let (_event_source, mut event_stream) = test |
| .new_event_stream(vec![EventType::Stopped.into(), EventType::Destroyed.into()]) |
| .await; |
| |
| // Create children "a" and "b" in collection, and start them. |
| for name in &["a", "b"] { |
| let collection_ref = fdecl::CollectionRef { name: "coll".to_string() }; |
| let res = test |
| .realm_proxy |
| .create_child( |
| &collection_ref, |
| &child_decl(name), |
| fcomponent::CreateChildArgs::default(), |
| ) |
| .await; |
| res.expect("fidl call failed") |
| .unwrap_or_else(|_| panic!("failed to create child {}", name)); |
| let child_ref = |
| fdecl::ChildRef { name: name.to_string(), collection: Some("coll".to_string()) }; |
| let (exposed_dir, server_end) = |
| endpoints::create_proxy::<fio::DirectoryMarker>().unwrap(); |
| let () = test |
| .realm_proxy |
| .open_exposed_dir(&child_ref, server_end) |
| .await |
| .expect("OpenExposedDir FIDL") |
| .expect("OpenExposedDir Error"); |
| let _: fcomponent::BinderProxy = |
| client::connect_to_protocol_at_dir_root::<fcomponent::BinderMarker>(&exposed_dir) |
| .expect("Connection to fuchsia.component.Binder"); |
| } |
| |
| let child = get_live_child(test.component(), "coll:a").await; |
| let instance_id = get_incarnation_id(test.component(), "coll:a").await; |
| assert_eq!("(system(coll:a,coll:b))", test.hook.print()); |
| assert_eq!(child.component_url, "test:///a".to_string()); |
| assert_eq!(instance_id, 1); |
| |
| // Destroy "a". "a" is no longer live from the client's perspective, although it's still |
| // being destroyed. |
| let child_ref = |
| fdecl::ChildRef { name: "a".to_string(), collection: Some("coll".to_string()) }; |
| let (f, destroy_handle) = test.realm_proxy.destroy_child(&child_ref).remote_handle(); |
| fasync::Task::spawn(f).detach(); |
| |
| // The component should be stopped (shut down) before it is destroyed. |
| event_stream |
| .wait_until(EventType::Stopped, vec!["system", "coll:a"].try_into().unwrap()) |
| .await |
| .unwrap(); |
| event_stream |
| .wait_until(EventType::Destroyed, vec!["system", "coll:a"].try_into().unwrap()) |
| .await |
| .unwrap(); |
| |
| // "a" is fully deleted now. |
| assert!(!has_child(test.component(), "coll:a").await); |
| { |
| let actual_children = get_live_children(test.component()).await; |
| let mut expected_children: HashSet<ChildName> = HashSet::new(); |
| expected_children.insert("coll:b".try_into().unwrap()); |
| let child_b = get_live_child(test.component(), "coll:b").await; |
| assert!(!execution_is_shut_down(&child_b).await); |
| assert_eq!(actual_children, expected_children); |
| assert_eq!("(system(coll:b))", test.hook.print()); |
| } |
| |
| let res = destroy_handle.await; |
| res.expect("fidl call failed").expect("failed to destroy child a"); |
| |
| // Recreate "a" and verify "a" is back (but it's a different "a"). The old "a" is gone |
| // from the client's point of view, but it hasn't been cleaned up yet. |
| let collection_ref = fdecl::CollectionRef { name: "coll".to_string() }; |
| let child_decl = fdecl::Child { |
| name: Some("a".to_string()), |
| url: Some("test:///a_alt".to_string()), |
| startup: Some(fdecl::StartupMode::Lazy), |
| environment: None, |
| ..Default::default() |
| }; |
| let res = test |
| .realm_proxy |
| .create_child(&collection_ref, &child_decl, fcomponent::CreateChildArgs::default()) |
| .await; |
| res.expect("fidl call failed").expect("failed to recreate child a"); |
| |
| assert_eq!("(system(coll:a,coll:b))", test.hook.print()); |
| let child = get_live_child(test.component(), "coll:a").await; |
| let instance_id = get_incarnation_id(test.component(), "coll:a").await; |
| assert_eq!(child.component_url, "test:///a_alt".to_string()); |
| assert_eq!(instance_id, 3); |
| } |
| |
| #[fuchsia::test] |
| async fn destroy_dynamic_child_errors() { |
| let mut test = RealmCapabilityTest::new( |
| vec![ |
| ("root", ComponentDeclBuilder::new().child_default("system").build()), |
| ("system", ComponentDeclBuilder::new().collection_default("coll").build()), |
| ], |
| vec!["system"].try_into().unwrap(), |
| ) |
| .await; |
| |
| // Create child "a" in collection. |
| let collection_ref = fdecl::CollectionRef { name: "coll".to_string() }; |
| let res = test |
| .realm_proxy |
| .create_child(&collection_ref, &child_decl("a"), fcomponent::CreateChildArgs::default()) |
| .await; |
| res.expect("fidl call failed").expect("failed to create child a"); |
| |
| // Invalid arguments. |
| { |
| let child_ref = fdecl::ChildRef { name: "a".to_string(), collection: None }; |
| let err = test |
| .realm_proxy |
| .destroy_child(&child_ref) |
| .await |
| .expect("fidl call failed") |
| .expect_err("unexpected success"); |
| assert_eq!(err, fcomponent::Error::InvalidArguments); |
| } |
| |
| // Instance not found. |
| { |
| let child_ref = |
| fdecl::ChildRef { name: "b".to_string(), collection: Some("coll".to_string()) }; |
| let err = test |
| .realm_proxy |
| .destroy_child(&child_ref) |
| .await |
| .expect("fidl call failed") |
| .expect_err("unexpected success"); |
| assert_eq!(err, fcomponent::Error::InstanceNotFound); |
| } |
| |
| // Instance died. |
| { |
| test.drop_component(); |
| let child_ref = |
| fdecl::ChildRef { name: "a".to_string(), collection: Some("coll".to_string()) }; |
| let err = test |
| .realm_proxy |
| .destroy_child(&child_ref) |
| .await |
| .expect("fidl call failed") |
| .expect_err("unexpected success"); |
| assert_eq!(err, fcomponent::Error::InstanceDied); |
| } |
| } |
| |
| #[fuchsia::test] |
| async fn dynamic_single_run_child() { |
| // Set up model and realm service. |
| let test = RealmCapabilityTest::new( |
| vec![ |
| ("root", ComponentDeclBuilder::new().child_default("system").build()), |
| ( |
| "system", |
| ComponentDeclBuilder::new() |
| .collection( |
| CollectionBuilder::new() |
| .name("coll") |
| .durability(fdecl::Durability::SingleRun), |
| ) |
| .build(), |
| ), |
| ("a", component_decl_with_test_runner()), |
| ], |
| vec!["system"].try_into().unwrap(), |
| ) |
| .await; |
| |
| let (_event_source, mut event_stream) = test |
| .new_event_stream(vec![EventType::Started.into(), EventType::Destroyed.into()]) |
| .await; |
| |
| // Create child "a" in collection. Expect a Started event. |
| let collection_ref = fdecl::CollectionRef { name: "coll".to_string() }; |
| test.realm_proxy |
| .create_child(&collection_ref, &child_decl("a"), fcomponent::CreateChildArgs::default()) |
| .await |
| .unwrap() |
| .unwrap(); |
| event_stream |
| .wait_until(EventType::Started, vec!["system", "coll:a"].try_into().unwrap()) |
| .await |
| .unwrap(); |
| |
| // Started action completes. |
| |
| let child = { |
| let state = test.component().lock_resolved_state().await.unwrap(); |
| let child = state.children().next().unwrap(); |
| assert_eq!("a", child.0.name().as_str()); |
| child.1.clone() |
| }; |
| |
| // The stop should trigger a delete/purge. |
| child.stop_instance_internal(false).await.unwrap(); |
| |
| event_stream |
| .wait_until(EventType::Destroyed, vec!["system", "coll:a"].try_into().unwrap()) |
| .await |
| .unwrap(); |
| |
| // Verify that the component topology matches expectations. |
| let actual_children = get_live_children(test.component()).await; |
| let expected_children: HashSet<ChildName> = HashSet::new(); |
| assert_eq!(actual_children, expected_children); |
| } |
| |
| #[fuchsia::test] |
| async fn list_children_errors() { |
| // Create a root component with a collection. |
| let mut test = RealmCapabilityTest::new( |
| vec![("root", ComponentDeclBuilder::new().collection_default("coll").build())], |
| Moniker::root(), |
| ) |
| .await; |
| |
| // Collection not found. |
| { |
| let collection_ref = fdecl::CollectionRef { name: "nonexistent".to_string() }; |
| let (_, server_end) = endpoints::create_proxy().unwrap(); |
| let err = test |
| .realm_proxy |
| .list_children(&collection_ref, server_end) |
| .await |
| .expect("fidl call failed") |
| .expect_err("unexpected success"); |
| assert_eq!(err, fcomponent::Error::CollectionNotFound); |
| } |
| |
| // Instance died. |
| { |
| test.drop_component(); |
| let collection_ref = fdecl::CollectionRef { name: "coll".to_string() }; |
| let (_, server_end) = endpoints::create_proxy().unwrap(); |
| let err = test |
| .realm_proxy |
| .list_children(&collection_ref, server_end) |
| .await |
| .expect("fidl call failed") |
| .expect_err("unexpected success"); |
| assert_eq!(err, fcomponent::Error::InstanceDied); |
| } |
| } |
| |
| #[fuchsia::test] |
| async fn open_exposed_dir() { |
| let test = RealmCapabilityTest::new( |
| vec![ |
| ("root", ComponentDeclBuilder::new().child_default("system").build()), |
| ( |
| "system", |
| ComponentDeclBuilder::new() |
| .protocol_default("foo") |
| .expose( |
| ExposeBuilder::protocol() |
| .name("foo") |
| .target_name("hippo") |
| .source(ExposeSource::Self_), |
| ) |
| .build(), |
| ), |
| ], |
| Moniker::root(), |
| ) |
| .await; |
| let (_event_source, mut event_stream) = test |
| .new_event_stream(vec![EventType::Resolved.into(), EventType::Started.into()]) |
| .await; |
| let mut out_dir = OutDir::new(); |
| out_dir.add_echo_protocol("/svc/foo".parse().unwrap()); |
| test.mock_runner.add_host_fn("test:///system_resolved", out_dir.host_fn()); |
| |
| // Open exposed directory of child. |
| let child_ref = fdecl::ChildRef { name: "system".to_string(), collection: None }; |
| let (dir_proxy, server_end) = endpoints::create_proxy::<fio::DirectoryMarker>().unwrap(); |
| let res = test.realm_proxy.open_exposed_dir(&child_ref, server_end).await; |
| res.expect("fidl call failed").expect("open_exposed_dir() failed"); |
| |
| // Assert that child was resolved. |
| let event = |
| event_stream.wait_until(EventType::Resolved, vec!["system"].try_into().unwrap()).await; |
| assert!(event.is_some()); |
| |
| // Assert that event stream doesn't have any outstanding messages. |
| // This ensures that EventType::Started for "system" has not been |
| // registered. |
| let event = event_stream |
| .wait_until(EventType::Started, vec!["system"].try_into().unwrap()) |
| .now_or_never(); |
| assert!(event.is_none()); |
| |
| // Now that it was asserted that "system:0" has yet to start, |
| // assert that it starts after making connection below. |
| let echo_proxy = |
| client::connect_to_named_protocol_at_dir_root::<echo::EchoMarker>(&dir_proxy, "hippo") |
| .expect("failed to open hippo service"); |
| let event = |
| event_stream.wait_until(EventType::Started, vec!["system"].try_into().unwrap()).await; |
| assert!(event.is_some()); |
| let res = echo_proxy.echo_string(Some("hippos")).await; |
| assert_eq!(res.expect("failed to use echo service"), Some("hippos".to_string())); |
| |
| // Verify topology matches expectations. |
| let expected_urls = &["test:///root_resolved", "test:///system_resolved"]; |
| test.mock_runner.wait_for_urls(expected_urls).await; |
| assert_eq!("(system)", test.hook.print()); |
| } |
| |
| #[fuchsia::test] |
| async fn open_exposed_dir_dynamic_child() { |
| let test = RealmCapabilityTest::new( |
| vec![ |
| ("root", ComponentDeclBuilder::new().collection_default("coll").build()), |
| ( |
| "system", |
| ComponentDeclBuilder::new() |
| .protocol_default("foo") |
| .expose( |
| ExposeBuilder::protocol() |
| .name("foo") |
| .target_name("hippo") |
| .source(ExposeSource::Self_), |
| ) |
| .build(), |
| ), |
| ], |
| Moniker::root(), |
| ) |
| .await; |
| |
| let (_event_source, mut event_stream) = test |
| .new_event_stream(vec![EventType::Resolved.into(), EventType::Started.into()]) |
| .await; |
| let mut out_dir = OutDir::new(); |
| out_dir.add_echo_protocol("/svc/foo".parse().unwrap()); |
| test.mock_runner.add_host_fn("test:///system_resolved", out_dir.host_fn()); |
| |
| // Add "system" to collection. |
| let collection_ref = fdecl::CollectionRef { name: "coll".to_string() }; |
| let res = test |
| .realm_proxy |
| .create_child( |
| &collection_ref, |
| &child_decl("system"), |
| fcomponent::CreateChildArgs::default(), |
| ) |
| .await; |
| res.expect("fidl call failed").expect("failed to create child system"); |
| |
| // Open exposed directory of child. |
| let child_ref = |
| fdecl::ChildRef { name: "system".to_string(), collection: Some("coll".to_owned()) }; |
| let (dir_proxy, server_end) = endpoints::create_proxy::<fio::DirectoryMarker>().unwrap(); |
| let res = test.realm_proxy.open_exposed_dir(&child_ref, server_end).await; |
| res.expect("fidl call failed").expect("open_exposed_dir() failed"); |
| |
| // Assert that child was resolved. |
| let event = event_stream |
| .wait_until(EventType::Resolved, vec!["coll:system"].try_into().unwrap()) |
| .await; |
| assert!(event.is_some()); |
| |
| // Assert that event stream doesn't have any outstanding messages. |
| // This ensures that EventType::Started for "system" has not been |
| // registered. |
| let event = event_stream |
| .wait_until(EventType::Started, vec!["coll:system"].try_into().unwrap()) |
| .now_or_never(); |
| assert!(event.is_none()); |
| |
| // Now that it was asserted that "system" has yet to start, |
| // assert that it starts after making connection below. |
| let echo_proxy = |
| client::connect_to_named_protocol_at_dir_root::<echo::EchoMarker>(&dir_proxy, "hippo") |
| .expect("failed to open hippo service"); |
| let event = event_stream |
| .wait_until(EventType::Started, vec!["coll:system"].try_into().unwrap()) |
| .await; |
| assert!(event.is_some()); |
| let res = echo_proxy.echo_string(Some("hippos")).await; |
| assert_eq!(res.expect("failed to use echo service"), Some("hippos".to_string())); |
| |
| // Verify topology matches expectations. |
| let expected_urls = &["test:///root_resolved", "test:///system_resolved"]; |
| test.mock_runner.wait_for_urls(expected_urls).await; |
| assert_eq!("(coll:system)", test.hook.print()); |
| } |
| |
| #[fuchsia::test] |
| async fn open_exposed_dir_errors() { |
| let mut test = RealmCapabilityTest::new( |
| vec![ |
| ( |
| "root", |
| ComponentDeclBuilder::new() |
| .child_default("system") |
| .child_default("unresolvable") |
| .child_default("unrunnable") |
| .build(), |
| ), |
| ("system", component_decl_with_test_runner()), |
| ("unrunnable", component_decl_with_test_runner()), |
| ], |
| Moniker::root(), |
| ) |
| .await; |
| test.mock_runner.cause_failure("unrunnable"); |
| |
| // Instance not found. |
| { |
| let child_ref = fdecl::ChildRef { name: "missing".to_string(), collection: None }; |
| let (_, server_end) = endpoints::create_proxy::<fio::DirectoryMarker>().unwrap(); |
| let err = test |
| .realm_proxy |
| .open_exposed_dir(&child_ref, server_end) |
| .await |
| .expect("fidl call failed") |
| .expect_err("unexpected success"); |
| assert_eq!(err, fcomponent::Error::InstanceNotFound); |
| } |
| |
| // Instance cannot resolve. |
| { |
| let child_ref = fdecl::ChildRef { name: "unresolvable".to_string(), collection: None }; |
| let (_, server_end) = endpoints::create_proxy::<fio::DirectoryMarker>().unwrap(); |
| let err = test |
| .realm_proxy |
| .open_exposed_dir(&child_ref, server_end) |
| .await |
| .expect("fidl call failed") |
| .expect_err("unexpected success"); |
| assert_eq!(err, fcomponent::Error::InstanceCannotResolve); |
| } |
| |
| // Instance can't run. |
| { |
| let child_ref = fdecl::ChildRef { name: "unrunnable".to_string(), collection: None }; |
| let (dir_proxy, server_end) = |
| endpoints::create_proxy::<fio::DirectoryMarker>().unwrap(); |
| let res = test.realm_proxy.open_exposed_dir(&child_ref, server_end).await; |
| res.expect("fidl call failed").expect("open_exposed_dir() failed"); |
| let echo_proxy = client::connect_to_named_protocol_at_dir_root::<echo::EchoMarker>( |
| &dir_proxy, "hippo", |
| ) |
| .expect("failed to open hippo service"); |
| let res = echo_proxy.echo_string(Some("hippos")).await; |
| assert!(res.is_err()); |
| } |
| |
| // Instance died. |
| { |
| test.drop_component(); |
| let child_ref = fdecl::ChildRef { name: "system".to_string(), collection: None }; |
| let (_, server_end) = endpoints::create_proxy::<fio::DirectoryMarker>().unwrap(); |
| let err = test |
| .realm_proxy |
| .open_exposed_dir(&child_ref, server_end) |
| .await |
| .expect("fidl call failed") |
| .expect_err("unexpected success"); |
| assert_eq!(err, fcomponent::Error::InstanceDied); |
| } |
| } |
| } |