blob: 878dd55aec54e5a1682aab61a724a407b56c99d3 [file] [log] [blame]
// 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 {
crate::error::*,
anyhow::{format_err, Context as _},
cm_rust::{self, NativeIntoFidl},
fidl::endpoints::{self, DiscoverableService, ServerEnd},
fidl_fuchsia_data as fdata, fidl_fuchsia_io as fio, fidl_fuchsia_realm_builder as ftrb,
fidl_fuchsia_sys2 as fsys,
fuchsia_component::client as fclient,
fuchsia_zircon as zx,
futures::{future::BoxFuture, FutureExt, TryFutureExt},
log::*,
rand::Rng,
std::{
collections::HashMap,
convert::TryInto,
fmt::{self, Display},
},
};
/// The default name of the child component collection that contains built topologies.
pub const DEFAULT_COLLECTION_NAME: &'static str = "fuchsia_component_test_collection";
const FRAMEWORK_INTERMEDIARY_CHILD_NAME: &'static str =
"fuchsia_component_test_framework_intermediary";
pub mod builder;
pub mod error;
pub mod mock;
#[derive(Debug, Clone, PartialEq, Eq, Hash, PartialOrd, Ord)]
pub struct Moniker {
path: Vec<String>,
}
impl From<&str> for Moniker {
fn from(s: &str) -> Self {
Moniker {
path: match s {
"" => vec![],
_ => s.split('/').map(|s| s.to_string()).collect(),
},
}
}
}
impl From<String> for Moniker {
fn from(s: String) -> Self {
s.as_str().into()
}
}
impl From<Vec<String>> for Moniker {
fn from(path: Vec<String>) -> Self {
Moniker { path }
}
}
impl Display for Moniker {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
if self.is_root() {
write!(f, "<root of test realms>")
} else {
write!(f, "{}", self.path.join("/"))
}
}
}
impl Moniker {
fn root() -> Self {
Moniker { path: vec![] }
}
fn is_root(&self) -> bool {
return self.path.is_empty();
}
fn child_name(&self) -> Option<&String> {
self.path.last()
}
fn path(&self) -> &Vec<String> {
&self.path
}
fn child(&self, child_name: String) -> Self {
let mut path = self.path.clone();
path.push(child_name);
Moniker { path }
}
/// Returns the list of components comprised of this component's parent, then that component's
/// parent, and so on. This list does not include the root component.
///
/// For example, `"a/b/c/d".into().ancestry()` would return `vec!["a/b/c".into(), "a/b".into(),
/// "a".into()]`
fn ancestry(&self) -> Vec<Moniker> {
let mut current_moniker = Moniker { path: vec![] };
let mut res = vec![];
let mut parent_path = self.path.clone();
parent_path.pop();
for part in parent_path {
current_moniker.path.push(part.clone());
res.push(current_moniker.clone());
}
res
}
fn parent(&self) -> Option<Self> {
let mut path = self.path.clone();
path.pop()?;
Some(Moniker { path })
}
fn is_ancestor_of(&self, other_moniker: &Moniker) -> bool {
if self.path.len() >= other_moniker.path.len() {
return false;
}
for (element_from_us, element_from_them) in self.path.iter().zip(other_moniker.path.iter())
{
if element_from_us != element_from_them {
return false;
}
}
return true;
}
}
#[derive(Debug, Clone)]
struct RealmNode {
decl: cm_rust::ComponentDecl,
eager: bool,
children: HashMap<String, RealmNode>,
}
impl RealmNode {
fn new(decl: cm_rust::ComponentDecl) -> Self {
Self { decl, eager: false, children: HashMap::new() }
}
}
/// A running instance of a created [`Realm`]. When this struct is dropped the child components
/// are destroyed.
pub struct RealmInstance {
/// The root component of this realm instance, which can be used to access exposed capabilities
/// from the realm.
pub root: ScopedInstance,
// We want to ensure that the mocks runner remains alive for as long as the realm exists, so
// the ScopedInstance is bundled up into a struct along with the mocks runner.
_mocks_runner: mock::MocksRunner,
}
// Empty Drop impl so that `RealmInstance` cannot be destructured.
// This avoids a common mistake where the `ScopedInstance` is moved out and the MocksRunner is
// dropped, leading to unexpected behavior.
impl Drop for RealmInstance {
fn drop(&mut self) {}
}
/// A custom built realm, which can be created at runtime in a component collection
pub struct Realm {
decl_tree: Option<RealmNode>,
framework_intermediary_proxy: ftrb::FrameworkIntermediaryProxy,
mocks_runner: mock::MocksRunner,
collection_name: String,
}
impl Realm {
pub async fn new() -> Result<Self, Error> {
let realm_proxy = fclient::connect_to_service::<fsys::RealmMarker>()
.map_err(RealmError::ConnectToRealmService)?;
let (exposed_dir_proxy, exposed_dir_server_end) =
endpoints::create_proxy::<fio::DirectoryMarker>().map_err(RealmError::CreateProxy)?;
realm_proxy
.bind_child(
&mut fsys::ChildRef {
name: FRAMEWORK_INTERMEDIARY_CHILD_NAME.to_string(),
collection: None,
},
exposed_dir_server_end,
)
.await
.map_err(RealmError::FailedToUseRealm)?
.map_err(RealmError::FailedBindToFrameworkIntermediary)?;
let framework_intermediary_proxy = fclient::connect_to_protocol_at_dir_root::<
ftrb::FrameworkIntermediaryMarker,
>(&exposed_dir_proxy)
.map_err(RealmError::ConnectToFrameworkIntermediaryService)?;
Realm::new_with_framework_intermediary_proxy(framework_intermediary_proxy)
}
fn new_with_framework_intermediary_proxy(
framework_intermediary_proxy: ftrb::FrameworkIntermediaryProxy,
) -> Result<Self, Error> {
let mocks_runner = mock::MocksRunner::new(framework_intermediary_proxy.take_event_stream());
Ok(Self {
decl_tree: None,
framework_intermediary_proxy,
mocks_runner,
collection_name: DEFAULT_COLLECTION_NAME.to_string(),
})
}
/// Adds a new mocked component to the realm. When the component is supposed to run the
/// provided [`Mock`] is called with the component's handles.
pub async fn add_mocked_component(
&mut self,
moniker: Moniker,
mock: mock::Mock,
) -> Result<(), Error> {
let mock_id = self
.framework_intermediary_proxy
.register_mock()
.await
.map_err(RealmError::FailedToUseFrameworkIntermediary)?;
self.mocks_runner.register_mock(mock_id.clone(), mock).await;
let decl = cm_rust::ComponentDecl {
program: Some(cm_rust::ProgramDecl {
runner: Some(mock::RUNNER_NAME.try_into().unwrap()),
info: fdata::Dictionary {
entries: Some(vec![fdata::DictionaryEntry {
key: mock::MOCK_ID_KEY.to_string(),
value: Some(Box::new(fdata::DictionaryValue::Str(mock_id))),
}]),
..fdata::Dictionary::EMPTY
},
}),
..cm_rust::ComponentDecl::default()
};
self.add_component(moniker, decl)
}
/// Adds a new component to the realm. Note that the provided `ComponentDecl` should not have
/// child declarations for other components described in this `Realm`, as those will be filled
/// in when [`Realm::create`] is called.
pub fn add_component(
&mut self,
moniker: Moniker,
decl: cm_rust::ComponentDecl,
) -> Result<(), Error> {
if moniker.is_root() {
if self.decl_tree.is_some() {
return Err(RealmError::RootComponentAlreadyExists.into());
}
// Validation is performed later, as we might end up adding ChildDecls to this
self.decl_tree = Some(RealmNode::new(decl));
return Ok(());
}
if self.decl_tree.is_none() {
return Err(RealmError::RootComponentNotSetYet.into());
}
let parent_node = self.get_node_mut(&moniker.parent().unwrap())?;
if parent_node.children.contains_key(moniker.child_name().unwrap())
|| parent_node.decl.children.iter().any(|c| &c.name == moniker.child_name().unwrap())
{
return Err(RealmError::ComponentAlreadyExists(moniker).into());
}
// Validation is performed later, as we might end up adding ChildDecls to this
parent_node.children.insert(moniker.child_name().unwrap().clone(), RealmNode::new(decl));
Ok(())
}
fn get_node_mut<'a>(&'a mut self, moniker: &Moniker) -> Result<&'a mut RealmNode, Error> {
if self.decl_tree.is_none() {
return Err(RealmError::RootComponentNotSetYet.into());
}
let mut current_node = self.decl_tree.as_mut().unwrap();
let mut current_moniker = Moniker::root();
for part in moniker.path() {
current_moniker = current_moniker.child(part.clone());
current_node = match current_node.children.get_mut(part) {
Some(n) => n,
None => {
if current_node.decl.children.iter().any(|c| &c.name == part) {
return Err(RealmError::ComponentNotModifiable(current_moniker).into());
}
return Err(RealmError::ComponentDoesntExist(current_moniker).into());
}
}
}
Ok(current_node)
}
/// Returns whether or not the given component exists in this realm. This will return true if
/// the component exists in the realm tree itself, or if the parent contains a child
/// declaration for the moniker.
pub fn contains(&mut self, moniker: &Moniker) -> bool {
if let Ok(_) = self.get_node_mut(moniker) {
return true;
}
if let Some(parent) = moniker.parent() {
if let Ok(decl) = self.get_decl_mut(&parent) {
return decl.children.iter().any(|c| Some(&c.name) == moniker.child_name());
}
}
return false;
}
/// Returns a mutable reference to a component decl in the realm.
pub fn get_decl_mut<'a>(
&'a mut self,
moniker: &Moniker,
) -> Result<&'a mut cm_rust::ComponentDecl, Error> {
Ok(&mut self.get_node_mut(moniker)?.decl)
}
/// Marks the target component as eager.
///
/// If the target component is a component that was added to this realm with
/// [`Realm::add_component`], then the component is marked as eager in the Realm's
/// internal structure. If the target component is a component referenced in an added
/// component's [`cm_rust::ComponentDecl`], then the `ChildDecl` for the component is modified.
pub fn mark_as_eager(&mut self, moniker: &Moniker) -> Result<(), Error> {
if moniker.is_root() {
return Err(RealmError::CantMarkRootAsEager.into());
}
// The referenced moniker might be a node in our local tree, or it might be a component
// with a source outside of our tree (like fuchsia-pkg://). Attempt to look up the node in
// our tree, and if it doesn't exist then look for a ChildDecl on the parent node
match self.get_node_mut(moniker) {
Ok(node) => node.eager = true,
Err(e) => {
let child_name = moniker.child_name().unwrap();
let parent_node = self.get_node_mut(&moniker.parent().unwrap()).map_err(|_| e)?;
let child_decl = parent_node
.decl
.children
.iter_mut()
.find(|c| &c.name == child_name)
.ok_or(RealmError::MissingChild(moniker.clone()))?;
child_decl.startup = fsys::StartupMode::Eager;
}
}
Ok(())
}
/// Sets the name of the collection that this realm will be created in
pub fn set_collection_name(&mut self, collection_name: impl Into<String>) {
self.collection_name = collection_name.into();
}
/// Initializes the realm, but doesn't create it. Returns the root URL, the collection name,
/// and the mocks runner. The caller should pass the URL and collection name into
/// `fuchsial.sys2.Realm#CreateChild`, and keep the mocks runner alive until after
/// `fuchsia.sys2.Realm#DestroyChild` has been called.
pub async fn initialize(self) -> Result<(String, String, mock::MocksRunner), Error> {
if self.decl_tree.is_none() {
return Err(RealmError::RootComponentNotSetYet.into());
}
let decl_tree = self.decl_tree.as_ref().unwrap().clone();
let root_url =
Self::registration_walker(self.framework_intermediary_proxy.clone(), decl_tree, vec![])
.await?;
Ok((root_url, self.collection_name, self.mocks_runner))
}
/// Creates this realm in a child component collection, using an autogenerated name for the
/// instance. By default this happens in the [`DEFAULT_COLLECTION_NAME`] collection.
pub async fn create(self) -> Result<RealmInstance, Error> {
let (root_url, collection_name, mocks_runner) = self.initialize().await?;
let root = ScopedInstance::new(collection_name, root_url)
.await
.map_err(RealmError::FailedToCreateChild)?;
Ok(RealmInstance { root, _mocks_runner: mocks_runner })
}
/// Creates this realm in a child component collection. By default this happens in the
/// [`DEFAULT_COLLECTION_NAME`] collection.
pub async fn create_with_name(self, child_name: String) -> Result<RealmInstance, Error> {
let (root_url, collection_name, mocks_runner) = self.initialize().await?;
let root = ScopedInstance::new_with_name(child_name, collection_name, root_url)
.await
.map_err(RealmError::FailedToCreateChild)?;
Ok(RealmInstance { root, _mocks_runner: mocks_runner })
}
/// Walks a topology, registering each node with the component resolver.
fn registration_walker(
framework_intermediary_proxy: ftrb::FrameworkIntermediaryProxy,
mut current_node: RealmNode,
walked_path: Vec<String>,
) -> BoxFuture<'static, Result<String, Error>> {
// This function is much cleaner written recursively, but we can't construct recursive
// futures as the size isn't knowable to rustc at compile time. Put the recursive call
// into a boxed future, as the redirection makes this possible
async move {
let mut children = current_node.children.into_iter().collect::<Vec<_>>();
children.sort_unstable_by_key(|t| t.0.clone());
for (name, node) in children {
let mut new_path = walked_path.clone();
new_path.push(name.clone());
let startup =
if node.eager { fsys::StartupMode::Eager } else { fsys::StartupMode::Lazy };
let url =
Self::registration_walker(framework_intermediary_proxy.clone(), node, new_path)
.await?;
current_node.decl.children.push(cm_rust::ChildDecl {
name,
url,
startup,
environment: None,
});
}
let fidl_decl = current_node.decl.native_into_fidl();
cm_fidl_validator::validate(&fidl_decl)
.map_err(|e| RealmError::InvalidDecl(walked_path.into(), e, fidl_decl.clone()))?;
framework_intermediary_proxy
.register_decl(fidl_decl)
.await
.map_err(RealmError::FailedToUseFrameworkIntermediary)?
.map_err(|s| RealmError::DeclRejectedByRegistry(zx::Status::from_raw(s)).into())
}
.boxed()
}
}
/// RAII object that keeps a component instance alive until it's dropped, and provides convenience
/// functions for using the instance. Components v2 only.
#[must_use = "Dropping `ScopedInstance` will cause the component instance to be stopped and destroyed."]
pub struct ScopedInstance {
child_name: String,
collection: String,
exposed_dir: fio::DirectoryProxy,
destroy_channel: Option<
futures::channel::oneshot::Sender<
Result<
fidl::client::QueryResponseFut<fidl_fuchsia_sys2::RealmDestroyChildResult>,
anyhow::Error,
>,
>,
>,
}
impl ScopedInstance {
/// Creates and binds to a new component just like `new_with_name`, but uses an autogenerated
/// name for the instance.
pub async fn new(coll: String, url: String) -> Result<Self, anyhow::Error> {
let id: u64 = rand::thread_rng().gen();
let child_name = format!("auto-{}", id);
Self::new_with_name(child_name, coll, url).await
}
/// Creates and binds to a new component named `child_name` in a collection `coll` with `url`,
/// and returning an object that represents the component's lifetime and can be used to access
/// the component's exposed directory. When the object is dropped, it will be asynchronously
/// stopped _and_ destroyed. This is useful for tests that wish to create components that
/// should be torn down at the end of the test. Components v2 only.
pub async fn new_with_name(
child_name: String,
collection: String,
url: String,
) -> Result<Self, anyhow::Error> {
let realm = fclient::realm().context("Failed to connect to Realm service")?;
let mut collection_ref = fsys::CollectionRef { name: collection.clone() };
let child_decl = fsys::ChildDecl {
name: Some(child_name.clone()),
url: Some(url),
startup: Some(fsys::StartupMode::Lazy),
environment: None,
..fsys::ChildDecl::EMPTY
};
let () = realm
.create_child(&mut collection_ref, child_decl)
.await
.context("CreateChild FIDL failed.")?
.map_err(|e| format_err!("Failed to create child: {:?}", e))?;
let mut child_ref =
fsys::ChildRef { name: child_name.clone(), collection: Some(collection.clone()) };
let (exposed_dir, server) = endpoints::create_proxy::<fidl_fuchsia_io::DirectoryMarker>()
.context("Failed to create directory proxy")?;
let () = realm
.bind_child(&mut child_ref, server)
.await
.context("BindChild FIDL failed.")?
.map_err(|e| format_err!("Failed to bind to child: {:?}", e))?;
Ok(Self { child_name, exposed_dir, collection, destroy_channel: None })
}
/// Connect to an instance of a FIDL protocol hosted in the component's exposed directory`,
pub fn connect_to_protocol_at_exposed_dir<S: DiscoverableService>(
&self,
) -> Result<S::Proxy, anyhow::Error> {
fclient::connect_to_protocol_at_dir_root::<S>(&self.exposed_dir)
}
/// Connects to an instance of a FIDL protocol hosted in the component's exposed directory
/// using the given `server_end`.
pub fn connect_request_to_protocol_at_exposed_dir<S: DiscoverableService>(
&self,
server_end: ServerEnd<S>,
) -> Result<(), anyhow::Error> {
self.connect_request_to_named_service_at_exposed_dir(
S::SERVICE_NAME,
server_end.into_channel(),
)
}
/// Connects to an instance of a FIDL service called `service_name` hosted in the component's
/// exposed directory using the given `server_end`.
pub fn connect_request_to_named_service_at_exposed_dir(
&self,
service_name: &str,
server_end: zx::Channel,
) -> Result<(), anyhow::Error> {
self.exposed_dir
.open(
fidl_fuchsia_io::OPEN_RIGHT_READABLE | fidl_fuchsia_io::OPEN_RIGHT_WRITABLE,
fidl_fuchsia_io::MODE_TYPE_SERVICE,
service_name,
ServerEnd::new(server_end),
)
.context("Failed to open protocol in directory")
}
/// Returns a reference to the component's read-only exposed directory.
pub fn get_exposed_dir(&self) -> &fio::DirectoryProxy {
&self.exposed_dir
}
/// Returns a future which can be awaited on for destruction to complete after the
/// `ScopedInstance` is dropped.
pub fn take_destroy_waiter(
&mut self,
) -> impl futures::Future<Output = Result<(), anyhow::Error>> {
if self.destroy_channel.is_some() {
panic!("destroy waiter already taken");
}
let (sender, receiver) = futures::channel::oneshot::channel();
self.destroy_channel = Some(sender);
receiver.err_into().and_then(futures::future::ready).and_then(
|fidl_fut: fidl::client::QueryResponseFut<_>| {
fidl_fut.map(|r: Result<Result<(), fidl_fuchsia_component::Error>, fidl::Error>| {
r.context("DestroyChild FIDL error")?
.map_err(|e| format_err!("Failed to destroy child: {:?}", e))
})
},
)
}
/// Return the name of this instance.
pub fn child_name(&self) -> String {
return self.child_name.clone();
}
}
impl Drop for ScopedInstance {
fn drop(&mut self) {
let Self { collection, child_name, destroy_channel, exposed_dir: _ } = self;
let result =
fclient::realm().context("Failed to connect to Realm service").and_then(|realm| {
let mut child_ref = fsys::ChildRef {
name: child_name.clone(),
collection: Some(collection.clone()),
};
// DestroyChild also stops the component.
//
// Calling destroy child within drop guarantees that the message
// goes out to the realm regardless of there existing a waiter on
// the destruction channel.
Ok(realm.destroy_child(&mut child_ref))
});
if let Some(chan) = destroy_channel.take() {
let () = chan.send(result).unwrap_or_else(|result| {
warn!("Failed to send result for destroyed scoped instance. Result={:?}", result);
});
}
}
}
#[cfg(test)]
mod tests {
use {
super::*,
cm_rust::*,
fidl::endpoints::create_proxy,
fidl_fuchsia_data as fdata, fuchsia_async as fasync,
futures::{lock::Mutex, TryStreamExt},
std::{collections::HashSet, sync::Arc},
};
struct FrameworkIntermediaryMock {
_stream_handling_task: fasync::Task<()>,
_decls: Arc<Mutex<HashMap<String, ComponentDecl>>>,
mocks: Arc<Mutex<HashSet<String>>>,
}
fn realm_with_mock_framework_intermediary() -> (FrameworkIntermediaryMock, Realm) {
let (framework_intermediary_proxy, framework_intermediary_server_end) =
create_proxy::<ftrb::FrameworkIntermediaryMarker>().unwrap();
let decls = Arc::new(Mutex::new(HashMap::new()));
let mocks = Arc::new(Mutex::new(HashSet::new()));
let decls_clone = decls.clone();
let mocks_clone = mocks.clone();
let task = fasync::Task::local(async move {
let mut framework_intermediary_stream =
framework_intermediary_server_end.into_stream().unwrap();
let mut mock_counter: u64 = 0;
let mut next_unique_component_id: u64 = 0;
while let Some(request) = framework_intermediary_stream.try_next().await.unwrap() {
match request {
ftrb::FrameworkIntermediaryRequest::RegisterDecl { responder, decl } => {
let native_decl = decl.fidl_into_native();
let url = format!("{}://{}", mock::RUNNER_NAME, next_unique_component_id);
decls_clone.lock().await.insert(url.clone(), native_decl);
next_unique_component_id += 1;
responder.send(&mut Ok(url)).unwrap()
}
ftrb::FrameworkIntermediaryRequest::RegisterMock { responder } => {
let mock_id = format!("{}", mock_counter);
mocks_clone.lock().await.insert(mock_id.clone());
mock_counter += 1;
responder.send(&mock_id).unwrap();
}
}
}
});
let realm_with_mock_framework_intermediary =
Realm::new_with_framework_intermediary_proxy(framework_intermediary_proxy).unwrap();
(
FrameworkIntermediaryMock { _stream_handling_task: task, _decls: decls, mocks },
realm_with_mock_framework_intermediary,
)
}
#[fasync::run_until_stalled(test)]
async fn set_root_decl() {
let (_fi_mock, mut realm) = realm_with_mock_framework_intermediary();
realm.add_component(Moniker::root(), ComponentDecl::default()).unwrap();
assert_eq!(ComponentDecl::default(), *realm.get_decl_mut(&Moniker::root()).unwrap());
}
#[fasync::run_until_stalled(test)]
async fn add_component() {
let (fi_mock, mut realm) = realm_with_mock_framework_intermediary();
let root_decl = ComponentDecl {
offers: vec![OfferDecl::Protocol(OfferProtocolDecl {
source: OfferSource::Parent,
source_name: "fidl.examples.routing.echo.Echo".try_into().unwrap(),
target: OfferTarget::Child("a".to_string()),
target_name: "fidl.examples.routing.echo.Echo".try_into().unwrap(),
dependency_type: DependencyType::Strong,
})],
..ComponentDecl::default()
};
let a_decl = ComponentDecl {
offers: vec![OfferDecl::Protocol(OfferProtocolDecl {
source: OfferSource::Parent,
source_name: "fidl.examples.routing.echo.Echo".try_into().unwrap(),
target: OfferTarget::Child("b".to_string()),
target_name: "fidl.examples.routing.echo.Echo".try_into().unwrap(),
dependency_type: DependencyType::Strong,
})],
children: vec![ChildDecl {
name: "b".to_string(),
url: "fuchsia-pkg://b".to_string(),
startup: fsys::StartupMode::Lazy,
environment: None,
}],
..ComponentDecl::default()
};
realm.add_component(Moniker::root(), root_decl.clone()).unwrap();
realm.add_component("a".into(), a_decl.clone()).unwrap();
assert_eq!(fi_mock.mocks.lock().await.len(), 0);
assert_eq!(*realm.get_decl_mut(&Moniker::root()).unwrap(), root_decl);
assert_eq!(*realm.get_decl_mut(&"a".into()).unwrap(), a_decl);
}
#[fasync::run_until_stalled(test)]
async fn add_mocked_component() {
let (fi_mock, mut realm) = realm_with_mock_framework_intermediary();
let root_decl = ComponentDecl {
offers: vec![OfferDecl::Protocol(OfferProtocolDecl {
source: OfferSource::Parent,
source_name: "fidl.examples.routing.echo.Echo".try_into().unwrap(),
target: OfferTarget::Child("a".to_string()),
target_name: "fidl.examples.routing.echo.Echo".try_into().unwrap(),
dependency_type: DependencyType::Strong,
})],
..ComponentDecl::default()
};
let use_echo_decl = UseDecl::Protocol(UseProtocolDecl {
source: UseSource::Parent,
source_name: "fidl.examples.routing.echo.Echo".try_into().unwrap(),
target_path: "/svc/fidl.examples.routing.echo.Echo".try_into().unwrap(),
});
realm.add_component(Moniker::root(), root_decl.clone()).unwrap();
realm
.add_mocked_component(
"a".into(),
mock::Mock::new(|_: mock::MockHandles| Box::pin(async move { Ok(()) })),
)
.await
.unwrap();
realm.get_decl_mut(&"a".into()).unwrap().uses.push(use_echo_decl.clone());
assert_eq!(fi_mock.mocks.lock().await.len(), 1);
assert!(fi_mock.mocks.lock().await.contains(&"0".to_string()));
assert_eq!(*realm.get_decl_mut(&Moniker::root()).unwrap(), root_decl);
assert_eq!(
*realm.get_decl_mut(&"a".into()).unwrap(),
ComponentDecl {
program: Some(cm_rust::ProgramDecl {
runner: Some(mock::RUNNER_NAME.try_into().unwrap()),
info: fdata::Dictionary {
entries: Some(vec![fdata::DictionaryEntry {
key: mock::MOCK_ID_KEY.to_string(),
value: Some(Box::new(fdata::DictionaryValue::Str("0".to_string()))),
}]),
..fdata::Dictionary::EMPTY
},
}),
uses: vec![use_echo_decl],
..ComponentDecl::default()
}
);
}
}