| // Copyright 2019 The Fuchsia Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| use { |
| crate::model::*, |
| futures::{executor::block_on, future::BoxFuture, lock::Mutex, prelude::*}, |
| std::{cmp::Eq, collections::HashMap, fmt, ops::Deref, pin::Pin, sync::Arc}, |
| }; |
| |
| struct ComponentInstance { |
| pub abs_moniker: AbsoluteMoniker, |
| pub children: Mutex<Vec<Arc<ComponentInstance>>>, |
| } |
| |
| impl Clone for ComponentInstance { |
| // This is used by TestHook. ComponentInstance is immutable so when a change |
| // needs to be made, TestHook clones ComponentInstance and makes the change |
| // in the new copy. |
| fn clone(&self) -> Self { |
| let children = block_on(self.children.lock()); |
| return ComponentInstance { |
| abs_moniker: self.abs_moniker.clone(), |
| children: Mutex::new(children.clone()), |
| }; |
| } |
| } |
| |
| impl PartialEq for ComponentInstance { |
| fn eq(&self, other: &Self) -> bool { |
| self.abs_moniker == other.abs_moniker |
| } |
| } |
| |
| impl Eq for ComponentInstance {} |
| |
| impl ComponentInstance { |
| pub async fn print(&self) -> String { |
| let mut s: String = self.abs_moniker.path().last().map_or("", |m| m.as_str()).to_string(); |
| let mut children = await!(self.children.lock()); |
| if children.is_empty() { |
| return s; |
| } |
| |
| // The position of a child in the children vector is a function of timing. |
| // In order to produce stable topology strings across runs, we sort the set |
| // of children here by moniker. |
| children.sort_by(|a, b| a.abs_moniker.cmp(&b.abs_moniker)); |
| |
| s.push('('); |
| let mut count = 0; |
| for child in children.iter() { |
| // If we've seen a previous child, then add a comma to separate children. |
| if count > 0 { |
| s.push(','); |
| } |
| s.push_str(&await!(child.boxed_print())); |
| count += 1; |
| } |
| s.push(')'); |
| s |
| } |
| |
| fn boxed_print<'a>(&'a self) -> Pin<Box<dyn Future<Output = String> + 'a>> { |
| Box::pin(self.print()) |
| } |
| } |
| |
| pub struct TestHook { |
| instances: Mutex<HashMap<AbsoluteMoniker, Arc<ComponentInstance>>>, |
| } |
| |
| impl fmt::Display for TestHook { |
| fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { |
| writeln!(f, "{}", self.print())?; |
| Ok(()) |
| } |
| } |
| |
| /// TestHook is a Hook that generates a strings representing the component |
| /// topology. |
| impl TestHook { |
| pub fn new() -> TestHook { |
| let abs_moniker = AbsoluteMoniker::root(); |
| let instance = |
| ComponentInstance { abs_moniker: abs_moniker.clone(), children: Mutex::new(vec![]) }; |
| let mut instances = HashMap::new(); |
| instances.insert(abs_moniker, Arc::new(instance)); |
| TestHook { instances: Mutex::new(instances) } |
| } |
| |
| // Recursively traverse the Instance tree to generate a string representing the component |
| // topology. |
| pub fn print(&self) -> String { |
| let instances = block_on(self.instances.lock()); |
| let abs_moniker = AbsoluteMoniker::root(); |
| let root_instance = |
| instances.get(&abs_moniker).map(|x| x.clone()).expect("Unable to find root instance."); |
| block_on(root_instance.print()) |
| } |
| |
| pub async fn on_bind_instance_async<'a>( |
| &'a self, |
| realm: Arc<Realm>, |
| realm_state: &'a RealmState, |
| _route_fn_factory: RoutingFnFactory, |
| ) -> Result<(), ModelError> { |
| await!(self.create_instance_if_necessary(realm.abs_moniker.clone()))?; |
| for child_realm in |
| realm_state.child_realms.as_ref().expect("Unable to access child realms.").values() |
| { |
| await!(self.create_instance_if_necessary(child_realm.abs_moniker.clone()))?; |
| } |
| Ok(()) |
| } |
| |
| pub async fn create_instance_if_necessary( |
| &self, |
| abs_moniker: AbsoluteMoniker, |
| ) -> Result<(), ModelError> { |
| let mut instances = await!(self.instances.lock()); |
| if let Some(parent_moniker) = abs_moniker.parent() { |
| // If the parent isn't available yet then opt_parent_instance will have a value |
| // of None. |
| let opt_parent_instance = instances.get(&parent_moniker).map(|x| x.clone()); |
| let new_instance = match instances.get(&abs_moniker) { |
| Some(old_instance) => Arc::new((old_instance.deref()).clone()), |
| None => Arc::new(ComponentInstance { |
| abs_moniker: abs_moniker.clone(), |
| children: Mutex::new(vec![]), |
| }), |
| }; |
| instances.insert(abs_moniker.clone(), new_instance.clone()); |
| // If the parent is available then add this instance as a child to it. |
| if let Some(parent_instance) = opt_parent_instance { |
| let mut children = await!(parent_instance.children.lock()); |
| let opt_index = |
| children.iter().position(|c| c.abs_moniker == new_instance.abs_moniker); |
| if let Some(index) = opt_index { |
| children.remove(index); |
| } |
| children.push(new_instance.clone()); |
| } |
| } |
| Ok(()) |
| } |
| } |
| |
| impl Hook for TestHook { |
| fn on_bind_instance<'a>( |
| &'a self, |
| realm: Arc<Realm>, |
| realm_state: &'a RealmState, |
| route_fn_factory: RoutingFnFactory, |
| ) -> BoxFuture<Result<(), ModelError>> { |
| Box::pin(self.on_bind_instance_async(realm, &realm_state, route_fn_factory)) |
| } |
| |
| fn on_add_dynamic_child(&self, realm: Arc<Realm>) -> BoxFuture<Result<(), ModelError>> { |
| Box::pin(self.create_instance_if_necessary(realm.abs_moniker.clone())) |
| } |
| } |
| |
| #[cfg(test)] |
| mod tests { |
| use super::*; |
| |
| #[fuchsia_async::run_singlethreaded(test)] |
| async fn test_hook_test() { |
| let a = AbsoluteMoniker::new(vec![ChildMoniker::new("a".to_string(), None)]); |
| let ab = AbsoluteMoniker::new(vec![ |
| ChildMoniker::new("a".to_string(), None), |
| ChildMoniker::new("b".to_string(), None), |
| ]); |
| let ac = AbsoluteMoniker::new(vec![ |
| ChildMoniker::new("a".to_string(), None), |
| ChildMoniker::new("c".to_string(), None), |
| ]); |
| let abd = AbsoluteMoniker::new(vec![ |
| ChildMoniker::new("a".to_string(), None), |
| ChildMoniker::new("b".to_string(), None), |
| ChildMoniker::new("d".to_string(), None), |
| ]); |
| let abe = AbsoluteMoniker::new(vec![ |
| ChildMoniker::new("a".to_string(), None), |
| ChildMoniker::new("b".to_string(), None), |
| ChildMoniker::new("e".to_string(), None), |
| ]); |
| let acf = AbsoluteMoniker::new(vec![ |
| ChildMoniker::new("a".to_string(), None), |
| ChildMoniker::new("c".to_string(), None), |
| ChildMoniker::new("f".to_string(), None), |
| ]); |
| |
| // Try adding parent followed by children then verify the topology string |
| // is correct. |
| { |
| let test_hook = TestHook::new(); |
| assert!(await!(test_hook.create_instance_if_necessary(a.clone())).is_ok()); |
| assert!(await!(test_hook.create_instance_if_necessary(ab.clone())).is_ok()); |
| assert!(await!(test_hook.create_instance_if_necessary(ac.clone())).is_ok()); |
| assert!(await!(test_hook.create_instance_if_necessary(abd.clone())).is_ok()); |
| assert!(await!(test_hook.create_instance_if_necessary(abe.clone())).is_ok()); |
| assert!(await!(test_hook.create_instance_if_necessary(acf.clone())).is_ok()); |
| assert_eq!("(a(b(d,e),c(f)))", test_hook.print()); |
| } |
| |
| // Changing the order of monikers should not affect the output string. |
| { |
| let test_hook = TestHook::new(); |
| assert!(await!(test_hook.create_instance_if_necessary(a.clone())).is_ok()); |
| assert!(await!(test_hook.create_instance_if_necessary(ac.clone())).is_ok()); |
| assert!(await!(test_hook.create_instance_if_necessary(ab.clone())).is_ok()); |
| assert!(await!(test_hook.create_instance_if_necessary(abd.clone())).is_ok()); |
| assert!(await!(test_hook.create_instance_if_necessary(abe.clone())).is_ok()); |
| assert!(await!(test_hook.create_instance_if_necessary(acf.clone())).is_ok()); |
| assert_eq!("(a(b(d,e),c(f)))", test_hook.print()); |
| } |
| |
| // Submitting children before parents should still succeed. |
| { |
| let test_hook = TestHook::new(); |
| assert!(await!(test_hook.create_instance_if_necessary(acf.clone())).is_ok()); |
| assert!(await!(test_hook.create_instance_if_necessary(abe.clone())).is_ok()); |
| assert!(await!(test_hook.create_instance_if_necessary(abd.clone())).is_ok()); |
| assert!(await!(test_hook.create_instance_if_necessary(ab.clone())).is_ok()); |
| // Model will call create_instance_if_necessary for ab's children again |
| // after the call to bind_instance for ab. |
| assert!(await!(test_hook.create_instance_if_necessary(abe.clone())).is_ok()); |
| assert!(await!(test_hook.create_instance_if_necessary(abd.clone())).is_ok()); |
| assert!(await!(test_hook.create_instance_if_necessary(ac.clone())).is_ok()); |
| // Model will call create_instance_if_necessary for ac's children again |
| // after the call to bind_instance for ac. |
| assert!(await!(test_hook.create_instance_if_necessary(acf.clone())).is_ok()); |
| assert!(await!(test_hook.create_instance_if_necessary(a.clone())).is_ok()); |
| // Model will call create_instance_if_necessary for a's children again |
| // after the call to bind_instance for a. |
| assert!(await!(test_hook.create_instance_if_necessary(ab.clone())).is_ok()); |
| assert!(await!(test_hook.create_instance_if_necessary(ac.clone())).is_ok()); |
| assert_eq!("(a(b(d,e),c(f)))", test_hook.print()); |
| } |
| } |
| } |