| // 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 { |
| cm_rust::{ChildDecl, ComponentDecl, EnvironmentDecl}, |
| moniker::PartialMoniker, |
| routing::environment::{DebugRegistry, EnvironmentExtends, RunnerRegistry}, |
| serde::{Deserialize, Serialize}, |
| std::{ |
| collections::{HashMap, VecDeque}, |
| convert::Into, |
| fmt, |
| fmt::Display, |
| }, |
| thiserror::Error, |
| }; |
| |
| /// Errors that may occur while building or operating on a `ComponentTree`. |
| #[derive(Debug, Error, PartialEq, Serialize, Deserialize)] |
| pub enum ComponentTreeError { |
| #[error("no component declaration found for url `{0}` requested by node `{1}`")] |
| ComponentDeclNotFound(String, String), |
| #[error("invalid child declaration containing url `{0}` at node `{1}`")] |
| InvalidChildDecl(String, String), |
| #[error("no node found with path `{0}`")] |
| ComponentNodeNotFound(String), |
| #[error("environment `{0}` requested by child `{1}` not found at node `{2}`")] |
| EnvironmentNotFound(String, String, String), |
| } |
| |
| /// A representation of a component's position in the component topology. The last segment of |
| /// a component's `NodePath` is its `PartialMoniker` as designated by its parent component, and the |
| /// prefix is the parent component's `NodePath`. |
| #[derive(Clone, Debug, Default, Eq, Hash, PartialEq)] |
| pub struct NodePath(Vec<PartialMoniker>); |
| |
| /// A representation of a v2 component containing a `ComponentDecl` as well as the `NodePath`s of |
| /// the component itself and of its parent and children (if any). |
| pub struct ComponentNode { |
| pub decl: ComponentDecl, |
| node_path: NodePath, |
| url: String, |
| parent: Option<NodePath>, |
| children: Vec<NodePath>, |
| environment: NodeEnvironment, |
| } |
| |
| /// The environment of a v2 component. |
| pub struct NodeEnvironment { |
| /// The name of this environment as defined by its creator. Should be `None` for the root |
| /// environment. |
| name: Option<String>, |
| /// The relationship of this environment to that of the component instance's parent. |
| extends: EnvironmentExtends, |
| /// The runners available in this environment. |
| runner_registry: RunnerRegistry, |
| /// Protocols available in this environment as debug capabilities. |
| debug_registry: DebugRegistry, |
| } |
| |
| /// A representation of the set of all v2 components, together with their parent/child relationships. |
| #[derive(Default)] |
| pub struct ComponentTree { |
| nodes: HashMap<NodePath, ComponentNode>, |
| } |
| |
| #[derive(Default)] |
| pub struct BuildTreeResult { |
| pub tree: Option<ComponentTree>, |
| pub errors: Vec<ComponentTreeError>, |
| } |
| |
| /// A builder which constructs a ComponentTree from a collection of component declarations indexed |
| /// by component URL. |
| pub struct ComponentTreeBuilder { |
| decls_by_url: HashMap<String, ComponentDecl>, |
| result: BuildTreeResult, |
| } |
| |
| /// The `ComponentNodeVisitor` trait defines an interface for operating on a `ComponentNode`. |
| /// An error should be returned if the operation fails. |
| pub trait ComponentNodeVisitor { |
| fn visit_node(&mut self, node: &ComponentNode) -> Result<(), anyhow::Error>; |
| } |
| |
| /// The `ComponentTreeWalker` trait defines an interface for iteratively operating on nodes |
| /// of a `ComponentTree`, given a type implementing a per-node operation via the |
| /// `ComponentNodeVisitor` trait. |
| pub trait ComponentTreeWalker<'a> { |
| /// Walks a `ComponentTree`, doing the operation implemented by `visitor` at each node. |
| /// If the operation fails at a node, terminates the walk and propagates the error. |
| fn walk<T: ComponentNodeVisitor>( |
| &mut self, |
| tree: &'a ComponentTree, |
| visitor: &mut T, |
| ) -> Result<(), anyhow::Error> { |
| let mut node = tree.get_root_node()?; |
| loop { |
| visitor.visit_node(node)?; |
| match self.get_next_node(tree)? { |
| Some(ref next_node) => { |
| node = next_node; |
| } |
| None => { |
| return Ok(()); |
| } |
| } |
| } |
| } |
| |
| /// Returns a `Some(next_node)` containing the node following `node` in some enumeration of a |
| /// subset of a `ComponentTree`'s nodes, or returns `None` if `node` is the final node. If an |
| /// error occurred while trying to get the next node, returns that error instead. |
| fn get_next_node( |
| &mut self, |
| tree: &'a ComponentTree, |
| ) -> Result<Option<&ComponentNode>, anyhow::Error>; |
| } |
| |
| /// A walker implementing breadth-first traversal of a full `ComponentTree`, starting at the root |
| /// node. |
| pub struct BreadthFirstWalker<'a> { |
| discovered: VecDeque<&'a ComponentNode>, |
| } |
| |
| impl NodePath { |
| pub fn new(monikers: Vec<PartialMoniker>) -> Self { |
| let mut node_path = NodePath::default(); |
| node_path.0 = monikers; |
| node_path |
| } |
| |
| /// Returns a new `NodePath` which extends `self` by appending `moniker` at the end of the path. |
| pub fn extended(&self, moniker: PartialMoniker) -> Self { |
| let mut node_path = NodePath::new(self.0.clone()); |
| node_path.0.push(moniker); |
| node_path |
| } |
| } |
| |
| impl Display for NodePath { |
| // Displays a `NodePath` as a slash-separated path. |
| fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { |
| if self.0.is_empty() { |
| return write!(f, "/"); |
| } |
| let mut path_string = "".to_owned(); |
| for moniker in self.0.iter() { |
| path_string.push('/'); |
| path_string.push_str(moniker.as_str()); |
| } |
| write!(f, "{}", path_string) |
| } |
| } |
| |
| impl NodeEnvironment { |
| pub fn name(&self) -> Option<&str> { |
| self.name.as_deref() |
| } |
| |
| pub fn extends(&self) -> &EnvironmentExtends { |
| &self.extends |
| } |
| |
| pub fn runner_registry(&self) -> &RunnerRegistry { |
| &self.runner_registry |
| } |
| |
| pub fn debug_registry(&self) -> &DebugRegistry { |
| &self.debug_registry |
| } |
| |
| // Defines an environment for the root node with the given `runner_registry` and `debug_registry`. |
| fn new_root(runner_registry: RunnerRegistry, debug_registry: DebugRegistry) -> Self { |
| Self { name: None, extends: EnvironmentExtends::None, runner_registry, debug_registry } |
| } |
| |
| // Defines an environment inheriting the parent realm's environment without modification. |
| fn new_inheriting() -> Self { |
| Self { |
| name: None, |
| extends: EnvironmentExtends::Realm, |
| runner_registry: RunnerRegistry::default(), |
| debug_registry: DebugRegistry::default(), |
| } |
| } |
| |
| // Defines a named environment from a parent instance's `EnvironmentDecl`. |
| fn from_decl(env_decl: &EnvironmentDecl) -> Self { |
| Self { |
| name: Some(env_decl.name.clone()), |
| extends: env_decl.extends.into(), |
| runner_registry: RunnerRegistry::from_decl(&env_decl.runners), |
| debug_registry: env_decl.debug_capabilities.clone().into(), |
| } |
| } |
| } |
| |
| impl ComponentNode { |
| /// Returns a string representing the path to this `ComponentNode` from the root node. |
| /// Each path segment is the `PartialMoniker` of a component relative to its parent. |
| /// The root node is represented as "/". |
| pub fn short_display(&self) -> String { |
| self.node_path.to_string() |
| } |
| |
| pub fn url(&self) -> String { |
| self.url.clone() |
| } |
| |
| pub fn moniker(&self) -> Option<&PartialMoniker> { |
| self.node_path.0.last() |
| } |
| |
| pub fn node_path(&self) -> NodePath { |
| self.node_path.clone() |
| } |
| |
| pub fn environment(&self) -> &NodeEnvironment { |
| &self.environment |
| } |
| |
| // Creates a new `ComponentNode` with the specified declaration, url, parent, moniker, and |
| // environment. Does not populate the node's `children` field. |
| fn new( |
| decl: ComponentDecl, |
| url: String, |
| parent: Option<NodePath>, |
| moniker: Option<PartialMoniker>, |
| environment: NodeEnvironment, |
| ) -> Self { |
| ComponentNode { |
| decl, |
| node_path: ComponentNode::get_node_path(parent.clone(), moniker), |
| url, |
| parent, |
| children: vec![], |
| environment, |
| } |
| } |
| |
| fn get_node_path(parent: Option<NodePath>, moniker: Option<PartialMoniker>) -> NodePath { |
| if let Some(postfix) = moniker { |
| if let Some(prefix) = parent { |
| return prefix.extended(postfix); |
| } else { |
| return NodePath::new(vec![postfix]); |
| } |
| } |
| NodePath::default() |
| } |
| |
| fn environment_for_child( |
| &self, |
| child_decl: &ChildDecl, |
| ) -> Result<NodeEnvironment, ComponentTreeError> { |
| match child_decl.environment.as_ref() { |
| Some(child_env_name) => { |
| let env_decl = |
| self.decl.environments.iter().find(|&env| &env.name == child_env_name).ok_or( |
| ComponentTreeError::EnvironmentNotFound( |
| child_env_name.clone(), |
| child_decl.name.clone(), |
| self.node_path().to_string(), |
| ), |
| )?; |
| Ok(NodeEnvironment::from_decl(env_decl)) |
| } |
| None => Ok(NodeEnvironment::new_inheriting()), |
| } |
| } |
| } |
| |
| impl ComponentTree { |
| pub fn len(&self) -> usize { |
| self.nodes.len() |
| } |
| |
| pub fn is_empty(&self) -> bool { |
| self.nodes.is_empty() |
| } |
| |
| /// Returns the node at `node_path`, or an error if there is no node at `node_path`. |
| pub fn get_node(&self, node_path: &NodePath) -> Result<&ComponentNode, ComponentTreeError> { |
| match self.nodes.get(node_path) { |
| Some(ref node) => Ok(node), |
| None => Err(ComponentTreeError::ComponentNodeNotFound(node_path.to_string())), |
| } |
| } |
| |
| /// Returns the root node, or an error if the root node is not found. |
| pub fn get_root_node(&self) -> Result<&ComponentNode, ComponentTreeError> { |
| self.get_node(&NodePath::new(vec![])) |
| } |
| |
| /// Returns the parent of `node`, or returns `None` if `node` has no parent, or else returns an |
| /// error if `node` contains an invalid `NodePath` as its parent identifier. |
| pub fn try_get_parent( |
| &self, |
| node: &ComponentNode, |
| ) -> Result<Option<&ComponentNode>, ComponentTreeError> { |
| match node.parent { |
| Some(ref parent) => Ok(Some(self.get_node(parent)?)), |
| None => Ok(None), |
| } |
| } |
| |
| pub fn get_child_node( |
| &self, |
| node: &ComponentNode, |
| moniker: PartialMoniker, |
| ) -> Result<&ComponentNode, ComponentTreeError> { |
| Ok(self.get_node(&node.node_path.extended(moniker))?) |
| } |
| |
| /// Returns the children of `node`, or returns an error if `node` contains an invalid `NodePath` as |
| /// one of its child identifier. |
| pub fn get_children( |
| &self, |
| node: &ComponentNode, |
| ) -> Result<Vec<&ComponentNode>, ComponentTreeError> { |
| let mut children = Vec::new(); |
| for child in node.children.iter() { |
| children.push(self.get_node(child)?); |
| } |
| Ok(children) |
| } |
| } |
| |
| impl ComponentTreeBuilder { |
| /// Constructs a `ComponentTreeBuilder` from a map of component declarations keyed by |
| /// component url. |
| pub fn new(decls_by_url: HashMap<String, ComponentDecl>) -> Self { |
| ComponentTreeBuilder { decls_by_url, result: BuildTreeResult::default() } |
| } |
| |
| /// Constructs and returns a `ComponentTree` based at the component with url `root_url`, |
| /// consuming the builder. Returns an error if `root_url` or the url of any subsequent |
| /// child is not present in the builder's `decls_by_url` map, or if any `ComponentDecl` |
| /// in `decls_by_url` contains an invalid `ChildDecl`. |
| pub fn build<T: Into<String>>(mut self, root_url: T) -> BuildTreeResult { |
| let mut tree = ComponentTree { nodes: HashMap::new() }; |
| |
| let root_url = root_url.into(); |
| match self.decls_by_url.get(&root_url) { |
| Some(root_decl) => { |
| let mut root_node = ComponentNode::new( |
| root_decl.clone(), |
| root_url, |
| None, |
| None, |
| // TODO(pesk): probably runner_registry and debug_registry should be args |
| // to this method. What are they derived from? |
| NodeEnvironment::new_root(RunnerRegistry::default(), DebugRegistry::default()), |
| ); |
| self.add_descendants(&mut tree, &mut root_node); |
| tree.nodes.insert(root_node.node_path.clone(), root_node); |
| self.result.tree = Some(tree) |
| } |
| None => self |
| .result |
| .errors |
| .push(ComponentTreeError::ComponentDeclNotFound(root_url, "".to_string())), |
| } |
| self.result |
| } |
| |
| fn add_descendants(&mut self, tree: &mut ComponentTree, node: &mut ComponentNode) { |
| for child in node.decl.children.iter() { |
| if child.name.is_empty() { |
| self.result.errors.push(ComponentTreeError::InvalidChildDecl( |
| child.url.to_string(), |
| node.short_display(), |
| )); |
| continue; |
| } |
| match self.decls_by_url.get(&child.url) { |
| Some(child_decl) => match node.environment_for_child(child) { |
| Ok(environment) => { |
| let mut child_node = ComponentNode::new( |
| child_decl.clone(), |
| child.url.clone(), |
| Some(node.node_path.clone()), |
| Some(PartialMoniker::new(child.name.clone(), None)), |
| environment, |
| ); |
| self.add_descendants(tree, &mut child_node); |
| node.children.push(child_node.node_path.clone()); |
| tree.nodes.insert(child_node.node_path.clone(), child_node); |
| } |
| Err(err) => self.result.errors.push(err), |
| }, |
| None => self.result.errors.push(ComponentTreeError::ComponentDeclNotFound( |
| child.url.to_string(), |
| node.short_display(), |
| )), |
| } |
| } |
| } |
| } |
| |
| impl<'a> BreadthFirstWalker<'a> { |
| pub fn new(tree: &'a ComponentTree) -> Result<Self, anyhow::Error> { |
| let mut walker = BreadthFirstWalker { discovered: VecDeque::new() }; |
| walker.discover_child_nodes(&tree, tree.get_root_node()?)?; |
| Ok(walker) |
| } |
| |
| fn discover_child_nodes( |
| &mut self, |
| tree: &'a ComponentTree, |
| node: &'a ComponentNode, |
| ) -> Result<(), anyhow::Error> { |
| let children = tree.get_children(node)?; |
| self.discovered.reserve(children.len()); |
| for child in children { |
| self.discovered.push_back(child); |
| } |
| Ok(()) |
| } |
| } |
| |
| impl<'a> ComponentTreeWalker<'a> for BreadthFirstWalker<'a> { |
| fn get_next_node( |
| &mut self, |
| tree: &'a ComponentTree, |
| ) -> Result<Option<&'a ComponentNode>, anyhow::Error> { |
| match self.discovered.pop_front() { |
| Some(next_node) => { |
| self.discover_child_nodes(tree, &next_node)?; |
| Ok(Some(next_node)) |
| } |
| None => Ok(None), |
| } |
| } |
| } |
| |
| #[cfg(test)] |
| mod tests { |
| use { |
| super::*, |
| cm_rust::{ |
| CapabilityName, ChildDecl, DebugProtocolRegistration, DebugRegistration, |
| RegistrationSource, RunnerRegistration, |
| }, |
| fidl_fuchsia_sys2::StartupMode, |
| }; |
| |
| fn display_nodes(nodes: Vec<&ComponentNode>) -> Vec<String> { |
| nodes.iter().map(|x| x.short_display()).collect() |
| } |
| |
| fn new_child_decl(name: String, url: String, environment: Option<String>) -> ChildDecl { |
| ChildDecl { name, url, startup: StartupMode::Lazy, environment } |
| } |
| |
| fn new_environment_decl( |
| name: String, |
| extends: fidl_fuchsia_sys2::EnvironmentExtends, |
| runners: Vec<RunnerRegistration>, |
| debug_capabilities: Vec<DebugRegistration>, |
| ) -> EnvironmentDecl { |
| EnvironmentDecl { |
| name, |
| extends, |
| runners, |
| resolvers: vec![], |
| debug_capabilities, |
| stop_timeout_ms: None, |
| } |
| } |
| |
| fn new_component_decl( |
| children: Vec<ChildDecl>, |
| environments: Vec<EnvironmentDecl>, |
| ) -> ComponentDecl { |
| ComponentDecl { |
| program: None, |
| uses: vec![], |
| exposes: vec![], |
| offers: vec![], |
| capabilities: vec![], |
| children, |
| collections: vec![], |
| facets: None, |
| environments, |
| } |
| } |
| |
| // Builds a `ComponentTree` with a single node. |
| fn build_single_node_tree() -> BuildTreeResult { |
| let root_url = "root_url".to_string(); |
| let root_decl = new_component_decl(vec![], vec![]); |
| let mut decls = HashMap::new(); |
| decls.insert(root_url.clone(), root_decl.clone()); |
| ComponentTreeBuilder::new(decls).build(root_url) |
| } |
| |
| // Builds a `ComponentTree` with 4 nodes and the following structure: |
| // |
| // root |
| // / \ |
| // foo bar |
| // / |
| // baz |
| // |
| fn build_multi_node_tree() -> BuildTreeResult { |
| let root_url = "root_url".to_string(); |
| let foo_url = "foo_url".to_string(); |
| let bar_url = "bar_url".to_string(); |
| let baz_url = "baz_url".to_string(); |
| |
| let foo_name = "foo".to_string(); |
| let bar_name = "bar".to_string(); |
| let baz_name = "baz".to_string(); |
| |
| let root_decl = new_component_decl( |
| vec![ |
| new_child_decl(foo_name, foo_url.clone(), None), |
| new_child_decl(bar_name, bar_url.clone(), None), |
| ], |
| vec![], |
| ); |
| let foo_decl = |
| new_component_decl(vec![new_child_decl(baz_name, baz_url.clone(), None)], vec![]); |
| let bar_decl = new_component_decl(vec![], vec![]); |
| let baz_decl = new_component_decl(vec![], vec![]); |
| |
| let mut decls = HashMap::new(); |
| decls.insert(root_url.to_string(), root_decl.clone()); |
| decls.insert(foo_url.to_string(), foo_decl.clone()); |
| decls.insert(bar_url.to_string(), bar_decl.clone()); |
| decls.insert(baz_url.to_string(), baz_decl.clone()); |
| |
| ComponentTreeBuilder::new(decls).build(root_url) |
| } |
| |
| // A test ComponentNodeVisitor which just records the path string of each ComponentNode visited. |
| #[derive(Default)] |
| struct RouteMappingVisitor { |
| pub route: Vec<String>, |
| } |
| |
| impl ComponentNodeVisitor for RouteMappingVisitor { |
| fn visit_node(&mut self, node: &ComponentNode) -> Result<(), anyhow::Error> { |
| self.route.push(node.short_display()); |
| Ok(()) |
| } |
| } |
| |
| // Tests the `extended()` and `to_string()` methods of `NodePath`. |
| #[test] |
| fn node_path_operations() { |
| let empty_node_path = NodePath::default(); |
| assert_eq!(empty_node_path.to_string(), "/"); |
| |
| let foo_moniker = PartialMoniker::new("foo".to_string(), None); |
| let foo_node_path = empty_node_path.extended(foo_moniker); |
| assert_eq!(foo_node_path.to_string(), "/foo"); |
| |
| let bar_moniker = PartialMoniker::new("bar".to_string(), None); |
| let bar_node_path = foo_node_path.extended(bar_moniker); |
| assert_eq!(bar_node_path.to_string(), "/foo/bar"); |
| } |
| |
| // Builds `ComponentNode`s with and without parents and children and tests the |
| // `short_display()` and `url()` methods. |
| #[test] |
| fn build_node() { |
| let foo_moniker = PartialMoniker::new("foo".to_string(), None); |
| let bar_moniker = PartialMoniker::new("bar".to_string(), None); |
| |
| let root_url = "root_url".to_string(); |
| let leaf_url = "leaf_url".to_string(); |
| |
| let root_node = ComponentNode::new( |
| new_component_decl(vec![], vec![]), |
| root_url.clone(), |
| None, |
| None, |
| NodeEnvironment::new_root(RunnerRegistry::default(), DebugRegistry::default()), |
| ); |
| assert_eq!(root_node.short_display(), "/"); |
| assert_eq!(root_node.url(), root_url); |
| |
| let leaf_node = ComponentNode::new( |
| new_component_decl(vec![], vec![]), |
| leaf_url.clone(), |
| Some(NodePath::new(vec![foo_moniker])), |
| Some(bar_moniker), |
| NodeEnvironment::new_inheriting(), |
| ); |
| assert_eq!(leaf_node.short_display(), "/foo/bar"); |
| assert_eq!(leaf_node.url(), leaf_url); |
| } |
| |
| // Builds a tree with a single node, retrieves the root node, and checks that `try_get_parent` |
| // and `get_children` return OK but trivial results. |
| #[test] |
| fn single_node_tree() -> Result<(), ComponentTreeError> { |
| let tree_result = build_single_node_tree(); |
| assert!(tree_result.errors.is_empty()); |
| let tree = tree_result.tree.unwrap(); |
| |
| let root_node = tree.get_root_node()?; |
| assert!(root_node.parent.is_none()); |
| assert!(root_node.children.is_empty()); |
| assert_eq!(root_node.short_display(), "/"); |
| |
| let parent = tree.try_get_parent(&root_node)?; |
| assert!(parent.is_none()); |
| |
| let children = tree.get_children(&root_node)?; |
| assert!(children.is_empty()); |
| |
| Ok(()) |
| } |
| |
| // Checks that the expected error is returned when the ComponentTreeBuilder's |
| // `build` method is called with an unrecognized component url. |
| #[test] |
| fn build_tree_root_url_not_found() { |
| let root_url = "root_url".to_string(); |
| let other_url = "other_url".to_string(); |
| let mut decls = HashMap::new(); |
| decls.insert(root_url.clone(), new_component_decl(vec![], vec![])); |
| let build_result = ComponentTreeBuilder::new(decls).build(other_url.clone()); |
| assert!(build_result.tree.is_none()); |
| assert_eq!(build_result.errors.len(), 1); |
| assert_eq!( |
| build_result.errors[0], |
| ComponentTreeError::ComponentDeclNotFound(other_url, "".to_string()) |
| ); |
| } |
| |
| // Builds a tree with 4 nodes using `build_multi_node_tree()`. Checks that the `node_path`, |
| // `parent`, and `children` fields of each node are populated correctly and can be looked up |
| // by a sequence of PartialMonikers. Also checks that lookup fails with an invalid sequence |
| // of PartialMonikers. |
| #[test] |
| fn build_tree_and_look_up_multi_node() -> Result<(), ComponentTreeError> { |
| let tree_result = build_multi_node_tree(); |
| assert!(tree_result.errors.is_empty()); |
| let tree = tree_result.tree.unwrap(); |
| |
| let foo_path = NodePath::new(vec![PartialMoniker::new("foo".to_string(), None)]); |
| let bar_path = NodePath::new(vec![PartialMoniker::new("bar".to_string(), None)]); |
| let baz_path = foo_path.extended(PartialMoniker::new("baz".to_string(), None)); |
| let other_path = NodePath::new(vec![PartialMoniker::new("other".to_string(), None)]); |
| |
| let root_node = tree.get_root_node()?; |
| assert_eq!(root_node.node_path.to_string(), "/"); |
| assert!(root_node.parent.is_none()); |
| assert_eq!(root_node.children, vec![foo_path.clone(), bar_path.clone()]); |
| |
| let foo_node = tree.get_node(&foo_path)?; |
| assert_eq!(foo_node.node_path.to_string(), "/foo"); |
| assert!(foo_node.parent.is_some()); |
| assert_eq!(foo_node.parent.as_ref().unwrap().to_string(), "/"); |
| assert_eq!(foo_node.children, vec![baz_path.clone()]); |
| |
| let bar_node = tree.get_node(&bar_path)?; |
| assert_eq!(bar_node.node_path.to_string(), "/bar"); |
| assert!(bar_node.parent.is_some()); |
| assert_eq!(bar_node.parent.as_ref().unwrap().to_string(), "/"); |
| assert!(bar_node.children.is_empty()); |
| |
| let baz_node = tree.get_node(&baz_path)?; |
| assert_eq!(baz_node.node_path.to_string(), "/foo/baz"); |
| assert!(baz_node.parent.is_some()); |
| assert_eq!(baz_node.parent.as_ref().unwrap().to_string(), "/foo"); |
| assert!(baz_node.children.is_empty()); |
| |
| let get_other_node_result = tree.get_node(&other_path); |
| assert!(get_other_node_result.is_err()); |
| assert_eq!( |
| get_other_node_result.err().unwrap(), |
| ComponentTreeError::ComponentNodeNotFound(other_path.to_string()) |
| ); |
| |
| Ok(()) |
| } |
| |
| // Builds a tree with a child node that inherits a named environment from |
| // its parent. Checks that the child node's environment is correctly populated. |
| #[test] |
| fn build_tree_with_environment() -> Result<(), ComponentTreeError> { |
| let root_url = "root_url".to_string(); |
| let foo_url = "foo_url".to_string(); |
| let foo_name = "foo".to_string(); |
| let foo_env_name = "foo_env".to_string(); |
| let foo_extends = fidl_fuchsia_sys2::EnvironmentExtends::Realm; |
| let foo_runner_name = CapabilityName("foo_runner".to_string()); |
| let foo_debug_name = CapabilityName("foo_debug".to_string()); |
| let foo_runner = RunnerRegistration { |
| source_name: foo_runner_name.clone(), |
| target_name: foo_runner_name.clone(), |
| source: RegistrationSource::Parent, |
| }; |
| let foo_debug = DebugRegistration::Protocol(DebugProtocolRegistration { |
| source_name: foo_debug_name.clone(), |
| source: RegistrationSource::Parent, |
| target_name: foo_debug_name.clone(), |
| }); |
| |
| let root_decl = new_component_decl( |
| vec![new_child_decl(foo_name.clone(), foo_url.clone(), Some(foo_env_name.clone()))], |
| vec![new_environment_decl( |
| foo_env_name.clone(), |
| foo_extends.clone(), |
| vec![foo_runner], |
| vec![foo_debug], |
| )], |
| ); |
| let foo_decl = new_component_decl(vec![], vec![]); |
| |
| let mut decls = HashMap::new(); |
| decls.insert(root_url.to_string(), root_decl); |
| decls.insert(foo_url.to_string(), foo_decl); |
| |
| let build_result = ComponentTreeBuilder::new(decls).build(root_url); |
| assert!(build_result.errors.is_empty()); |
| let tree = build_result.tree.unwrap(); |
| |
| let foo_node = tree.get_node(&NodePath::new(vec![PartialMoniker::new(foo_name, None)]))?; |
| assert_eq!(foo_node.environment().name(), Some(foo_env_name).as_deref()); |
| assert_eq!(foo_node.environment().extends(), &foo_extends.into()); |
| assert!(foo_node.environment().runner_registry().get_runner(&foo_runner_name).is_some()); |
| assert!(foo_node.environment().debug_registry().get_capability(&foo_debug_name).is_some()); |
| Ok(()) |
| } |
| |
| // Builds a tree with a child node that inherits a named environment from its parent, |
| // but the parent does not declare that environment. Checks that the tree builder logs |
| // an EnvironmentNotFound error describing the problem. |
| #[test] |
| fn build_tree_missing_environment() { |
| let root_url = "root_url".to_string(); |
| let foo_url = "foo_url".to_string(); |
| let foo_name = "foo".to_string(); |
| let foo_env_name = "foo_env".to_string(); |
| |
| let root_decl = new_component_decl( |
| vec![new_child_decl(foo_name.clone(), foo_url.clone(), Some(foo_env_name.clone()))], |
| vec![], |
| ); |
| let foo_decl = new_component_decl(vec![], vec![]); |
| |
| let mut decls = HashMap::new(); |
| decls.insert(root_url.to_string(), root_decl); |
| decls.insert(foo_url.to_string(), foo_decl); |
| |
| let build_result = ComponentTreeBuilder::new(decls).build(root_url); |
| assert_eq!(build_result.errors.len(), 1); |
| assert_eq!( |
| build_result.errors[0], |
| ComponentTreeError::EnvironmentNotFound(foo_env_name, foo_name, "/".to_string()) |
| ); |
| } |
| |
| // Builds a tree with 4 nodes using `build_multi_node_tree()` and checks that `try_get_parent` |
| // returns the expected result for each node. |
| #[test] |
| fn try_get_parent() -> Result<(), ComponentTreeError> { |
| let tree_result = build_multi_node_tree(); |
| assert!(tree_result.errors.is_empty()); |
| let tree = tree_result.tree.unwrap(); |
| |
| let foo_path = NodePath::new(vec![PartialMoniker::new("foo".to_string(), None)]); |
| let bar_path = NodePath::new(vec![PartialMoniker::new("bar".to_string(), None)]); |
| let baz_path = foo_path.extended(PartialMoniker::new("baz".to_string(), None)); |
| |
| let root_parent = tree.try_get_parent(tree.get_root_node()?)?; |
| assert!(root_parent.is_none()); |
| |
| let foo_parent = tree.try_get_parent(tree.get_node(&foo_path)?)?; |
| assert_eq!(foo_parent.unwrap().short_display(), "/"); |
| |
| let bar_parent = tree.try_get_parent(tree.get_node(&bar_path)?)?; |
| assert_eq!(bar_parent.unwrap().short_display(), "/"); |
| |
| let baz_parent = tree.try_get_parent(tree.get_node(&baz_path)?)?; |
| assert_eq!(baz_parent.unwrap().short_display(), "/foo"); |
| |
| Ok(()) |
| } |
| |
| // Builds a tree with 4 nodes using `build_multi_node_tree()` and checks that `get_children` |
| // returns the expected result for each node. |
| #[test] |
| fn get_children() -> Result<(), ComponentTreeError> { |
| let tree_result = build_multi_node_tree(); |
| assert!(tree_result.errors.is_empty()); |
| let tree = tree_result.tree.unwrap(); |
| |
| let foo_path = NodePath::new(vec![PartialMoniker::new("foo".to_string(), None)]); |
| let bar_path = NodePath::new(vec![PartialMoniker::new("bar".to_string(), None)]); |
| let baz_path = foo_path.extended(PartialMoniker::new("baz".to_string(), None)); |
| |
| let root_children = tree.get_children(tree.get_root_node()?)?; |
| assert_eq!(display_nodes(root_children), vec!["/foo", "/bar"]); |
| |
| let foo_children = tree.get_children(tree.get_node(&foo_path)?)?; |
| assert_eq!(display_nodes(foo_children), vec!["/foo/baz"]); |
| |
| let bar_children = tree.get_children(tree.get_node(&bar_path)?)?; |
| assert!(bar_children.is_empty()); |
| |
| let baz_children = tree.get_children(tree.get_node(&baz_path)?)?; |
| assert!(baz_children.is_empty()); |
| |
| Ok(()) |
| } |
| |
| // Checks that the BreadthFirstWalker visits each node once when the |
| // ComponentTree has a single node. |
| #[test] |
| fn breadth_first_walker_single_node() -> Result<(), anyhow::Error> { |
| let tree_result = build_single_node_tree(); |
| assert!(tree_result.errors.is_empty()); |
| let tree = tree_result.tree.unwrap(); |
| |
| let mut walker = BreadthFirstWalker::new(&tree)?; |
| let mut visitor = RouteMappingVisitor::default(); |
| assert!(walker.walk(&tree, &mut visitor).is_ok()); |
| assert_eq!(visitor.route, vec!["/"]); |
| |
| Ok(()) |
| } |
| |
| // Checks that the BreadthFirstWalker visits each node once in breadth-first |
| // order when the ComponentTree has multiple nodes. |
| #[test] |
| fn breadth_first_walker_multi_node() -> Result<(), anyhow::Error> { |
| let tree_result = build_multi_node_tree(); |
| assert!(tree_result.errors.is_empty()); |
| let tree = tree_result.tree.unwrap(); |
| |
| let mut walker = BreadthFirstWalker::new(&tree)?; |
| let mut visitor = RouteMappingVisitor::default(); |
| assert!(walker.walk(&tree, &mut visitor).is_ok()); |
| // Sibling nodes "/foo" and "/bar" may be visited in either order. |
| assert!( |
| (visitor.route == vec!["/", "/foo", "/bar", "/foo/baz"]) |
| | (visitor.route == vec!["/", "/bar", "/foo", "/foo/baz"]) |
| ); |
| |
| Ok(()) |
| } |
| } |