| // Copyright 2021 The Fuchsia Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| use { |
| anyhow::anyhow, |
| assert_matches::assert_matches, |
| async_trait::async_trait, |
| cm_fidl_analyzer::{ |
| component_instance::ComponentInstanceForAnalyzer, |
| component_model::{AnalyzerModelError, ComponentModelForAnalyzer, ModelBuilderForAnalyzer}, |
| environment::{BOOT_RESOLVER_NAME, BOOT_SCHEME}, |
| node_path::NodePath, |
| route::{CapabilityRouteError, RouteSegment, VerifyRouteResult}, |
| }, |
| cm_rust::{ |
| Availability, CapabilityDecl, CapabilityName, CapabilityPath, CapabilityTypeName, ChildRef, |
| ComponentDecl, DependencyType, ExposeDecl, ExposeDeclCommon, ExposeDirectoryDecl, |
| ExposeProtocolDecl, ExposeResolverDecl, ExposeServiceDecl, ExposeSource, ExposeTarget, |
| OfferDecl, OfferDirectoryDecl, OfferEventDecl, OfferProtocolDecl, OfferServiceDecl, |
| OfferSource, OfferStorageDecl, OfferTarget, ProtocolDecl, RegistrationSource, ResolverDecl, |
| ResolverRegistration, RunnerDecl, RunnerRegistration, ServiceDecl, StorageDecl, |
| StorageDirectorySource, UseDecl, UseDirectoryDecl, UseEventDecl, UseEventStreamDecl, |
| UseProtocolDecl, UseServiceDecl, UseSource, UseStorageDecl, |
| }, |
| cm_rust_testing::{ |
| ChildDeclBuilder, ComponentDeclBuilder, DirectoryDeclBuilder, EnvironmentDeclBuilder, |
| ProtocolDeclBuilder, |
| }, |
| fidl::prelude::*, |
| fidl_fuchsia_component_decl as fdecl, fidl_fuchsia_component_internal as component_internal, |
| fidl_fuchsia_sys2 as fsys, fuchsia_zircon_status as zx_status, |
| futures::FutureExt, |
| moniker::{AbsoluteMoniker, AbsoluteMonikerBase}, |
| routing::{ |
| component_id_index::ComponentIdIndex, |
| component_instance::ComponentInstanceInterface, |
| config::{AllowlistEntry, CapabilityAllowlistKey, RuntimeConfig, SecurityPolicy}, |
| environment::RunnerRegistry, |
| error::RoutingError, |
| rights::{READ_RIGHTS, WRITE_RIGHTS}, |
| RegistrationDecl, |
| }, |
| routing_test_helpers::{ |
| CheckUse, ComponentEventRoute, ExpectedResult, RoutingTestModel, RoutingTestModelBuilder, |
| }, |
| std::{ |
| collections::{HashMap, HashSet}, |
| convert::{TryFrom, TryInto}, |
| iter::FromIterator, |
| path::Path, |
| sync::Arc, |
| }, |
| thiserror::Error, |
| url::Url, |
| }; |
| |
| const TEST_URL_PREFIX: &str = "test:///"; |
| // Placeholder for when a component resolves to itself, and its name is unknown as a result. |
| const USE_TARGET_PLACEHOLDER_NAME: &str = "target"; |
| |
| fn make_test_url(component_name: &str) -> String { |
| format!("{}{}", TEST_URL_PREFIX, component_name) |
| } |
| |
| pub struct RoutingTestForAnalyzer { |
| model: Arc<ComponentModelForAnalyzer>, |
| } |
| |
| pub struct RoutingTestBuilderForAnalyzer { |
| root_url: cm_types::Url, |
| decls_by_url: HashMap<url::Url, ComponentDecl>, |
| namespace_capabilities: Vec<CapabilityDecl>, |
| builtin_capabilities: Vec<CapabilityDecl>, |
| builtin_runner_registrations: Vec<RunnerRegistration>, |
| capability_policy: HashMap<CapabilityAllowlistKey, HashSet<AllowlistEntry>>, |
| debug_capability_policy: HashMap<CapabilityAllowlistKey, HashSet<(AbsoluteMoniker, String)>>, |
| component_id_index_path: Option<String>, |
| builtin_boot_resolver: component_internal::BuiltinBootResolver, |
| } |
| |
| impl RoutingTestBuilderForAnalyzer { |
| fn set_builtin_boot_resolver(&mut self, resolver: component_internal::BuiltinBootResolver) { |
| self.builtin_boot_resolver = resolver; |
| } |
| |
| // Creates a new builder with the specified map of component URLs to `ComponentDecl`s, rather |
| // than using the default test URL scheme. |
| fn new_with_custom_urls(root_url: String, components: Vec<(String, ComponentDecl)>) -> Self { |
| let root_url = cm_types::Url::new(root_url).expect("failed to parse root component url"); |
| let decls_by_url = |
| HashMap::from_iter(components.into_iter().map(|(url_string, component_decl)| { |
| (Url::parse(&url_string).unwrap(), component_decl) |
| })); |
| Self { |
| root_url, |
| decls_by_url, |
| namespace_capabilities: Vec::new(), |
| builtin_capabilities: Vec::new(), |
| builtin_runner_registrations: Vec::new(), |
| capability_policy: HashMap::new(), |
| debug_capability_policy: HashMap::new(), |
| component_id_index_path: None, |
| builtin_boot_resolver: component_internal::BuiltinBootResolver::None, |
| } |
| } |
| } |
| |
| #[async_trait] |
| impl RoutingTestModelBuilder for RoutingTestBuilderForAnalyzer { |
| type Model = RoutingTestForAnalyzer; |
| |
| // Creates a new builder with the specified components. Components are specified by name; |
| // the method assigns each component a test URL. |
| fn new(root_component: &str, components: Vec<(&'static str, ComponentDecl)>) -> Self { |
| let root_url = cm_types::Url::new(make_test_url(root_component)) |
| .expect("failed to parse root component url"); |
| let decls_by_url = HashMap::from_iter(components.into_iter().map(|(name, decl)| { |
| (Url::parse(&format!("{}{}", TEST_URL_PREFIX, name)).unwrap(), decl) |
| })); |
| Self { |
| root_url, |
| decls_by_url, |
| namespace_capabilities: Vec::new(), |
| builtin_capabilities: Vec::new(), |
| builtin_runner_registrations: Vec::new(), |
| capability_policy: HashMap::new(), |
| debug_capability_policy: HashMap::new(), |
| component_id_index_path: None, |
| builtin_boot_resolver: component_internal::BuiltinBootResolver::None, |
| } |
| } |
| |
| fn set_namespace_capabilities(&mut self, caps: Vec<CapabilityDecl>) { |
| self.namespace_capabilities = caps; |
| } |
| |
| fn set_builtin_capabilities(&mut self, caps: Vec<CapabilityDecl>) { |
| self.builtin_capabilities = caps; |
| } |
| |
| fn register_mock_builtin_runner(&mut self, runner: &str) { |
| let runner_name = CapabilityName(runner.into()); |
| self.builtin_runner_registrations.push(RunnerRegistration { |
| source_name: runner_name.clone(), |
| target_name: runner_name.clone(), |
| source: RegistrationSource::Self_, |
| }); |
| } |
| |
| /// Add a custom capability security policy to restrict routing of certain caps. |
| fn add_capability_policy( |
| &mut self, |
| key: CapabilityAllowlistKey, |
| allowlist: HashSet<AllowlistEntry>, |
| ) { |
| self.capability_policy.insert(key, allowlist); |
| } |
| |
| /// Add a custom debug capability security policy to restrict routing of certain caps. |
| fn add_debug_capability_policy( |
| &mut self, |
| key: CapabilityAllowlistKey, |
| allowlist: HashSet<(AbsoluteMoniker, String)>, |
| ) { |
| self.debug_capability_policy.insert(key, allowlist); |
| } |
| |
| fn set_component_id_index_path(&mut self, index_path: String) { |
| self.component_id_index_path = Some(index_path); |
| } |
| |
| async fn build(self) -> RoutingTestForAnalyzer { |
| let mut config = RuntimeConfig::default(); |
| config.root_component_url = Some(self.root_url.clone()); |
| config.namespace_capabilities = self.namespace_capabilities; |
| config.builtin_capabilities = self.builtin_capabilities; |
| |
| let mut security_policy = SecurityPolicy::default(); |
| security_policy.capability_policy = self.capability_policy; |
| security_policy.debug_capability_policy = self.debug_capability_policy; |
| config.security_policy = security_policy; |
| |
| config.component_id_index_path = self.component_id_index_path; |
| let component_id_index = match config.component_id_index_path { |
| Some(ref index_path) => ComponentIdIndex::new(index_path) |
| .await |
| .expect(&format!("failed to create component ID index with path {}", index_path)), |
| None => ComponentIdIndex::default(), |
| }; |
| config.builtin_boot_resolver = self.builtin_boot_resolver; |
| |
| let root_url = Url::parse(self.root_url.as_str()).unwrap(); |
| let build_model_result = ModelBuilderForAnalyzer::new(root_url).build( |
| self.decls_by_url, |
| Arc::new(config), |
| Arc::new(component_id_index), |
| RunnerRegistry::from_decl(&self.builtin_runner_registrations), |
| ); |
| let model = build_model_result.model.expect("failed to build ComponentModelForAnalyzer"); |
| RoutingTestForAnalyzer { model } |
| } |
| } |
| |
| #[derive(Debug, Error)] |
| pub enum TestModelError { |
| #[error("matching use decl not found")] |
| UseDeclNotFound, |
| #[error("matching expose decl not found")] |
| ExposeDeclNotFound, |
| } |
| |
| impl TestModelError { |
| pub fn as_zx_status(&self) -> zx_status::Status { |
| match self { |
| Self::UseDeclNotFound | Self::ExposeDeclNotFound => zx_status::Status::NOT_FOUND, |
| } |
| } |
| } |
| |
| impl RoutingTestForAnalyzer { |
| fn assert_event_stream_scope( |
| &self, |
| use_decl: &UseEventStreamDecl, |
| scope: &Vec<ComponentEventRoute>, |
| target: &Arc<ComponentInstanceForAnalyzer>, |
| ) { |
| // Perform secondary routing to find scope |
| let mut map = vec![]; |
| ComponentModelForAnalyzer::route_event_stream_sync(use_decl.clone(), &target, &mut map) |
| .expect("Expected event_stream routing to succeed."); |
| let mut route = use_decl |
| .scope |
| .as_ref() |
| .map(|scope| { |
| let route = ComponentEventRoute { |
| component: USE_TARGET_PLACEHOLDER_NAME.to_string(), |
| scope: Some( |
| scope |
| .iter() |
| .map(|s| match s { |
| cm_rust::EventScope::Child(child) => child.name.to_string(), |
| cm_rust::EventScope::Collection(collection) => { |
| collection.to_string() |
| } |
| }) |
| .collect(), |
| ), |
| }; |
| vec![route] |
| }) |
| .unwrap_or_default(); |
| map.reverse(); |
| let search_name = use_decl.source_name.clone(); |
| |
| // Generate a unified route from the component topology |
| generate_unified_route(map, search_name, &mut route); |
| assert_eq!(scope, &route); |
| } |
| |
| fn find_matching_use( |
| &self, |
| check: CheckUse, |
| decl: &ComponentDecl, |
| ) -> (Result<UseDecl, TestModelError>, ExpectedResult) { |
| match check { |
| CheckUse::Directory { path, expected_res, .. } => ( |
| decl.uses |
| .iter() |
| .find_map(|u| match u { |
| UseDecl::Directory(d) if d.target_path == path => Some(u.clone()), |
| _ => None, |
| }) |
| .ok_or(TestModelError::UseDeclNotFound), |
| expected_res, |
| ), |
| CheckUse::Event { request, expected_res, .. } => { |
| let find_decl = decl.uses.iter().find_map(|u| match u { |
| UseDecl::Event(d) if (d.target_name == request.event_name) => Some(d.clone()), |
| _ => None, |
| }); |
| let decl_result = match find_decl { |
| Some(d) => Ok(UseDecl::Event(d)), |
| None => Err(TestModelError::UseDeclNotFound), |
| }; |
| (decl_result, expected_res) |
| } |
| CheckUse::Protocol { path, expected_res, .. } => ( |
| decl.uses |
| .iter() |
| .find_map(|u| match u { |
| UseDecl::Protocol(d) if d.target_path == path => Some(u.clone()), |
| _ => None, |
| }) |
| .ok_or(TestModelError::UseDeclNotFound), |
| expected_res, |
| ), |
| CheckUse::Service { path, expected_res, .. } => ( |
| decl.uses |
| .iter() |
| .find_map(|u| match u { |
| UseDecl::Service(d) if d.target_path == path => Some(u.clone()), |
| _ => None, |
| }) |
| .ok_or(TestModelError::UseDeclNotFound), |
| expected_res, |
| ), |
| CheckUse::Storage { path, expected_res, .. } => ( |
| decl.uses |
| .iter() |
| .find_map(|u| match u { |
| UseDecl::Storage(d) if d.target_path == path => Some(u.clone()), |
| _ => None, |
| }) |
| .ok_or(TestModelError::UseDeclNotFound), |
| expected_res, |
| ), |
| CheckUse::StorageAdmin { expected_res, .. } => ( |
| decl.uses |
| .iter() |
| .find_map(|u| match u { |
| UseDecl::Protocol(d) |
| if d.source_name.to_string() |
| == fsys::StorageAdminMarker::PROTOCOL_NAME => |
| { |
| Some(u.clone()) |
| } |
| _ => None, |
| }) |
| .ok_or(TestModelError::UseDeclNotFound), |
| expected_res, |
| ), |
| CheckUse::EventStream { path, scope: _, name, expected_res } => ( |
| decl.uses |
| .iter() |
| .find_map(|u| match u { |
| UseDecl::EventStream(d) |
| if d.source_name.to_string() == name.to_string() |
| && path == d.target_path => |
| { |
| Some(u.clone()) |
| } |
| _ => None, |
| }) |
| .ok_or(TestModelError::UseDeclNotFound), |
| expected_res, |
| ), |
| } |
| } |
| |
| fn find_matching_expose( |
| &self, |
| check: CheckUse, |
| decl: &ComponentDecl, |
| ) -> (Result<ExposeDecl, TestModelError>, ExpectedResult) { |
| match check { |
| CheckUse::Directory { path, expected_res, .. } |
| | CheckUse::Protocol { path, expected_res, .. } |
| | CheckUse::EventStream { path, expected_res, .. } |
| | CheckUse::Service { path, expected_res, .. } => ( |
| decl.exposes |
| .iter() |
| .find(|&e| e.target_name().to_string() == path.basename) |
| .cloned() |
| .ok_or(TestModelError::ExposeDeclNotFound), |
| expected_res, |
| ), |
| CheckUse::Event { .. } | CheckUse::Storage { .. } | CheckUse::StorageAdmin { .. } => { |
| panic!("attempted to use from expose for unsupported capability type") |
| } |
| } |
| } |
| } |
| |
| /// Converts a component framework route to a strongly-typed stringified route |
| /// which can be compared against a string of paths for testing purposes. |
| fn generate_unified_route( |
| map: Vec<Arc<ComponentInstanceForAnalyzer>>, |
| mut search_name: CapabilityName, |
| route: &mut Vec<ComponentEventRoute>, |
| ) { |
| for component in &map { |
| add_component_to_route(component, &mut search_name, route); |
| } |
| } |
| |
| /// Adds a specified component to the route |
| fn add_component_to_route( |
| component: &Arc<ComponentInstanceForAnalyzer>, |
| search_name: &mut CapabilityName, |
| route: &mut Vec<ComponentEventRoute>, |
| ) { |
| let locked_state = component.lock_resolved_state().now_or_never().unwrap().unwrap(); |
| let offers = locked_state.offers(); |
| let exposes = locked_state.exposes(); |
| let mut component_route = ComponentEventRoute { |
| component: if let Some(moniker) = component.child_moniker() { |
| moniker.name.clone() |
| } else { |
| "/".to_string() |
| }, |
| scope: None, |
| }; |
| scan_event_stream_offers(offers, search_name, &mut component_route); |
| scan_event_stream_exposes(exposes, search_name, &mut component_route); |
| route.push(component_route); |
| } |
| |
| /// Scans exposes for event streams and serializes any scopes that are found to the component_route |
| fn scan_event_stream_exposes( |
| exposes: Vec<ExposeDecl>, |
| search_name: &mut CapabilityName, |
| component_route: &mut ComponentEventRoute, |
| ) { |
| for expose in exposes { |
| // Found match, continue up tree. |
| if let cm_rust::ExposeDecl::EventStream(stream) = expose { |
| if stream.source_name == *search_name { |
| if let Some(scopes) = stream.scope { |
| component_route.scope = Some( |
| scopes |
| .iter() |
| .map(|s| match s { |
| cm_rust::EventScope::Child(child) => child.name.to_string(), |
| cm_rust::EventScope::Collection(collection) => { |
| collection.to_string() |
| } |
| }) |
| .collect(), |
| ); |
| } |
| *search_name = stream.target_name; |
| } |
| } |
| } |
| } |
| |
| /// Scans offers for event streams and serializes any scopes that are found to the component route |
| fn scan_event_stream_offers( |
| offers: Vec<OfferDecl>, |
| search_name: &mut CapabilityName, |
| component_route: &mut ComponentEventRoute, |
| ) { |
| for offer in offers { |
| if let cm_rust::OfferDecl::EventStream(stream) = offer { |
| // Found match, continue up tree. |
| if stream.source_name == *search_name { |
| if let Some(scopes) = stream.scope { |
| component_route.scope = Some( |
| scopes |
| .iter() |
| .map(|s| match s { |
| cm_rust::EventScope::Child(child) => child.name.to_string(), |
| cm_rust::EventScope::Collection(collection) => { |
| collection.to_string() |
| } |
| }) |
| .collect(), |
| ); |
| } |
| *search_name = stream.target_name; |
| } |
| } |
| } |
| } |
| |
| #[async_trait] |
| impl RoutingTestModel for RoutingTestForAnalyzer { |
| type C = ComponentInstanceForAnalyzer; |
| |
| async fn check_use(&self, moniker: AbsoluteMoniker, check: CheckUse) { |
| let target_id = NodePath::new(moniker.path().clone()); |
| let target = self.model.get_instance(&target_id).expect("target instance not found"); |
| let scope = |
| if let CheckUse::EventStream { path: _, ref scope, name: _, expected_res: _ } = check { |
| Some(scope.clone()) |
| } else { |
| None |
| }; |
| let (find_decl, expected) = self.find_matching_use(check, target.decl_for_testing()); |
| |
| // If `find_decl` is not OK, check that `expected` has a matching error. |
| // Otherwise, route the capability and compare the result to `expected`. |
| match &find_decl { |
| Err(err) => { |
| match expected { |
| ExpectedResult::Ok => panic!("expected UseDecl was not found: {}", err), |
| ExpectedResult::Err(status) => { |
| assert_eq!(err.as_zx_status(), status); |
| } |
| ExpectedResult::ErrWithNoEpitaph => {} |
| }; |
| return; |
| } |
| Ok(use_decl) => { |
| for result in self.model.check_use_capability(use_decl, &target).iter() { |
| match result.result { |
| Err(ref err) => match expected { |
| ExpectedResult::Ok => { |
| panic!("routing failed, expected success: {:?}", err) |
| } |
| ExpectedResult::Err(status) => { |
| assert_eq!(err.as_zx_status(), status); |
| } |
| ExpectedResult::ErrWithNoEpitaph => {} |
| }, |
| Ok(_) => match expected { |
| ExpectedResult::Ok => { |
| if let UseDecl::EventStream(use_decl) = use_decl { |
| self.assert_event_stream_scope( |
| use_decl, |
| scope |
| .as_ref() |
| .expect("scope should be non-null for event streams"), |
| &target, |
| ); |
| } |
| } |
| _ => panic!("capability use succeeded, expected failure"), |
| }, |
| } |
| } |
| } |
| } |
| } |
| |
| async fn check_use_exposed_dir(&self, moniker: AbsoluteMoniker, check: CheckUse) { |
| let target = |
| self.model.get_instance(&NodePath::from(moniker)).expect("target instance not found"); |
| |
| let (find_decl, expected) = self.find_matching_expose(check, target.decl_for_testing()); |
| |
| // If `find_decl` is not OK, check that `expected` has a matching error. |
| // Otherwise, route the capability and compare the result to `expected`. |
| match &find_decl { |
| Err(err) => { |
| match expected { |
| ExpectedResult::Ok => panic!("expected ExposeDecl was not found: {}", err), |
| ExpectedResult::Err(status) => { |
| assert_eq!(err.as_zx_status(), status); |
| } |
| _ => unimplemented![], |
| }; |
| return; |
| } |
| Ok(expose_decl) => { |
| match self |
| .model |
| .check_use_exposed_capability(expose_decl, &target) |
| .expect("expected result for exposed directory") |
| .result |
| { |
| Err(CapabilityRouteError::AnalyzerModelError(err)) => match expected { |
| ExpectedResult::Ok => panic!("routing failed, expected success"), |
| ExpectedResult::Err(status) => { |
| assert_eq!(err.as_zx_status(), status); |
| } |
| _ => unimplemented![], |
| }, |
| Err(_) => panic!("expected CapabilityRouteError::AnalyzerModelError"), |
| Ok(_) => match expected { |
| ExpectedResult::Ok => {} |
| _ => panic!("capability use succeeded, expected failure"), |
| }, |
| } |
| } |
| } |
| } |
| |
| async fn look_up_instance( |
| &self, |
| moniker: &AbsoluteMoniker, |
| ) -> Result<Arc<ComponentInstanceForAnalyzer>, anyhow::Error> { |
| self.model.get_instance(&NodePath::from(moniker.clone())).map_err(|err| anyhow!(err)) |
| } |
| |
| // File and directory operations |
| // |
| // All file and directory operations are no-ops for the static model. |
| #[allow(unused_variables)] |
| async fn check_open_file(&self, moniker: AbsoluteMoniker, path: CapabilityPath) {} |
| |
| #[allow(unused_variables)] |
| async fn create_static_file(&self, path: &Path, contents: &str) -> Result<(), anyhow::Error> { |
| Ok(()) |
| } |
| |
| #[allow(unused_variables)] |
| fn install_namespace_directory(&self, path: &str) {} |
| |
| #[allow(unused_variables)] |
| fn add_subdir_to_data_directory(&self, subdir: &str) {} |
| |
| #[allow(unused_variables)] |
| async fn check_test_subdir_contents(&self, path: &str, expected: Vec<String>) {} |
| |
| #[allow(unused_variables)] |
| async fn check_namespace_subdir_contents(&self, path: &str, expected: Vec<String>) {} |
| |
| #[allow(unused_variables)] |
| async fn check_test_subdir_contains(&self, path: &str, expected: String) {} |
| |
| #[allow(unused_variables)] |
| async fn check_test_dir_tree_contains(&self, expected: String) {} |
| } |
| |
| #[cfg(test)] |
| mod tests { |
| use { |
| super::*, |
| cm_rust::{ |
| EventScope, EventStreamDecl, ExposeEventStreamDecl, OfferEventStreamDecl, |
| UseEventStreamDecl, |
| }, |
| routing_test_helpers::instantiate_common_routing_tests, |
| std::str::FromStr, |
| }; |
| |
| instantiate_common_routing_tests! { RoutingTestBuilderForAnalyzer } |
| |
| // Routing of service capabilities is tested as part of the common routing tests |
| // generated by `instantiate_common_routing_tests`. |
| // |
| // In order to test additional validation beyond routing, we need much more setup for component |
| // manager's routing model than we do for the static analyzer's model. The following tests |
| // suffice to test the static analyzer's `check_use_capability()` method for service capabilities. |
| |
| /// a |
| /// / |
| /// b |
| /// |
| /// a: offer to b from self |
| /// b: use from parent |
| #[fuchsia::test] |
| async fn check_use_service_from_parent() { |
| let use_decl = UseServiceDecl { |
| dependency_type: DependencyType::Strong, |
| source: UseSource::Parent, |
| source_name: "foo".into(), |
| target_path: CapabilityPath::try_from("/foo").unwrap(), |
| availability: Availability::Required, |
| }; |
| let components = vec![ |
| ( |
| "a", |
| ComponentDeclBuilder::new() |
| .offer(OfferDecl::Service(OfferServiceDecl { |
| source: OfferSource::Self_, |
| source_name: "foo".into(), |
| source_instance_filter: None, |
| renamed_instances: None, |
| target_name: "foo".into(), |
| target: OfferTarget::static_child("b".to_string()), |
| availability: Availability::Required, |
| })) |
| .service(ServiceDecl { |
| name: "foo".into(), |
| source_path: Some("/svc/foo".try_into().unwrap()), |
| }) |
| .add_lazy_child("b") |
| .build(), |
| ), |
| ("b", ComponentDeclBuilder::new().use_(use_decl.clone().into()).build()), |
| ]; |
| let model = RoutingTestBuilderForAnalyzer::new("a", components).build().await; |
| model |
| .check_use( |
| vec!["b"].into(), |
| CheckUse::Service { |
| path: CapabilityPath::try_from("/foo").unwrap(), |
| instance: "".into(), |
| member: "".into(), |
| expected_res: ExpectedResult::Ok, |
| }, |
| ) |
| .await |
| } |
| |
| /// a |
| /// / |
| /// b |
| /// |
| /// a: offer to b from self |
| /// b: use from parent, but parent component is not executable |
| #[fuchsia::test] |
| async fn check_service_source_is_executable() { |
| let use_decl = UseServiceDecl { |
| dependency_type: DependencyType::Strong, |
| source: UseSource::Parent, |
| source_name: "foo".into(), |
| target_path: CapabilityPath::try_from("/foo").unwrap(), |
| availability: Availability::Required, |
| }; |
| let components = vec![ |
| ( |
| "a", |
| ComponentDeclBuilder::new_empty_component() |
| .offer(OfferDecl::Service(OfferServiceDecl { |
| source: OfferSource::Self_, |
| source_name: "foo".into(), |
| source_instance_filter: None, |
| renamed_instances: None, |
| target_name: "foo".into(), |
| target: OfferTarget::static_child("b".to_string()), |
| availability: Availability::Required, |
| })) |
| .service(ServiceDecl { |
| name: "foo".into(), |
| source_path: Some("/svc/foo".try_into().unwrap()), |
| }) |
| .add_lazy_child("b") |
| .build(), |
| ), |
| ("b", ComponentDeclBuilder::new().use_(use_decl.clone().into()).build()), |
| ]; |
| let model = RoutingTestBuilderForAnalyzer::new("a", components).build().await; |
| model |
| .check_use( |
| vec!["b"].into(), |
| CheckUse::Service { |
| path: CapabilityPath::try_from("/foo").unwrap(), |
| instance: "".into(), |
| member: "".into(), |
| expected_res: ExpectedResult::Err(zx_status::Status::UNAVAILABLE), |
| }, |
| ) |
| .await |
| } |
| |
| /// a |
| /// / |
| /// b |
| /// |
| /// a: use from b |
| /// b: expose to parent from self |
| #[fuchsia::test] |
| async fn check_use_service_from_child() { |
| let use_decl = UseServiceDecl { |
| dependency_type: DependencyType::Strong, |
| source: UseSource::Child("b".to_string()), |
| source_name: "foo".into(), |
| target_path: CapabilityPath::try_from("/foo").unwrap(), |
| availability: Availability::Required, |
| }; |
| let components = vec![ |
| ( |
| "a", |
| ComponentDeclBuilder::new() |
| .use_(use_decl.clone().into()) |
| .add_lazy_child("b") |
| .build(), |
| ), |
| ( |
| "b", |
| ComponentDeclBuilder::new() |
| .service(ServiceDecl { |
| name: "foo".into(), |
| source_path: Some("/svc/foo".try_into().unwrap()), |
| }) |
| .expose(ExposeDecl::Service(ExposeServiceDecl { |
| source: ExposeSource::Self_, |
| source_name: "foo".into(), |
| target_name: "foo".into(), |
| target: ExposeTarget::Parent, |
| })) |
| .build(), |
| ), |
| ]; |
| let model = RoutingTestBuilderForAnalyzer::new("a", components).build().await; |
| model |
| .check_use( |
| vec![].into(), |
| CheckUse::Service { |
| path: CapabilityPath::try_from("/foo").unwrap(), |
| instance: "".into(), |
| member: "".into(), |
| expected_res: ExpectedResult::Ok, |
| }, |
| ) |
| .await |
| } |
| |
| /// a |
| /// / \ |
| /// b c |
| /// |
| /// a: offer to b from child c |
| /// b: use from parent |
| /// c: expose from self |
| #[fuchsia::test] |
| async fn check_use_service_from_sibling() { |
| let use_decl = UseServiceDecl { |
| dependency_type: DependencyType::Strong, |
| source: UseSource::Parent, |
| source_name: "foo".into(), |
| target_path: CapabilityPath::try_from("/foo").unwrap(), |
| availability: Availability::Required, |
| }; |
| let components = vec![ |
| ( |
| "a", |
| ComponentDeclBuilder::new() |
| .offer(OfferDecl::Service(OfferServiceDecl { |
| source: OfferSource::static_child("c".into()), |
| source_name: "foo".into(), |
| source_instance_filter: None, |
| renamed_instances: None, |
| target_name: "foo".into(), |
| target: OfferTarget::static_child("b".to_string()), |
| availability: Availability::Required, |
| })) |
| .add_lazy_child("b") |
| .add_lazy_child("c") |
| .build(), |
| ), |
| ("b", ComponentDeclBuilder::new().use_(use_decl.clone().into()).build()), |
| ( |
| "c", |
| ComponentDeclBuilder::new() |
| .expose(ExposeDecl::Service(ExposeServiceDecl { |
| source: ExposeSource::Self_, |
| source_name: "foo".into(), |
| target_name: "foo".into(), |
| target: ExposeTarget::Parent, |
| })) |
| .service(ServiceDecl { |
| name: "foo".into(), |
| source_path: Some("/svc/foo".try_into().unwrap()), |
| }) |
| .build(), |
| ), |
| ]; |
| let model = RoutingTestBuilderForAnalyzer::new("a", components).build().await; |
| model |
| .check_use( |
| vec!["b"].into(), |
| CheckUse::Service { |
| path: CapabilityPath::try_from("/foo").unwrap(), |
| instance: "".into(), |
| member: "".into(), |
| expected_res: ExpectedResult::Ok, |
| }, |
| ) |
| .await |
| } |
| |
| /// a |
| /// \ |
| /// b |
| /// \ |
| /// c |
| /// |
| /// a: declares runner "elf" as service "/svc/runner" from self. |
| /// a: registers runner "elf" from realm in environment as "hobbit". |
| /// b: creates environment extending from realm. |
| /// c: uses runner "hobbit" in its ProgramDecl. |
| #[fuchsia::test] |
| async fn check_program_runner_from_inherited_environment() { |
| let components = vec![ |
| ( |
| "a", |
| ComponentDeclBuilder::new() |
| .add_child(ChildDeclBuilder::new_lazy_child("b").environment("env").build()) |
| .add_environment( |
| EnvironmentDeclBuilder::new() |
| .name("env") |
| .extends(fdecl::EnvironmentExtends::Realm) |
| .add_runner(RunnerRegistration { |
| source_name: "elf".into(), |
| source: RegistrationSource::Self_, |
| target_name: "hobbit".into(), |
| }) |
| .build(), |
| ) |
| .runner(RunnerDecl { |
| name: "elf".into(), |
| source_path: Some(CapabilityPath::try_from("/svc/runner").unwrap()), |
| }) |
| .build(), |
| ), |
| ( |
| "b", |
| ComponentDeclBuilder::new() |
| .add_child(ChildDeclBuilder::new_lazy_child("c").environment("env").build()) |
| .add_environment( |
| EnvironmentDeclBuilder::new() |
| .name("env") |
| .extends(fdecl::EnvironmentExtends::Realm) |
| .build(), |
| ) |
| .build(), |
| ), |
| ("c", ComponentDeclBuilder::new_empty_component().add_program("hobbit").build()), |
| ]; |
| |
| let test = RoutingTestBuilderForAnalyzer::new("a", components).build().await; |
| let c_component = test.look_up_instance(&vec!["b", "c"].into()).await.expect("c instance"); |
| |
| assert!(test |
| .model |
| .check_program_runner( |
| c_component.decl_for_testing().program.as_ref().expect("missing program decl"), |
| &c_component |
| ) |
| .expect("expected results of program runner check") |
| .result |
| .is_ok()); |
| } |
| |
| /// a |
| /// \ |
| /// b |
| /// |
| /// b: uses framework events "started", and "capability_requested" |
| #[fuchsia::test] |
| pub async fn test_use_event_stream_from_framework_2() { |
| let components = vec![ |
| ("a", ComponentDeclBuilder::new().add_lazy_child("b").build()), |
| ( |
| "b", |
| ComponentDeclBuilder::new() |
| .use_(UseDecl::EventStream(UseEventStreamDecl { |
| source: UseSource::Framework, |
| source_name: "started".into(), |
| target_path: CapabilityPath::from_str("/event/stream").unwrap(), |
| scope: Some(vec![EventScope::Child(cm_rust::ChildRef { |
| collection: None, |
| name: "a".to_string(), |
| })]), |
| availability: Availability::Required, |
| })) |
| .use_(UseDecl::EventStream(UseEventStreamDecl { |
| source: UseSource::Framework, |
| source_name: "capability_requested".into(), |
| target_path: CapabilityPath::from_str("/event/stream").unwrap(), |
| scope: Some(vec![EventScope::Child(cm_rust::ChildRef { |
| collection: None, |
| name: "a".to_string(), |
| })]), |
| availability: Availability::Required, |
| })) |
| .build(), |
| ), |
| ]; |
| |
| let mut builder = RoutingTestBuilderForAnalyzer::new("a", components); |
| builder.set_builtin_capabilities(vec![CapabilityDecl::Protocol(ProtocolDecl { |
| name: "capability_requested".into(), |
| source_path: None, |
| })]); |
| let model = builder.build().await; |
| |
| model |
| .check_use( |
| vec!["b"].into(), |
| CheckUse::EventStream { |
| expected_res: ExpectedResult::Ok, |
| path: CapabilityPath::from_str("/event/stream").unwrap(), |
| scope: vec![ComponentEventRoute { |
| component: "target".to_string(), |
| scope: Some(vec!["a".to_string()]), |
| }], |
| name: "capability_requested".into(), |
| }, |
| ) |
| .await; |
| model |
| .check_use( |
| vec!["b"].into(), |
| CheckUse::EventStream { |
| expected_res: ExpectedResult::Ok, |
| path: CapabilityPath::from_str("/event/stream").unwrap(), |
| scope: vec![ComponentEventRoute { |
| component: "target".to_string(), |
| scope: Some(vec!["a".to_string()]), |
| }], |
| name: "started".into(), |
| }, |
| ) |
| .await; |
| } |
| |
| /// Tests exposing an event_stream from a child through its parent down to another |
| /// unrelated child. |
| /// a |
| /// \ |
| /// b |
| /// /\ |
| /// c f |
| /// /\ |
| /// d e |
| /// c exposes started with a scope of e (but not d) |
| /// to b, which then offers that to f. |
| #[fuchsia::test] |
| pub async fn test_expose_event_stream_with_scope_2() { |
| let components = vec![ |
| ("a", ComponentDeclBuilder::new().add_lazy_child("b").build()), |
| ( |
| "b", |
| ComponentDeclBuilder::new() |
| .offer(OfferDecl::EventStream(OfferEventStreamDecl { |
| source: OfferSource::Child(ChildRef { |
| name: "c".to_string(), |
| collection: None, |
| }), |
| source_name: "started".into(), |
| scope: None, |
| filter: None, |
| target: OfferTarget::Child(ChildRef { |
| name: "f".to_string(), |
| collection: None, |
| }), |
| target_name: CapabilityName::from("started"), |
| availability: Availability::Required, |
| })) |
| .add_lazy_child("c") |
| .add_lazy_child("f") |
| .build(), |
| ), |
| ( |
| "c", |
| ComponentDeclBuilder::new() |
| .expose(ExposeDecl::EventStream(ExposeEventStreamDecl { |
| source: ExposeSource::Framework, |
| source_name: "started".into(), |
| scope: Some(vec![EventScope::Child(ChildRef { |
| name: "e".to_string(), |
| collection: None, |
| })]), |
| target: ExposeTarget::Parent, |
| target_name: CapabilityName::from("started"), |
| })) |
| .add_lazy_child("d") |
| .add_lazy_child("e") |
| .build(), |
| ), |
| ("d", ComponentDeclBuilder::new().build()), |
| ("e", ComponentDeclBuilder::new().build()), |
| ( |
| "f", |
| ComponentDeclBuilder::new() |
| .use_(UseDecl::EventStream(UseEventStreamDecl { |
| source: UseSource::Parent, |
| source_name: "started".into(), |
| target_path: CapabilityPath::from_str("/event/stream").unwrap(), |
| scope: None, |
| availability: Availability::Required, |
| })) |
| .build(), |
| ), |
| ]; |
| |
| let mut builder = RoutingTestBuilderForAnalyzer::new("a", components); |
| builder.set_builtin_capabilities(vec![CapabilityDecl::EventStream(EventStreamDecl { |
| name: "started".into(), |
| })]); |
| |
| let model = builder.build().await; |
| model |
| .check_use( |
| vec!["b", "f"].into(), |
| CheckUse::EventStream { |
| expected_res: ExpectedResult::Ok, |
| path: CapabilityPath::from_str("/event/stream").unwrap(), |
| scope: vec![ |
| ComponentEventRoute { |
| component: "c".to_string(), |
| scope: Some(vec!["e".to_string()]), |
| }, |
| ComponentEventRoute { component: "b".to_string(), scope: None }, |
| ], |
| name: "started".into(), |
| }, |
| ) |
| .await; |
| } |
| |
| /// a |
| /// \ |
| /// b |
| /// |
| /// b: uses framework events "started", and "capability_requested" |
| #[fuchsia::test] |
| pub async fn test_use_event_stream_from_above_root_2() { |
| let components = vec![( |
| "a", |
| ComponentDeclBuilder::new() |
| .use_(UseDecl::EventStream(UseEventStreamDecl { |
| source: UseSource::Parent, |
| source_name: "started".into(), |
| target_path: CapabilityPath::from_str("/event/stream").unwrap(), |
| scope: None, |
| availability: Availability::Required, |
| })) |
| .build(), |
| )]; |
| |
| let mut builder = RoutingTestBuilderForAnalyzer::new("a", components); |
| builder.set_builtin_capabilities(vec![CapabilityDecl::EventStream(EventStreamDecl { |
| name: "started".into(), |
| })]); |
| |
| let model = builder.build().await; |
| model |
| .check_use( |
| vec![].into(), |
| CheckUse::EventStream { |
| expected_res: ExpectedResult::Ok, |
| path: CapabilityPath::from_str("/event/stream").unwrap(), |
| scope: vec![], |
| name: "started".into(), |
| }, |
| ) |
| .await; |
| } |
| |
| /// a |
| /// /\ |
| /// b c |
| /// / \ |
| /// d e |
| /// c: uses framework events "started", and "capability_requested", |
| /// scoped to b and c. |
| /// d receives started which is scoped to b, c, and e. |
| #[fuchsia::test] |
| pub async fn test_use_event_stream_from_above_root_and_downscoped_2() { |
| let components = vec![ |
| ( |
| "a", |
| ComponentDeclBuilder::new() |
| .offer(OfferDecl::EventStream(OfferEventStreamDecl { |
| source: OfferSource::Parent, |
| source_name: "started".into(), |
| scope: Some(vec![ |
| EventScope::Child(ChildRef { name: "b".to_string(), collection: None }), |
| EventScope::Child(ChildRef { name: "c".to_string(), collection: None }), |
| ]), |
| filter: None, |
| target: OfferTarget::Child(ChildRef { |
| name: "b".to_string(), |
| collection: None, |
| }), |
| target_name: CapabilityName::from("started"), |
| availability: Availability::Required, |
| })) |
| .offer(OfferDecl::EventStream(OfferEventStreamDecl { |
| source: OfferSource::Parent, |
| source_name: "started".into(), |
| scope: Some(vec![ |
| EventScope::Child(ChildRef { name: "b".to_string(), collection: None }), |
| EventScope::Child(ChildRef { name: "c".to_string(), collection: None }), |
| ]), |
| filter: None, |
| target: OfferTarget::Child(ChildRef { |
| name: "c".to_string(), |
| collection: None, |
| }), |
| target_name: CapabilityName::from("started"), |
| availability: Availability::Required, |
| })) |
| .add_lazy_child("b") |
| .add_lazy_child("c") |
| .build(), |
| ), |
| ( |
| "b", |
| ComponentDeclBuilder::new() |
| .use_(UseDecl::EventStream(UseEventStreamDecl { |
| source: UseSource::Parent, |
| source_name: "started".into(), |
| target_path: CapabilityPath::from_str("/event/stream").unwrap(), |
| scope: None, |
| availability: Availability::Required, |
| })) |
| .build(), |
| ), |
| ( |
| "c", |
| ComponentDeclBuilder::new() |
| .use_(UseDecl::EventStream(UseEventStreamDecl { |
| source: UseSource::Parent, |
| source_name: "started".into(), |
| target_path: CapabilityPath::from_str("/event/stream").unwrap(), |
| scope: None, |
| availability: Availability::Required, |
| })) |
| .offer(OfferDecl::EventStream(OfferEventStreamDecl { |
| source: OfferSource::Parent, |
| source_name: "started".into(), |
| scope: Some(vec![EventScope::Child(ChildRef { |
| name: "e".to_string(), |
| collection: None, |
| })]), |
| filter: None, |
| target: OfferTarget::Child(ChildRef { |
| name: "d".to_string(), |
| collection: None, |
| }), |
| target_name: CapabilityName::from("started"), |
| availability: Availability::Required, |
| })) |
| .add_lazy_child("d") |
| .add_lazy_child("e") |
| .build(), |
| ), |
| ( |
| "d", |
| ComponentDeclBuilder::new() |
| .use_(UseDecl::EventStream(UseEventStreamDecl { |
| source: UseSource::Parent, |
| source_name: "started".into(), |
| target_path: CapabilityPath::from_str("/event/stream").unwrap(), |
| scope: None, |
| availability: Availability::Required, |
| })) |
| .build(), |
| ), |
| ("e", ComponentDeclBuilder::new().build()), |
| ]; |
| |
| let mut builder = RoutingTestBuilderForAnalyzer::new("a", components); |
| builder.set_builtin_capabilities(vec![CapabilityDecl::EventStream(EventStreamDecl { |
| name: "started".into(), |
| })]); |
| |
| let model = builder.build().await; |
| model |
| .check_use( |
| vec!["b"].into(), |
| CheckUse::EventStream { |
| expected_res: ExpectedResult::Ok, |
| path: CapabilityPath::from_str("/event/stream").unwrap(), |
| scope: vec![ComponentEventRoute { |
| component: "/".to_string(), |
| scope: Some(vec!["b".to_string(), "c".to_string()]), |
| }], |
| name: "started".into(), |
| }, |
| ) |
| .await; |
| model |
| .check_use( |
| vec!["c"].into(), |
| CheckUse::EventStream { |
| expected_res: ExpectedResult::Ok, |
| path: CapabilityPath::from_str("/event/stream").unwrap(), |
| scope: vec![ComponentEventRoute { |
| component: "/".to_string(), |
| scope: Some(vec!["b".to_string(), "c".to_string()]), |
| }], |
| name: "started".into(), |
| }, |
| ) |
| .await; |
| model |
| .check_use( |
| vec!["c", "d"].into(), // Should get e's event from parent |
| CheckUse::EventStream { |
| expected_res: ExpectedResult::Ok, |
| path: CapabilityPath::from_str("/event/stream").unwrap(), |
| scope: vec![ |
| ComponentEventRoute { |
| component: "/".to_string(), |
| scope: Some(vec!["b".to_string(), "c".to_string()]), |
| }, |
| ComponentEventRoute { |
| component: "c".to_string(), |
| scope: Some(vec!["e".to_string()]), |
| }, |
| ], |
| name: "started".into(), |
| }, |
| ) |
| .await; |
| } |
| |
| /// a |
| /// \ |
| /// b |
| /// |
| /// a: declares runner "elf" with service "/svc/runner" from "self". |
| /// a: registers runner "elf" from self in environment as "hobbit". |
| /// b: uses runner "hobbit" in its ProgramDecl. Fails because "hobbit" was not in environment. |
| #[fuchsia::test] |
| async fn check_program_runner_from_environment_not_found() { |
| let components = vec![ |
| ( |
| "a", |
| ComponentDeclBuilder::new() |
| .add_child(ChildDeclBuilder::new_lazy_child("b").environment("env").build()) |
| .add_environment( |
| EnvironmentDeclBuilder::new() |
| .name("env") |
| .extends(fdecl::EnvironmentExtends::Realm) |
| .add_runner(RunnerRegistration { |
| source_name: "elf".into(), |
| source: RegistrationSource::Self_, |
| target_name: "dwarf".into(), |
| }) |
| .build(), |
| ) |
| .runner(RunnerDecl { |
| name: "elf".into(), |
| source_path: Some(CapabilityPath::try_from("/svc/runner").unwrap()), |
| }) |
| .build(), |
| ), |
| ("b", ComponentDeclBuilder::new_empty_component().add_program("hobbit").build()), |
| ]; |
| |
| let test = RoutingTestBuilderForAnalyzer::new("a", components).build().await; |
| let b_component = test.look_up_instance(&vec!["b"].into()).await.expect("b instance"); |
| let check_result = test |
| .model |
| .check_program_runner( |
| &b_component.decl_for_testing().program.as_ref().expect("missing program decl"), |
| &b_component, |
| ) |
| .expect("expected result of program runner check"); |
| |
| assert_matches!( |
| check_result.result, |
| Err(CapabilityRouteError::AnalyzerModelError( |
| AnalyzerModelError::RoutingError( |
| RoutingError::UseFromEnvironmentNotFound { |
| moniker, |
| capability_type, |
| capability_name, |
| }))) |
| if moniker == *b_component.abs_moniker() && |
| capability_type == "runner" && |
| capability_name == CapabilityName("hobbit".to_string()) |
| ); |
| } |
| |
| /// a |
| /// \ |
| /// b |
| /// |
| /// a: creates environment "env" and registers resolver "base" in "env" from self. |
| /// b: has environment "env" and is resolved by the "base" resolver. |
| #[fuchsia::test] |
| async fn check_resolver_from_extended_environment() { |
| let a_url = make_test_url("a"); |
| let b_url = "base://b/".to_string(); |
| |
| let components = vec![ |
| ( |
| a_url.clone(), |
| ComponentDeclBuilder::new() |
| .add_child(ChildDeclBuilder::new().name("b").url(&b_url).environment("env")) |
| .add_environment( |
| EnvironmentDeclBuilder::new() |
| .name("env") |
| .extends(fdecl::EnvironmentExtends::Realm) |
| .add_resolver(ResolverRegistration { |
| resolver: "base".into(), |
| source: RegistrationSource::Self_, |
| scheme: "base".into(), |
| }), |
| ) |
| .resolver(ResolverDecl { |
| name: "base".into(), |
| source_path: Some( |
| "/svc/fuchsia.component.resolution.Resolver".parse().unwrap(), |
| ), |
| }) |
| .build(), |
| ), |
| (b_url, ComponentDeclBuilder::new_empty_component().build()), |
| ]; |
| |
| let test = |
| RoutingTestBuilderForAnalyzer::new_with_custom_urls(a_url, components).build().await; |
| let b_component = test.look_up_instance(&vec!["b"].into()).await.expect("b instance"); |
| |
| let result = test.model.check_resolver(&b_component); |
| assert!(result.result.is_ok()); |
| assert_eq!(result.using_node, NodePath::absolute_from_vec(vec!["b"])); |
| assert_eq!(result.capability, "base"); |
| } |
| |
| /// a |
| /// \ |
| /// b |
| /// \ |
| /// c |
| /// |
| /// a: creates environment "b_env" and registers resolver "base" in "b_env" from self. |
| /// b: inherits environment "b_env" but creates a new empty environment "c_env" for c. |
| /// c: doesn't inherit the "base" resolver. |
| #[fuchsia::test] |
| async fn check_resolver_from_grandparent_environment_not_found() { |
| let a_url = make_test_url("a"); |
| let b_url = make_test_url("b"); |
| let c_url = "base://c/".to_string(); |
| |
| let components = vec![ |
| ( |
| a_url.clone(), |
| ComponentDeclBuilder::new() |
| .add_child(ChildDeclBuilder::new_lazy_child("b").environment("b_env")) |
| .add_environment( |
| EnvironmentDeclBuilder::new() |
| .name("b_env") |
| .extends(fdecl::EnvironmentExtends::Realm) |
| .add_resolver(ResolverRegistration { |
| resolver: "base".into(), |
| source: RegistrationSource::Self_, |
| scheme: "base".into(), |
| }), |
| ) |
| .resolver(ResolverDecl { |
| name: "base".into(), |
| source_path: Some( |
| "/svc/fuchsia.component.resolution.Resolver".parse().unwrap(), |
| ), |
| }) |
| .build(), |
| ), |
| ( |
| b_url, |
| ComponentDeclBuilder::new_empty_component() |
| .add_child(ChildDeclBuilder::new().name("c").url(&c_url).environment("c_env")) |
| .add_environment( |
| EnvironmentDeclBuilder::new() |
| .name("c_env") |
| .extends(fdecl::EnvironmentExtends::None), |
| ) |
| .build(), |
| ), |
| (c_url, ComponentDeclBuilder::new_empty_component().build()), |
| ]; |
| |
| let test = |
| RoutingTestBuilderForAnalyzer::new_with_custom_urls(a_url, components).build().await; |
| let c_component = test.look_up_instance(&vec!["b", "c"].into()).await.expect("c instance"); |
| |
| let result = test.model.check_resolver(&c_component); |
| |
| assert_matches!( |
| &result.result, |
| Err(CapabilityRouteError::AnalyzerModelError( |
| AnalyzerModelError::MissingResolverForScheme( |
| resolver |
| ))) |
| if resolver == "base" |
| ); |
| } |
| |
| /// a |
| /// \ |
| /// b |
| /// |
| /// a: has the standard boot resolver registered in its environment, but |
| /// the resolver is not provided as a built-in capability. |
| /// b: is resolved by the standard boot resolver. |
| #[fuchsia::test] |
| async fn check_resolver_from_builtin_environment_not_found() { |
| let a_url = make_test_url("a"); |
| let b_url = format!("{}://b/", BOOT_SCHEME); |
| |
| let components = vec![ |
| ( |
| a_url.clone(), |
| ComponentDeclBuilder::new() |
| .add_child( |
| ChildDeclBuilder::new().name("b").url(&format!("{}://b", BOOT_SCHEME)), |
| ) |
| .build(), |
| ), |
| (b_url, ComponentDeclBuilder::new().build()), |
| ]; |
| |
| let mut builder = RoutingTestBuilderForAnalyzer::new_with_custom_urls(a_url, components); |
| builder.set_builtin_boot_resolver(component_internal::BuiltinBootResolver::Boot); |
| let test = builder.build().await; |
| let b_component = test.look_up_instance(&vec!["b"].into()).await.expect("b instance"); |
| |
| let result = test.model.check_resolver(&b_component); |
| |
| assert_matches!( |
| &result.result, |
| Err(CapabilityRouteError::AnalyzerModelError( |
| AnalyzerModelError::RoutingError( |
| RoutingError::UseFromComponentManagerNotFound{ |
| capability_id: resolver |
| }))) |
| if resolver == BOOT_RESOLVER_NAME |
| ); |
| } |
| |
| /// a |
| /// \ |
| /// b |
| /// |
| /// a: offers protocol /svc/foo from self as /svc/bar |
| /// b: uses protocol /svc/bar as /svc/hippo |
| #[fuchsia::test] |
| async fn map_route_use_from_parent() { |
| let use_decl = UseDecl::Protocol(UseProtocolDecl { |
| source: UseSource::Parent, |
| source_name: "bar_svc".into(), |
| target_path: CapabilityPath::try_from("/svc/hippo").unwrap(), |
| dependency_type: DependencyType::Strong, |
| availability: Availability::Required, |
| }); |
| let offer_decl = OfferDecl::Protocol(OfferProtocolDecl { |
| source: OfferSource::Self_, |
| source_name: "foo_svc".into(), |
| target_name: "bar_svc".into(), |
| target: OfferTarget::Child(ChildRef { name: "b".to_string(), collection: None }), |
| dependency_type: DependencyType::Strong, |
| availability: Availability::Required, |
| }); |
| let protocol_decl = ProtocolDeclBuilder::new("foo_svc").build(); |
| let components = vec![ |
| ( |
| "a", |
| ComponentDeclBuilder::new() |
| .protocol(protocol_decl.clone()) |
| .offer(offer_decl.clone()) |
| .add_lazy_child("b") |
| .build(), |
| ), |
| ("b", ComponentDeclBuilder::new().use_(use_decl.clone()).build()), |
| ]; |
| let test = RoutingTestBuilderForAnalyzer::new("a", components).build().await; |
| let b_component = test.look_up_instance(&vec!["b"].into()).await.expect("b instance"); |
| let route_result = test.model.check_use_capability(&use_decl, &b_component); |
| assert_eq!(route_result.len(), 1); |
| let route_map = route_result[0].result.clone().expect("expected OK route"); |
| |
| assert_eq!( |
| route_map, |
| vec![ |
| RouteSegment::UseBy { |
| node_path: NodePath::absolute_from_vec(vec!["b"]), |
| capability: use_decl |
| }, |
| RouteSegment::OfferBy { |
| node_path: NodePath::absolute_from_vec(vec![]), |
| capability: offer_decl |
| }, |
| RouteSegment::DeclareBy { |
| node_path: NodePath::absolute_from_vec(vec![]), |
| capability: CapabilityDecl::Protocol(protocol_decl) |
| } |
| ] |
| ) |
| } |
| |
| /// a |
| /// \ |
| /// b |
| /// |
| /// a: uses protocol /svc/bar from b as /svc/hippo |
| /// b: exposes protocol /svc/foo from self as /svc/bar |
| #[fuchsia::test] |
| async fn map_route_use_from_child() { |
| let use_decl = UseDecl::Protocol(UseProtocolDecl { |
| source: UseSource::Child("b".to_string()), |
| source_name: "bar_svc".into(), |
| target_path: CapabilityPath::try_from("/svc/hippo").unwrap(), |
| dependency_type: DependencyType::Strong, |
| availability: Availability::Required, |
| }); |
| let expose_decl = ExposeDecl::Protocol(ExposeProtocolDecl { |
| source: ExposeSource::Self_, |
| source_name: "foo_svc".into(), |
| target_name: "bar_svc".into(), |
| target: ExposeTarget::Parent, |
| }); |
| let protocol_decl = ProtocolDeclBuilder::new("foo_svc").build(); |
| let components = vec![ |
| ("a", ComponentDeclBuilder::new().use_(use_decl.clone()).add_lazy_child("b").build()), |
| ( |
| "b", |
| ComponentDeclBuilder::new() |
| .protocol(protocol_decl.clone()) |
| .expose(expose_decl.clone()) |
| .build(), |
| ), |
| ]; |
| let test = RoutingTestBuilderForAnalyzer::new("a", components).build().await; |
| let a_component = test.look_up_instance(&vec![].into()).await.expect("a instance"); |
| let route_results = test.model.check_use_capability(&use_decl, &a_component); |
| assert_eq!(route_results.len(), 1); |
| let route_map = route_results[0].result.clone().expect("expected OK route"); |
| |
| assert_eq!( |
| route_map, |
| vec![ |
| RouteSegment::UseBy { |
| node_path: NodePath::absolute_from_vec(vec![]), |
| capability: use_decl |
| }, |
| RouteSegment::ExposeBy { |
| node_path: NodePath::absolute_from_vec(vec!["b"]), |
| capability: expose_decl |
| }, |
| RouteSegment::DeclareBy { |
| node_path: NodePath::absolute_from_vec(vec!["b"]), |
| capability: CapabilityDecl::Protocol(protocol_decl) |
| } |
| ] |
| ) |
| } |
| |
| /// a: uses protocol /svc/hippo from self |
| #[fuchsia::test] |
| async fn map_route_use_from_self() { |
| let use_decl = UseDecl::Protocol(UseProtocolDecl { |
| source: UseSource::Self_, |
| source_name: "hippo".into(), |
| target_path: CapabilityPath::try_from("/svc/hippo").unwrap(), |
| dependency_type: DependencyType::Strong, |
| availability: Availability::Required, |
| }); |
| let protocol_decl = ProtocolDeclBuilder::new("hippo").build(); |
| let components = vec![( |
| "a", |
| ComponentDeclBuilder::new() |
| .protocol(protocol_decl.clone()) |
| .use_(use_decl.clone()) |
| .build(), |
| )]; |
| |
| let test = RoutingTestBuilderForAnalyzer::new("a", components).build().await; |
| let a_component = test.look_up_instance(&vec![].into()).await.expect("a instance"); |
| let route_results = test.model.check_use_capability(&use_decl, &a_component); |
| assert_eq!(route_results.len(), 1); |
| let route_map = route_results[0].result.clone().expect("expected OK route"); |
| |
| assert_eq!( |
| route_map, |
| vec![ |
| RouteSegment::UseBy { |
| node_path: NodePath::absolute_from_vec(vec![]), |
| capability: use_decl |
| }, |
| RouteSegment::DeclareBy { |
| node_path: NodePath::absolute_from_vec(vec![]), |
| capability: CapabilityDecl::Protocol(protocol_decl) |
| } |
| ] |
| ) |
| } |
| |
| /// a |
| /// / \ |
| /// b c |
| /// / |
| /// d |
| /// |
| /// d: exposes directory /data/foo from self as /data/bar |
| /// b: exposes directory /data/bar from d as /data/baz |
| /// a: offers directory /data/baz from b as /data/foobar to c |
| /// c: uses /data/foobar as /data/hippo |
| #[fuchsia::test] |
| async fn map_route_use_from_niece() { |
| let use_decl = UseDecl::Directory(UseDirectoryDecl { |
| source: UseSource::Parent, |
| source_name: "foobar_data".into(), |
| target_path: CapabilityPath::try_from("/data/hippo").unwrap(), |
| rights: *READ_RIGHTS, |
| subdir: None, |
| dependency_type: DependencyType::Strong, |
| availability: Availability::Required, |
| }); |
| let a_offer_decl = OfferDecl::Directory(OfferDirectoryDecl { |
| source: OfferSource::static_child("b".to_string()), |
| source_name: "baz_data".into(), |
| target_name: "foobar_data".into(), |
| target: OfferTarget::static_child("c".to_string()), |
| rights: Some(*READ_RIGHTS), |
| subdir: None, |
| dependency_type: DependencyType::Strong, |
| availability: Availability::Required, |
| }); |
| let b_expose_decl = ExposeDecl::Directory(ExposeDirectoryDecl { |
| source: ExposeSource::Child("d".to_string()), |
| source_name: "bar_data".into(), |
| target_name: "baz_data".into(), |
| target: ExposeTarget::Parent, |
| rights: Some(*READ_RIGHTS), |
| subdir: None, |
| }); |
| let d_expose_decl = ExposeDecl::Directory(ExposeDirectoryDecl { |
| source: ExposeSource::Self_, |
| source_name: "foo_data".into(), |
| target_name: "bar_data".into(), |
| target: ExposeTarget::Parent, |
| rights: Some(*READ_RIGHTS), |
| subdir: None, |
| }); |
| let directory_decl = DirectoryDeclBuilder::new("foo_data").build(); |
| |
| let components = vec![ |
| ( |
| "a", |
| ComponentDeclBuilder::new() |
| .offer(a_offer_decl.clone()) |
| .add_lazy_child("b") |
| .add_lazy_child("c") |
| .build(), |
| ), |
| ( |
| "b", |
| ComponentDeclBuilder::new() |
| .expose(b_expose_decl.clone()) |
| .add_lazy_child("d") |
| .build(), |
| ), |
| ("c", ComponentDeclBuilder::new().use_(use_decl.clone()).build()), |
| ( |
| "d", |
| ComponentDeclBuilder::new() |
| .directory(directory_decl.clone()) |
| .expose(d_expose_decl.clone()) |
| .build(), |
| ), |
| ]; |
| |
| let test = RoutingTestBuilderForAnalyzer::new("a", components).build().await; |
| let c_component = test.look_up_instance(&vec!["c"].into()).await.expect("c instance"); |
| let route_results = test.model.check_use_capability(&use_decl, &c_component); |
| assert_eq!(route_results.len(), 1); |
| let route_map = route_results[0].result.clone().expect("expected OK route"); |
| |
| assert_eq!( |
| route_map, |
| vec![ |
| RouteSegment::UseBy { |
| node_path: NodePath::absolute_from_vec(vec!["c"]), |
| capability: use_decl |
| }, |
| RouteSegment::OfferBy { |
| node_path: NodePath::absolute_from_vec(vec![]), |
| capability: a_offer_decl |
| }, |
| RouteSegment::ExposeBy { |
| node_path: NodePath::absolute_from_vec(vec!["b"]), |
| capability: b_expose_decl |
| }, |
| RouteSegment::ExposeBy { |
| node_path: NodePath::absolute_from_vec(vec!["b", "d"]), |
| capability: d_expose_decl |
| }, |
| RouteSegment::DeclareBy { |
| node_path: NodePath::absolute_from_vec(vec!["b", "d"]), |
| capability: CapabilityDecl::Directory(directory_decl), |
| } |
| ] |
| ) |
| } |
| |
| /// a |
| /// \ |
| /// b |
| /// |
| /// a: declares runner "elf" with service "/svc/runner" from "self". |
| /// a: registers runner "elf" from self in environment as "hobbit". |
| /// b: refers to runner "hobbit" in its `ProgramDecl`. |
| #[fuchsia::test] |
| async fn map_route_for_program_runner() { |
| let runner_reg = RunnerRegistration { |
| source_name: "elf".into(), |
| source: RegistrationSource::Self_, |
| target_name: "hobbit".into(), |
| }; |
| let runner_decl = RunnerDecl { |
| name: "elf".into(), |
| source_path: Some(CapabilityPath::try_from("/svc/runner").unwrap()), |
| }; |
| let components = vec![ |
| ( |
| "a", |
| ComponentDeclBuilder::new() |
| .add_child(ChildDeclBuilder::new_lazy_child("b").environment("env").build()) |
| .add_environment( |
| EnvironmentDeclBuilder::new() |
| .name("env") |
| .extends(fdecl::EnvironmentExtends::Realm) |
| .add_runner(runner_reg.clone()) |
| .build(), |
| ) |
| .runner(runner_decl.clone()) |
| .build(), |
| ), |
| ("b", ComponentDeclBuilder::new_empty_component().add_program("hobbit").build()), |
| ]; |
| |
| let test = RoutingTestBuilderForAnalyzer::new("a", components).build().await; |
| let b_component = test.look_up_instance(&vec!["b"].into()).await.expect("b instance"); |
| let route_map = test |
| .model |
| .check_program_runner( |
| &b_component |
| .decl_for_testing() |
| .program |
| .as_ref() |
| .expect("expected ProgramDecl for b"), |
| &b_component, |
| ) |
| .expect("expected result of program runner route") |
| .result |
| .expect("expected OK program runner route"); |
| |
| assert_eq!( |
| route_map, |
| vec![ |
| RouteSegment::RequireRunner { |
| node_path: NodePath::absolute_from_vec(vec!["b"]), |
| runner: "hobbit".into(), |
| }, |
| RouteSegment::RegisterBy { |
| node_path: NodePath::absolute_from_vec(vec![]), |
| capability: RegistrationDecl::Runner(runner_reg) |
| }, |
| RouteSegment::DeclareBy { |
| node_path: NodePath::absolute_from_vec(vec![]), |
| capability: CapabilityDecl::Runner(runner_decl) |
| }, |
| ] |
| ) |
| } |
| |
| /// a |
| /// \ |
| /// b |
| /// |
| /// a: has storage decl with name "cache" with a source of self at path /data |
| /// a: offers cache storage to b from "mystorage" |
| /// b: uses cache storage as /storage |
| /// |
| /// We expect 2 route maps: one for the storage capability and one for the backing |
| /// directory. |
| #[fuchsia::test] |
| async fn map_route_storage_and_dir_from_parent() { |
| let directory_decl = DirectoryDeclBuilder::new("data") |
| .path("/data") |
| .rights(*READ_RIGHTS | *WRITE_RIGHTS) |
| .build(); |
| let storage_decl = StorageDecl { |
| name: "cache".into(), |
| backing_dir: "data".try_into().unwrap(), |
| source: StorageDirectorySource::Self_, |
| subdir: None, |
| storage_id: fdecl::StorageId::StaticInstanceIdOrMoniker, |
| }; |
| let offer_storage_decl = OfferDecl::Storage(OfferStorageDecl { |
| source: OfferSource::Self_, |
| target: OfferTarget::static_child("b".to_string()), |
| source_name: "cache".into(), |
| target_name: "cache".into(), |
| availability: Availability::Required, |
| }); |
| let use_storage_decl = UseDecl::Storage(UseStorageDecl { |
| source_name: "cache".into(), |
| target_path: "/storage".try_into().unwrap(), |
| availability: Availability::Required, |
| }); |
| let components = vec![ |
| ( |
| "a", |
| ComponentDeclBuilder::new() |
| .storage(storage_decl.clone()) |
| .directory(directory_decl.clone()) |
| .offer(offer_storage_decl.clone()) |
| .add_lazy_child("b") |
| .build(), |
| ), |
| ("b", ComponentDeclBuilder::new().use_(use_storage_decl.clone()).build()), |
| ]; |
| |
| let test = RoutingTestBuilderForAnalyzer::new("a", components).build().await; |
| let b_component = test.look_up_instance(&vec!["b"].into()).await.expect("b instance"); |
| let route_results = test.model.check_use_capability(&use_storage_decl, &b_component); |
| assert_eq!(route_results.len(), 2); |
| let storage_route_map = |
| route_results[0].result.clone().expect("expected OK route for storage capability"); |
| let backing_directory_route_map = route_results[1] |
| .result |
| .clone() |
| .expect("expected OK route for storage-backing directory"); |
| |
| assert_eq!( |
| storage_route_map, |
| vec![ |
| RouteSegment::UseBy { |
| node_path: NodePath::absolute_from_vec(vec!["b"]), |
| capability: use_storage_decl |
| }, |
| RouteSegment::OfferBy { |
| node_path: NodePath::absolute_from_vec(vec![]), |
| capability: offer_storage_decl |
| }, |
| RouteSegment::DeclareBy { |
| node_path: NodePath::absolute_from_vec(vec![]), |
| capability: CapabilityDecl::Storage(storage_decl.clone()) |
| } |
| ] |
| ); |
| assert_eq!( |
| backing_directory_route_map, |
| vec![ |
| RouteSegment::RegisterBy { |
| node_path: NodePath::absolute_from_vec(vec![]), |
| capability: RegistrationDecl::Storage(storage_decl.into()) |
| }, |
| RouteSegment::DeclareBy { |
| node_path: NodePath::absolute_from_vec(vec![]), |
| capability: CapabilityDecl::Directory(directory_decl) |
| } |
| ] |
| ); |
| } |
| |
| /// a |
| /// \ |
| /// b |
| /// |
| /// a: offers framework event "started" to b as "started_on_a" |
| /// a: offers built-in protocol "fuchsia.sys2.EventSource" to b |
| /// b: uses "started_on_a" as "started" |
| /// b: uses protocol "fuchsia.sys2.EventSource" |
| #[fuchsia::test] |
| async fn route_map_use_from_framework_and_builtin() { |
| let offer_event_decl = OfferDecl::Event(OfferEventDecl { |
| source: OfferSource::Framework, |
| source_name: "started".into(), |
| target_name: "started_on_a".into(), |
| target: OfferTarget::static_child("b".to_string()), |
| filter: None, |
| availability: Availability::Required, |
| }); |
| let use_event_decl = UseDecl::Event(UseEventDecl { |
| source: UseSource::Parent, |
| source_name: "started_on_a".into(), |
| target_name: "started".into(), |
| filter: None, |
| dependency_type: DependencyType::Strong, |
| availability: Availability::Required, |
| }); |
| |
| let offer_event_source_decl = OfferDecl::Protocol(OfferProtocolDecl { |
| source: OfferSource::Parent, |
| source_name: "fuchsia.sys2.EventSource".try_into().unwrap(), |
| target_name: "fuchsia.sys2.EventSource".try_into().unwrap(), |
| target: OfferTarget::static_child("b".to_string()), |
| dependency_type: DependencyType::Strong, |
| availability: Availability::Required, |
| }); |
| let use_event_source_decl = UseDecl::Protocol(UseProtocolDecl { |
| source: UseSource::Parent, |
| source_name: "fuchsia.sys2.EventSource".try_into().unwrap(), |
| target_path: "/svc/fuchsia.sys2.EventSource".try_into().unwrap(), |
| dependency_type: DependencyType::Strong, |
| availability: Availability::Required, |
| }); |
| let event_source_decl = CapabilityDecl::Protocol(ProtocolDecl { |
| name: "fuchsia.sys2.EventSource".into(), |
| source_path: None, |
| }); |
| |
| let components = vec![ |
| ( |
| "a", |
| ComponentDeclBuilder::new() |
| .offer(offer_event_decl.clone()) |
| .offer(offer_event_source_decl.clone()) |
| .add_lazy_child("b") |
| .build(), |
| ), |
| ( |
| "b", |
| ComponentDeclBuilder::new() |
| .use_(use_event_source_decl.clone()) |
| .use_(use_event_decl.clone()) |
| .build(), |
| ), |
| ]; |
| |
| let mut builder = RoutingTestBuilderForAnalyzer::new("a", components); |
| builder.set_builtin_capabilities(vec![event_source_decl.clone()]); |
| let test = builder.build().await; |
| |
| let b_component = test.look_up_instance(&vec!["b"].into()).await.expect("b instance"); |
| let event_route_results = test.model.check_use_capability(&use_event_decl, &b_component); |
| assert_eq!(event_route_results.len(), 1); |
| let event_route_map = event_route_results[0].result.clone().expect("expected OK route"); |
| |
| assert_eq!( |
| event_route_map, |
| vec![ |
| RouteSegment::UseBy { |
| node_path: NodePath::absolute_from_vec(vec!["b"]), |
| capability: use_event_decl |
| }, |
| RouteSegment::OfferBy { |
| node_path: NodePath::absolute_from_vec(vec![]), |
| capability: offer_event_decl |
| }, |
| RouteSegment::ProvideFromFramework { capability: "started".into() } |
| ] |
| ); |
| |
| let event_source_route_results = |
| test.model.check_use_capability(&use_event_source_decl, &b_component); |
| assert_eq!(event_source_route_results.len(), 1); |
| let event_source_route_map = |
| event_source_route_results[0].result.clone().expect("expected OK route"); |
| |
| assert_eq!( |
| event_source_route_map, |
| vec![ |
| RouteSegment::UseBy { |
| node_path: NodePath::absolute_from_vec(vec!["b"]), |
| capability: use_event_source_decl |
| }, |
| RouteSegment::OfferBy { |
| node_path: NodePath::absolute_from_vec(vec![]), |
| capability: offer_event_source_decl |
| }, |
| RouteSegment::ProvideAsBuiltin { capability: event_source_decl } |
| ] |
| ) |
| } |
| |
| /// component manager's namespace |
| /// | |
| /// a |
| /// \ |
| /// b |
| /// |
| /// a: offers protocol /offer_from_cm_namespace/svc/foo from component manager's |
| /// namespace as bar_svc |
| /// b: uses protocol bar_svc as /svc/hippo |
| #[fuchsia::test] |
| async fn route_map_offer_from_component_manager_namespace() { |
| let offer_decl = OfferDecl::Protocol(OfferProtocolDecl { |
| source: OfferSource::Parent, |
| source_name: "foo_svc".into(), |
| target_name: "bar_svc".into(), |
| target: OfferTarget::static_child("b".to_string()), |
| dependency_type: DependencyType::Strong, |
| availability: Availability::Required, |
| }); |
| let use_decl = UseDecl::Protocol(UseProtocolDecl { |
| source: UseSource::Parent, |
| source_name: "bar_svc".into(), |
| target_path: CapabilityPath::try_from("/svc/hippo").unwrap(), |
| dependency_type: DependencyType::Strong, |
| availability: Availability::Required, |
| }); |
| let capability_decl = CapabilityDecl::Protocol( |
| ProtocolDeclBuilder::new("foo_svc").path("/offer_from_cm_namespace/svc/foo").build(), |
| ); |
| let components = vec![ |
| ( |
| "a", |
| ComponentDeclBuilder::new().offer(offer_decl.clone()).add_lazy_child("b").build(), |
| ), |
| ("b", ComponentDeclBuilder::new().use_(use_decl.clone()).build()), |
| ]; |
| |
| let mut builder = RoutingTestBuilderForAnalyzer::new("a", components); |
| builder.set_namespace_capabilities(vec![capability_decl.clone()]); |
| let test = builder.build().await; |
| test.install_namespace_directory("/offer_from_cm_namespace"); |
| |
| let b_component = test.look_up_instance(&vec!["b"].into()).await.expect("b instance"); |
| let route_results = test.model.check_use_capability(&use_decl, &b_component); |
| assert_eq!(route_results.len(), 1); |
| let route_map = route_results[0].result.clone().expect("expected OK route"); |
| |
| assert_eq!( |
| route_map, |
| vec![ |
| RouteSegment::UseBy { |
| node_path: NodePath::absolute_from_vec(vec!["b"]), |
| capability: use_decl |
| }, |
| RouteSegment::OfferBy { |
| node_path: NodePath::absolute_from_vec(vec![]), |
| capability: offer_decl |
| }, |
| RouteSegment::ProvideFromNamespace { capability: capability_decl } |
| ] |
| ); |
| } |
| |
| /// a |
| /// / \ |
| /// b c |
| /// |
| /// a: creates environment "env" and registers resolver "base" from c. |
| /// b: resolved by resolver "base" through "env". |
| /// c: exposes resolver "base" from self. |
| #[fuchsia::test] |
| async fn route_map_resolver_from_parent_environment() { |
| let a_url = make_test_url("a"); |
| let b_url = "base://b/".to_string(); |
| let c_url = make_test_url("c"); |
| |
| let registration_decl = ResolverRegistration { |
| resolver: "base".into(), |
| source: RegistrationSource::Child("c".to_string()), |
| scheme: "base".into(), |
| }; |
| let expose_decl = ExposeDecl::Resolver(ExposeResolverDecl { |
| source: ExposeSource::Self_, |
| source_name: "base".into(), |
| target: ExposeTarget::Parent, |
| target_name: "base".into(), |
| }); |
| let resolver_decl = ResolverDecl { |
| name: "base".into(), |
| source_path: Some("/svc/fuchsia.component.resolution.Resolver".parse().unwrap()), |
| }; |
| |
| let components = vec![ |
| ( |
| a_url.clone(), |
| ComponentDeclBuilder::new_empty_component() |
| .add_child(ChildDeclBuilder::new().name("b").url(&b_url).environment("env")) |
| .add_child(ChildDeclBuilder::new_lazy_child("c")) |
| .add_environment( |
| EnvironmentDeclBuilder::new() |
| .name("env") |
| .extends(fdecl::EnvironmentExtends::Realm) |
| .add_resolver(registration_decl.clone()), |
| ) |
| .build(), |
| ), |
| (b_url, ComponentDeclBuilder::new().build()), |
| ( |
| c_url, |
| ComponentDeclBuilder::new() |
| .expose(expose_decl.clone()) |
| .resolver(resolver_decl.clone()) |
| .build(), |
| ), |
| ]; |
| |
| let test = |
| RoutingTestBuilderForAnalyzer::new_with_custom_urls(a_url, components).build().await; |
| let b_component = test.look_up_instance(&vec!["b"].into()).await.expect("b instance"); |
| |
| let route_map = test.model.check_resolver(&b_component); |
| |
| assert_eq!(route_map.using_node, NodePath::absolute_from_vec(vec!["b"])); |
| assert_eq!(route_map.capability, "base"); |
| assert_eq!( |
| route_map.result.clone().expect("expected OK route"), |
| vec![ |
| RouteSegment::RequireResolver { |
| node_path: NodePath::absolute_from_vec(vec!["b"]), |
| scheme: "base".to_string(), |
| }, |
| RouteSegment::RegisterBy { |
| node_path: NodePath::absolute_from_vec(vec![]), |
| capability: RegistrationDecl::Resolver(registration_decl) |
| }, |
| RouteSegment::ExposeBy { |
| node_path: NodePath::absolute_from_vec(vec!["c"]), |
| capability: expose_decl |
| }, |
| RouteSegment::DeclareBy { |
| node_path: NodePath::absolute_from_vec(vec!["c"]), |
| capability: CapabilityDecl::Resolver(resolver_decl) |
| } |
| ] |
| ); |
| } |
| |
| /// a |
| /// \ |
| /// b |
| /// \ |
| /// c |
| /// |
| /// a: creates environment "env" and registers resolver "base" from self. |
| /// b: has environment "env" extending the realm's environment. |
| /// c: inherits "env" and is resolved by "base" from grandparent. |
| #[fuchsia::test] |
| async fn route_map_resolver_from_grandparent_environment() { |
| let a_url = make_test_url("a"); |
| let b_url = make_test_url("b"); |
| let c_url = "base://c/".to_string(); |
| |
| let registration_decl = ResolverRegistration { |
| resolver: "base".into(), |
| source: RegistrationSource::Self_, |
| scheme: "base".into(), |
| }; |
| let resolver_decl = ResolverDecl { |
| name: "base".into(), |
| source_path: Some("/svc/fuchsia.component.resolution.Resolver".parse().unwrap()), |
| }; |
| let components = vec![ |
| ( |
| a_url.clone(), |
| ComponentDeclBuilder::new() |
| .add_child(ChildDeclBuilder::new_lazy_child("b").environment("env")) |
| .add_environment( |
| EnvironmentDeclBuilder::new() |
| .name("env") |
| .extends(fdecl::EnvironmentExtends::Realm) |
| .add_resolver(registration_decl.clone()), |
| ) |
| .resolver(resolver_decl.clone()) |
| .build(), |
| ), |
| ( |
| b_url, |
| ComponentDeclBuilder::new_empty_component() |
| .add_child(ChildDeclBuilder::new().name("c").url(&c_url)) |
| .build(), |
| ), |
| (c_url, ComponentDeclBuilder::new_empty_component().build()), |
| ]; |
| |
| let test = |
| RoutingTestBuilderForAnalyzer::new_with_custom_urls(a_url, components).build().await; |
| let c_component = test.look_up_instance(&vec!["b", "c"].into()).await.expect("c instance"); |
| |
| let route_map = test.model.check_resolver(&c_component); |
| |
| assert_eq!(route_map.using_node, NodePath::absolute_from_vec(vec!["b", "c"])); |
| assert_eq!(route_map.capability, "base"); |
| assert_eq!( |
| route_map.result.clone().expect("expected OK route"), |
| vec![ |
| RouteSegment::RequireResolver { |
| node_path: NodePath::absolute_from_vec(vec!["b", "c"]), |
| scheme: "base".to_string(), |
| }, |
| RouteSegment::RegisterBy { |
| node_path: NodePath::absolute_from_vec(vec![]), |
| capability: RegistrationDecl::Resolver(registration_decl) |
| }, |
| RouteSegment::DeclareBy { |
| node_path: NodePath::absolute_from_vec(vec![]), |
| capability: CapabilityDecl::Resolver(resolver_decl) |
| } |
| ] |
| ); |
| } |
| |
| /// a |
| /// \ |
| /// b |
| /// |
| /// a: is provided with the standard built-in boot resolver. |
| /// b: is resolved by the standard boot resolver. |
| #[fuchsia::test] |
| async fn route_map_resolver_from_builtin_environment() { |
| let a_url = make_test_url("a"); |
| let b_url = format!("{}://b/", BOOT_SCHEME); |
| |
| let boot_resolver_decl = CapabilityDecl::Resolver(ResolverDecl { |
| name: BOOT_RESOLVER_NAME.into(), |
| source_path: Some("/builtin/source/path".parse().unwrap()), |
| }); |
| |
| let components = vec![ |
| ( |
| a_url.clone(), |
| ComponentDeclBuilder::new() |
| .add_child(ChildDeclBuilder::new().name("b").url(&b_url)) |
| .build(), |
| ), |
| (b_url, ComponentDeclBuilder::new().build()), |
| ]; |
| |
| let mut builder = RoutingTestBuilderForAnalyzer::new_with_custom_urls(a_url, components); |
| builder.set_builtin_boot_resolver(component_internal::BuiltinBootResolver::Boot); |
| builder.set_builtin_capabilities(vec![boot_resolver_decl.clone()]); |
| let test = builder.build().await; |
| let b_component = test.look_up_instance(&vec!["b"].into()).await.expect("b instance"); |
| |
| let route_map = test.model.check_resolver(&b_component); |
| |
| assert_eq!(route_map.using_node, NodePath::absolute_from_vec(vec!["b"])); |
| assert_eq!(route_map.capability, BOOT_RESOLVER_NAME); |
| assert_eq!( |
| route_map.result.clone().expect("expected OK route"), |
| vec![ |
| RouteSegment::RequireResolver { |
| node_path: NodePath::absolute_from_vec(vec!["b"]), |
| scheme: BOOT_SCHEME.to_string(), |
| }, |
| RouteSegment::ProvideAsBuiltin { capability: boot_resolver_decl } |
| ] |
| ); |
| } |
| |
| /// a |
| /// \ |
| /// b |
| /// |
| /// a: creates environment "env" and registers resolver "test" from self. |
| /// b: has environment "env" and a relative url that is resolved by resolver "test" from parent. |
| #[fuchsia::test] |
| async fn route_map_resolver_relative_child_url() { |
| let a_url = make_test_url("a"); |
| let b_relative = "#b"; |
| let b_url = format!("{}{}", a_url, b_relative); |
| |
| let resolver_registration = ResolverRegistration { |
| resolver: "test".into(), |
| source: RegistrationSource::Self_, |
| scheme: "test".into(), |
| }; |
| let resolver_decl = ResolverDecl { |
| name: "test".into(), |
| source_path: Some("/svc/fuchsia.component.resolution.Resolver".parse().unwrap()), |
| }; |
| let components = vec![ |
| ( |
| a_url.clone(), |
| ComponentDeclBuilder::new() |
| .add_child(ChildDeclBuilder::new().name("b").url(b_relative).environment("env")) |
| .add_environment( |
| EnvironmentDeclBuilder::new() |
| .name("env") |
| .extends(fdecl::EnvironmentExtends::Realm) |
| .add_resolver(resolver_registration.clone()), |
| ) |
| .resolver(resolver_decl.clone()) |
| .build(), |
| ), |
| (b_url, ComponentDeclBuilder::new().build()), |
| ]; |
| |
| let test = |
| RoutingTestBuilderForAnalyzer::new_with_custom_urls(a_url, components).build().await; |
| let b_component = test.look_up_instance(&vec!["b"].into()).await.expect("b instance"); |
| |
| let route_map = test.model.check_resolver(&b_component); |
| |
| assert_eq!(route_map.using_node, NodePath::absolute_from_vec(vec!["b"])); |
| assert_eq!(route_map.capability, "test"); |
| assert_eq!( |
| route_map.result.clone().expect("expected OK route"), |
| vec![ |
| RouteSegment::RequireResolver { |
| node_path: NodePath::absolute_from_vec(vec!["b"]), |
| scheme: "test".to_string(), |
| }, |
| RouteSegment::RegisterBy { |
| node_path: NodePath::absolute_from_vec(vec![]), |
| capability: RegistrationDecl::Resolver(resolver_registration) |
| }, |
| RouteSegment::DeclareBy { |
| node_path: NodePath::absolute_from_vec(vec![]), |
| capability: CapabilityDecl::Resolver(resolver_decl) |
| } |
| ] |
| ); |
| } |
| |
| /// a: is provided with the built-in ELF runner, and requires that runner |
| /// in its `ProgramDecl`. |
| #[fuchsia::test] |
| async fn route_map_program_runner_from_builtin_environment() { |
| let elf_runner_decl = CapabilityDecl::Runner(RunnerDecl { |
| name: "elf".into(), |
| source_path: Some("/builtin/source/path".parse().unwrap()), |
| }); |
| let component_decl = ComponentDeclBuilder::new_empty_component().add_program("elf").build(); |
| |
| let components = vec![("a", component_decl.clone())]; |
| |
| let mut builder = RoutingTestBuilderForAnalyzer::new("a", components); |
| builder.set_builtin_capabilities(vec![elf_runner_decl.clone()]); |
| builder.register_mock_builtin_runner("elf"); |
| let test = builder.build().await; |
| let a_component = test.look_up_instance(&vec![].into()).await.expect("a instance"); |
| |
| let route_map = test |
| .model |
| .check_program_runner( |
| &component_decl.program.expect("expected ProgramDecl for a"), |
| &a_component, |
| ) |
| .expect("expected program runner route") |
| .result |
| .expect("expected OK route"); |
| |
| assert_eq!( |
| route_map, |
| vec![ |
| RouteSegment::RequireRunner { |
| node_path: NodePath::absolute_from_vec(vec![]), |
| runner: "elf".into(), |
| }, |
| RouteSegment::ProvideAsBuiltin { capability: elf_runner_decl }, |
| ] |
| ); |
| } |
| |
| /// a |
| /// \ |
| /// b |
| /// \ |
| /// c |
| /// |
| /// a: Creates environment "env". |
| /// Registers resolver "base" from self. |
| /// Registers runner "hobbit" from as "dwarf" from self. |
| /// Offers directory "foo_data" from self as "bar_data". |
| /// b: Has environment "env". |
| /// Requires runner "dwarf", routed successfully from "env". |
| /// Requires resolver "base" to resolve child "c", routed successfully from "env". |
| /// Uses directory "bar_data", routed successfully from parent. |
| /// Exposes "bad_protocol" from child, routing should fail. |
| /// Uses event "started", but routing is not checked because the "event" capability type is not |
| /// selected. |
| /// c: is resolved by resolver "base" from grandparent. |
| #[fuchsia::test] |
| async fn route_maps_all_routes_for_instance() { |
| let a_url = make_test_url("a"); |
| let b_url = "base://b/".to_string(); |
| |
| let resolver_registration_decl = ResolverRegistration { |
| resolver: "base_resolver".into(), |
| source: RegistrationSource::Self_, |
| scheme: "base".into(), |
| }; |
| let runner_registration_decl = RunnerRegistration { |
| source_name: "hobbit".into(), |
| source: RegistrationSource::Self_, |
| target_name: "dwarf".into(), |
| }; |
| let resolver_decl = ResolverDecl { |
| name: "base_resolver".into(), |
| source_path: Some("/svc/fuchsia.component.resolution.Resolver".parse().unwrap()), |
| }; |
| let runner_decl = RunnerDecl { |
| name: "hobbit".into(), |
| source_path: Some(CapabilityPath::try_from("/svc/runner").unwrap()), |
| }; |
| let use_directory_decl = UseDecl::Directory(UseDirectoryDecl { |
| source: UseSource::Parent, |
| source_name: "bar_data".into(), |
| target_path: CapabilityPath::try_from("/data/hippo").unwrap(), |
| rights: *READ_RIGHTS, |
| subdir: None, |
| dependency_type: DependencyType::Strong, |
| availability: Availability::Required, |
| }); |
| let offer_directory_decl = OfferDecl::Directory(OfferDirectoryDecl { |
| source: OfferSource::Self_, |
| source_name: "foo_data".into(), |
| target_name: "bar_data".into(), |
| target: OfferTarget::static_child("b".to_string()), |
| rights: Some(*READ_RIGHTS), |
| subdir: None, |
| dependency_type: DependencyType::Strong, |
| availability: Availability::Required, |
| }); |
| let directory_decl = DirectoryDeclBuilder::new("foo_data").build(); |
| let expose_protocol_decl = ExposeDecl::Protocol(ExposeProtocolDecl { |
| source: ExposeSource::Child("c".to_string()), |
| source_name: "bad_protocol".into(), |
| target_name: "bad_protocol".into(), |
| target: ExposeTarget::Parent, |
| }); |
| let use_event_decl = UseDecl::Event(UseEventDecl { |
| source: UseSource::Parent, |
| source_name: "started_on_a".into(), |
| target_name: "started".into(), |
| filter: None, |
| dependency_type: DependencyType::Strong, |
| availability: Availability::Required, |
| }); |
| |
| let components = vec![ |
| ( |
| a_url.clone(), |
| ComponentDeclBuilder::new() |
| .add_child(ChildDeclBuilder::new().name("b").url(&b_url).environment("env")) |
| .add_environment( |
| EnvironmentDeclBuilder::new() |
| .name("env") |
| .extends(fdecl::EnvironmentExtends::Realm) |
| .add_resolver(resolver_registration_decl.clone()) |
| .add_runner(runner_registration_decl.clone()), |
| ) |
| .offer(offer_directory_decl.clone()) |
| .directory(directory_decl.clone()) |
| .resolver(resolver_decl.clone()) |
| .runner(runner_decl.clone()) |
| .build(), |
| ), |
| ( |
| b_url, |
| ComponentDeclBuilder::new_empty_component() |
| .add_program("dwarf") |
| .expose(expose_protocol_decl) |
| .use_(use_directory_decl.clone()) |
| .use_(use_event_decl) |
| .build(), |
| ), |
| ]; |
| |
| let test = |
| RoutingTestBuilderForAnalyzer::new_with_custom_urls(a_url, components).build().await; |
| let b_component = test.look_up_instance(&vec!["b"].into()).await.expect("b instance"); |
| |
| let route_maps = test.model.check_routes_for_instance( |
| &b_component, |
| &HashSet::from_iter( |
| vec![ |
| CapabilityTypeName::Resolver, |
| CapabilityTypeName::Runner, |
| CapabilityTypeName::Directory, |
| CapabilityTypeName::Protocol, |
| ] |
| .into_iter(), |
| ), |
| ); |
| assert_eq!(route_maps.len(), 4); |
| |
| let directories = |
| route_maps.get(&CapabilityTypeName::Directory).expect("expected directory results"); |
| assert_eq!( |
| directories, |
| &vec![VerifyRouteResult { |
| using_node: NodePath::absolute_from_vec(vec!["b"]), |
| capability: "bar_data".into(), |
| result: Ok(vec![ |
| RouteSegment::UseBy { |
| node_path: NodePath::absolute_from_vec(vec!["b"]), |
| capability: use_directory_decl, |
| }, |
| RouteSegment::OfferBy { |
| node_path: NodePath::absolute_from_vec(vec![]), |
| capability: offer_directory_decl, |
| }, |
| RouteSegment::DeclareBy { |
| node_path: NodePath::absolute_from_vec(vec![]), |
| capability: CapabilityDecl::Directory(directory_decl), |
| } |
| ]) |
| }] |
| ); |
| |
| let runners = route_maps.get(&CapabilityTypeName::Runner).expect("expected runner results"); |
| assert_eq!( |
| runners, |
| &vec![VerifyRouteResult { |
| using_node: NodePath::absolute_from_vec(vec!["b"]), |
| capability: "dwarf".into(), |
| result: Ok(vec![ |
| RouteSegment::RequireRunner { |
| node_path: NodePath::absolute_from_vec(vec!["b"]), |
| runner: "dwarf".into(), |
| }, |
| RouteSegment::RegisterBy { |
| node_path: NodePath::absolute_from_vec(vec![]), |
| capability: RegistrationDecl::Runner(runner_registration_decl) |
| }, |
| RouteSegment::DeclareBy { |
| node_path: NodePath::absolute_from_vec(vec![]), |
| capability: CapabilityDecl::Runner(runner_decl) |
| } |
| ]) |
| }] |
| ); |
| |
| let resolvers = |
| route_maps.get(&CapabilityTypeName::Resolver).expect("expected resolver results"); |
| assert_eq!( |
| resolvers, |
| &vec![VerifyRouteResult { |
| using_node: NodePath::absolute_from_vec(vec!["b"]), |
| capability: "base_resolver".into(), |
| result: Ok(vec![ |
| RouteSegment::RequireResolver { |
| node_path: NodePath::absolute_from_vec(vec!["b"]), |
| scheme: "base".to_string(), |
| }, |
| RouteSegment::RegisterBy { |
| node_path: NodePath::absolute_from_vec(vec![]), |
| capability: RegistrationDecl::Resolver(resolver_registration_decl) |
| }, |
| RouteSegment::DeclareBy { |
| node_path: NodePath::absolute_from_vec(vec![]), |
| capability: CapabilityDecl::Resolver(resolver_decl) |
| } |
| ]) |
| }] |
| ); |
| |
| let protocols = |
| route_maps.get(&CapabilityTypeName::Protocol).expect("expected protocol results"); |
| assert_eq!( |
| protocols, |
| &vec![VerifyRouteResult { |
| using_node: NodePath::absolute_from_vec(vec!["b"]), |
| capability: "bad_protocol".into(), |
| result: Err(CapabilityRouteError::AnalyzerModelError( |
| AnalyzerModelError::RoutingError( |
| RoutingError::ExposeFromChildInstanceNotFound { |
| capability_id: "bad_protocol".to_string(), |
| child_moniker: "c".into(), |
| moniker: b_component.abs_moniker().clone(), |
| }, |
| ) |
| )), |
| }], |
| ); |
| } |
| |
| /// a |
| /// \ |
| /// b |
| /// |
| /// a: Offers protocol "fuchsia.examples.Echo" from void to b |
| /// b: Uses "fuchsia.examples.Echo" optionally |
| #[fuchsia::test] |
| async fn route_maps_do_not_include_valid_void_routes() { |
| let a_url = make_test_url("a"); |
| let b_url = "base://b/".to_string(); |
| |
| let use_protocol_decl = UseDecl::Protocol(UseProtocolDecl { |
| source: UseSource::Parent, |
| source_name: "fuchsia.examples.Echo".into(), |
| target_path: CapabilityPath::try_from("/svc/fuchsia.examples.Echo").unwrap(), |
| dependency_type: DependencyType::Strong, |
| availability: Availability::Optional, |
| }); |
| let offer_protocol_decl = OfferDecl::Protocol(OfferProtocolDecl { |
| source: OfferSource::Void, |
| source_name: "fuchsia.examples.Echo".into(), |
| target_name: "fuchsia.examples.Echo".into(), |
| target: OfferTarget::static_child("b".to_string()), |
| dependency_type: DependencyType::Strong, |
| availability: Availability::Optional, |
| }); |
| |
| let components = vec![ |
| ( |
| a_url.clone(), |
| ComponentDeclBuilder::new() |
| .add_child(ChildDeclBuilder::new().name("b").url(&b_url)) |
| .offer(offer_protocol_decl.clone()) |
| .build(), |
| ), |
| ( |
| b_url, |
| ComponentDeclBuilder::new_empty_component() |
| .add_program("dwarf") |
| .use_(use_protocol_decl.clone()) |
| .build(), |
| ), |
| ]; |
| |
| let test = |
| RoutingTestBuilderForAnalyzer::new_with_custom_urls(a_url, components).build().await; |
| let b_component = test.look_up_instance(&vec!["b"].into()).await.expect("b instance"); |
| |
| let route_maps = test.model.check_routes_for_instance( |
| &b_component, |
| &HashSet::from_iter(vec![CapabilityTypeName::Protocol].into_iter()), |
| ); |
| assert_eq!(route_maps.len(), 1); |
| let protocols = |
| route_maps.get(&CapabilityTypeName::Protocol).expect("expected protocol results"); |
| assert_eq!(protocols, &vec![]); |
| } |
| } |