| // Copyright 2020 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::{self, Context}, |
| cm_rust::{FidlIntoNative, NativeIntoFidl}, |
| fidl::endpoints::{ProtocolMarker, ServerEnd}, |
| fidl_fuchsia_component as fcomponent, fidl_fuchsia_component_config as fconfig, |
| fidl_fuchsia_component_decl as fcdecl, fidl_fuchsia_component_runner as fcrunner, |
| fidl_fuchsia_component_test as ftest, fidl_fuchsia_data as fdata, fidl_fuchsia_io as fio, |
| fuchsia_component::server as fserver, |
| fuchsia_zircon_status as zx_status, |
| futures::{future::BoxFuture, join, lock::Mutex, FutureExt, StreamExt, TryStreamExt}, |
| io_util, |
| lazy_static::lazy_static, |
| std::{ |
| collections::HashMap, |
| convert::TryInto, |
| ops::{Deref, DerefMut}, |
| path::PathBuf, |
| sync::{ |
| atomic::{AtomicBool, Ordering}, |
| Arc, |
| }, |
| }, |
| thiserror::{self, Error}, |
| tracing::*, |
| url::Url, |
| vfs::execution_scope::ExecutionScope, |
| }; |
| |
| mod builtin; |
| mod resolver; |
| mod runner; |
| |
| lazy_static! { |
| pub static ref BINDER_EXPOSE_DECL: cm_rust::ExposeDecl = |
| cm_rust::ExposeDecl::Protocol(cm_rust::ExposeProtocolDecl { |
| source: cm_rust::ExposeSource::Framework, |
| source_name: fcomponent::BinderMarker::DEBUG_NAME.into(), |
| target: cm_rust::ExposeTarget::Parent, |
| target_name: fcomponent::BinderMarker::DEBUG_NAME.into(), |
| },); |
| } |
| |
| // The program section of a component decl is not allowed to be mutated |
| // for local and legacy components except for one instance. In the event, |
| // that an `args` key is added for a legacy component, replacement of the |
| // component decl is allowed. This is to enable passing argv to the |
| // legacy component runner. |
| const ALLOWLISTED_PROGRAM_ARGS_KEY: &'static str = "args"; |
| |
| #[fuchsia::main] |
| async fn main() { |
| info!("started"); |
| |
| let mut fs = fserver::ServiceFs::new_local(); |
| let registry = resolver::Registry::new(); |
| let runner = runner::Runner::new(); |
| |
| let registry_clone = registry.clone(); |
| fs.dir("svc").add_fidl_service(move |stream| registry_clone.run_resolver_service(stream)); |
| |
| let runner_clone = runner.clone(); |
| fs.dir("svc").add_fidl_service(move |stream| runner_clone.run_runner_service(stream)); |
| |
| let execution_scope = ExecutionScope::new(); |
| |
| let execution_scope_clone = execution_scope.clone(); |
| fs.dir("svc").add_fidl_service(move |stream| { |
| let factory = RealmBuilderFactory::new( |
| registry.clone(), |
| runner.clone(), |
| execution_scope_clone.clone(), |
| ); |
| execution_scope_clone.spawn(async move { |
| if let Err(e) = factory.handle_stream(stream).await { |
| error!("error encountered while running realm builder service: {:?}", e); |
| } |
| }); |
| }); |
| |
| fs.take_and_serve_directory_handle().expect("did not receive directory handle"); |
| |
| join!(execution_scope.wait(), fs.collect::<()>()); |
| } |
| |
| struct RealmBuilderFactory { |
| registry: Arc<resolver::Registry>, |
| runner: Arc<runner::Runner>, |
| execution_scope: ExecutionScope, |
| } |
| |
| impl RealmBuilderFactory { |
| fn new( |
| registry: Arc<resolver::Registry>, |
| runner: Arc<runner::Runner>, |
| execution_scope: ExecutionScope, |
| ) -> Self { |
| Self { registry, runner, execution_scope } |
| } |
| |
| async fn handle_stream( |
| self, |
| mut stream: ftest::RealmBuilderFactoryRequestStream, |
| ) -> Result<(), anyhow::Error> { |
| while let Some(req) = stream.try_next().await? { |
| match req { |
| ftest::RealmBuilderFactoryRequest::CreateFromRelativeUrl { |
| pkg_dir_handle, |
| relative_url, |
| realm_server_end, |
| builder_server_end, |
| responder, |
| } => { |
| if !is_relative_url(&relative_url) { |
| responder.send(&mut Err(ftest::RealmBuilderError2::UrlIsNotRelative))?; |
| continue; |
| } |
| let pkg_dir = match pkg_dir_handle |
| .into_proxy() |
| .context("failed to convert pkg_dir ClientEnd to proxy") |
| { |
| Ok(pkg_dir) => pkg_dir, |
| Err(e) => { |
| responder |
| .send(&mut Err(ftest::RealmBuilderError2::InvalidPkgDirHandle))?; |
| return Err(e); |
| } |
| }; |
| if let Err(e) = pkg_dir.describe().await.context("pkg_dir.describe() failed") { |
| responder.send(&mut Err(ftest::RealmBuilderError2::InvalidPkgDirHandle))?; |
| return Err(e); |
| } |
| let realm_node = match RealmNode2::load_from_pkg( |
| relative_url.clone(), |
| Clone::clone(&pkg_dir), |
| ) |
| .await |
| { |
| Ok(realm_node) => realm_node, |
| Err(e) => { |
| warn!("unable to load manifest at {:?}: {}", relative_url, e); |
| responder.send(&mut Err(e.into()))?; |
| continue; |
| } |
| }; |
| self.create_realm_and_builder( |
| realm_node, |
| pkg_dir, |
| realm_server_end, |
| builder_server_end, |
| )?; |
| responder.send(&mut Ok(()))?; |
| } |
| ftest::RealmBuilderFactoryRequest::Create { |
| pkg_dir_handle, |
| realm_server_end, |
| builder_server_end, |
| responder, |
| } => { |
| let pkg_dir = pkg_dir_handle |
| .into_proxy() |
| .context("failed to convert pkg_dir ClientEnd to proxy")?; |
| if let Err(e) = pkg_dir.describe().await.context("pkg_dir.describe() failed") { |
| responder.send(&mut Err(ftest::RealmBuilderError2::InvalidPkgDirHandle))?; |
| return Err(e); |
| } |
| self.create_realm_and_builder( |
| RealmNode2::new(), |
| pkg_dir, |
| realm_server_end, |
| builder_server_end, |
| )?; |
| responder.send(&mut Ok(()))?; |
| } |
| } |
| } |
| Ok(()) |
| } |
| |
| fn create_realm_and_builder( |
| &self, |
| realm_node: RealmNode2, |
| pkg_dir: fio::DirectoryProxy, |
| realm_server_end: ServerEnd<ftest::RealmMarker>, |
| builder_server_end: ServerEnd<ftest::BuilderMarker>, |
| ) -> Result<(), anyhow::Error> { |
| let runner_proxy_placeholder = Arc::new(Mutex::new(None)); |
| |
| let realm_stream = realm_server_end |
| .into_stream() |
| .context("failed to convert realm_server_end to stream")?; |
| |
| let realm_has_been_built = Arc::new(AtomicBool::new(false)); |
| |
| let realm = Realm { |
| pkg_dir: Clone::clone(&pkg_dir), |
| realm_node: realm_node.clone(), |
| registry: self.registry.clone(), |
| runner: self.runner.clone(), |
| runner_proxy_placeholder: runner_proxy_placeholder.clone(), |
| realm_path: vec![], |
| execution_scope: self.execution_scope.clone(), |
| realm_has_been_built: realm_has_been_built.clone(), |
| }; |
| |
| self.execution_scope.spawn(async move { |
| if let Err(e) = realm.handle_stream(realm_stream).await { |
| error!("error encountered while handling Realm requests: {:?}", e); |
| } |
| }); |
| |
| let builder_stream = builder_server_end |
| .into_stream() |
| .context("failed to convert builder_server_end to stream")?; |
| |
| let builder = Builder { |
| pkg_dir: Clone::clone(&pkg_dir), |
| realm_node, |
| registry: self.registry.clone(), |
| runner_proxy_placeholder: runner_proxy_placeholder.clone(), |
| realm_has_been_built: realm_has_been_built, |
| }; |
| self.execution_scope.spawn(async move { |
| if let Err(e) = builder.handle_stream(builder_stream).await { |
| error!("error encountered while handling Builder requests: {:?}", e); |
| } |
| }); |
| Ok(()) |
| } |
| } |
| |
| struct Builder { |
| pkg_dir: fio::DirectoryProxy, |
| realm_node: RealmNode2, |
| registry: Arc<resolver::Registry>, |
| runner_proxy_placeholder: Arc<Mutex<Option<fcrunner::ComponentRunnerProxy>>>, |
| realm_has_been_built: Arc<AtomicBool>, |
| } |
| |
| impl Builder { |
| async fn handle_stream( |
| &self, |
| mut stream: ftest::BuilderRequestStream, |
| ) -> Result<(), anyhow::Error> { |
| while let Some(req) = stream.try_next().await? { |
| match req { |
| ftest::BuilderRequest::Build { runner, responder } => { |
| if self.realm_has_been_built.swap(true, Ordering::Relaxed) { |
| responder.send(&mut Err(ftest::RealmBuilderError2::BuildAlreadyCalled))?; |
| continue; |
| } |
| let runner_proxy = runner |
| .into_proxy() |
| .context("failed to convert runner ClientEnd into proxy")?; |
| *self.runner_proxy_placeholder.lock().await = Some(runner_proxy); |
| let res = self |
| .realm_node |
| .build(self.registry.clone(), vec![], Clone::clone(&self.pkg_dir)) |
| .await; |
| match res { |
| Ok(url) => responder.send(&mut Ok(url))?, |
| Err(e) => { |
| warn!("unable to build the realm the client requested: {}", e); |
| responder.send(&mut Err(e.into()))?; |
| } |
| } |
| } |
| } |
| } |
| Ok(()) |
| } |
| } |
| |
| #[allow(unused)] |
| struct Realm { |
| pkg_dir: fio::DirectoryProxy, |
| realm_node: RealmNode2, |
| registry: Arc<resolver::Registry>, |
| runner: Arc<runner::Runner>, |
| runner_proxy_placeholder: Arc<Mutex<Option<fcrunner::ComponentRunnerProxy>>>, |
| realm_has_been_built: Arc<AtomicBool>, |
| realm_path: Vec<String>, |
| execution_scope: ExecutionScope, |
| } |
| |
| impl Realm { |
| async fn handle_stream( |
| &self, |
| mut stream: ftest::RealmRequestStream, |
| ) -> Result<(), anyhow::Error> { |
| while let Some(req) = stream.try_next().await? { |
| match req { |
| ftest::RealmRequest::AddChild { name, url, options, responder } => { |
| if self.realm_has_been_built.load(Ordering::Relaxed) { |
| responder.send(&mut Err(ftest::RealmBuilderError2::BuildAlreadyCalled))?; |
| continue; |
| } |
| match self.add_child(name.clone(), url.clone(), options).await { |
| Ok(()) => responder.send(&mut Ok(()))?, |
| Err(e) => { |
| warn!( |
| "unable to add child {:?} with url {:?} to realm: {}", |
| name, url, e |
| ); |
| responder.send(&mut Err(e.into()))?; |
| } |
| } |
| } |
| ftest::RealmRequest::AddLegacyChild { name, legacy_url, options, responder } => { |
| if self.realm_has_been_built.load(Ordering::Relaxed) { |
| responder.send(&mut Err(ftest::RealmBuilderError2::BuildAlreadyCalled))?; |
| continue; |
| } |
| match self.add_legacy_child(name.clone(), legacy_url.clone(), options).await { |
| Ok(()) => responder.send(&mut Ok(()))?, |
| Err(e) => { |
| warn!( |
| "unable to add legacy child {:?} with url {:?} to realm: {}", |
| name, legacy_url, e |
| ); |
| responder.send(&mut Err(e.into()))?; |
| } |
| } |
| } |
| ftest::RealmRequest::AddChildFromDecl { name, decl, options, responder } => { |
| if self.realm_has_been_built.load(Ordering::Relaxed) { |
| responder.send(&mut Err(ftest::RealmBuilderError2::BuildAlreadyCalled))?; |
| continue; |
| } |
| match self.add_child_from_decl(name.clone(), decl, options).await { |
| Ok(()) => responder.send(&mut Ok(()))?, |
| Err(e) => { |
| warn!("unable to add child {:?} from decl to realm: {}", name, e); |
| responder.send(&mut Err(e.into()))?; |
| } |
| } |
| } |
| ftest::RealmRequest::AddLocalChild { name, options, responder } => { |
| if self.realm_has_been_built.load(Ordering::Relaxed) { |
| responder.send(&mut Err(ftest::RealmBuilderError2::BuildAlreadyCalled))?; |
| continue; |
| } |
| match self.add_local_child(name.clone(), options).await { |
| Ok(()) => responder.send(&mut Ok(()))?, |
| Err(e) => { |
| warn!("unable to add local child {:?} to realm: {}", name, e); |
| responder.send(&mut Err(e.into()))?; |
| } |
| } |
| } |
| ftest::RealmRequest::AddChildRealm { name, options, child_realm, responder } => { |
| if self.realm_has_been_built.load(Ordering::Relaxed) { |
| responder.send(&mut Err(ftest::RealmBuilderError2::BuildAlreadyCalled))?; |
| continue; |
| } |
| match self.add_child_realm(name.clone(), options, child_realm).await { |
| Ok(()) => responder.send(&mut Ok(()))?, |
| Err(e) => { |
| warn!("unable to add child realm {:?}: {}", name, e); |
| responder.send(&mut Err(e.into()))?; |
| } |
| } |
| } |
| ftest::RealmRequest::GetComponentDecl { name, responder } => { |
| if self.realm_has_been_built.load(Ordering::Relaxed) { |
| responder.send(&mut Err(ftest::RealmBuilderError2::BuildAlreadyCalled))?; |
| continue; |
| } |
| match self.get_component_decl(name.clone()).await { |
| Ok(decl) => responder.send(&mut Ok(decl))?, |
| Err(e) => { |
| warn!("unable to get component decl for child {:?}: {}", name, e); |
| responder.send(&mut Err(e.into()))?; |
| } |
| } |
| } |
| ftest::RealmRequest::ReplaceComponentDecl { name, component_decl, responder } => { |
| if self.realm_has_been_built.load(Ordering::Relaxed) { |
| responder.send(&mut Err(ftest::RealmBuilderError2::BuildAlreadyCalled))?; |
| continue; |
| } |
| match self.replace_component_decl(name.clone(), component_decl).await { |
| Ok(()) => responder.send(&mut Ok(()))?, |
| Err(e) => { |
| warn!("unable to replace component decl for child {:?}: {}", name, e); |
| responder.send(&mut Err(e.into()))?; |
| } |
| } |
| } |
| ftest::RealmRequest::GetRealmDecl { responder } => { |
| if self.realm_has_been_built.load(Ordering::Relaxed) { |
| responder.send(&mut Err(ftest::RealmBuilderError2::BuildAlreadyCalled))?; |
| continue; |
| } |
| responder.send(&mut Ok(self.get_realm_decl().await))?; |
| } |
| ftest::RealmRequest::ReplaceRealmDecl { component_decl, responder } => { |
| if self.realm_has_been_built.load(Ordering::Relaxed) { |
| responder.send(&mut Err(ftest::RealmBuilderError2::BuildAlreadyCalled))?; |
| continue; |
| } |
| match self.replace_realm_decl(component_decl).await { |
| Ok(()) => responder.send(&mut Ok(()))?, |
| Err(e) => { |
| warn!("unable to replace realm decl: {}", e); |
| responder.send(&mut Err(e.into()))?; |
| } |
| } |
| } |
| ftest::RealmRequest::AddRoute { capabilities, from, to, responder } => { |
| if self.realm_has_been_built.load(Ordering::Relaxed) { |
| responder.send(&mut Err(ftest::RealmBuilderError2::BuildAlreadyCalled))?; |
| continue; |
| } |
| match self.realm_node.route_capabilities(capabilities, from, to).await { |
| Ok(()) => { |
| responder.send(&mut Ok(()))?; |
| } |
| Err(e) => { |
| warn!("unable to add route: {}", e); |
| responder.send(&mut Err(e.into()))?; |
| } |
| } |
| } |
| ftest::RealmRequest::ReadOnlyDirectory { |
| name, |
| to, |
| directory_contents, |
| responder, |
| } => { |
| if self.realm_has_been_built.load(Ordering::Relaxed) { |
| responder.send(&mut Err(ftest::RealmBuilderError2::BuildAlreadyCalled))?; |
| continue; |
| } |
| match self.read_only_directory(name, to, directory_contents).await { |
| Ok(()) => { |
| responder.send(&mut Ok(()))?; |
| } |
| Err(e) => { |
| warn!("unable to add route: {}", e); |
| responder.send(&mut Err(e.into()))?; |
| } |
| } |
| } |
| ftest::RealmRequest::ReplaceConfigValue { name, key, value, responder } => { |
| if self.realm_has_been_built.load(Ordering::Relaxed) { |
| responder.send(&mut Err(ftest::RealmBuilderError2::BuildAlreadyCalled))?; |
| continue; |
| } |
| match self.replace_config_value(name, key, value).await { |
| Ok(()) => { |
| responder.send(&mut Ok(()))?; |
| } |
| Err(e) => { |
| warn!("unable to set config value: {:?}", e); |
| responder.send(&mut Err(e.into()))?; |
| } |
| } |
| } |
| } |
| } |
| Ok(()) |
| } |
| |
| async fn add_child( |
| &self, |
| name: String, |
| url: String, |
| options: ftest::ChildOptions, |
| ) -> Result<(), RealmBuilderError> { |
| if is_legacy_url(&url) { |
| return Err(RealmBuilderError::InvalidManifestExtension); |
| } |
| if is_relative_url(&url) { |
| let child_realm_node = |
| RealmNode2::load_from_pkg(url, Clone::clone(&self.pkg_dir)).await?; |
| self.realm_node.add_child(name.clone(), options, child_realm_node).await |
| } else { |
| self.realm_node.add_child_decl(name, url, options).await |
| } |
| } |
| |
| async fn add_legacy_child( |
| &self, |
| name: String, |
| legacy_url: String, |
| options: ftest::ChildOptions, |
| ) -> Result<(), RealmBuilderError> { |
| if !is_legacy_url(&legacy_url) { |
| return Err(RealmBuilderError::InvalidManifestExtension); |
| } |
| let child_realm_node = RealmNode2::new_from_decl( |
| new_decl_with_program_entries(vec![(runner::LEGACY_URL_KEY.to_string(), legacy_url)]), |
| true, |
| ); |
| self.realm_node.add_child(name, options, child_realm_node).await |
| } |
| |
| async fn add_child_from_decl( |
| &self, |
| name: String, |
| component_decl: fcdecl::Component, |
| options: ftest::ChildOptions, |
| ) -> Result<(), RealmBuilderError> { |
| if let Err(e) = cm_fidl_validator::validate(&component_decl) { |
| return Err(RealmBuilderError::InvalidComponentDecl(e, component_decl)); |
| } |
| let child_realm_node = RealmNode2::new_from_decl(component_decl.fidl_into_native(), false); |
| self.realm_node.add_child(name, options, child_realm_node).await |
| } |
| |
| async fn add_local_child( |
| &self, |
| name: String, |
| options: ftest::ChildOptions, |
| ) -> Result<(), RealmBuilderError> { |
| let local_component_id = |
| self.runner.register_local_component(self.runner_proxy_placeholder.clone()).await; |
| let mut child_path = self.realm_path.clone(); |
| child_path.push(name.clone()); |
| let child_realm_node = RealmNode2::new_from_decl( |
| new_decl_with_program_entries(vec![ |
| (runner::LOCAL_COMPONENT_ID_KEY.to_string(), local_component_id.into()), |
| (ftest::LOCAL_COMPONENT_NAME_KEY.to_string(), child_path.join("/").to_string()), |
| ]), |
| true, |
| ); |
| self.realm_node.add_child(name, options, child_realm_node).await |
| } |
| |
| // `Realm::handle_stream` calls `Realm::add_child_realm` which calls `Realm::handle_stream`. |
| // Cycles are not allowed in constructed futures, so we need to place this in a `BoxFuture` to |
| // break the cycle. |
| fn add_child_realm( |
| &self, |
| name: String, |
| options: ftest::ChildOptions, |
| child_realm_server_end: ServerEnd<ftest::RealmMarker>, |
| ) -> BoxFuture<'static, Result<(), RealmBuilderError>> { |
| let mut child_path = self.realm_path.clone(); |
| child_path.push(name.clone()); |
| |
| let child_realm_node = RealmNode2::new(); |
| |
| let child_realm = Realm { |
| pkg_dir: Clone::clone(&self.pkg_dir), |
| realm_node: child_realm_node.clone(), |
| registry: self.registry.clone(), |
| runner: self.runner.clone(), |
| runner_proxy_placeholder: self.runner_proxy_placeholder.clone(), |
| realm_path: child_path.clone(), |
| execution_scope: self.execution_scope.clone(), |
| realm_has_been_built: self.realm_has_been_built.clone(), |
| }; |
| |
| let self_realm_node = self.realm_node.clone(); |
| let self_execution_scope = self.execution_scope.clone(); |
| |
| async move { |
| let child_realm_stream = child_realm_server_end |
| .into_stream() |
| .map_err(RealmBuilderError::InvalidChildRealmHandle)?; |
| self_realm_node.add_child(name, options, child_realm_node).await?; |
| |
| self_execution_scope.spawn(async move { |
| if let Err(e) = child_realm.handle_stream(child_realm_stream).await { |
| error!( |
| "error encountered while handling requests for child realm {:?}: {:?}", |
| child_path.join("/"), |
| e, |
| ); |
| } |
| }); |
| |
| Ok(()) |
| } |
| .boxed() |
| } |
| |
| async fn get_component_decl( |
| &self, |
| name: String, |
| ) -> Result<fcdecl::Component, RealmBuilderError> { |
| let child_node = self.realm_node.get_sub_realm(&name).await?; |
| Ok(child_node.get_decl().await.native_into_fidl()) |
| } |
| |
| async fn replace_component_decl( |
| &self, |
| name: String, |
| component_decl: fcdecl::Component, |
| ) -> Result<(), RealmBuilderError> { |
| let child_node = self.realm_node.get_sub_realm(&name).await?; |
| child_node.replace_decl_with_untrusted(component_decl).await |
| } |
| |
| async fn get_realm_decl(&self) -> fcdecl::Component { |
| self.realm_node.get_decl().await.native_into_fidl() |
| } |
| |
| async fn replace_realm_decl( |
| &self, |
| component_decl: fcdecl::Component, |
| ) -> Result<(), RealmBuilderError> { |
| self.realm_node.replace_decl_with_untrusted(component_decl).await |
| } |
| |
| async fn replace_config_value( |
| &self, |
| name: String, |
| key: String, |
| value_spec: fconfig::ValueSpec, |
| ) -> Result<(), RealmBuilderError> { |
| let child_node = self.realm_node.get_sub_realm(&name).await?; |
| let decl = child_node.get_decl().await; |
| let config = decl.config.ok_or(RealmBuilderError::NoConfigSchema)?; |
| cm_fidl_validator::validate_value_spec(&value_spec).map_err(|e| { |
| error!("Error validating value spec for {}: {}", key, e); |
| RealmBuilderError::ConfigValueInvalid |
| })?; |
| let value_spec = value_spec.fidl_into_native(); |
| for (index, field) in config.fields.iter().enumerate() { |
| if field.key == key { |
| config_encoder::ConfigField::resolve(value_spec.clone(), &field) |
| .map_err(|_| RealmBuilderError::ConfigValueInvalid)?; |
| let mut state_guard = child_node.state.lock().await; |
| state_guard.config_value_replacements.insert(index, value_spec); |
| return Ok(()); |
| } |
| } |
| Err(RealmBuilderError::NoSuchConfigField(key)) |
| } |
| |
| async fn read_only_directory( |
| &self, |
| directory_name: String, |
| to: Vec<fcdecl::Ref>, |
| directory_contents: ftest::DirectoryContents, |
| ) -> Result<(), RealmBuilderError> { |
| // Add a new component to the realm to serve the directory capability from |
| let dir_name = directory_name.clone(); |
| let directory_contents = Arc::new(directory_contents); |
| let local_component_id = self |
| .runner |
| .register_builtin_component(move |outgoing_dir| { |
| builtin::read_only_directory( |
| dir_name.clone(), |
| directory_contents.clone(), |
| outgoing_dir, |
| ) |
| .boxed() |
| }) |
| .await; |
| let string_id: String = local_component_id.clone().into(); |
| let child_name = format!("read-only-directory-{}", string_id); |
| |
| let mut child_path = self.realm_path.clone(); |
| child_path.push(child_name.clone()); |
| |
| let child_realm_node = RealmNode2::new_from_decl( |
| new_decl_with_program_entries(vec![( |
| runner::LOCAL_COMPONENT_ID_KEY.to_string(), |
| local_component_id.into(), |
| )]), |
| true, |
| ); |
| self.realm_node |
| .add_child(child_name.clone(), ftest::ChildOptions::EMPTY, child_realm_node) |
| .await?; |
| let path = Some(format!("/{}", directory_name)); |
| self.realm_node |
| .route_capabilities( |
| vec![ftest::Capability2::Directory(ftest::Directory { |
| name: Some(directory_name), |
| rights: Some(fio::R_STAR_DIR), |
| path, |
| ..ftest::Directory::EMPTY |
| })], |
| fcdecl::Ref::Child(fcdecl::ChildRef { name: child_name.clone(), collection: None }), |
| to, |
| ) |
| .await |
| } |
| } |
| |
| fn new_decl_with_program_entries(entries: Vec<(String, String)>) -> cm_rust::ComponentDecl { |
| cm_rust::ComponentDecl { |
| program: Some(cm_rust::ProgramDecl { |
| runner: Some(runner::RUNNER_NAME.try_into().unwrap()), |
| info: fdata::Dictionary { |
| entries: Some( |
| entries |
| .into_iter() |
| .map(|(key, val)| fdata::DictionaryEntry { |
| key: key, |
| value: Some(Box::new(fdata::DictionaryValue::Str(val))), |
| }) |
| .collect(), |
| ), |
| ..fdata::Dictionary::EMPTY |
| }, |
| }), |
| ..cm_rust::ComponentDecl::default() |
| } |
| } |
| |
| #[derive(Debug, Clone, Default)] |
| struct RealmNodeState { |
| decl: cm_rust::ComponentDecl, |
| |
| /// Stores indices to configuration values that must be replaced when the config value file |
| /// of a component is read in from the package directory during resolve. |
| config_value_replacements: HashMap<usize, cm_rust::ValueSpec>, |
| |
| /// Children stored in this HashMap can be mutated. Children stored in `decl.children` can not. |
| /// Any children stored in `mutable_children` do NOT have a corresponding `ChildDecl` stored in |
| /// `decl.children`, the two should be fully mutually exclusive. |
| /// |
| /// Suitable `ChildDecl`s for the contents of `mutable_children` are generated and added to |
| /// `decl.children` when `commit()` is called. |
| mutable_children: HashMap<String, (ftest::ChildOptions, RealmNode2)>, |
| } |
| |
| impl RealmNodeState { |
| // Returns true if a child with the given name exists either as a mutable child or as a |
| // ChildDecl in this node's ComponentDecl. |
| fn contains_child(&self, child_name: &String) -> bool { |
| self.decl.children.iter().any(|c| &c.name == child_name) |
| || self.mutable_children.contains_key(child_name) |
| } |
| |
| fn add_child_decl( |
| &mut self, |
| child_name: String, |
| child_url: String, |
| child_options: ftest::ChildOptions, |
| ) { |
| self.decl.children.push(cm_rust::ChildDecl { |
| name: child_name, |
| url: child_url, |
| startup: match child_options.startup { |
| Some(fcdecl::StartupMode::Lazy) => fcdecl::StartupMode::Lazy, |
| Some(fcdecl::StartupMode::Eager) => fcdecl::StartupMode::Eager, |
| None => fcdecl::StartupMode::Lazy, |
| }, |
| environment: child_options.environment, |
| on_terminate: match child_options.on_terminate { |
| Some(fcdecl::OnTerminate::None) => Some(fcdecl::OnTerminate::None), |
| Some(fcdecl::OnTerminate::Reboot) => Some(fcdecl::OnTerminate::Reboot), |
| None => None, |
| }, |
| }); |
| } |
| |
| // Returns children whose manifest must be updated during invocations to |
| // AddRoute. |
| fn get_updateable_children(&mut self) -> HashMap<String, &mut RealmNode2> { |
| self.mutable_children |
| .iter_mut() |
| .map(|(key, (_options, child))| (key.clone(), child)) |
| .filter(|(_k, c)| c.update_decl_in_add_route) |
| .collect::<HashMap<_, _>>() |
| } |
| |
| // Whenever this realm node is going to get a new decl we'd like to validate the new |
| // hypothetical decl, but the decl likely references children within `self.mutable_children`. |
| // Since these children do not (yet) exist in `decl.children`, the decl will fail validation. |
| // To get around this, generate hypothetical `fcdecl::Child` structs and add them to |
| // `decl.children`, and then run validation. |
| fn validate_with_hypothetical_children( |
| &self, |
| mut decl: fcdecl::Component, |
| ) -> Result<(), RealmBuilderError> { |
| let child_decls = |
| self.mutable_children.iter().map(|(name, _options_and_node)| fcdecl::Child { |
| name: Some(name.clone()), |
| url: Some("invalid://url".to_string()), |
| startup: Some(fcdecl::StartupMode::Lazy), |
| ..fcdecl::Child::EMPTY |
| }); |
| decl.children.get_or_insert(vec![]).extend(child_decls); |
| if let Err(e) = cm_fidl_validator::validate(&decl) { |
| return Err(RealmBuilderError::InvalidComponentDecl(e, decl)); |
| } |
| Ok(()) |
| } |
| } |
| |
| #[derive(Debug, Clone)] |
| struct RealmNode2 { |
| state: Arc<Mutex<RealmNodeState>>, |
| |
| /// We shouldn't mutate component decls that are loaded from the test package. Track the source |
| /// of this component declaration here, so we know when to treat it as immutable. |
| component_loaded_from_pkg: bool, |
| |
| /// Flag used to determine if this component's manifest should be updated |
| /// when a capability is routed to or from it during invocations of AddRoute. |
| pub update_decl_in_add_route: bool, |
| } |
| |
| impl RealmNode2 { |
| fn new() -> Self { |
| Self { |
| state: Arc::new(Mutex::new(RealmNodeState::default())), |
| component_loaded_from_pkg: false, |
| update_decl_in_add_route: false, |
| } |
| } |
| |
| fn new_from_decl(decl: cm_rust::ComponentDecl, update_decl_in_add_route: bool) -> Self { |
| Self { |
| state: Arc::new(Mutex::new(RealmNodeState { decl, ..RealmNodeState::default() })), |
| component_loaded_from_pkg: false, |
| update_decl_in_add_route, |
| } |
| } |
| |
| async fn get_decl(&self) -> cm_rust::ComponentDecl { |
| self.state.lock().await.decl.clone() |
| } |
| |
| // Validates `new_decl`, confirms that `new_decl` isn't overwriting anything necessary for the |
| // realm builder runner to work, and then replaces this realm's decl with `new_decl`. |
| async fn replace_decl_with_untrusted( |
| &self, |
| new_decl: fcdecl::Component, |
| ) -> Result<(), RealmBuilderError> { |
| let mut state_guard = self.state.lock().await; |
| state_guard.validate_with_hypothetical_children(new_decl.clone())?; |
| let new_decl = new_decl.fidl_into_native(); |
| let () = validate_program_modifications(&state_guard.decl, &new_decl)?; |
| state_guard.decl = new_decl; |
| Ok(()) |
| } |
| |
| // Replaces the decl for this realm with `new_decl`. |
| async fn replace_decl( |
| &self, |
| new_decl: cm_rust::ComponentDecl, |
| ) -> Result<(), RealmBuilderError> { |
| self.state.lock().await.decl = new_decl; |
| Ok(()) |
| } |
| |
| async fn add_child( |
| &self, |
| child_name: String, |
| child_options: ftest::ChildOptions, |
| node: RealmNode2, |
| ) -> Result<(), RealmBuilderError> { |
| let mut state_guard = self.state.lock().await; |
| if state_guard.contains_child(&child_name) { |
| return Err(RealmBuilderError::ChildAlreadyExists(child_name)); |
| } |
| state_guard.mutable_children.insert(child_name, (child_options, node)); |
| Ok(()) |
| } |
| |
| async fn add_child_decl( |
| &self, |
| child_name: String, |
| child_url: String, |
| child_options: ftest::ChildOptions, |
| ) -> Result<(), RealmBuilderError> { |
| let mut state_guard = self.state.lock().await; |
| if state_guard.contains_child(&child_name) { |
| return Err(RealmBuilderError::ChildAlreadyExists(child_name)); |
| } |
| state_guard.add_child_decl(child_name, child_url, child_options); |
| Ok(()) |
| } |
| |
| fn load_from_pkg( |
| relative_url: String, |
| test_pkg_dir: fio::DirectoryProxy, |
| ) -> BoxFuture<'static, Result<RealmNode2, RealmBuilderError>> { |
| async move { |
| let path = relative_url.trim_start_matches('#'); |
| |
| let file_proxy_res = io_util::directory::open_file( |
| &test_pkg_dir, |
| &path, |
| io_util::OpenFlags::RIGHT_READABLE, |
| ) |
| .await; |
| let file_proxy = match file_proxy_res { |
| Ok(file_proxy) => file_proxy, |
| Err(io_util::node::OpenError::OpenError(zx_status::Status::NOT_FOUND)) => { |
| return Err(RealmBuilderError::DeclNotFound(relative_url.clone())) |
| } |
| Err(e) => { |
| return Err(RealmBuilderError::DeclReadError(relative_url.clone(), e.into())) |
| } |
| }; |
| |
| let fidl_decl = io_util::read_file_fidl::<fcdecl::Component>(&file_proxy) |
| .await |
| .map_err(|e| RealmBuilderError::DeclReadError(relative_url.clone(), e))?; |
| cm_fidl_validator::validate(&fidl_decl).map_err(|e| { |
| RealmBuilderError::InvalidComponentDeclWithName(relative_url, e, fidl_decl.clone()) |
| })?; |
| |
| let mut self_ = RealmNode2::new_from_decl(fidl_decl.fidl_into_native(), false); |
| self_.component_loaded_from_pkg = true; |
| let mut state_guard = self_.state.lock().await; |
| |
| let children = state_guard.decl.children.drain(..).collect::<Vec<_>>(); |
| for child in children { |
| if !is_relative_url(&child.url) { |
| state_guard.decl.children.push(child); |
| } else { |
| let child_node = |
| RealmNode2::load_from_pkg(child.url, Clone::clone(&test_pkg_dir)).await?; |
| let child_options = ftest::ChildOptions { |
| startup: match child.startup { |
| fcdecl::StartupMode::Lazy => Some(fcdecl::StartupMode::Lazy), |
| fcdecl::StartupMode::Eager => Some(fcdecl::StartupMode::Eager), |
| }, |
| environment: child.environment, |
| on_terminate: match child.on_terminate { |
| Some(fcdecl::OnTerminate::None) => Some(fcdecl::OnTerminate::None), |
| Some(fcdecl::OnTerminate::Reboot) => Some(fcdecl::OnTerminate::Reboot), |
| None => None, |
| }, |
| ..ftest::ChildOptions::EMPTY |
| }; |
| state_guard.mutable_children.insert(child.name, (child_options, child_node)); |
| } |
| } |
| |
| drop(state_guard); |
| Ok(self_) |
| } |
| .boxed() |
| } |
| |
| async fn get_sub_realm(&self, child_name: &String) -> Result<RealmNode2, RealmBuilderError> { |
| let state_guard = self.state.lock().await; |
| if state_guard.decl.children.iter().any(|c| &c.name == child_name) { |
| return Err(RealmBuilderError::ChildDeclNotVisible(child_name.clone())); |
| } |
| state_guard |
| .mutable_children |
| .get(child_name) |
| .cloned() |
| .map(|(_, r)| r) |
| .ok_or_else(|| RealmBuilderError::NoSuchChild(child_name.clone())) |
| } |
| |
| async fn route_capabilities( |
| &self, |
| capabilities: Vec<ftest::Capability2>, |
| from: fcdecl::Ref, |
| to: Vec<fcdecl::Ref>, |
| ) -> Result<(), RealmBuilderError> { |
| if capabilities.is_empty() { |
| return Err(RealmBuilderError::CapabilitiesEmpty); |
| } |
| |
| let mut state_guard = self.state.lock().await; |
| if !contains_child(state_guard.deref(), &from) { |
| return Err(RealmBuilderError::NoSuchSource(from, state_guard.deref().clone())); |
| } |
| |
| for capability in capabilities { |
| for target in &to { |
| if &from == target { |
| return Err(RealmBuilderError::SourceAndTargetMatch(from)); |
| } |
| |
| if !contains_child(state_guard.deref(), target) { |
| return Err(RealmBuilderError::NoSuchTarget( |
| target.clone(), |
| state_guard.deref().clone(), |
| )); |
| } |
| |
| if is_parent_ref(&target) { |
| let decl = |
| create_expose_decl(capability.clone(), from.clone(), ExposingIn::Realm)?; |
| push_if_not_present(&mut state_guard.decl.exposes, decl); |
| } else { |
| let decl = create_offer_decl(capability.clone(), from.clone(), target.clone())?; |
| push_if_not_present(&mut state_guard.decl.offers, decl); |
| } |
| |
| let () = add_use_decl_if_needed( |
| state_guard.deref_mut(), |
| target.clone(), |
| capability.clone(), |
| ) |
| .await?; |
| } |
| |
| let () = add_expose_decl_if_needed( |
| state_guard.deref_mut(), |
| from.clone(), |
| capability.clone(), |
| ) |
| .await?; |
| } |
| |
| Ok(()) |
| } |
| |
| fn build( |
| &self, |
| registry: Arc<resolver::Registry>, |
| walked_path: Vec<String>, |
| package_dir: fio::DirectoryProxy, |
| ) -> BoxFuture<'static, Result<String, RealmBuilderError>> { |
| // This function is much cleaner written recursively, but we can't construct recursive |
| // futures as the size isn't knowable to rustc at compile time. Put the recursive call |
| // into a boxed future, as the redirection makes this possible |
| let self_state = self.state.clone(); |
| async move { |
| let mut state_guard = self_state.lock().await; |
| // Expose the fuchsia.component.Binder protocol from root in order to give users the ability to manually |
| // start the realm. |
| if walked_path.is_empty() { |
| state_guard.decl.exposes.push(BINDER_EXPOSE_DECL.clone()); |
| } |
| |
| let mut mutable_children = state_guard.mutable_children.drain().collect::<Vec<_>>(); |
| mutable_children.sort_unstable_by_key(|t| t.0.clone()); |
| for (child_name, (child_options, node)) in mutable_children { |
| let mut new_path = walked_path.clone(); |
| new_path.push(child_name.clone()); |
| |
| let child_url = |
| node.build(registry.clone(), new_path, Clone::clone(&package_dir)).await?; |
| state_guard.add_child_decl(child_name, child_url, child_options); |
| } |
| |
| let name = |
| if walked_path.is_empty() { "root".to_string() } else { walked_path.join("-") }; |
| let decl = state_guard.decl.clone().native_into_fidl(); |
| let config_value_replacements = state_guard.config_value_replacements.clone(); |
| match registry |
| .validate_and_register( |
| &decl, |
| name, |
| Some(Clone::clone(&package_dir)), |
| config_value_replacements, |
| ) |
| .await |
| { |
| Ok(url) => Ok(url), |
| Err(e) => { |
| warn!( |
| "manifest validation failed during build step for component {:?}: {:?}", |
| walked_path, e |
| ); |
| Err(RealmBuilderError::InvalidComponentDeclWithName( |
| walked_path.join("/"), |
| e, |
| decl, |
| )) |
| } |
| } |
| } |
| .boxed() |
| } |
| } |
| |
| async fn add_use_decl_if_needed( |
| realm: &mut RealmNodeState, |
| ref_: fcdecl::Ref, |
| capability: ftest::Capability2, |
| ) -> Result<(), RealmBuilderError> { |
| if let fcdecl::Ref::Child(child) = ref_ { |
| if let Some(child) = realm.get_updateable_children().get(&child.name) { |
| let mut decl = child.get_decl().await; |
| push_if_not_present(&mut decl.uses, create_use_decl(capability)?); |
| let () = child.replace_decl(decl).await?; |
| } |
| } |
| |
| Ok(()) |
| } |
| |
| async fn add_expose_decl_if_needed( |
| realm: &mut RealmNodeState, |
| ref_: fcdecl::Ref, |
| capability: ftest::Capability2, |
| ) -> Result<(), RealmBuilderError> { |
| if let fcdecl::Ref::Child(child) = ref_ { |
| if let Some(child) = realm.get_updateable_children().get(&child.name) { |
| let mut decl = child.get_decl().await; |
| push_if_not_present( |
| &mut decl.capabilities, |
| create_capability_decl(capability.clone())?, |
| ); |
| push_if_not_present( |
| &mut decl.exposes, |
| create_expose_decl( |
| capability, |
| fcdecl::Ref::Self_(fcdecl::SelfRef {}), |
| ExposingIn::Child, |
| )?, |
| ); |
| let () = child.replace_decl(decl).await?; |
| } |
| } |
| |
| Ok(()) |
| } |
| |
| fn into_dependency_type(type_: &Option<fcdecl::DependencyType>) -> cm_rust::DependencyType { |
| type_ |
| .as_ref() |
| .cloned() |
| .map(FidlIntoNative::fidl_into_native) |
| .unwrap_or(cm_rust::DependencyType::Strong) |
| } |
| |
| /// Attempts to produce the target name from the set of "name" and "as" fields from a capability. |
| fn try_into_source_name( |
| name: &Option<String>, |
| ) -> Result<cm_rust::CapabilityName, RealmBuilderError> { |
| Ok(name |
| .as_ref() |
| .ok_or_else(|| { |
| RealmBuilderError::CapabilityInvalid(anyhow::format_err!( |
| "capability `name` received was empty" |
| )) |
| })? |
| .as_str() |
| .into()) |
| } |
| |
| /// Attempts to produce the target name from the set of "name" and "as" fields from a capability. |
| fn try_into_target_name( |
| name: &Option<String>, |
| as_: &Option<String>, |
| ) -> Result<cm_rust::CapabilityName, RealmBuilderError> { |
| let name = name.as_ref().ok_or_else(|| { |
| RealmBuilderError::CapabilityInvalid(anyhow::format_err!( |
| "capability `name` received was empty" |
| )) |
| })?; |
| Ok(as_.as_ref().unwrap_or(name).clone().into()) |
| } |
| |
| /// Attempts to produce a valid CapabilityPath from the "path" field from a capability |
| fn try_into_capability_path( |
| input: &Option<String>, |
| ) -> Result<cm_rust::CapabilityPath, RealmBuilderError> { |
| input |
| .as_ref() |
| .ok_or_else(|| { |
| RealmBuilderError::CapabilityInvalid(anyhow::format_err!( |
| "`path` field is required on capability when routing to or from a local component", |
| )) |
| })? |
| .as_str() |
| .try_into() |
| .map_err(|e| { |
| RealmBuilderError::CapabilityInvalid(anyhow::format_err!( |
| "`path` field in capability is invalid: {:?}", |
| e |
| )) |
| }) |
| } |
| |
| /// Attempts to produce a valid CapabilityPath from the "path" field from a capability, and if that |
| /// fails then attempts to produce a valid CapabilityPath from the "name" field following |
| /// "/svc/{name}" |
| fn try_into_service_path( |
| name: &Option<String>, |
| path: &Option<String>, |
| ) -> Result<cm_rust::CapabilityPath, RealmBuilderError> { |
| let name = name.as_ref().ok_or_else(|| { |
| RealmBuilderError::CapabilityInvalid(anyhow::format_err!( |
| "capability `name` received was empty" |
| )) |
| })?; |
| let path = path.as_ref().cloned().unwrap_or_else(|| format!("/svc/{}", name)); |
| path.as_str().try_into().map_err(|e| { |
| RealmBuilderError::CapabilityInvalid(anyhow::format_err!( |
| "unable to create service path: {:?}", |
| e |
| )) |
| }) |
| } |
| |
| fn create_capability_decl( |
| capability: ftest::Capability2, |
| ) -> Result<cm_rust::CapabilityDecl, RealmBuilderError> { |
| Ok(match capability { |
| ftest::Capability2::Protocol(protocol) => { |
| let name = try_into_source_name(&protocol.name)?; |
| let source_path = Some(try_into_service_path(&protocol.name, &protocol.path)?); |
| cm_rust::CapabilityDecl::Protocol(cm_rust::ProtocolDecl { name, source_path }) |
| } |
| ftest::Capability2::Directory(directory) => { |
| let name = try_into_source_name(&directory.name)?; |
| let source_path = Some(try_into_capability_path(&directory.path)?); |
| let rights = directory.rights.ok_or_else(|| RealmBuilderError::CapabilityInvalid( |
| anyhow::format_err!( |
| "`rights` field is required on directory capabilities when routing to or from a local component", |
| ), |
| ))?; |
| cm_rust::CapabilityDecl::Directory(cm_rust::DirectoryDecl { name, source_path, rights }) |
| } |
| ftest::Capability2::Storage(_) => { |
| return Err(RealmBuilderError::CapabilityInvalid(anyhow::format_err!( |
| "declaring storage capabilities with add_route is unsupported" |
| )))?; |
| } |
| ftest::Capability2::Service(service) => { |
| let name = try_into_source_name(&service.name)?; |
| let source_path = Some(try_into_service_path(&service.name, &service.path)?); |
| cm_rust::CapabilityDecl::Service(cm_rust::ServiceDecl { name, source_path }) |
| } |
| ftest::Capability2::Event(event) => { |
| let name = try_into_source_name(&event.name)?; |
| cm_rust::CapabilityDecl::Event(cm_rust::EventDecl { name }) |
| } |
| _ => { |
| return Err(RealmBuilderError::CapabilityInvalid(anyhow::format_err!( |
| "encountered unsupported capability variant: {:?}", |
| capability.clone() |
| ))); |
| } |
| }) |
| } |
| |
| fn create_offer_decl( |
| capability: ftest::Capability2, |
| source: fcdecl::Ref, |
| target: fcdecl::Ref, |
| ) -> Result<cm_rust::OfferDecl, RealmBuilderError> { |
| let source: cm_rust::OfferSource = source.fidl_into_native(); |
| let target: cm_rust::OfferTarget = target.fidl_into_native(); |
| |
| Ok(match capability { |
| ftest::Capability2::Protocol(protocol) => { |
| let source_name = try_into_source_name(&protocol.name)?; |
| let target_name = try_into_target_name(&protocol.name, &protocol.as_)?; |
| let dependency_type = into_dependency_type(&protocol.type_); |
| cm_rust::OfferDecl::Protocol(cm_rust::OfferProtocolDecl { |
| source, |
| source_name, |
| target, |
| target_name, |
| dependency_type, |
| }) |
| } |
| ftest::Capability2::Directory(directory) => { |
| let source_name = try_into_source_name(&directory.name)?; |
| let target_name = try_into_target_name(&directory.name, &directory.as_)?; |
| let dependency_type = into_dependency_type(&directory.type_); |
| cm_rust::OfferDecl::Directory(cm_rust::OfferDirectoryDecl { |
| source, |
| source_name, |
| target, |
| target_name, |
| rights: directory.rights, |
| subdir: directory.subdir.map(PathBuf::from), |
| dependency_type, |
| }) |
| } |
| ftest::Capability2::Storage(storage) => { |
| let source_name = try_into_source_name(&storage.name)?; |
| let target_name = try_into_target_name(&storage.name, &storage.as_)?; |
| cm_rust::OfferDecl::Storage(cm_rust::OfferStorageDecl { |
| source, |
| source_name, |
| target, |
| target_name, |
| }) |
| } |
| ftest::Capability2::Service(service) => { |
| let source_name = try_into_source_name(&service.name)?; |
| let target_name = try_into_target_name(&service.name, &service.as_)?; |
| cm_rust::OfferDecl::Service(cm_rust::OfferServiceDecl { |
| source, |
| source_name, |
| target, |
| target_name, |
| source_instance_filter: None, |
| renamed_instances: None, |
| }) |
| } |
| ftest::Capability2::Event(event) => { |
| let source_name = try_into_source_name(&event.name)?; |
| let target_name = try_into_target_name(&event.name, &event.as_)?; |
| let filter = event.filter.as_ref().cloned().map(FidlIntoNative::fidl_into_native); |
| cm_rust::OfferDecl::Event(cm_rust::OfferEventDecl { |
| source, |
| source_name, |
| target, |
| target_name, |
| filter, |
| }) |
| } |
| _ => { |
| return Err(RealmBuilderError::CapabilityInvalid(anyhow::format_err!( |
| "encountered unsupported capability variant: {:?}", |
| capability.clone() |
| ))); |
| } |
| }) |
| } |
| |
| // We only want to apply the rename for a capability once. If we're handling a route from a local |
| // component child to the parent, we want to use the source name in the child for the source and |
| // target names, and apply the rename (where the source_name and target_name fields don't match) in |
| // the parent. This field is used to track when an expose declaration is being generated for a |
| // child versus the parent realm. |
| enum ExposingIn { |
| Realm, |
| Child, |
| } |
| |
| fn create_expose_decl( |
| capability: ftest::Capability2, |
| source: fcdecl::Ref, |
| exposing_in: ExposingIn, |
| ) -> Result<cm_rust::ExposeDecl, RealmBuilderError> { |
| let source: cm_rust::ExposeSource = source.fidl_into_native(); |
| |
| Ok(match capability { |
| ftest::Capability2::Protocol(protocol) => { |
| let source_name = try_into_source_name(&protocol.name)?; |
| let target_name = match exposing_in { |
| ExposingIn::Child => try_into_source_name(&protocol.name)?, |
| ExposingIn::Realm => try_into_target_name(&protocol.name, &protocol.as_)?, |
| }; |
| cm_rust::ExposeDecl::Protocol(cm_rust::ExposeProtocolDecl { |
| source: source.clone(), |
| source_name, |
| target: cm_rust::ExposeTarget::Parent, |
| target_name, |
| }) |
| } |
| ftest::Capability2::Directory(directory) => { |
| let source_name = try_into_source_name(&directory.name)?; |
| let target_name = match exposing_in { |
| ExposingIn::Child => try_into_source_name(&directory.name)?, |
| ExposingIn::Realm => try_into_target_name(&directory.name, &directory.as_)?, |
| }; |
| // Much like capability renames, we want to only apply the subdir field once. Use the |
| // exposing_in field to ensure that we apply the subdir field in the parent, and not in |
| // a local child's manifest. |
| let subdir = match exposing_in { |
| ExposingIn::Child => None, |
| ExposingIn::Realm => directory.subdir.map(PathBuf::from), |
| }; |
| cm_rust::ExposeDecl::Directory(cm_rust::ExposeDirectoryDecl { |
| source, |
| source_name, |
| target: cm_rust::ExposeTarget::Parent, |
| target_name, |
| rights: directory.rights, |
| subdir, |
| }) |
| } |
| ftest::Capability2::Storage(_) => { |
| return Err(RealmBuilderError::CapabilityInvalid(anyhow::format_err!( |
| "storage capabilities cannot be exposed: {:?}", |
| capability.clone() |
| ))); |
| } |
| ftest::Capability2::Service(service) => { |
| let source_name = try_into_source_name(&service.name)?; |
| let target_name = match exposing_in { |
| ExposingIn::Child => try_into_source_name(&service.name)?, |
| ExposingIn::Realm => try_into_target_name(&service.name, &service.as_)?, |
| }; |
| cm_rust::ExposeDecl::Service(cm_rust::ExposeServiceDecl { |
| source, |
| source_name, |
| target: cm_rust::ExposeTarget::Parent, |
| target_name, |
| }) |
| } |
| ftest::Capability2::Event(_) => { |
| return Err(RealmBuilderError::CapabilityInvalid(anyhow::format_err!( |
| "event capabilities cannot be exposed: {:?}", |
| capability.clone() |
| ))); |
| } |
| _ => { |
| return Err(RealmBuilderError::CapabilityInvalid(anyhow::format_err!( |
| "encountered unsupported capability variant: {:?}", |
| capability.clone() |
| ))); |
| } |
| }) |
| } |
| |
| fn create_use_decl(capability: ftest::Capability2) -> Result<cm_rust::UseDecl, RealmBuilderError> { |
| Ok(match capability { |
| ftest::Capability2::Protocol(protocol) => { |
| // If the capability was renamed in the parent's offer declaration, we want to use the |
| // post-rename version of it here. |
| let source_name = try_into_target_name(&protocol.name, &protocol.as_)?; |
| let target_path = try_into_service_path( |
| &Some(source_name.clone().native_into_fidl()), |
| &protocol.path, |
| )?; |
| let dependency_type = protocol |
| .type_ |
| .map(FidlIntoNative::fidl_into_native) |
| .unwrap_or(cm_rust::DependencyType::Strong); |
| cm_rust::UseDecl::Protocol(cm_rust::UseProtocolDecl { |
| source: cm_rust::UseSource::Parent, |
| source_name, |
| target_path, |
| dependency_type, |
| }) |
| } |
| ftest::Capability2::Directory(directory) => { |
| // If the capability was renamed in the parent's offer declaration, we want to use the |
| // post-rename version of it here. |
| let source_name = try_into_target_name(&directory.name, &directory.as_)?; |
| let target_path = try_into_capability_path(&directory.path)?; |
| let rights = directory.rights.ok_or_else(|| RealmBuilderError::CapabilityInvalid( |
| anyhow::format_err!( |
| "`rights` field is required on directory capabilities when routing to or from a local component", |
| ), |
| ))?; |
| let dependency_type = directory |
| .type_ |
| .map(FidlIntoNative::fidl_into_native) |
| .unwrap_or(cm_rust::DependencyType::Strong); |
| cm_rust::UseDecl::Directory(cm_rust::UseDirectoryDecl { |
| source: cm_rust::UseSource::Parent, |
| source_name, |
| target_path, |
| rights, |
| // We only want to set the sub-directory field once, and if we're generating a use |
| // declaration then we've already generated an offer declaration in the parent and |
| // we'll set the sub-directory field there. |
| subdir: None, |
| dependency_type, |
| }) |
| } |
| ftest::Capability2::Storage(storage) => { |
| // If the capability was renamed in the parent's offer declaration, we want to use the |
| // post-rename version of it here. |
| let source_name = try_into_target_name(&storage.name, &storage.as_)?; |
| let target_path = try_into_capability_path(&storage.path)?; |
| cm_rust::UseDecl::Storage(cm_rust::UseStorageDecl { source_name, target_path }) |
| } |
| ftest::Capability2::Service(service) => { |
| // If the capability was renamed in the parent's offer declaration, we want to use the |
| // post-rename version of it here. |
| let source_name = try_into_target_name(&service.name, &service.as_)?; |
| let target_path = try_into_service_path( |
| &Some(source_name.clone().native_into_fidl()), |
| &service.path, |
| )?; |
| cm_rust::UseDecl::Service(cm_rust::UseServiceDecl { |
| source: cm_rust::UseSource::Parent, |
| source_name, |
| target_path, |
| dependency_type: cm_rust::DependencyType::Strong, |
| }) |
| } |
| ftest::Capability2::Event(event) => { |
| // If the capability was renamed in the parent's offer declaration, we want to use the |
| // post-rename version of it here. |
| let source_name = try_into_target_name(&event.name, &event.as_)?; |
| let filter = event.filter.as_ref().cloned().map(FidlIntoNative::fidl_into_native); |
| cm_rust::UseDecl::Event(cm_rust::UseEventDecl { |
| source: cm_rust::UseSource::Parent, |
| source_name: source_name.clone(), |
| target_name: source_name, |
| filter, |
| dependency_type: cm_rust::DependencyType::Strong, |
| }) |
| } |
| _ => { |
| return Err(RealmBuilderError::CapabilityInvalid(anyhow::format_err!( |
| "encountered unsupported capability variant: {:?}", |
| capability.clone() |
| ))); |
| } |
| }) |
| } |
| |
| fn contains_child(realm: &RealmNodeState, ref_: &fcdecl::Ref) -> bool { |
| match ref_ { |
| fcdecl::Ref::Child(child) => { |
| let children = realm |
| .decl |
| .children |
| .iter() |
| .map(|c| c.name.clone()) |
| .chain(realm.mutable_children.iter().map(|(name, _)| name.clone())) |
| .collect::<Vec<_>>(); |
| children.contains(&child.name) |
| } |
| _ => true, |
| } |
| } |
| |
| fn is_parent_ref(ref_: &fcdecl::Ref) -> bool { |
| match ref_ { |
| fcdecl::Ref::Parent(_) => true, |
| _ => false, |
| } |
| } |
| |
| fn push_if_not_present<T: PartialEq>(container: &mut Vec<T>, value: T) { |
| if !container.contains(&value) { |
| container.push(value); |
| } |
| } |
| |
| // If this realm node is going have its decl replaced, we need to ensure |
| // that the program section isn't corrupted. This is only necessary for |
| // legacy components, whose program section is generated by |
| // realm builder. In that case, we only allow specific modifications to |
| // the program section. Specifically, we only allow adding an `args` entry for |
| // legacy components. |
| fn validate_program_modifications( |
| old_decl: &cm_rust::ComponentDecl, |
| new_decl: &cm_rust::ComponentDecl, |
| ) -> Result<(), RealmBuilderError> { |
| if old_decl.program.as_ref().and_then(|p| p.runner.as_ref()) |
| == Some(&runner::RUNNER_NAME.into()) |
| { |
| let new_decl_program = match new_decl.program.as_ref() { |
| Some(program) => { |
| if program_contains_entry(program, runner::LEGACY_URL_KEY) |
| && program_contains_entry(program, ALLOWLISTED_PROGRAM_ARGS_KEY) |
| { |
| program_with_entry_removed(program.clone(), ALLOWLISTED_PROGRAM_ARGS_KEY) |
| } else { |
| program.clone() |
| } |
| } |
| None => { |
| return Err(RealmBuilderError::ImmutableProgram); |
| } |
| }; |
| |
| // We know that `old_decl.program` is `Some(_)` because we're inside |
| // this `if` clause. Therefore, it's safe to check equality against |
| // `Some(new_decl_program)`. |
| if old_decl.program != Some(new_decl_program) { |
| return Err(RealmBuilderError::ImmutableProgram); |
| } |
| } |
| |
| Ok(()) |
| } |
| |
| fn program_contains_entry(program: &cm_rust::ProgramDecl, key: &str) -> bool { |
| if let Some(entries) = program.info.entries.as_ref() { |
| return entries.iter().any(|entry| entry.key == key); |
| } |
| |
| return false; |
| } |
| |
| fn program_with_entry_removed( |
| mut program: cm_rust::ProgramDecl, |
| key: &str, |
| ) -> cm_rust::ProgramDecl { |
| if let Some(entries) = program.info.entries.as_mut() { |
| entries.retain(|entry| entry.key != key); |
| } |
| |
| return program; |
| } |
| |
| #[allow(unused)] |
| #[derive(Debug, Error)] |
| enum RealmBuilderError { |
| /// Child cannot be added to the realm, as there is already a child in the realm with that |
| /// name. |
| #[error("unable to add child to the realm because a child already exists with the name {0:?}")] |
| ChildAlreadyExists(String), |
| |
| /// A legacy component URL was given to `AddChild`, or a modern component url was given to |
| /// `AddLegacyChild`. |
| #[error("manifest extension was inappropriate for the function, use AddChild for `.cm` URLs and AddLegacyChild for `.cmx` URLs")] |
| InvalidManifestExtension, |
| |
| /// A component declaration failed validation. |
| #[error("a component manifest failed validation: {0:?}\n{1:#?}")] |
| InvalidComponentDecl(cm_fidl_validator::error::ErrorList, fcdecl::Component), |
| |
| /// A component declaration failed validation. |
| #[error("the manifest for component {0:?} failed validation: {1:?}\n{2:#?}")] |
| InvalidComponentDeclWithName(String, cm_fidl_validator::error::ErrorList, fcdecl::Component), |
| |
| /// The referenced child does not exist. |
| #[error("there is no component named {0:?} in this realm")] |
| NoSuchChild(String), |
| |
| /// The component declaration for the referenced child cannot be viewed nor manipulated by |
| /// RealmBuilder because the child was added to the realm using an URL that was neither a |
| /// relative nor a legacy URL. |
| #[error("the component declaration for {0:?} cannot be viewed nor manipulated")] |
| ChildDeclNotVisible(String), |
| |
| /// The source does not exist. |
| #[error("the source for the route does not exist: {0:?}\namong declared children of: {:#?}")] |
| NoSuchSource(fcdecl::Ref, RealmNodeState), |
| |
| /// A target does not exist. |
| #[error("the target for the route does not exist: {0:?}\namong declared children of: {:#?}")] |
| NoSuchTarget(fcdecl::Ref, RealmNodeState), |
| |
| /// The `capabilities` field is empty. |
| #[error("the \"capabilities\" field is empty")] |
| CapabilitiesEmpty, |
| |
| /// The `targets` field is empty. |
| #[error("the \"targets\" field is empty")] |
| TargetsEmpty, |
| |
| /// The `from` value is equal to one of the elements in `to`. |
| #[error("one of the targets is equal to the source: {0:?}")] |
| SourceAndTargetMatch(fcdecl::Ref), |
| |
| /// The test package does not contain the component declaration referenced by a relative URL. |
| #[error("the test package does not contain this component: {0:?}")] |
| DeclNotFound(String), |
| |
| /// Encountered an I/O error when attempting to read a component declaration referenced by a |
| /// relative URL from the test package. |
| #[error("failed to read the manifest for {0:?} from the test package: {1:?}")] |
| DeclReadError(String, anyhow::Error), |
| |
| /// The `Build` function has been called multiple times on this channel. |
| #[error("the build function was called multiple times")] |
| BuildAlreadyCalled, |
| |
| #[error("invalid capability received: {0:?}")] |
| CapabilityInvalid(anyhow::Error), |
| |
| /// The handle the client provided is not usable |
| #[error("unable to use the provided handle: {0:?}")] |
| InvalidChildRealmHandle(fidl::Error), |
| |
| /// `ReplaceComponentDecl` was called on a legacy or local component with a program declaration |
| /// that did not match the one from the old component declaration. This could render a legacy |
| /// or local component non-functional, and is disallowed. |
| #[error( |
| "the program section of the manifest for a legacy or local component cannot be changed" |
| )] |
| ImmutableProgram, |
| |
| /// The component does not have a config schema defined. Attempting to |
| /// set a config value is not allowed. |
| #[error("component decl does not have a config schema defined")] |
| NoConfigSchema, |
| |
| /// The component's config schema does not have a field with that name. |
| #[error("could not find config field in config schema: {0:?}")] |
| NoSuchConfigField(String), |
| |
| /// A config value is invalid. This may mean a type mismatch or an issue |
| /// with constraints like string/vector length. |
| #[error("config value is invalid")] |
| ConfigValueInvalid, |
| } |
| |
| impl From<RealmBuilderError> for ftest::RealmBuilderError2 { |
| fn from(err: RealmBuilderError) -> Self { |
| match err { |
| RealmBuilderError::ChildAlreadyExists(_) => Self::ChildAlreadyExists, |
| RealmBuilderError::InvalidManifestExtension => Self::InvalidManifestExtension, |
| RealmBuilderError::InvalidComponentDecl(_, _) => Self::InvalidComponentDecl, |
| RealmBuilderError::InvalidComponentDeclWithName(_, _, _) => Self::InvalidComponentDecl, |
| RealmBuilderError::NoSuchChild(_) => Self::NoSuchChild, |
| RealmBuilderError::ChildDeclNotVisible(_) => Self::ChildDeclNotVisible, |
| RealmBuilderError::NoSuchSource(_, _) => Self::NoSuchSource, |
| RealmBuilderError::NoSuchTarget(_, _) => Self::NoSuchTarget, |
| RealmBuilderError::CapabilitiesEmpty => Self::CapabilitiesEmpty, |
| RealmBuilderError::TargetsEmpty => Self::TargetsEmpty, |
| RealmBuilderError::SourceAndTargetMatch(_) => Self::SourceAndTargetMatch, |
| RealmBuilderError::DeclNotFound(_) => Self::DeclNotFound, |
| RealmBuilderError::DeclReadError(_, _) => Self::DeclReadError, |
| RealmBuilderError::BuildAlreadyCalled => Self::BuildAlreadyCalled, |
| RealmBuilderError::CapabilityInvalid(_) => Self::CapabilityInvalid, |
| RealmBuilderError::InvalidChildRealmHandle(_) => Self::InvalidChildRealmHandle, |
| RealmBuilderError::ImmutableProgram => Self::ImmutableProgram, |
| RealmBuilderError::NoConfigSchema => Self::NoConfigSchema, |
| RealmBuilderError::NoSuchConfigField(_) => Self::NoSuchConfigField, |
| RealmBuilderError::ConfigValueInvalid => Self::ConfigValueInvalid, |
| } |
| } |
| } |
| |
| fn is_relative_url(url: &str) -> bool { |
| if url.len() == 0 || url.chars().nth(0) != Some('#') { |
| return false; |
| } |
| if Url::parse(url) != Err(url::ParseError::RelativeUrlWithoutBase) { |
| return false; |
| } |
| true |
| } |
| |
| fn is_legacy_url(url: &str) -> bool { |
| url.trim().ends_with(".cmx") |
| } |
| |
| #[cfg(test)] |
| mod tests { |
| use { |
| super::*, |
| assert_matches::assert_matches, |
| fidl::endpoints::{ |
| create_endpoints, create_proxy, create_proxy_and_stream, create_request_stream, |
| ClientEnd, |
| }, |
| fidl_fuchsia_io as fio, fidl_fuchsia_mem as fmem, fuchsia_async as fasync, |
| fuchsia_zircon as zx, |
| maplit::hashmap, |
| std::convert::TryInto, |
| test_case::test_case, |
| }; |
| |
| const EXAMPLE_LEGACY_URL: &'static str = "fuchsia-pkg://fuchsia.com/a#meta/a.cmx"; |
| |
| #[derive(Debug, Clone, PartialEq)] |
| struct ComponentTree { |
| decl: cm_rust::ComponentDecl, |
| children: Vec<(String, ftest::ChildOptions, ComponentTree)>, |
| } |
| |
| impl ComponentTree { |
| fn new_from_resolver( |
| url: String, |
| registry: Arc<resolver::Registry>, |
| ) -> BoxFuture<'static, Option<ComponentTree>> { |
| async move { |
| let decl_from_resolver = match registry.get_decl_for_url(&url).await { |
| Some(decl) => decl, |
| None => return None, |
| }; |
| |
| let mut self_ = ComponentTree { decl: decl_from_resolver, children: vec![] }; |
| let children = self_.decl.children.drain(..).collect::<Vec<_>>(); |
| for child in children { |
| match Self::new_from_resolver(child.url.clone(), registry.clone()).await { |
| None => { |
| self_.decl.children.push(child); |
| } |
| Some(child_tree) => { |
| let child_options = ftest::ChildOptions { |
| startup: match child.startup { |
| fcdecl::StartupMode::Eager => Some(fcdecl::StartupMode::Eager), |
| fcdecl::StartupMode::Lazy => None, |
| }, |
| environment: child.environment, |
| on_terminate: match child.on_terminate { |
| Some(fcdecl::OnTerminate::None) => { |
| Some(fcdecl::OnTerminate::None) |
| } |
| Some(fcdecl::OnTerminate::Reboot) => { |
| Some(fcdecl::OnTerminate::Reboot) |
| } |
| None => None, |
| }, |
| ..ftest::ChildOptions::EMPTY |
| }; |
| self_.children.push((child.name, child_options, child_tree)); |
| } |
| } |
| } |
| Some(self_) |
| } |
| .boxed() |
| } |
| |
| // Adds the `BINDER_EXPOSE_DECL` to the root component in the tree |
| fn add_binder_expose(&mut self) { |
| self.decl.exposes.push(BINDER_EXPOSE_DECL.clone()); |
| } |
| } |
| |
| fn tree_to_realm_node(tree: ComponentTree) -> BoxFuture<'static, RealmNode2> { |
| async move { |
| let node = RealmNode2::new_from_decl(tree.decl, false); |
| for (child_name, options, tree) in tree.children { |
| let child_node = tree_to_realm_node(tree).await; |
| node.state.lock().await.mutable_children.insert(child_name, (options, child_node)); |
| } |
| node |
| } |
| .boxed() |
| } |
| |
| // Builds the given ComponentTree, and returns the root URL and the resolver that holds the |
| // built declarations |
| async fn build_tree( |
| tree: &mut ComponentTree, |
| ) -> Result<(String, Arc<resolver::Registry>), ftest::RealmBuilderError2> { |
| let res = build_tree_helper(tree.clone()).await; |
| |
| // We want to be able to check our component tree against the registry later, but the |
| // builder automatically puts stuff into the root realm when building. Add that to our |
| // local tree here, so that our tree looks the same as what hopefully got put in the |
| // resolver. |
| tree.add_binder_expose(); |
| |
| res |
| } |
| |
| fn launch_builder_task( |
| realm_node: RealmNode2, |
| registry: Arc<resolver::Registry>, |
| runner_proxy_placeholder: Arc<Mutex<Option<fcrunner::ComponentRunnerProxy>>>, |
| realm_has_been_built: Arc<AtomicBool>, |
| ) -> (ftest::BuilderProxy, fasync::Task<()>) { |
| let (pkg_dir, pkg_dir_stream) = create_proxy_and_stream::<fio::DirectoryMarker>().unwrap(); |
| drop(pkg_dir_stream); |
| |
| let builder = Builder { |
| pkg_dir, |
| realm_node, |
| registry, |
| runner_proxy_placeholder, |
| realm_has_been_built, |
| }; |
| |
| let (builder_proxy, builder_stream) = |
| create_proxy_and_stream::<ftest::BuilderMarker>().unwrap(); |
| |
| let builder_stream_task = fasync::Task::local(async move { |
| builder.handle_stream(builder_stream).await.expect("failed to handle builder stream"); |
| }); |
| (builder_proxy, builder_stream_task) |
| } |
| |
| async fn build_tree_helper( |
| tree: ComponentTree, |
| ) -> Result<(String, Arc<resolver::Registry>), ftest::RealmBuilderError2> { |
| let realm_node = tree_to_realm_node(tree).await; |
| |
| let registry = resolver::Registry::new(); |
| let (builder_proxy, _builder_stream_task) = launch_builder_task( |
| realm_node, |
| registry.clone(), |
| Arc::new(Mutex::new(None)), |
| Arc::new(AtomicBool::new(false)), |
| ); |
| |
| let (runner_client_end, runner_server_end) = create_endpoints().unwrap(); |
| drop(runner_server_end); |
| let res = |
| builder_proxy.build(runner_client_end).await.expect("failed to send build command"); |
| match res { |
| Ok(url) => Ok((url, registry)), |
| Err(e) => Err(e), |
| } |
| } |
| |
| // Holds the task for handling a new realm stream and a new builder stream, along with proxies |
| // for those streams and the registry and runner the tasks will manipulate. |
| #[allow(unused)] |
| struct RealmAndBuilderTask { |
| realm_proxy: ftest::RealmProxy, |
| builder_proxy: ftest::BuilderProxy, |
| registry: Arc<resolver::Registry>, |
| runner: Arc<runner::Runner>, |
| _realm_and_builder_task: fasync::Task<()>, |
| runner_stream: fcrunner::ComponentRunnerRequestStream, |
| runner_client_end: Option<ClientEnd<fcrunner::ComponentRunnerMarker>>, |
| } |
| |
| impl RealmAndBuilderTask { |
| fn new() -> Self { |
| let (realm_proxy, realm_stream) = |
| create_proxy_and_stream::<ftest::RealmMarker>().unwrap(); |
| let pkg_dir = io_util::open_directory_in_namespace( |
| "/pkg", |
| fio::OpenFlags::RIGHT_READABLE | fio::OpenFlags::RIGHT_EXECUTABLE, |
| ) |
| .unwrap(); |
| let realm_root = RealmNode2::new(); |
| |
| let registry = resolver::Registry::new(); |
| let runner = runner::Runner::new(); |
| let runner_proxy_placeholder = Arc::new(Mutex::new(None)); |
| |
| let realm_has_been_built = Arc::new(AtomicBool::new(false)); |
| |
| let (builder_proxy, builder_task) = launch_builder_task( |
| realm_root.clone(), |
| registry.clone(), |
| runner_proxy_placeholder.clone(), |
| realm_has_been_built.clone(), |
| ); |
| |
| let realm = Realm { |
| pkg_dir, |
| realm_node: realm_root, |
| registry: registry.clone(), |
| runner: runner.clone(), |
| runner_proxy_placeholder, |
| realm_path: vec![], |
| execution_scope: ExecutionScope::new(), |
| realm_has_been_built, |
| }; |
| |
| let realm_and_builder_task = fasync::Task::local(async move { |
| realm.handle_stream(realm_stream).await.expect("failed to handle realm stream"); |
| builder_task.await; |
| }); |
| let (runner_client_end, runner_stream) = |
| create_request_stream::<fcrunner::ComponentRunnerMarker>().unwrap(); |
| Self { |
| realm_proxy, |
| builder_proxy, |
| registry, |
| runner, |
| _realm_and_builder_task: realm_and_builder_task, |
| runner_stream, |
| runner_client_end: Some(runner_client_end), |
| } |
| } |
| |
| async fn call_build(&mut self) -> Result<String, ftest::RealmBuilderError2> { |
| self.builder_proxy |
| .build(self.runner_client_end.take().expect("call_build called twice")) |
| .await |
| .expect("failed to send build command") |
| } |
| |
| // Calls `Builder.Build` on `self.builder_proxy`, which should populate `self.registry` |
| // with the contents of the realm and then return the URL for the root of this realm. That |
| // URL is then used to look up the `ComponentTree` that ended up in the resolver, which can |
| // be `assert_eq`'d against what the tree is expected to be. |
| async fn call_build_and_get_tree(&mut self) -> ComponentTree { |
| let url = self.call_build().await.expect("builder unexpectedly returned an error"); |
| ComponentTree::new_from_resolver(url, self.registry.clone()) |
| .await |
| .expect("tree missing from resolver") |
| } |
| |
| async fn add_child_or_panic(&self, name: &str, url: &str, options: ftest::ChildOptions) { |
| let () = self |
| .realm_proxy |
| .add_child(name, url, options) |
| .await |
| .expect("failed to make Realm.AddChild call") |
| .expect("failed to add child"); |
| } |
| |
| async fn add_route_or_panic( |
| &self, |
| mut capabilities: Vec<ftest::Capability2>, |
| mut from: fcdecl::Ref, |
| mut tos: Vec<fcdecl::Ref>, |
| ) { |
| let () = self |
| .realm_proxy |
| .add_route(&mut capabilities.iter_mut(), &mut from, &mut tos.iter_mut()) |
| .await |
| .expect("failed to make Realm.AddRoute call") |
| .expect("failed to add route"); |
| } |
| } |
| |
| #[fuchsia::test] |
| async fn build_called_twice() { |
| let realm_node = RealmNode2::new(); |
| |
| let (builder_proxy, _builder_stream_task) = launch_builder_task( |
| realm_node, |
| resolver::Registry::new(), |
| Arc::new(Mutex::new(None)), |
| Arc::new(AtomicBool::new(false)), |
| ); |
| |
| let (runner_client_end, runner_server_end) = create_endpoints().unwrap(); |
| drop(runner_server_end); |
| let res = |
| builder_proxy.build(runner_client_end).await.expect("failed to send build command"); |
| assert!(res.is_ok()); |
| |
| let (runner_client_end, runner_server_end) = create_endpoints().unwrap(); |
| drop(runner_server_end); |
| let res = |
| builder_proxy.build(runner_client_end).await.expect("failed to send build command"); |
| assert_eq!(Err(ftest::RealmBuilderError2::BuildAlreadyCalled), res); |
| } |
| |
| #[fuchsia::test] |
| async fn build_empty_realm() { |
| let mut tree = ComponentTree { decl: cm_rust::ComponentDecl::default(), children: vec![] }; |
| let (root_url, registry) = build_tree(&mut tree).await.expect("failed to build tree"); |
| let tree_from_resolver = ComponentTree::new_from_resolver(root_url, registry).await; |
| assert_eq!(Some(tree), tree_from_resolver); |
| } |
| |
| #[fuchsia::test] |
| async fn building_invalid_realm_errors() { |
| let mut tree = ComponentTree { |
| decl: cm_rust::ComponentDecl { |
| offers: vec![cm_rust::OfferDecl::Protocol(cm_rust::OfferProtocolDecl { |
| source: cm_rust::OfferSource::Parent, |
| source_name: "fuchsia.logger.LogSink".into(), |
| target_name: "fuchsia.logger.LogSink".into(), |
| dependency_type: cm_rust::DependencyType::Strong, |
| |
| // This doesn't exist |
| target: cm_rust::OfferTarget::Child(cm_rust::ChildRef { |
| name: "a".to_string(), |
| collection: None, |
| }), |
| })], |
| ..cm_rust::ComponentDecl::default() |
| }, |
| children: vec![], |
| }; |
| let error = build_tree(&mut tree).await.expect_err("builder didn't notice invalid decl"); |
| assert_eq!(error, ftest::RealmBuilderError2::InvalidComponentDecl); |
| } |
| |
| #[fuchsia::test] |
| async fn build_realm_with_child_decl() { |
| let mut tree = ComponentTree { |
| decl: cm_rust::ComponentDecl { |
| offers: vec![cm_rust::OfferDecl::Protocol(cm_rust::OfferProtocolDecl { |
| source: cm_rust::OfferSource::Parent, |
| target: cm_rust::OfferTarget::Child(cm_rust::ChildRef { |
| name: "a".to_string(), |
| collection: None, |
| }), |
| source_name: "fuchsia.logger.LogSink".into(), |
| target_name: "fuchsia.logger.LogSink".into(), |
| dependency_type: cm_rust::DependencyType::Strong, |
| })], |
| children: vec![cm_rust::ChildDecl { |
| name: "a".to_string(), |
| url: "test://a".to_string(), |
| startup: fcdecl::StartupMode::Lazy, |
| on_terminate: None, |
| environment: None, |
| }], |
| ..cm_rust::ComponentDecl::default() |
| }, |
| children: vec![], |
| }; |
| let (root_url, registry) = build_tree(&mut tree).await.expect("failed to build tree"); |
| let tree_from_resolver = ComponentTree::new_from_resolver(root_url, registry).await; |
| assert_eq!(Some(tree), tree_from_resolver); |
| } |
| |
| #[fuchsia::test] |
| async fn build_realm_with_mutable_child() { |
| let mut tree = ComponentTree { |
| decl: cm_rust::ComponentDecl { |
| offers: vec![cm_rust::OfferDecl::Protocol(cm_rust::OfferProtocolDecl { |
| source: cm_rust::OfferSource::Parent, |
| target: cm_rust::OfferTarget::Child(cm_rust::ChildRef { |
| name: "a".to_string(), |
| collection: None, |
| }), |
| source_name: "fuchsia.logger.LogSink".into(), |
| target_name: "fuchsia.logger.LogSink".into(), |
| dependency_type: cm_rust::DependencyType::Strong, |
| })], |
| ..cm_rust::ComponentDecl::default() |
| }, |
| children: vec![( |
| "a".to_string(), |
| ftest::ChildOptions::EMPTY, |
| ComponentTree { decl: cm_rust::ComponentDecl::default(), children: vec![] }, |
| )], |
| }; |
| let (root_url, registry) = build_tree(&mut tree).await.expect("failed to build tree"); |
| let tree_from_resolver = ComponentTree::new_from_resolver(root_url, registry).await; |
| assert_eq!(Some(tree), tree_from_resolver); |
| } |
| |
| #[fuchsia::test] |
| async fn build_realm_with_child_decl_and_mutable_child() { |
| let mut tree = ComponentTree { |
| decl: cm_rust::ComponentDecl { |
| offers: vec![cm_rust::OfferDecl::Protocol(cm_rust::OfferProtocolDecl { |
| source: cm_rust::OfferSource::Parent, |
| target: cm_rust::OfferTarget::Child(cm_rust::ChildRef { |
| name: "a".to_string(), |
| collection: None, |
| }), |
| source_name: "fuchsia.logger.LogSink".into(), |
| target_name: "fuchsia.logger.LogSink".into(), |
| dependency_type: cm_rust::DependencyType::Strong, |
| })], |
| children: vec![cm_rust::ChildDecl { |
| name: "a".to_string(), |
| url: "test://a".to_string(), |
| startup: fcdecl::StartupMode::Lazy, |
| on_terminate: None, |
| environment: None, |
| }], |
| ..cm_rust::ComponentDecl::default() |
| }, |
| children: vec![( |
| "b".to_string(), |
| ftest::ChildOptions::EMPTY, |
| ComponentTree { decl: cm_rust::ComponentDecl::default(), children: vec![] }, |
| )], |
| }; |
| let (root_url, registry) = build_tree(&mut tree).await.expect("failed to build tree"); |
| let tree_from_resolver = ComponentTree::new_from_resolver(root_url, registry).await; |
| assert_eq!(Some(tree), tree_from_resolver); |
| } |
| |
| #[fuchsia::test] |
| async fn build_realm_with_mutable_grandchild() { |
| let mut tree = ComponentTree { |
| decl: cm_rust::ComponentDecl { |
| offers: vec![cm_rust::OfferDecl::Protocol(cm_rust::OfferProtocolDecl { |
| source: cm_rust::OfferSource::Parent, |
| target: cm_rust::OfferTarget::Child(cm_rust::ChildRef { |
| name: "a".to_string(), |
| collection: None, |
| }), |
| source_name: "fuchsia.logger.LogSink".into(), |
| target_name: "fuchsia.logger.LogSink".into(), |
| dependency_type: cm_rust::DependencyType::Strong, |
| })], |
| ..cm_rust::ComponentDecl::default() |
| }, |
| children: vec![( |
| "a".to_string(), |
| ftest::ChildOptions::EMPTY, |
| ComponentTree { |
| decl: cm_rust::ComponentDecl { |
| offers: vec![cm_rust::OfferDecl::Protocol(cm_rust::OfferProtocolDecl { |
| source: cm_rust::OfferSource::Parent, |
| target: cm_rust::OfferTarget::Child(cm_rust::ChildRef { |
| name: "b".to_string(), |
| collection: None, |
| }), |
| source_name: "fuchsia.logger.LogSink".into(), |
| target_name: "fuchsia.logger.LogSink".into(), |
| dependency_type: cm_rust::DependencyType::Strong, |
| })], |
| ..cm_rust::ComponentDecl::default() |
| }, |
| children: vec![( |
| "b".to_string(), |
| ftest::ChildOptions::EMPTY, |
| ComponentTree { decl: cm_rust::ComponentDecl::default(), children: vec![] }, |
| )], |
| }, |
| )], |
| }; |
| let (root_url, registry) = build_tree(&mut tree).await.expect("failed to build tree"); |
| let tree_from_resolver = ComponentTree::new_from_resolver(root_url, registry).await; |
| assert_eq!(Some(tree), tree_from_resolver); |
| } |
| |
| #[fuchsia::test] |
| async fn build_realm_with_eager_mutable_child() { |
| let mut tree = ComponentTree { |
| decl: cm_rust::ComponentDecl { |
| offers: vec![cm_rust::OfferDecl::Protocol(cm_rust::OfferProtocolDecl { |
| source: cm_rust::OfferSource::Parent, |
| target: cm_rust::OfferTarget::Child(cm_rust::ChildRef { |
| name: "a".to_string(), |
| collection: None, |
| }), |
| source_name: "fuchsia.logger.LogSink".into(), |
| target_name: "fuchsia.logger.LogSink".into(), |
| dependency_type: cm_rust::DependencyType::Strong, |
| })], |
| ..cm_rust::ComponentDecl::default() |
| }, |
| children: vec![( |
| "a".to_string(), |
| ftest::ChildOptions { |
| startup: Some(fcdecl::StartupMode::Eager), |
| ..ftest::ChildOptions::EMPTY |
| }, |
| ComponentTree { decl: cm_rust::ComponentDecl::default(), children: vec![] }, |
| )], |
| }; |
| let (root_url, registry) = build_tree(&mut tree).await.expect("failed to build tree"); |
| let tree_from_resolver = ComponentTree::new_from_resolver(root_url, registry).await; |
| assert_eq!(Some(tree), tree_from_resolver); |
| } |
| |
| #[fuchsia::test] |
| async fn build_realm_with_mutable_child_in_a_new_environment() { |
| let mut tree = ComponentTree { |
| decl: cm_rust::ComponentDecl { |
| offers: vec![cm_rust::OfferDecl::Protocol(cm_rust::OfferProtocolDecl { |
| source: cm_rust::OfferSource::Parent, |
| target: cm_rust::OfferTarget::Child(cm_rust::ChildRef { |
| name: "a".to_string(), |
| collection: None, |
| }), |
| source_name: "fuchsia.logger.LogSink".into(), |
| target_name: "fuchsia.logger.LogSink".into(), |
| dependency_type: cm_rust::DependencyType::Strong, |
| })], |
| environments: vec![cm_rust::EnvironmentDecl { |
| name: "new-env".to_string(), |
| extends: fcdecl::EnvironmentExtends::None, |
| resolvers: vec![cm_rust::ResolverRegistration { |
| resolver: "test".try_into().unwrap(), |
| source: cm_rust::RegistrationSource::Parent, |
| scheme: "test".to_string(), |
| }], |
| runners: vec![], |
| debug_capabilities: vec![], |
| stop_timeout_ms: Some(1), |
| }], |
| ..cm_rust::ComponentDecl::default() |
| }, |
| children: vec![( |
| "a".to_string(), |
| ftest::ChildOptions { |
| environment: Some("new-env".to_string()), |
| ..ftest::ChildOptions::EMPTY |
| }, |
| ComponentTree { decl: cm_rust::ComponentDecl::default(), children: vec![] }, |
| )], |
| }; |
| let (root_url, registry) = build_tree(&mut tree).await.expect("failed to build tree"); |
| let tree_from_resolver = ComponentTree::new_from_resolver(root_url, registry).await; |
| assert_eq!(Some(tree), tree_from_resolver); |
| } |
| |
| #[fuchsia::test] |
| async fn build_realm_with_mutable_child_with_on_terminate() { |
| let mut tree = ComponentTree { |
| decl: cm_rust::ComponentDecl { |
| offers: vec![cm_rust::OfferDecl::Protocol(cm_rust::OfferProtocolDecl { |
| source: cm_rust::OfferSource::Parent, |
| target: cm_rust::OfferTarget::Child(cm_rust::ChildRef { |
| name: "a".to_string(), |
| collection: None, |
| }), |
| source_name: "fuchsia.logger.LogSink".into(), |
| target_name: "fuchsia.logger.LogSink".into(), |
| dependency_type: cm_rust::DependencyType::Strong, |
| })], |
| ..cm_rust::ComponentDecl::default() |
| }, |
| children: vec![( |
| "a".to_string(), |
| ftest::ChildOptions { |
| on_terminate: Some(fcdecl::OnTerminate::Reboot), |
| ..ftest::ChildOptions::EMPTY |
| }, |
| ComponentTree { decl: cm_rust::ComponentDecl::default(), children: vec![] }, |
| )], |
| }; |
| let (root_url, registry) = build_tree(&mut tree).await.expect("failed to build tree"); |
| let tree_from_resolver = ComponentTree::new_from_resolver(root_url, registry).await; |
| assert_eq!(Some(tree), tree_from_resolver); |
| } |
| |
| #[fuchsia::test] |
| async fn build_fills_in_the_runner_proxy() { |
| let mut realm_and_builder_task = RealmAndBuilderTask::new(); |
| |
| // Add two local children |
| realm_and_builder_task |
| .realm_proxy |
| .add_local_child("a", ftest::ChildOptions::EMPTY) |
| .await |
| .expect("failed to call add_child") |
| .expect("add_local_child returned an error"); |
| realm_and_builder_task |
| .realm_proxy |
| .add_local_child("b", ftest::ChildOptions::EMPTY) |
| .await |
| .expect("failed to call add_local_child") |
| .expect("add_local_child returned an error"); |
| |
| // Confirm that the local component runner has entries for the two children we just added |
| let local_component_proxies = realm_and_builder_task.runner.local_component_proxies().await; |
| // "a" was added first, so it gets 0 |
| assert!(local_component_proxies.contains_key(&"0".to_string())); |
| // "b" was added second, so it gets 1 |
| assert!(local_component_proxies.contains_key(&"1".to_string())); |
| |
| // Confirm that the entries in the local_components runner for these children does not have a |
| // `fcrunner::ComponentRunnerProxy` for these children, as this value is supposed to be |
| // populated with the channel provided by `Builder.Build`, and we haven't called that yet. |
| let get_runner_proxy = |
| |local_component_proxies: &HashMap<_, _>, id: &str| match local_component_proxies |
| .clone() |
| .remove(&id.to_string()) |
| { |
| Some(runner::ComponentImplementer::RunnerProxy(rp)) => rp, |
| Some(_) => { |
| panic!("unexpected component implementer") |
| } |
| None => panic!("value unexpectedly missing"), |
| }; |
| |
| assert!(get_runner_proxy(&local_component_proxies, "0").lock().await.is_none()); |
| assert!(get_runner_proxy(&local_component_proxies, "1").lock().await.is_none()); |
| |
| // Call `Builder.Build`, and confirm that the entries for our local children in the local |
| // component runner now has a `fcrunner::ComponentRunnerProxy`. |
| let _ = realm_and_builder_task.call_build().await.expect("build failed"); |
| |
| assert!(get_runner_proxy(&local_component_proxies, "0").lock().await.is_some()); |
| assert!(get_runner_proxy(&local_component_proxies, "1").lock().await.is_some()); |
| |
| // Confirm that the `fcrunner::ComponentRunnerProxy` for one of the local children has the |
| // value we expect, by writing a value into it and seeing the same value come out on the |
| // other side of our channel. |
| let example_program = fdata::Dictionary { |
| entries: Some(vec![fdata::DictionaryEntry { |
| key: "hippos".to_string(), |
| value: Some(Box::new(fdata::DictionaryValue::Str("rule!".to_string()))), |
| }]), |
| ..fdata::Dictionary::EMPTY |
| }; |
| |
| let (_controller_client_end, controller_server_end) = |
| create_endpoints::<fcrunner::ComponentControllerMarker>().unwrap(); |
| let runner_proxy_for_a = |
| get_runner_proxy(&local_component_proxies, "0").lock().await.clone().unwrap(); |
| runner_proxy_for_a |
| .start( |
| fcrunner::ComponentStartInfo { |
| program: Some(example_program.clone()), |
| ..fcrunner::ComponentStartInfo::EMPTY |
| }, |
| controller_server_end, |
| ) |
| .expect("failed to write start message"); |
| assert_matches!( |
| realm_and_builder_task |
| .runner_stream |
| .try_next() |
| .await |
| .expect("failed to read from runner_stream"), |
| Some(fcrunner::ComponentRunnerRequest::Start { start_info, .. }) |
| if start_info.program == Some(example_program) |
| ); |
| } |
| |
| #[fuchsia::test] |
| async fn add_child() { |
| let mut realm_and_builder_task = RealmAndBuilderTask::new(); |
| realm_and_builder_task |
| .realm_proxy |
| .add_child("a", "test:///a", ftest::ChildOptions::EMPTY) |
| .await |
| .expect("failed to call add_child") |
| .expect("add_child returned an error"); |
| let tree_from_resolver = realm_and_builder_task.call_build_and_get_tree().await; |
| let mut expected_tree = ComponentTree { |
| decl: cm_rust::ComponentDecl { |
| children: vec![cm_rust::ChildDecl { |
| name: "a".to_string(), |
| url: "test:///a".to_string(), |
| startup: fcdecl::StartupMode::Lazy, |
| on_terminate: None, |
| environment: None, |
| }], |
| ..cm_rust::ComponentDecl::default() |
| }, |
| children: vec![], |
| }; |
| expected_tree.add_binder_expose(); |
| assert_eq!(expected_tree, tree_from_resolver); |
| } |
| |
| #[fuchsia::test] |
| async fn add_child_with_invalid_manifest_extension() { |
| let realm_and_builder_task = RealmAndBuilderTask::new(); |
| let err = realm_and_builder_task |
| .realm_proxy |
| .add_child("a", "test:///a.cmx", ftest::ChildOptions::EMPTY) |
| .await |
| .expect("failed to call add_child") |
| .expect_err("add_child was supposed to return an error"); |
| assert_eq!(err, ftest::RealmBuilderError2::InvalidManifestExtension); |
| } |
| |
| #[fuchsia::test] |
| async fn add_absolute_child_that_conflicts_with_child_decl() { |
| let realm_and_builder_task = RealmAndBuilderTask::new(); |
| realm_and_builder_task |
| .realm_proxy |
| .add_child("a", "test:///a", ftest::ChildOptions::EMPTY) |
| .await |
| .expect("failed to call add_child") |
| .expect("add_child returned an error"); |
| let err = realm_and_builder_task |
| .realm_proxy |
| .add_child("a", "test:///a", ftest::ChildOptions::EMPTY) |
| .await |
| .expect("failed to call add_child") |
| .expect_err("add_child was supposed to return an error"); |
| assert_eq!(err, ftest::RealmBuilderError2::ChildAlreadyExists); |
| } |
| |
| #[fuchsia::test] |
| async fn add_absolute_child_that_conflicts_with_mutable_child() { |
| let realm_and_builder_task = RealmAndBuilderTask::new(); |
| realm_and_builder_task |
| .realm_proxy |
| .add_child("a", "#meta/realm_builder_server_unit_tests.cm", ftest::ChildOptions::EMPTY) |
| .await |
| .expect("failed to call add_child") |
| .expect("add_child returned an error"); |
| let err = realm_and_builder_task |
| .realm_proxy |
| .add_child("a", "test:///a", ftest::ChildOptions::EMPTY) |
| .await |
| .expect("failed to call add_child") |
| .expect_err("add_child was supposed to return an error"); |
| assert_eq!(err, ftest::RealmBuilderError2::ChildAlreadyExists); |
| } |
| |
| #[fuchsia::test] |
| async fn add_relative_child_that_conflicts_with_child_decl() { |
| let realm_and_builder_task = RealmAndBuilderTask::new(); |
| realm_and_builder_task |
| .realm_proxy |
| .add_child("a", "test:///a", ftest::ChildOptions::EMPTY) |
| .await |
| .expect("failed to call add_child") |
| .expect("add_child returned an error"); |
| let err = realm_and_builder_task |
| .realm_proxy |
| .add_child("a", "#meta/realm_builder_server_unit_tests.cm", ftest::ChildOptions::EMPTY) |
| .await |
| .expect("failed to call add_child") |
| .expect_err("add_child was supposed to return an error"); |
| assert_eq!(err, ftest::RealmBuilderError2::ChildAlreadyExists); |
| } |
| |
| #[fuchsia::test] |
| async fn add_relative_child_that_conflicts_with_mutable_child() { |
| let realm_and_builder_task = RealmAndBuilderTask::new(); |
| realm_and_builder_task |
| .realm_proxy |
| .add_child("a", "#meta/realm_builder_server_unit_tests.cm", ftest::ChildOptions::EMPTY) |
| .await |
| .expect("failed to call add_child") |
| .expect("add_child returned an error"); |
| let err = realm_and_builder_task |
| .realm_proxy |
| .add_child("a", "#meta/realm_builder_server_unit_tests.cm", ftest::ChildOptions::EMPTY) |
| .await |
| .expect("failed to call add_child") |
| .expect_err("add_child was supposed to return an error"); |
| assert_eq!(err, ftest::RealmBuilderError2::ChildAlreadyExists); |
| } |
| |
| #[fuchsia::test] |
| async fn add_relative_child() { |
| let mut realm_and_builder_task = RealmAndBuilderTask::new(); |
| realm_and_builder_task |
| .realm_proxy |
| .add_child("a", "#meta/realm_builder_server_unit_tests.cm", ftest::ChildOptions::EMPTY) |
| .await |
| .expect("failed to call add_child") |
| .expect("add_child returned an error"); |
| let tree_from_resolver = realm_and_builder_task.call_build_and_get_tree().await; |
| |
| let a_decl_file = io_util::open_file_in_namespace( |
| "/pkg/meta/realm_builder_server_unit_tests.cm", |
| fio::OpenFlags::RIGHT_READABLE, |
| ) |
| .expect("failed to open manifest"); |
| let a_decl = io_util::read_file_fidl::<fcdecl::Component>(&a_decl_file) |
| .await |
| .expect("failed to read manifest") |
| .fidl_into_native(); |
| |
| let mut expected_tree = ComponentTree { |
| decl: cm_rust::ComponentDecl::default(), |
| children: vec![( |
| "a".to_string(), |
| ftest::ChildOptions::EMPTY, |
| ComponentTree { decl: a_decl, children: vec![] }, |
| )], |
| }; |
| expected_tree.add_binder_expose(); |
| assert_eq!(expected_tree, tree_from_resolver); |
| } |
| |
| #[fuchsia::test] |
| async fn add_relative_child_with_child() { |
| let mut realm_and_builder_task = RealmAndBuilderTask::new(); |
| realm_and_builder_task |
| .realm_proxy |
| .add_child( |
| "realm_with_child", |
| "#meta/realm_with_child.cm", |
| ftest::ChildOptions { |
| startup: Some(fcdecl::StartupMode::Eager), |
| ..ftest::ChildOptions::EMPTY |
| }, |
| ) |
| .await |
| .expect("failed to call add_child") |
| .expect("add_child returned an error"); |
| let tree_from_resolver = realm_and_builder_task.call_build_and_get_tree().await; |
| |
| let realm_with_child_decl_file = io_util::open_file_in_namespace( |
| "/pkg/meta/realm_with_child.cm", |
| fio::OpenFlags::RIGHT_READABLE, |
| ) |
| .expect("failed to open manifest"); |
| let mut realm_with_child_decl = |
| io_util::read_file_fidl::<fcdecl::Component>(&realm_with_child_decl_file) |
| .await |
| .expect("failed to read manifest") |
| .fidl_into_native(); |
| |
| // The "a" child is rewritten by realm builder |
| realm_with_child_decl.children = |
| realm_with_child_decl.children.into_iter().filter(|c| &c.name != "a").collect(); |
| |
| let a_decl_file = |
| io_util::open_file_in_namespace("/pkg/meta/a.cm", fio::OpenFlags::RIGHT_READABLE) |
| .expect("failed to open manifest"); |
| let a_decl = io_util::read_file_fidl::<fcdecl::Component>(&a_decl_file) |
| .await |
| .expect("failed to read manifest") |
| .fidl_into_native(); |
| |
| let mut expected_tree = ComponentTree { |
| decl: cm_rust::ComponentDecl::default(), |
| children: vec![( |
| "realm_with_child".to_string(), |
| ftest::ChildOptions { |
| startup: Some(fcdecl::StartupMode::Eager), |
| ..ftest::ChildOptions::EMPTY |
| }, |
| ComponentTree { |
| decl: realm_with_child_decl, |
| children: vec![( |
| "a".to_string(), |
| ftest::ChildOptions::EMPTY, |
| ComponentTree { decl: a_decl, children: vec![] }, |
| )], |
| }, |
| )], |
| }; |
| expected_tree.add_binder_expose(); |
| assert_eq!(expected_tree, tree_from_resolver); |
| } |
| |
| #[fuchsia::test] |
| async fn add_legacy_child() { |
| let mut realm_and_builder_task = RealmAndBuilderTask::new(); |
| realm_and_builder_task |
| .realm_proxy |
| .add_legacy_child("a", EXAMPLE_LEGACY_URL, ftest::ChildOptions::EMPTY) |
| .await |
| .expect("failed to call add_child") |
| .expect("add_child returned an error"); |
| let tree_from_resolver = realm_and_builder_task.call_build_and_get_tree().await; |
| let expected_a_decl = cm_rust::ComponentDecl { |
| program: Some(cm_rust::ProgramDecl { |
| runner: Some(crate::runner::RUNNER_NAME.try_into().unwrap()), |
| info: fdata::Dictionary { |
| entries: Some(vec![fdata::DictionaryEntry { |
| key: runner::LEGACY_URL_KEY.to_string(), |
| value: Some(Box::new(fdata::DictionaryValue::Str( |
| EXAMPLE_LEGACY_URL.to_string(), |
| ))), |
| }]), |
| ..fdata::Dictionary::EMPTY |
| }, |
| }), |
| ..cm_rust::ComponentDecl::default() |
| }; |
| let mut expected_tree = ComponentTree { |
| decl: cm_rust::ComponentDecl::default(), |
| children: vec![( |
| "a".to_string(), |
| ftest::ChildOptions::EMPTY, |
| ComponentTree { decl: expected_a_decl, children: vec![] }, |
| )], |
| }; |
| expected_tree.add_binder_expose(); |
| assert_eq!(expected_tree, tree_from_resolver); |
| } |
| |
| #[fuchsia::test] |
| async fn add_legacy_child_that_conflicts_with_child_decl() { |
| let realm_and_builder_task = RealmAndBuilderTask::new(); |
| realm_and_builder_task |
| .realm_proxy |
| .add_child("a", "test:///a", ftest::ChildOptions::EMPTY) |
| .await |
| .expect("failed to call add_child") |
| .expect("add_child returned an error"); |
| let err = realm_and_builder_task |
| .realm_proxy |
| .add_legacy_child("a", EXAMPLE_LEGACY_URL, ftest::ChildOptions::EMPTY) |
| .await |
| .expect("failed to call add_child") |
| .expect_err("add_legacy_child was supposed to error"); |
| assert_eq!(err, ftest::RealmBuilderError2::ChildAlreadyExists); |
| } |
| |
| #[fuchsia::test] |
| async fn add_legacy_child_that_conflicts_with_mutable_child() { |
| let realm_and_builder_task = RealmAndBuilderTask::new(); |
| realm_and_builder_task |
| .realm_proxy |
| .add_child("a", "#meta/realm_builder_server_unit_tests.cm", ftest::ChildOptions::EMPTY) |
| .await |
| .expect("failed to call add_child") |
| .expect("add_child returned an error"); |
| let err = realm_and_builder_task |
| .realm_proxy |
| .add_legacy_child("a", EXAMPLE_LEGACY_URL, ftest::ChildOptions::EMPTY) |
| .await |
| .expect("failed to call add_child") |
| .expect_err("add_legacy_child was supposed to error"); |
| assert_eq!(err, ftest::RealmBuilderError2::ChildAlreadyExists); |
| } |
| |
| #[fuchsia::test] |
| async fn add_legacy_child_with_modern_url_returns_error() { |
| let realm_and_builder_task = RealmAndBuilderTask::new(); |
| let err = realm_and_builder_task |
| .realm_proxy |
| .add_legacy_child("a", "#meta/a.cm", ftest::ChildOptions::EMPTY) |
| .await |
| .expect("failed to call add_child") |
| .expect_err("add_legacy_child was supposed to error"); |
| assert_eq!(err, ftest::RealmBuilderError2::InvalidManifestExtension); |
| } |
| |
| #[fuchsia::test] |
| async fn add_child_with_legacy_url_returns_error() { |
| let realm_and_builder_task = RealmAndBuilderTask::new(); |
| let err = realm_and_builder_task |
| .realm_proxy |
| .add_child("a", EXAMPLE_LEGACY_URL, ftest::ChildOptions::EMPTY) |
| .await |
| .expect("failed to call add_child") |
| .expect_err("add_legacy_child was supposed to error"); |
| assert_eq!(err, ftest::RealmBuilderError2::InvalidManifestExtension); |
| } |
| |
| #[fuchsia::test] |
| async fn add_child_from_decl() { |
| let a_decl = cm_rust::ComponentDecl { |
| program: Some(cm_rust::ProgramDecl { |
| runner: Some("hippo".try_into().unwrap()), |
| info: fdata::Dictionary::EMPTY, |
| }), |
| uses: vec![cm_rust::UseDecl::Protocol(cm_rust::UseProtocolDecl { |
| source: cm_rust::UseSource::Parent, |
| source_name: "example.Hippo".into(), |
| target_path: "/svc/example.Hippo".try_into().unwrap(), |
| dependency_type: cm_rust::DependencyType::Strong, |
| })], |
| ..cm_rust::ComponentDecl::default() |
| }; |
| |
| let mut realm_and_builder_task = RealmAndBuilderTask::new(); |
| realm_and_builder_task |
| .realm_proxy |
| .add_child_from_decl("a", a_decl.clone().native_into_fidl(), ftest::ChildOptions::EMPTY) |
| .await |
| .expect("failed to call add_child") |
| .expect("add_child_from_decl returned an error"); |
| let tree_from_resolver = realm_and_builder_task.call_build_and_get_tree().await; |
| let mut expected_tree = ComponentTree { |
| decl: cm_rust::ComponentDecl::default(), |
| children: vec![( |
| "a".to_string(), |
| ftest::ChildOptions::EMPTY, |
| ComponentTree { decl: a_decl, children: vec![] }, |
| )], |
| }; |
| expected_tree.add_binder_expose(); |
| assert_eq!(expected_tree, tree_from_resolver); |
| } |
| |
| #[fuchsia::test] |
| async fn add_child_from_decl_that_conflicts_with_child_decl() { |
| let realm_and_builder_task = RealmAndBuilderTask::new(); |
| realm_and_builder_task |
| .realm_proxy |
| .add_child("a", "test:///a", ftest::ChildOptions::EMPTY) |
| .await |
| .expect("failed to call add_child") |
| .expect("add_child returned an error"); |
| let err = realm_and_builder_task |
| .realm_proxy |
| .add_child_from_decl("a", fcdecl::Component::EMPTY, ftest::ChildOptions::EMPTY) |
| .await |
| .expect("failed to call add_child") |
| .expect_err("add_legacy_child was supposed to error"); |
| assert_eq!(err, ftest::RealmBuilderError2::ChildAlreadyExists); |
| } |
| |
| #[fuchsia::test] |
| async fn add_child_from_decl_that_conflicts_with_mutable_child() { |
| let realm_and_builder_task = RealmAndBuilderTask::new(); |
| realm_and_builder_task |
| .realm_proxy |
| .add_child("a", "#meta/realm_builder_server_unit_tests.cm", ftest::ChildOptions::EMPTY) |
| .await |
| .expect("failed to call add_child") |
| .expect("add_child returned an error"); |
| let err = realm_and_builder_task |
| .realm_proxy |
| .add_child_from_decl("a", fcdecl::Component::EMPTY, ftest::ChildOptions::EMPTY) |
| .await |
| .expect("failed to call add_child") |
| .expect_err("add_legacy_child was supposed to error"); |
| assert_eq!(err, ftest::RealmBuilderError2::ChildAlreadyExists); |
| } |
| |
| #[fuchsia::test] |
| async fn add_route_does_not_mutate_children_added_from_decl() { |
| let a_decl = cm_rust::ComponentDecl { |
| program: Some(cm_rust::ProgramDecl { |
| runner: Some("hippo".try_into().unwrap()), |
| info: fdata::Dictionary::EMPTY, |
| }), |
| uses: vec![cm_rust::UseDecl::Protocol(cm_rust::UseProtocolDecl { |
| source: cm_rust::UseSource::Parent, |
| source_name: "example.Hippo".into(), |
| target_path: "/svc/non-default-path".try_into().unwrap(), |
| dependency_type: cm_rust::DependencyType::Strong, |
| })], |
| ..cm_rust::ComponentDecl::default() |
| } |
| .native_into_fidl(); |
| |
| let realm_and_builder_task = RealmAndBuilderTask::new(); |
| realm_and_builder_task |
| .realm_proxy |
| .add_child_from_decl("a", a_decl.clone(), ftest::ChildOptions::EMPTY) |
| .await |
| .expect("failed to call add_child") |
| .expect("add_child_from_decl returned an error"); |
| realm_and_builder_task |
| .add_route_or_panic( |
| vec![ftest::Capability2::Protocol(ftest::Protocol { |
| name: Some("example.Hippo".to_owned()), |
| type_: Some(fcdecl::DependencyType::Strong), |
| ..ftest::Protocol::EMPTY |
| })], |
| fcdecl::Ref::Parent(fcdecl::ParentRef {}), |
| vec![fcdecl::Ref::Child(fcdecl::ChildRef { |
| name: "a".to_owned(), |
| collection: None, |
| })], |
| ) |
| .await; |
| let resulting_a_decl = realm_and_builder_task |
| .realm_proxy |
| .get_component_decl("a") |
| .await |
| .expect("failed to call get_component_decl") |
| .expect("get_component_decl returned an error"); |
| assert_eq!(a_decl, resulting_a_decl); |
| } |
| |
| #[fuchsia::test] |
| async fn add_local_child() { |
| let mut realm_and_builder_task = RealmAndBuilderTask::new(); |
| realm_and_builder_task |
| .realm_proxy |
| .add_local_child("a", ftest::ChildOptions::EMPTY) |
| .await |
| .expect("failed to call add_child") |
| .expect("add_child returned an error"); |
| let tree_from_resolver = realm_and_builder_task.call_build_and_get_tree().await; |
| let a_decl = cm_rust::ComponentDecl { |
| program: Some(cm_rust::ProgramDecl { |
| runner: Some(crate::runner::RUNNER_NAME.try_into().unwrap()), |
| info: fdata::Dictionary { |
| entries: Some(vec![ |
| fdata::DictionaryEntry { |
| key: runner::LOCAL_COMPONENT_ID_KEY.to_string(), |
| value: Some(Box::new(fdata::DictionaryValue::Str("0".to_string()))), |
| }, |
| fdata::DictionaryEntry { |
| key: ftest::LOCAL_COMPONENT_NAME_KEY.to_string(), |
| value: Some(Box::new(fdata::DictionaryValue::Str("a".to_string()))), |
| }, |
| ]), |
| ..fdata::Dictionary::EMPTY |
| }, |
| }), |
| ..cm_rust::ComponentDecl::default() |
| }; |
| let mut expected_tree = ComponentTree { |
| decl: cm_rust::ComponentDecl::default(), |
| children: vec![( |
| "a".to_string(), |
| ftest::ChildOptions::EMPTY, |
| ComponentTree { decl: a_decl, children: vec![] }, |
| )], |
| }; |
| expected_tree.add_binder_expose(); |
| assert_eq!(expected_tree, tree_from_resolver); |
| assert!(realm_and_builder_task |
| .runner |
| .local_component_proxies() |
| .await |
| .contains_key(&"0".to_string())); |
| } |
| |
| #[fuchsia::test] |
| async fn add_local_child_that_conflicts_with_child_decl() { |
| let realm_and_builder_task = RealmAndBuilderTask::new(); |
| realm_and_builder_task |
| .realm_proxy |
| .add_child("a", "test:///a", ftest::ChildOptions::EMPTY) |
| .await |
| .expect("failed to call add_child") |
| .expect("add_child returned an error"); |
| let err = realm_and_builder_task |
| .realm_proxy |
| .add_local_child("a", ftest::ChildOptions::EMPTY) |
| .await |
| .expect("failed to call add_child") |
| .expect_err("add_local_child was supposed to error"); |
| assert_eq!(err, ftest::RealmBuilderError2::ChildAlreadyExists); |
| } |
| |
| #[fuchsia::test] |
| async fn add_local_child_that_conflicts_with_mutable_child() { |
| let realm_and_builder_task = RealmAndBuilderTask::new(); |
| realm_and_builder_task |
| .realm_proxy |
| .add_child("a", "#meta/realm_builder_server_unit_tests.cm", ftest::ChildOptions::EMPTY) |
| .await |
| .expect("failed to call add_child") |
| .expect("add_child returned an error"); |
| let err = realm_and_builder_task |
| .realm_proxy |
| .add_local_child("a", ftest::ChildOptions::EMPTY) |
| .await |
| .expect("failed to call add_child") |
| .expect_err("add_local_child was supposed to error"); |
| assert_eq!(err, ftest::RealmBuilderError2::ChildAlreadyExists); |
| } |
| |
| #[fuchsia::test] |
| async fn add_route() { |
| let mut realm_and_builder_task = RealmAndBuilderTask::new(); |
| realm_and_builder_task |
| .add_child_or_panic("a", "test:///a", ftest::ChildOptions::EMPTY) |
| .await; |
| realm_and_builder_task |
| .add_child_or_panic("b", "test:///b", ftest::ChildOptions::EMPTY) |
| .await; |
| |
| // Assert that parent -> child capabilities generate proper offer decls. |
| realm_and_builder_task |
| .add_route_or_panic( |
| vec![ |
| ftest::Capability2::Protocol(ftest::Protocol { |
| name: Some("fuchsia.examples.Hippo".to_owned()), |
| as_: Some("fuchsia.examples.Elephant".to_owned()), |
| type_: Some(fcdecl::DependencyType::Strong), |
| ..ftest::Protocol::EMPTY |
| }), |
| ftest::Capability2::Directory(ftest::Directory { |
| name: Some("config-data".to_owned()), |
| rights: Some(fio::RW_STAR_DIR), |
| subdir: Some("component".to_owned()), |
| ..ftest::Directory::EMPTY |
| }), |
| ftest::Capability2::Storage(ftest::Storage { |
| name: Some("temp".to_string()), |
| as_: Some("data".to_string()), |
| ..ftest::Storage::EMPTY |
| }), |
| ftest::Capability2::Service(ftest::Service { |
| name: Some("fuchsia.examples.Whale".to_string()), |
| as_: Some("fuchsia.examples.Orca".to_string()), |
| ..ftest::Service::EMPTY |
| }), |
| ], |
| fcdecl::Ref::Parent(fcdecl::ParentRef {}), |
| vec![fcdecl::Ref::Child(fcdecl::ChildRef { |
| name: "a".to_owned(), |
| collection: None, |
| })], |
| ) |
| .await; |
| |
| // Assert that child -> child capabilities generate proper offer decls. |
| realm_and_builder_task |
| .add_route_or_panic( |
| vec![ftest::Capability2::Protocol(ftest::Protocol { |
| name: Some("fuchsia.examples.Echo".to_owned()), |
| ..ftest::Protocol::EMPTY |
| })], |
| fcdecl::Ref::Child(fcdecl::ChildRef { name: "a".to_owned(), collection: None }), |
| vec![fcdecl::Ref::Child(fcdecl::ChildRef { |
| name: "b".to_owned(), |
| collection: None, |
| })], |
| ) |
| .await; |
| |
| // Assert that child -> parent capabilities generate proper expose decls. |
| realm_and_builder_task |
| .add_route_or_panic( |
| vec![ftest::Capability2::Protocol(ftest::Protocol { |
| name: Some("fuchsia.examples.Echo".to_owned()), |
| type_: Some(fcdecl::DependencyType::Weak), |
| ..ftest::Protocol::EMPTY |
| })], |
| fcdecl::Ref::Child(fcdecl::ChildRef { name: "a".to_owned(), collection: None }), |
| vec![fcdecl::Ref::Parent(fcdecl::ParentRef {})], |
| ) |
| .await; |
| |
| let tree_from_resolver = realm_and_builder_task.call_build_and_get_tree().await; |
| let mut expected_tree = ComponentTree { |
| decl: cm_rust::ComponentDecl { |
| children: vec![ |
| cm_rust::ChildDecl { |
| name: "a".to_string(), |
| url: "test:///a".to_string(), |
| startup: fcdecl::StartupMode::Lazy, |
| on_terminate: None, |
| environment: None, |
| }, |
| cm_rust::ChildDecl { |
| name: "b".to_string(), |
| url: "test:///b".to_string(), |
| startup: fcdecl::StartupMode::Lazy, |
| on_terminate: None, |
| environment: None, |
| }, |
| ], |
| offers: vec![ |
| cm_rust::OfferDecl::Protocol(cm_rust::OfferProtocolDecl { |
| source: cm_rust::OfferSource::Parent, |
| source_name: "fuchsia.examples.Hippo".into(), |
| target: cm_rust::OfferTarget::static_child("a".to_string()), |
| target_name: "fuchsia.examples.Elephant".into(), |
| dependency_type: cm_rust::DependencyType::Strong, |
| }), |
| cm_rust::OfferDecl::Directory(cm_rust::OfferDirectoryDecl { |
| source: cm_rust::OfferSource::Parent, |
| source_name: "config-data".into(), |
| target: cm_rust::OfferTarget::static_child("a".to_string()), |
| target_name: "config-data".into(), |
| dependency_type: cm_rust::DependencyType::Strong, |
| rights: Some(fio::RW_STAR_DIR), |
| subdir: Some(PathBuf::from("component")), |
| }), |
| cm_rust::OfferDecl::Storage(cm_rust::OfferStorageDecl { |
| source: cm_rust::OfferSource::Parent, |
| source_name: "temp".into(), |
| target: cm_rust::OfferTarget::static_child("a".to_string()), |
| target_name: "data".into(), |
| }), |
| cm_rust::OfferDecl::Service(cm_rust::OfferServiceDecl { |
| source: cm_rust::OfferSource::Parent, |
| source_name: "fuchsia.examples.Whale".into(), |
| target: cm_rust::OfferTarget::static_child("a".to_string()), |
| target_name: "fuchsia.examples.Orca".into(), |
| source_instance_filter: None, |
| renamed_instances: None, |
| }), |
| cm_rust::OfferDecl::Protocol(cm_rust::OfferProtocolDecl { |
| source: cm_rust::OfferSource::static_child("a".to_string()), |
| source_name: "fuchsia.examples.Echo".into(), |
| target: cm_rust::OfferTarget::static_child("b".to_string()), |
| target_name: "fuchsia.examples.Echo".into(), |
| dependency_type: cm_rust::DependencyType::Strong, |
| }), |
| ], |
| exposes: vec![cm_rust::ExposeDecl::Protocol(cm_rust::ExposeProtocolDecl { |
| source: cm_rust::ExposeSource::Child("a".to_owned()), |
| source_name: "fuchsia.examples.Echo".into(), |
| target: cm_rust::ExposeTarget::Parent, |
| target_name: "fuchsia.examples.Echo".into(), |
| })], |
| ..cm_rust::ComponentDecl::default() |
| }, |
| children: vec![], |
| }; |
| expected_tree.add_binder_expose(); |
| assert_eq!(expected_tree, tree_from_resolver); |
| } |
| |
| #[fuchsia::test] |
| async fn add_route_duplicate_decls() { |
| let mut realm_and_builder_task = RealmAndBuilderTask::new(); |
| realm_and_builder_task |
| .realm_proxy |
| .add_local_child("a", ftest::ChildOptions::EMPTY) |
| .await |
| .expect("failed to call AddChildFromDecl") |
| .expect("call failed"); |
| realm_and_builder_task |
| .add_child_or_panic("b", "test:///b", ftest::ChildOptions::EMPTY) |
| .await; |
| realm_and_builder_task |
| .add_child_or_panic("c", "test:///c", ftest::ChildOptions::EMPTY) |
| .await; |
| |
| // Routing protocol from `a` should yield one and only one ExposeDecl. |
| realm_and_builder_task |
| .add_route_or_panic( |
| vec![ftest::Capability2::Protocol(ftest::Protocol { |
| name: Some("fuchsia.examples.Hippo".to_owned()), |
| ..ftest::Protocol::EMPTY |
| })], |
| fcdecl::Ref::Child(fcdecl::ChildRef { name: "a".to_owned(), collection: None }), |
| vec![fcdecl::Ref::Child(fcdecl::ChildRef { |
| name: "b".to_owned(), |
| collection: None, |
| })], |
| ) |
| .await; |
| realm_and_builder_task |
| .add_route_or_panic( |
| vec![ftest::Capability2::Protocol(ftest::Protocol { |
| name: Some("fuchsia.examples.Hippo".to_owned()), |
| ..ftest::Protocol::EMPTY |
| })], |
| fcdecl::Ref::Child(fcdecl::ChildRef { name: "a".to_owned(), collection: None }), |
| vec![fcdecl::Ref::Child(fcdecl::ChildRef { |
| name: "c".to_owned(), |
| collection: None, |
| })], |
| ) |
| .await; |
| |
| let tree_from_resolver = realm_and_builder_task.call_build_and_get_tree().await; |
| let mut expected_tree = ComponentTree { |
| decl: cm_rust::ComponentDecl { |
| children: vec![ |
| cm_rust::ChildDecl { |
| name: "b".to_string(), |
| url: "test:///b".to_string(), |
| startup: fcdecl::StartupMode::Lazy, |
| on_terminate: None, |
| environment: None, |
| }, |
| cm_rust::ChildDecl { |
| name: "c".to_string(), |
| url: "test:///c".to_string(), |
| startup: fcdecl::StartupMode::Lazy, |
| on_terminate: None, |
| environment: None, |
| }, |
| ], |
| offers: vec![ |
| cm_rust::OfferDecl::Protocol(cm_rust::OfferProtocolDecl { |
| source: cm_rust::OfferSource::Child(cm_rust::ChildRef { |
| name: "a".to_owned(), |
| collection: None, |
| }), |
| source_name: cm_rust::CapabilityName("fuchsia.examples.Hippo".to_owned()), |
| target: cm_rust::OfferTarget::Child(cm_rust::ChildRef { |
| name: "b".to_owned(), |
| collection: None, |
| }), |
| target_name: cm_rust::CapabilityName("fuchsia.examples.Hippo".to_owned()), |
| dependency_type: cm_rust::DependencyType::Strong, |
| }), |
| cm_rust::OfferDecl::Protocol(cm_rust::OfferProtocolDecl { |
| source: cm_rust::OfferSource::Child(cm_rust::ChildRef { |
| name: "a".to_owned(), |
| collection: None, |
| }), |
| source_name: cm_rust::CapabilityName("fuchsia.examples.Hippo".to_owned()), |
| target: cm_rust::OfferTarget::Child(cm_rust::ChildRef { |
| name: "c".to_owned(), |
| collection: None, |
| }), |
| target_name: cm_rust::CapabilityName("fuchsia.examples.Hippo".to_owned()), |
| dependency_type: cm_rust::DependencyType::Strong, |
| }), |
| ], |
| ..cm_rust::ComponentDecl::default() |
| }, |
| children: vec![( |
| "a".to_owned(), |
| ftest::ChildOptions::EMPTY, |
| ComponentTree { |
| decl: cm_rust::ComponentDecl { |
| program: Some(cm_rust::ProgramDecl { |
| runner: Some(crate::runner::RUNNER_NAME.try_into().unwrap()), |
| info: fdata::Dictionary { |
| entries: Some(vec![ |
| fdata::DictionaryEntry { |
| key: runner::LOCAL_COMPONENT_ID_KEY.to_string(), |
| value: Some(Box::new(fdata::DictionaryValue::Str( |
| "0".to_string(), |
| ))), |
| }, |
| fdata::DictionaryEntry { |
| key: ftest::LOCAL_COMPONENT_NAME_KEY.to_string(), |
| value: Some(Box::new(fdata::DictionaryValue::Str( |
| "a".to_string(), |
| ))), |
| }, |
| ]), |
| ..fdata::Dictionary::EMPTY |
| }, |
| }), |
| capabilities: vec![cm_rust::CapabilityDecl::Protocol( |
| cm_rust::ProtocolDecl { |
| name: cm_rust::CapabilityName("fuchsia.examples.Hippo".to_owned()), |
| source_path: Some(cm_rust::CapabilityPath { |
| dirname: "/svc".to_owned(), |
| basename: "fuchsia.examples.Hippo".to_owned(), |
| }), |
| }, |
| )], |
| exposes: vec![cm_rust::ExposeDecl::Protocol(cm_rust::ExposeProtocolDecl { |
| source: cm_rust::ExposeSource::Self_, |
| source_name: cm_rust::CapabilityName( |
| "fuchsia.examples.Hippo".to_owned(), |
| ), |
| target: cm_rust::ExposeTarget::Parent, |
| target_name: cm_rust::CapabilityName( |
| "fuchsia.examples.Hippo".to_owned(), |
| ), |
| })], |
| ..cm_rust::ComponentDecl::default() |
| }, |
| children: vec![], |
| }, |
| )], |
| }; |
| expected_tree.add_binder_expose(); |
| assert_eq!(expected_tree, tree_from_resolver); |
| } |
| |
| #[fuchsia::test] |
| async fn add_route_mutates_decl() { |
| let mut realm_and_builder_task = RealmAndBuilderTask::new(); |
| |
| realm_and_builder_task |
| .realm_proxy |
| .add_local_child("a", ftest::ChildOptions::EMPTY) |
| .await |
| .expect("failed to call AddChildFromDecl") |
| .expect("call failed"); |
| realm_and_builder_task |
| .add_child_or_panic("b", "test:///b", ftest::ChildOptions::EMPTY) |
| .await; |
| realm_and_builder_task |
| .add_child_or_panic("c", "test:///c", ftest::ChildOptions::EMPTY) |
| .await; |
| realm_and_builder_task |
| .add_route_or_panic( |
| vec![ftest::Capability2::Protocol(ftest::Protocol { |
| name: Some("fuchsia.examples.Echo".to_owned()), |
| ..ftest::Protocol::EMPTY |
| })], |
| fcdecl::Ref::Child(fcdecl::ChildRef { name: "a".to_owned(), collection: None }), |
| vec![fcdecl::Ref::Child(fcdecl::ChildRef { |
| name: "b".to_owned(), |
| collection: None, |
| })], |
| ) |
| .await; |
| realm_and_builder_task |
| .add_route_or_panic( |
| vec![ftest::Capability2::Protocol(ftest::Protocol { |
| name: Some("fuchsia.examples.RandonNumberGenerator".to_owned()), |
| ..ftest::Protocol::EMPTY |
| })], |
| fcdecl::Ref::Child(fcdecl::ChildRef { name: "c".to_owned(), collection: None }), |
| vec![fcdecl::Ref::Child(fcdecl::ChildRef { |
| name: "a".to_owned(), |
| collection: None, |
| })], |
| ) |
| .await; |
| |
| let tree_from_resolver = realm_and_builder_task.call_build_and_get_tree().await; |
| let mut expected_tree = ComponentTree { |
| decl: cm_rust::ComponentDecl { |
| children: vec![ |
| cm_rust::ChildDecl { |
| name: "b".to_owned(), |
| url: "test:///b".to_owned(), |
| startup: fcdecl::StartupMode::Lazy, |
| on_terminate: None, |
| environment: None, |
| }, |
| cm_rust::ChildDecl { |
| name: "c".to_owned(), |
| url: "test:///c".to_owned(), |
| startup: fcdecl::StartupMode::Lazy, |
| on_terminate: None, |
| environment: None, |
| }, |
| ], |
| offers: vec![ |
| cm_rust::OfferDecl::Protocol(cm_rust::OfferProtocolDecl { |
| source: cm_rust::OfferSource::Child(cm_rust::ChildRef { |
| name: "a".to_owned(), |
| collection: None, |
| }), |
| source_name: cm_rust::CapabilityName("fuchsia.examples.Echo".to_owned()), |
| target: cm_rust::OfferTarget::Child(cm_rust::ChildRef { |
| name: "b".to_owned(), |
| collection: None, |
| }), |
| target_name: cm_rust::CapabilityName("fuchsia.examples.Echo".to_owned()), |
| dependency_type: cm_rust::DependencyType::Strong, |
| }), |
| cm_rust::OfferDecl::Protocol(cm_rust::OfferProtocolDecl { |
| source: cm_rust::OfferSource::Child(cm_rust::ChildRef { |
| name: "c".to_owned(), |
| collection: None, |
| }), |
| source_name: cm_rust::CapabilityName( |
| "fuchsia.examples.RandonNumberGenerator".to_owned(), |
| ), |
| target: cm_rust::OfferTarget::Child(cm_rust::ChildRef { |
| name: "a".to_owned(), |
| collection: None, |
| }), |
| target_name: cm_rust::CapabilityName( |
| "fuchsia.examples.RandonNumberGenerator".to_owned(), |
| ), |
| dependency_type: cm_rust::DependencyType::Strong, |
| }), |
| ], |
| ..cm_rust::ComponentDecl::default() |
| }, |
| children: vec![( |
| "a".to_owned(), |
| ftest::ChildOptions::EMPTY, |
| ComponentTree { |
| decl: cm_rust::ComponentDecl { |
| program: Some(cm_rust::ProgramDecl { |
| runner: Some(crate::runner::RUNNER_NAME.try_into().unwrap()), |
| info: fdata::Dictionary { |
| entries: Some(vec![ |
| fdata::DictionaryEntry { |
| key: runner::LOCAL_COMPONENT_ID_KEY.to_string(), |
| value: Some(Box::new(fdata::DictionaryValue::Str( |
| "0".to_string(), |
| ))), |
| }, |
| fdata::DictionaryEntry { |
| key: ftest::LOCAL_COMPONENT_NAME_KEY.to_string(), |
| value: Some(Box::new(fdata::DictionaryValue::Str( |
| "a".to_string(), |
| ))), |
| }, |
| ]), |
| ..fdata::Dictionary::EMPTY |
| }, |
| }), |
| capabilities: vec![cm_rust::CapabilityDecl::Protocol( |
| cm_rust::ProtocolDecl { |
| name: cm_rust::CapabilityName("fuchsia.examples.Echo".to_owned()), |
| source_path: Some(cm_rust::CapabilityPath { |
| dirname: "/svc".to_owned(), |
| basename: "fuchsia.examples.Echo".to_owned(), |
| }), |
| }, |
| )], |
| uses: vec![cm_rust::UseDecl::Protocol(cm_rust::UseProtocolDecl { |
| source: cm_rust::UseSource::Parent, |
| source_name: cm_rust::CapabilityName( |
| "fuchsia.examples.RandonNumberGenerator".to_owned(), |
| ), |
| target_path: cm_rust::CapabilityPath { |
| dirname: "/svc".to_owned(), |
| basename: "fuchsia.examples.RandonNumberGenerator".to_owned(), |
| }, |
| dependency_type: cm_rust::DependencyType::Strong, |
| })], |
| exposes: vec![cm_rust::ExposeDecl::Protocol(cm_rust::ExposeProtocolDecl { |
| source: cm_rust::ExposeSource::Self_, |
| source_name: cm_rust::CapabilityName( |
| "fuchsia.examples.Echo".to_owned(), |
| ), |
| target: cm_rust::ExposeTarget::Parent, |
| target_name: cm_rust::CapabilityName( |
| "fuchsia.examples.Echo".to_owned(), |
| ), |
| })], |
| ..cm_rust::ComponentDecl::default() |
| }, |
| children: vec![], |
| }, |
| )], |
| }; |
| expected_tree.add_binder_expose(); |
| assert_eq!(expected_tree, tree_from_resolver); |
| } |
| |
| #[fuchsia::test] |
| async fn add_child_to_child_realm() { |
| let mut realm_and_builder_task = RealmAndBuilderTask::new(); |
| let (child_realm_proxy, child_realm_server_end) = |
| create_proxy::<ftest::RealmMarker>().unwrap(); |
| realm_and_builder_task |
| .realm_proxy |
| .add_child_realm("a", ftest::ChildOptions::EMPTY, child_realm_server_end) |
| .await |
| .expect("failed to call add_child_realm") |
| .expect("add_child_realm returned an error"); |
| child_realm_proxy |
| .add_child("b", "test:///b", ftest::ChildOptions::EMPTY) |
| .await |
| .expect("failed to call add_child") |
| .expect("add_child returned an error"); |
| let tree_from_resolver = realm_and_builder_task.call_build_and_get_tree().await; |
| let mut expected_tree = ComponentTree { |
| decl: cm_rust::ComponentDecl::default(), |
| children: vec![( |
| "a".to_string(), |
| ftest::ChildOptions::EMPTY, |
| ComponentTree { |
| decl: cm_rust::ComponentDecl { |
| children: vec![cm_rust::ChildDecl { |
| name: "b".to_string(), |
| url: "test:///b".to_string(), |
| startup: fcdecl::StartupMode::Lazy, |
| on_terminate: None, |
| environment: None, |
| }], |
| ..cm_rust::ComponentDecl::default() |
| }, |
| children: vec![], |
| }, |
| )], |
| }; |
| expected_tree.add_binder_expose(); |
| assert_eq!(expected_tree, tree_from_resolver); |
| } |
| |
| #[fuchsia::test] |
| async fn get_component_decl() { |
| let realm_and_builder_task = RealmAndBuilderTask::new(); |
| realm_and_builder_task |
| .realm_proxy |
| .add_local_child("a", ftest::ChildOptions::EMPTY) |
| .await |
| .expect("failed to call add_child") |
| .expect("add_child returned an error"); |
| let a_decl = realm_and_builder_task |
| .realm_proxy |
| .get_component_decl("a") |
| .await |
| .expect("failed to call get_component_decl") |
| .expect("get_component_decl returned an error"); |
| assert_eq!( |
| a_decl, |
| cm_rust::ComponentDecl { |
| program: Some(cm_rust::ProgramDecl { |
| runner: Some(crate::runner::RUNNER_NAME.try_into().unwrap()), |
| info: fdata::Dictionary { |
| entries: Some(vec![ |
| fdata::DictionaryEntry { |
| key: runner::LOCAL_COMPONENT_ID_KEY.to_string(), |
| value: Some(Box::new(fdata::DictionaryValue::Str("0".to_string()))), |
| }, |
| fdata::DictionaryEntry { |
| key: ftest::LOCAL_COMPONENT_NAME_KEY.to_string(), |
| value: Some(Box::new(fdata::DictionaryValue::Str("a".to_string()))), |
| }, |
| ]), |
| ..fdata::Dictionary::EMPTY |
| }, |
| }), |
| ..cm_rust::ComponentDecl::default() |
| } |
| .native_into_fidl(), |
| ); |
| } |
| |
| #[fuchsia::test] |
| async fn get_component_decl_for_nonexistent_child() { |
| let realm_and_builder_task = RealmAndBuilderTask::new(); |
| let err = realm_and_builder_task |
| .realm_proxy |
| .get_component_decl("a") |
| .await |
| .expect("failed to call get_component_decl") |
| .expect_err("get_component_decl did not return an error"); |
| assert_eq!(err, ftest::RealmBuilderError2::NoSuchChild); |
| } |
| |
| #[fuchsia::test] |
| async fn get_component_decl_for_child_behind_child_decl() { |
| let realm_and_builder_task = RealmAndBuilderTask::new(); |
| realm_and_builder_task |
| .realm_proxy |
| .add_child("a", "test:///a", ftest::ChildOptions::EMPTY) |
| .await |
| .expect("failed to call add_child") |
| .expect("add_child returned an error"); |
| let err = realm_and_builder_task |
| .realm_proxy |
| .get_component_decl("a") |
| .await |
| .expect("failed to call get_component_decl") |
| .expect_err("get_component_decl did not return an error"); |
| assert_eq!(err, ftest::RealmBuilderError2::ChildDeclNotVisible); |
| } |
| |
| #[fuchsia::test] |
| async fn replace_component_decl() { |
| let mut realm_and_builder_task = RealmAndBuilderTask::new(); |
| realm_and_builder_task |
| .realm_proxy |
| .add_local_child("a", ftest::ChildOptions::EMPTY) |
| .await |
| .expect("failed to call add_child") |
| .expect("add_child returned an error"); |
| let mut a_decl = realm_and_builder_task |
| .realm_proxy |
| .get_component_decl("a") |
| .await |
| .expect("failed to call get_component_decl") |
| .expect("get_component_decl returned an error") |
| .fidl_into_native(); |
| a_decl.uses.push(cm_rust::UseDecl::Protocol(cm_rust::UseProtocolDecl { |
| source: cm_rust::UseSource::Parent, |
| source_name: "example.Hippo".into(), |
| target_path: "/svc/example.Hippo".try_into().unwrap(), |
| dependency_type: cm_rust::DependencyType::Strong, |
| })); |
| realm_and_builder_task |
| .realm_proxy |
| .replace_component_decl("a", a_decl.clone().native_into_fidl()) |
| .await |
| .expect("failed to call replace_component_decl") |
| .expect("replace_component_decl returned an error"); |
| let tree_from_resolver = realm_and_builder_task.call_build_and_get_tree().await; |
| let mut expected_tree = ComponentTree { |
| decl: cm_rust::ComponentDecl::default(), |
| children: vec![( |
| "a".to_string(), |
| ftest::ChildOptions::EMPTY, |
| ComponentTree { decl: a_decl, children: vec![] }, |
| )], |
| }; |
| expected_tree.add_binder_expose(); |
| assert_eq!(expected_tree, tree_from_resolver); |
| } |
| |
| #[fuchsia::test] |
| async fn route_event_to_child_component() { |
| let mut realm_and_builder_task = RealmAndBuilderTask::new(); |
| realm_and_builder_task |
| .realm_proxy |
| .add_child("a", "test://a", ftest::ChildOptions::EMPTY) |
| .await |
| .expect("failed to call add_child") |
| .expect("add_child returned an error"); |
| realm_and_builder_task |
| .realm_proxy |
| .add_route( |
| &mut vec![ftest::Capability2::Event(ftest::Event { |
| name: Some("directory_ready".to_string()), |
| as_: None, |
| filter: Some(fdata::Dictionary { |
| entries: Some(vec![fdata::DictionaryEntry { |
| key: "name".to_string(), |
| value: Some(Box::new(fdata::DictionaryValue::Str( |
| "hippos".to_string(), |
| ))), |
| }]), |
| ..fdata::Dictionary::EMPTY |
| }), |
| ..ftest::Event::EMPTY |
| })] |
| .iter_mut(), |
| &mut fcdecl::Ref::Parent(fcdecl::ParentRef {}), |
| &mut vec![fcdecl::Ref::Child(fcdecl::ChildRef { |
| name: "a".to_string(), |
| collection: None, |
| })] |
| .iter_mut(), |
| ) |
| .await |
| .expect("failed to call add_child") |
| .expect("add_route returned an error"); |
| let tree_from_resolver = realm_and_builder_task.call_build_and_get_tree().await; |
| let mut expected_tree = ComponentTree { |
| decl: cm_rust::ComponentDecl { |
| children: vec![cm_rust::ChildDecl { |
| name: "a".to_string(), |
| url: "test://a".to_string(), |
| startup: fcdecl::StartupMode::Lazy, |
| on_terminate: None, |
| environment: None, |
| }], |
| offers: vec![cm_rust::OfferDecl::Event(cm_rust::OfferEventDecl { |
| source: cm_rust::OfferSource::Parent, |
| source_name: "directory_ready".into(), |
| target: cm_rust::OfferTarget::Child(cm_rust::ChildRef { |
| name: "a".to_string(), |
| collection: None, |
| }), |
| target_name: "directory_ready".into(), |
| filter: Some(hashmap!( |
| "name".to_string() => cm_rust::DictionaryValue::Str("hippos".to_string()), |
| )), |
| })], |
| ..cm_rust::ComponentDecl::default() |
| }, |
| children: vec![], |
| }; |
| expected_tree.add_binder_expose(); |
| assert_eq!(expected_tree, tree_from_resolver); |
| } |
| |
| #[test_case(vec![ |
| create_valid_capability()], |
| fcdecl::Ref::Child(fcdecl::ChildRef { |
| name: "unknown".to_owned(), |
| collection: None |
| }), |
| vec![], |
| ftest::RealmBuilderError2::NoSuchSource ; "no_such_source")] |
| #[test_case(vec![ |
| create_valid_capability()], |
| fcdecl::Ref::Child(fcdecl::ChildRef { |
| name: "a".to_owned(), |
| collection: None |
| }), |
| vec![ |
| fcdecl::Ref::Child(fcdecl::ChildRef { |
| name: "unknown".to_owned(), |
| collection: None |
| }), |
| ], |
| ftest::RealmBuilderError2::NoSuchTarget ; "no_such_target")] |
| #[test_case(vec![ |
| create_valid_capability()], |
| fcdecl::Ref::Child(fcdecl::ChildRef { |
| name: "a".to_owned(), |
| collection: None |
| }), |
| vec![ |
| fcdecl::Ref::Child(fcdecl::ChildRef { |
| name: "a".to_owned(), |
| collection: None |
| }), |
| ], |
| ftest::RealmBuilderError2::SourceAndTargetMatch ; "source_and_target_match")] |
| #[test_case(vec![], |
| fcdecl::Ref::Child(fcdecl::ChildRef { |
| name: "a".to_owned(), |
| collection: None |
| }), |
| vec![fcdecl::Ref::Parent(fcdecl::ParentRef {})], |
| ftest::RealmBuilderError2::CapabilitiesEmpty ; "capabilities_empty")] |
| #[test_case(vec![ftest::Capability2::unknown(100, vec![])], |
| fcdecl::Ref::Child(fcdecl::ChildRef { |
| name: "a".to_owned(), |
| collection: None |
| }), |
| vec![fcdecl::Ref::Parent(fcdecl::ParentRef {})], |
| ftest::RealmBuilderError2::CapabilityInvalid ; "invalid_capability")] |
| #[fuchsia::test] |
| async fn add_route_error( |
| mut capabilities: Vec<ftest::Capability2>, |
| mut from: fcdecl::Ref, |
| mut to: Vec<fcdecl::Ref>, |
| expected_err: ftest::RealmBuilderError2, |
| ) { |
| let realm_and_builder_task = RealmAndBuilderTask::new(); |
| realm_and_builder_task |
| .add_child_or_panic("a", "test:///a", ftest::ChildOptions::EMPTY) |
| .await; |
| |
| let err = realm_and_builder_task |
| .realm_proxy |
| .add_route(&mut capabilities.iter_mut(), &mut from, &mut to.iter_mut()) |
| .await |
| .expect("failed to call AddRoute") |
| .expect_err("AddRoute succeeded unexpectedly"); |
| |
| assert_eq!(err, expected_err); |
| } |
| |
| fn create_valid_capability() -> ftest::Capability2 { |
| ftest::Capability2::Protocol(ftest::Protocol { |
| name: Some("fuchsia.examples.Hippo".to_owned()), |
| as_: None, |
| type_: None, |
| ..ftest::Protocol::EMPTY |
| }) |
| } |
| |
| #[fuchsia::test] |
| async fn add_local_child_to_child_realm() { |
| let mut realm_and_builder_task = RealmAndBuilderTask::new(); |
| let (child_realm_proxy, child_realm_server_end) = |
| create_proxy::<ftest::RealmMarker>().unwrap(); |
| realm_and_builder_task |
| .realm_proxy |
| .add_child_realm("a", ftest::ChildOptions::EMPTY, child_realm_server_end) |
| .await |
| .expect("failed to call add_child_realm") |
| .expect("add_child_realm returned an error"); |
| child_realm_proxy |
| .add_local_child("b", ftest::ChildOptions::EMPTY) |
| .await |
| .expect("failed to call add_child") |
| .expect("add_child returned an error"); |
| let tree_from_resolver = realm_and_builder_task.call_build_and_get_tree().await; |
| let b_decl = cm_rust::ComponentDecl { |
| program: Some(cm_rust::ProgramDecl { |
| runner: Some(crate::runner::RUNNER_NAME.try_into().unwrap()), |
| info: fdata::Dictionary { |
| entries: Some(vec![ |
| fdata::DictionaryEntry { |
| key: runner::LOCAL_COMPONENT_ID_KEY.to_string(), |
| value: Some(Box::new(fdata::DictionaryValue::Str("0".to_string()))), |
| }, |
| fdata::DictionaryEntry { |
| key: ftest::LOCAL_COMPONENT_NAME_KEY.to_string(), |
| value: Some(Box::new(fdata::DictionaryValue::Str("a/b".to_string()))), |
| }, |
| ]), |
| ..fdata::Dictionary::EMPTY |
| }, |
| }), |
| ..cm_rust::ComponentDecl::default() |
| }; |
| let mut expected_tree = ComponentTree { |
| decl: cm_rust::ComponentDecl::default(), |
| children: vec![( |
| "a".to_string(), |
| ftest::ChildOptions::EMPTY, |
| ComponentTree { |
| decl: cm_rust::ComponentDecl::default(), |
| children: vec![( |
| "b".to_string(), |
| ftest::ChildOptions::EMPTY, |
| ComponentTree { decl: b_decl, children: vec![] }, |
| )], |
| }, |
| )], |
| }; |
| expected_tree.add_binder_expose(); |
| assert_eq!(expected_tree, tree_from_resolver); |
| } |
| |
| #[fuchsia::test] |
| async fn replace_component_decl_immutable_program() { |
| let realm_and_builder_task = RealmAndBuilderTask::new(); |
| realm_and_builder_task |
| .realm_proxy |
| .add_local_child("a", ftest::ChildOptions::EMPTY) |
| .await |
| .expect("failed to call add_child") |
| .expect("add_child returned an error"); |
| let err = realm_and_builder_task |
| .realm_proxy |
| .replace_component_decl("a", fcdecl::Component::EMPTY) |
| .await |
| .expect("failed to call replace_component_decl") |
| .expect_err("replace_component_decl did not return an error"); |
| assert_eq!(err, ftest::RealmBuilderError2::ImmutableProgram); |
| } |
| |
| #[fuchsia::test] |
| async fn replace_component_decl_immutable_program_not_raised_if_args_changed() { |
| let mut realm_and_builder_task = RealmAndBuilderTask::new(); |
| realm_and_builder_task |
| .realm_proxy |
| .add_legacy_child("a", EXAMPLE_LEGACY_URL, ftest::ChildOptions::EMPTY) |
| .await |
| .expect("failed to call add_child") |
| .expect("add_child returned an error"); |
| let mut a_decl = realm_and_builder_task |
| .realm_proxy |
| .get_component_decl("a") |
| .await |
| .expect("failed to call get_component_decl") |
| .expect("get_component_decl returned an error") |
| .fidl_into_native(); |
| let program = a_decl.program.as_mut().expect("program section is None"); |
| let entries = program.info.entries.as_mut().expect("program info is None"); |
| let args_entry = fdata::DictionaryEntry { |
| key: crate::ALLOWLISTED_PROGRAM_ARGS_KEY.to_string(), |
| value: Some(Box::new(fdata::DictionaryValue::Str("test".to_string()))), |
| }; |
| entries.push(args_entry.clone()); |
| realm_and_builder_task |
| .realm_proxy |
| .replace_component_decl("a", a_decl.clone().native_into_fidl()) |
| .await |
| .expect("failed to call replace_component_decl") |
| .expect("replace_component_decl returned an error"); |
| let tree_from_resolver = realm_and_builder_task.call_build_and_get_tree().await; |
| let expected_a_decl = cm_rust::ComponentDecl { |
| program: Some(cm_rust::ProgramDecl { |
| runner: Some(crate::runner::RUNNER_NAME.try_into().unwrap()), |
| info: fdata::Dictionary { |
| entries: Some(vec![ |
| fdata::DictionaryEntry { |
| key: runner::LEGACY_URL_KEY.to_string(), |
| value: Some(Box::new(fdata::DictionaryValue::Str( |
| EXAMPLE_LEGACY_URL.to_string(), |
| ))), |
| }, |
| args_entry, |
| ]), |
| ..fdata::Dictionary::EMPTY |
| }, |
| }), |
| ..cm_rust::ComponentDecl::default() |
| }; |
| let mut expected_tree = ComponentTree { |
| decl: cm_rust::ComponentDecl::default(), |
| children: vec![( |
| "a".to_string(), |
| ftest::ChildOptions::EMPTY, |
| ComponentTree { decl: expected_a_decl, children: vec![] }, |
| )], |
| }; |
| expected_tree.add_binder_expose(); |
| assert_eq!(expected_tree, tree_from_resolver); |
| } |
| |
| #[fuchsia::test] |
| async fn replace_component_decl_for_nonexistent_child() { |
| let realm_and_builder_task = RealmAndBuilderTask::new(); |
| let err = realm_and_builder_task |
| .realm_proxy |
| .replace_component_decl("a", fcdecl::Component::EMPTY) |
| .await |
| .expect("failed to call replace_component_decl") |
| .expect_err("replace_component_decl did not return an error"); |
| assert_eq!(err, ftest::RealmBuilderError2::NoSuchChild); |
| } |
| |
| #[fuchsia::test] |
| async fn replace_component_decl_for_child_behind_child_decl() { |
| let realm_and_builder_task = RealmAndBuilderTask::new(); |
| realm_and_builder_task |
| .realm_proxy |
| .add_child("a", "test:///a", ftest::ChildOptions::EMPTY) |
| .await |
| .expect("failed to call add_child") |
| .expect("add_child returned an error"); |
| let err = realm_and_builder_task |
| .realm_proxy |
| .replace_component_decl("a", fcdecl::Component::EMPTY) |
| .await |
| .expect("failed to call replace_component_decl") |
| .expect_err("replace_component_decl did not return an error"); |
| assert_eq!(err, ftest::RealmBuilderError2::ChildDeclNotVisible); |
| } |
| |
| #[fuchsia::test] |
| async fn get_and_replace_realm_decl() { |
| let realm_and_builder_task = RealmAndBuilderTask::new(); |
| let mut realm_decl = realm_and_builder_task |
| .realm_proxy |
| .get_realm_decl() |
| .await |
| .expect("failed to call get_realm_decl") |
| .expect("get_realm_decl returned an error"); |
| realm_decl.children = Some(vec![fcdecl::Child { |
| name: Some("example-child".to_string()), |
| url: Some("example://url".to_string()), |
| startup: Some(fcdecl::StartupMode::Eager), |
| ..fcdecl::Child::EMPTY |
| }]); |
| realm_and_builder_task |
| .realm_proxy |
| .replace_realm_decl(realm_decl.clone()) |
| .await |
| .expect("failed to call replace_realm_decl") |
| .expect("replace_realm_decl returned an error"); |
| assert_eq!( |
| realm_decl, |
| realm_and_builder_task |
| .realm_proxy |
| .get_realm_decl() |
| .await |
| .expect("failed to call get_realm_decl") |
| .expect("get_realm_decl returned an error"), |
| ); |
| } |
| |
| #[fuchsia::test] |
| async fn replace_decl_enforces_validation() { |
| let realm_and_builder_task = RealmAndBuilderTask::new(); |
| let realm_decl = fcdecl::Component { |
| children: Some(vec![fcdecl::Child { |
| name: Some("example-child".to_string()), |
| url: Some("example://url".to_string()), |
| startup: Some(fcdecl::StartupMode::Eager), |
| environment: Some("i-dont-exist".to_string()), |
| ..fcdecl::Child::EMPTY |
| }]), |
| ..fcdecl::Component::EMPTY |
| }; |
| let err = realm_and_builder_task |
| .realm_proxy |
| .replace_realm_decl(realm_decl) |
| .await |
| .expect("failed to call replace_realm_decl") |
| .expect_err("replace_realm_decl did not return an error"); |
| assert_eq!(err, ftest::RealmBuilderError2::InvalidComponentDecl); |
| } |
| |
| #[fuchsia::test] |
| async fn all_functions_error_after_build() { |
| let mut rabt = RealmAndBuilderTask::new(); |
| let (child_realm_proxy, child_realm_server_end) = |
| create_proxy::<ftest::RealmMarker>().unwrap(); |
| rabt.realm_proxy |
| .add_child_realm("a", ftest::ChildOptions::EMPTY, child_realm_server_end) |
| .await |
| .expect("failed to call add_child_realm") |
| .expect("add_child_realm returned an error"); |
| child_realm_proxy |
| .add_local_child("b", ftest::ChildOptions::EMPTY) |
| .await |
| .expect("failed to call add_child") |
| .expect("add_child returned an error"); |
| let _tree_from_resolver = rabt.call_build_and_get_tree().await; |
| |
| async fn assert_err<V: std::fmt::Debug>( |
| fut: impl futures::Future< |
| Output = Result<Result<V, ftest::RealmBuilderError2>, fidl::Error>, |
| >, |
| ) { |
| assert_eq!( |
| ftest::RealmBuilderError2::BuildAlreadyCalled, |
| fut.await.expect("failed to call function").expect_err("expected an error"), |
| ); |
| } |
| let empty_opts = || ftest::ChildOptions::EMPTY; |
| let empty_decl = || fcdecl::Component::EMPTY; |
| |
| assert_err(rabt.realm_proxy.add_child("a", "test:///a", empty_opts())).await; |
| assert_err(rabt.realm_proxy.add_legacy_child("a", "test:///a.cmx", empty_opts())).await; |
| assert_err(rabt.realm_proxy.add_child_from_decl("a", empty_decl(), empty_opts())).await; |
| assert_err(rabt.realm_proxy.add_local_child("a", empty_opts())).await; |
| let (_child_realm_proxy, server_end) = create_proxy::<ftest::RealmMarker>().unwrap(); |
| assert_err(rabt.realm_proxy.add_child_realm("a", empty_opts(), server_end)).await; |
| assert_err(rabt.realm_proxy.get_component_decl("b")).await; |
| assert_err(rabt.realm_proxy.replace_component_decl("b", empty_decl())).await; |
| assert_err(rabt.realm_proxy.replace_realm_decl(empty_decl())).await; |
| assert_err(rabt.realm_proxy.add_route( |
| &mut vec![].iter_mut(), |
| &mut fcdecl::Ref::Self_(fcdecl::SelfRef {}), |
| &mut vec![].iter_mut(), |
| )) |
| .await; |
| |
| assert_err(child_realm_proxy.add_child("a", "test:///a", empty_opts())).await; |
| assert_err(child_realm_proxy.add_legacy_child("a", "test:///a.cmx", empty_opts())).await; |
| assert_err(child_realm_proxy.add_child_from_decl("a", empty_decl(), empty_opts())).await; |
| assert_err(child_realm_proxy.add_local_child("a", empty_opts())).await; |
| let (_child_realm_proxy, server_end) = create_proxy::<ftest::RealmMarker>().unwrap(); |
| assert_err(child_realm_proxy.add_child_realm("a", empty_opts(), server_end)).await; |
| assert_err(child_realm_proxy.get_component_decl("b")).await; |
| assert_err(child_realm_proxy.replace_component_decl("b", empty_decl())).await; |
| assert_err(child_realm_proxy.replace_realm_decl(empty_decl())).await; |
| assert_err(child_realm_proxy.add_route( |
| &mut vec![].iter_mut(), |
| &mut fcdecl::Ref::Self_(fcdecl::SelfRef {}), |
| &mut vec![].iter_mut(), |
| )) |
| .await; |
| } |
| |
| #[fuchsia::test] |
| async fn read_only_directory() { |
| let mut realm_and_builder_task = RealmAndBuilderTask::new(); |
| realm_and_builder_task |
| .realm_proxy |
| .add_child("a", "test://a", ftest::ChildOptions::EMPTY) |
| .await |
| .expect("failed to call add_child") |
| .expect("add_child returned an error"); |
| realm_and_builder_task |
| .realm_proxy |
| .read_only_directory( |
| "data", |
| &mut vec![fcdecl::Ref::Child(fcdecl::ChildRef { |
| name: "a".to_string(), |
| collection: None, |
| })] |
| .iter_mut(), |
| &mut ftest::DirectoryContents { |
| entries: vec![ftest::DirectoryEntry { |
| file_path: "hippos".to_string(), |
| file_contents: { |
| let value = "rule!"; |
| let vmo = |
| zx::Vmo::create(value.len() as u64).expect("failed to create vmo"); |
| vmo.write(value.as_bytes(), 0).expect("failed to write to vmo"); |
| fmem::Buffer { vmo, size: value.len() as u64 } |
| }, |
| }], |
| }, |
| ) |
| .await |
| .expect("failed to call read_only_directory") |
| .expect("read_only_directory returned an error"); |
| let tree_from_resolver = realm_and_builder_task.call_build_and_get_tree().await; |
| let read_only_dir_decl = cm_rust::ComponentDecl { |
| program: Some(cm_rust::ProgramDecl { |
| runner: Some(crate::runner::RUNNER_NAME.try_into().unwrap()), |
| info: fdata::Dictionary { |
| entries: Some(vec![fdata::DictionaryEntry { |
| key: runner::LOCAL_COMPONENT_ID_KEY.to_string(), |
| value: Some(Box::new(fdata::DictionaryValue::Str("0".to_string()))), |
| }]), |
| ..fdata::Dictionary::EMPTY |
| }, |
| }), |
| capabilities: vec![cm_rust::CapabilityDecl::Directory(cm_rust::DirectoryDecl { |
| name: "data".into(), |
| source_path: Some("/data".try_into().unwrap()), |
| rights: fio::R_STAR_DIR, |
| })], |
| exposes: vec![cm_rust::ExposeDecl::Directory(cm_rust::ExposeDirectoryDecl { |
| source: cm_rust::ExposeSource::Self_, |
| source_name: "data".into(), |
| target: cm_rust::ExposeTarget::Parent, |
| target_name: "data".into(), |
| rights: Some(fio::R_STAR_DIR), |
| subdir: None, |
| })], |
| ..cm_rust::ComponentDecl::default() |
| }; |
| let mut expected_tree = ComponentTree { |
| decl: cm_rust::ComponentDecl { |
| children: vec![cm_rust::ChildDecl { |
| name: "a".to_string(), |
| url: "test://a".to_string(), |
| startup: fcdecl::StartupMode::Lazy, |
| on_terminate: None, |
| environment: None, |
| }], |
| offers: vec![cm_rust::OfferDecl::Directory(cm_rust::OfferDirectoryDecl { |
| source: cm_rust::OfferSource::Child(cm_rust::ChildRef { |
| name: "read-only-directory-0".to_string(), |
| collection: None, |
| }), |
| source_name: "data".into(), |
| target: cm_rust::OfferTarget::Child(cm_rust::ChildRef { |
| name: "a".to_string(), |
| collection: None, |
| }), |
| target_name: "data".into(), |
| dependency_type: cm_rust::DependencyType::Strong, |
| rights: Some(fio::R_STAR_DIR), |
| subdir: None, |
| })], |
| ..cm_rust::ComponentDecl::default() |
| }, |
| children: vec![( |
| "read-only-directory-0".to_string(), |
| ftest::ChildOptions::EMPTY, |
| ComponentTree { decl: read_only_dir_decl, children: vec![] }, |
| )], |
| }; |
| expected_tree.add_binder_expose(); |
| assert_eq!(expected_tree, tree_from_resolver); |
| assert!(realm_and_builder_task |
| .runner |
| .local_component_proxies() |
| .await |
| .contains_key(&"0".to_string())); |
| } |
| |
| // TODO(88429): The following test is impossible to write until sub-realms are supported |
| // #[fuchsia::test] |
| // async fn replace_component_decl_where_decl_children_conflict_with_mutable_children() { |
| // } |
| } |