blob: 6ddf48accffeca1184e41636e8c143b9eb04bf57 [file] [log] [blame]
// 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::{
builtin_environment::{BuiltinEnvironment, BuiltinEnvironmentBuilder},
config::RuntimeConfig,
klog,
model::{
hooks::HooksRegistration,
model::Model,
realm::{Realm, WeakRealm},
rights,
testing::{
mocks::{ControlMessage, MockResolver, MockRunner},
test_hook::TestHook,
},
},
startup::Arguments,
},
cm_rust::{ChildDecl, ComponentDecl, NativeIntoFidl},
cm_types::Url,
fidl::endpoints::{self, Proxy, ServerEnd},
fidl_fidl_examples_echo as echo, fidl_fuchsia_component_runner as fcrunner,
fidl_fuchsia_data as fdata,
fidl_fuchsia_io::{
DirectoryProxy, CLONE_FLAG_SAME_RIGHTS, MODE_TYPE_SERVICE, OPEN_FLAG_CREATE,
OPEN_RIGHT_READABLE, OPEN_RIGHT_WRITABLE,
},
fidl_fuchsia_io2::{self as fio2},
fidl_fuchsia_sys2 as fsys, files_async, fuchsia_async as fasync,
fuchsia_zircon::{self as zx, AsHandleRef, Koid},
futures::{channel::mpsc::Receiver, StreamExt, TryStreamExt},
moniker::{AbsoluteMoniker, PartialMoniker},
std::collections::HashSet,
std::default::Default,
std::path::Path,
std::sync::Arc,
vfs::directory::entry::DirectoryEntry,
};
pub struct ComponentInfo {
pub realm: Arc<Realm>,
pub channel_id: Koid,
}
impl ComponentInfo {
/// Given a `Realm` which has been bound, look up the resolved URL
/// and package into a `ComponentInfo` struct.
pub async fn new(realm: Arc<Realm>) -> ComponentInfo {
// The koid is the only unique piece of information we have about
// a component start request. Two start requests for the same
// component URL look identical to the Runner, the only difference
// being the Channel passed to the Runner to use for the
// ComponentController protocol.
let koid = {
let realm = realm.lock_execution().await;
let runtime = realm.runtime.as_ref().expect("runtime is unexpectedly missing");
let controller =
runtime.controller.as_ref().expect("controller is unexpectedly missing");
let basic_info = controller
.as_channel()
.basic_info()
.expect("error getting basic info about controller channel");
// should be the koid of the other side of the channel
basic_info.related_koid
};
ComponentInfo { realm, channel_id: koid }
}
/// Checks that the component is shut down, panics if this is not true.
pub async fn check_is_shut_down(&self, runner: &MockRunner) {
// Check the list of requests for this component
let request_map = runner.get_request_map();
let unlocked_map = request_map.lock().await;
let request_vec = unlocked_map
.get(&self.channel_id)
.expect("request map didn't have channel id, perhaps the controller wasn't started?");
assert_eq!(*request_vec, vec![ControlMessage::Stop]);
let execution = self.realm.lock_execution().await;
assert!(execution.runtime.is_none());
assert!(execution.is_shut_down());
}
/// Checks that the component has not been shut down, panics if it has.
pub async fn check_not_shut_down(&self, runner: &MockRunner) {
// If the MockController has started, check that no stop requests have
// been received.
let request_map = runner.get_request_map();
let unlocked_map = request_map.lock().await;
if let Some(request_vec) = unlocked_map.get(&self.channel_id) {
assert_eq!(*request_vec, vec![]);
}
let execution = self.realm.lock_execution().await;
assert!(execution.runtime.is_some());
assert!(!execution.is_shut_down());
}
}
pub async fn execution_is_shut_down(realm: &Realm) -> bool {
let execution = realm.lock_execution().await;
execution.runtime.is_none() && execution.is_shut_down()
}
/// Returns true if the given child realm (live or deleting) exists.
pub async fn has_child<'a>(realm: &'a Realm, moniker: &'a str) -> bool {
realm
.lock_state()
.await
.as_ref()
.expect("not resolved")
.all_child_realms()
.contains_key(&moniker.into())
}
/// Return the instance id of the given live child.
pub async fn get_instance_id<'a>(realm: &'a Realm, moniker: &'a str) -> u32 {
realm
.lock_state()
.await
.as_ref()
.expect("not resolved")
.get_live_child_instance_id(&moniker.into())
.unwrap()
}
/// Return all monikers of the live children of the given `realm`.
pub async fn get_live_children(realm: &Realm) -> HashSet<PartialMoniker> {
realm
.lock_state()
.await
.as_ref()
.expect("not resolved")
.live_child_realms()
.map(|(m, _)| m.clone())
.collect()
}
/// Return the child realm of the given `realm` with moniker `child`.
pub async fn get_live_child<'a>(realm: &'a Realm, child: &'a str) -> Arc<Realm> {
realm
.lock_state()
.await
.as_ref()
.expect("not resolved")
.get_live_child_realm(&child.into())
.unwrap()
.clone()
}
/// Returns an empty component decl for an executable component.
pub fn default_component_decl() -> ComponentDecl {
ComponentDecl {
program: Some(fdata::Dictionary { entries: Some(vec![]), ..fdata::Dictionary::EMPTY }),
..Default::default()
}
}
/// Name of the test runner.
///
/// Several functions assume the existance of a runner with this name.
pub const TEST_RUNNER_NAME: &str = "test_runner";
/// Returns an empty component decl set up to have a non-empty program and to use the "test_runner"
/// runner.
pub fn component_decl_with_test_runner() -> ComponentDecl {
ComponentDecl {
program: Some(fdata::Dictionary { entries: Some(vec![]), ..fdata::Dictionary::EMPTY }),
uses: vec![cm_rust::UseDecl::Runner(cm_rust::UseRunnerDecl {
source_name: TEST_RUNNER_NAME.into(),
})],
..Default::default()
}
}
/// Builder for constructing a ComponentDecl.
#[derive(Debug, Clone)]
pub struct ComponentDeclBuilder {
result: ComponentDecl,
}
impl ComponentDeclBuilder {
/// An empty ComponentDeclBuilder, with no program.
pub fn new_empty_component() -> Self {
ComponentDeclBuilder { result: Default::default() }
}
/// A ComponentDeclBuilder prefilled with a program and using a runner named "test_runner",
/// which we assume is offered to us.
pub fn new() -> Self {
Self::new_empty_component().use_runner(TEST_RUNNER_NAME)
}
/// Add a child element.
pub fn add_child(mut self, decl: impl Into<cm_rust::ChildDecl>) -> Self {
self.result.children.push(decl.into());
self
}
// Add a collection element.
pub fn add_collection(mut self, decl: impl Into<cm_rust::CollectionDecl>) -> Self {
self.result.collections.push(decl.into());
self
}
/// Add a lazily instantiated child with a default test URL derived from the name.
pub fn add_lazy_child(self, name: &str) -> Self {
self.add_child(ChildDeclBuilder::new_lazy_child(name))
}
/// Add an eagerly instantiated child with a default test URL derived from the name.
pub fn add_eager_child(self, name: &str) -> Self {
self.add_child(
ChildDeclBuilder::new()
.name(name)
.url(&format!("test:///{}", name))
.startup(fsys::StartupMode::Eager),
)
}
/// Add a transient collection.
pub fn add_transient_collection(self, name: &str) -> Self {
self.add_collection(CollectionDeclBuilder::new_transient_collection(name))
}
/// Add a "use" clause, using the given runner.
pub fn use_runner(mut self, name: &str) -> Self {
self.result
.uses
.push(cm_rust::UseDecl::Runner(cm_rust::UseRunnerDecl { source_name: name.into() }));
self
}
/// Add a custom offer.
pub fn offer(mut self, offer: cm_rust::OfferDecl) -> Self {
self.result.offers.push(offer);
self
}
/// Add a custom expose.
pub fn expose(mut self, expose: cm_rust::ExposeDecl) -> Self {
self.result.exposes.push(expose);
self
}
/// Add a custom use decl.
pub fn use_(mut self, use_: cm_rust::UseDecl) -> Self {
self.result.uses.push(use_);
self
}
/// Add a custom protocol declaration.
pub fn protocol(mut self, protocol: cm_rust::ProtocolDecl) -> Self {
self.result.capabilities.push(cm_rust::CapabilityDecl::Protocol(protocol));
self
}
/// Add a custom directory declaration.
pub fn directory(mut self, directory: cm_rust::DirectoryDecl) -> Self {
self.result.capabilities.push(cm_rust::CapabilityDecl::Directory(directory));
self
}
/// Add a custom storage declaration.
pub fn storage(mut self, storage: cm_rust::StorageDecl) -> Self {
self.result.capabilities.push(cm_rust::CapabilityDecl::Storage(storage));
self
}
/// Add a custom runner declaration.
pub fn runner(mut self, runner: cm_rust::RunnerDecl) -> Self {
self.result.capabilities.push(cm_rust::CapabilityDecl::Runner(runner));
self
}
/// Add a custom resolver declaration.
pub fn resolver(mut self, resolver: cm_rust::ResolverDecl) -> Self {
self.result.capabilities.push(cm_rust::CapabilityDecl::Resolver(resolver));
self
}
/// Add an environment declaration.
pub fn add_environment(mut self, environment: impl Into<cm_rust::EnvironmentDecl>) -> Self {
self.result.environments.push(environment.into());
self
}
/// Generate the final ComponentDecl.
pub fn build(self) -> ComponentDecl {
self.result
}
}
/// A convenience builder for constructing ChildDecls.
#[derive(Debug)]
pub struct ChildDeclBuilder(cm_rust::ChildDecl);
impl ChildDeclBuilder {
/// Creates a new builder.
pub fn new() -> Self {
ChildDeclBuilder(cm_rust::ChildDecl {
name: String::new(),
url: String::new(),
startup: fsys::StartupMode::Lazy,
environment: None,
})
}
/// Creates a new builder initialized with a lazy child.
pub fn new_lazy_child(name: &str) -> Self {
Self::new().name(name).url(&format!("test:///{}", name)).startup(fsys::StartupMode::Lazy)
}
/// Sets the ChildDecl's name.
pub fn name(mut self, name: &str) -> Self {
self.0.name = name.to_string();
self
}
/// Sets the ChildDecl's url.
pub fn url(mut self, url: &str) -> Self {
self.0.url = url.to_string();
self
}
/// Sets the ChildDecl's startup mode.
pub fn startup(mut self, startup: fsys::StartupMode) -> Self {
self.0.startup = startup;
self
}
/// Sets the ChildDecl's environment name.
pub fn environment(mut self, environment: &str) -> Self {
self.0.environment = Some(environment.to_string());
self
}
/// Consumes the builder and returns a ChildDecl.
pub fn build(self) -> cm_rust::ChildDecl {
self.0
}
}
impl From<ChildDeclBuilder> for cm_rust::ChildDecl {
fn from(builder: ChildDeclBuilder) -> Self {
builder.build()
}
}
/// A convenience builder for constructing CollectionDecls.
#[derive(Debug)]
pub struct CollectionDeclBuilder(cm_rust::CollectionDecl);
impl CollectionDeclBuilder {
/// Creates a new builder.
pub fn new() -> Self {
CollectionDeclBuilder(cm_rust::CollectionDecl {
name: String::new(),
durability: fsys::Durability::Transient,
environment: None,
})
}
/// Creates a new builder initialized with a transient collection.
pub fn new_transient_collection(name: &str) -> Self {
Self::new().name(name).durability(fsys::Durability::Transient)
}
/// Sets the CollectionDecl's name.
pub fn name(mut self, name: &str) -> Self {
self.0.name = name.to_string();
self
}
/// Sets the CollectionDecl's durability
pub fn durability(mut self, durability: fsys::Durability) -> Self {
self.0.durability = durability;
self
}
/// Sets the CollectionDecl's environment name.
pub fn environment(mut self, environment: &str) -> Self {
self.0.environment = Some(environment.to_string());
self
}
/// Consumes the builder and returns a CollectionDecl.
pub fn build(self) -> cm_rust::CollectionDecl {
self.0
}
}
impl From<CollectionDeclBuilder> for cm_rust::CollectionDecl {
fn from(builder: CollectionDeclBuilder) -> Self {
builder.build()
}
}
/// A convenience builder for constructing EnvironmentDecls.
#[derive(Debug)]
pub struct EnvironmentDeclBuilder(cm_rust::EnvironmentDecl);
impl EnvironmentDeclBuilder {
/// Creates a new builder.
pub fn new() -> Self {
EnvironmentDeclBuilder(cm_rust::EnvironmentDecl {
name: String::new(),
extends: fsys::EnvironmentExtends::None,
runners: vec![],
resolvers: vec![],
stop_timeout_ms: None,
})
}
/// Sets the EnvironmentDecl's name.
pub fn name(mut self, name: &str) -> Self {
self.0.name = name.to_string();
self
}
/// Sets whether the environment extends from its realm.
pub fn extends(mut self, extends: fsys::EnvironmentExtends) -> Self {
self.0.extends = extends;
self
}
/// Registers a runner with the environment.
pub fn add_runner(mut self, runner: cm_rust::RunnerRegistration) -> Self {
self.0.runners.push(runner);
self
}
/// Registers a resolver with the environment.
pub fn add_resolver(mut self, resolver: cm_rust::ResolverRegistration) -> Self {
self.0.resolvers.push(resolver);
self
}
pub fn stop_timeout(mut self, timeout_ms: u32) -> Self {
self.0.stop_timeout_ms = Some(timeout_ms);
self
}
/// Consumes the builder and returns an EnvironmentDecl.
pub fn build(self) -> cm_rust::EnvironmentDecl {
self.0
}
}
impl From<EnvironmentDeclBuilder> for cm_rust::EnvironmentDecl {
fn from(builder: EnvironmentDeclBuilder) -> Self {
builder.build()
}
}
// A convenience builder for constructing ProtocolDecls.
#[derive(Debug)]
pub struct ProtocolDeclBuilder(cm_rust::ProtocolDecl);
impl ProtocolDeclBuilder {
/// Creates a new builder.
pub fn new(name: &str) -> Self {
Self(cm_rust::ProtocolDecl {
name: name.into(),
source_path: format!("/svc/foo").parse().unwrap(),
})
}
/// Sets the source path.
pub fn path(mut self, path: &str) -> Self {
self.0.source_path = path.parse().unwrap();
self
}
/// Consumes the builder and returns a ProtocolDecl.
pub fn build(self) -> cm_rust::ProtocolDecl {
self.0
}
}
impl From<ProtocolDeclBuilder> for cm_rust::ProtocolDecl {
fn from(builder: ProtocolDeclBuilder) -> Self {
builder.build()
}
}
// A convenience builder for constructing DirectoryDecls.
#[derive(Debug)]
pub struct DirectoryDeclBuilder(cm_rust::DirectoryDecl);
impl DirectoryDeclBuilder {
/// Creates a new builder.
pub fn new(name: &str) -> Self {
Self(cm_rust::DirectoryDecl {
name: name.into(),
source_path: format!("/data/foo").parse().unwrap(),
rights: *rights::READ_RIGHTS,
})
}
/// Sets the source path.
pub fn path(mut self, path: &str) -> Self {
self.0.source_path = path.parse().unwrap();
self
}
/// Sets the rights.
pub fn rights(mut self, rights: fio2::Operations) -> Self {
self.0.rights = rights;
self
}
/// Consumes the builder and returns a DirectoryDecl.
pub fn build(self) -> cm_rust::DirectoryDecl {
self.0
}
}
impl From<DirectoryDeclBuilder> for cm_rust::DirectoryDecl {
fn from(builder: DirectoryDeclBuilder) -> Self {
builder.build()
}
}
/// Create a fsys::OfferRunnerDecl offering the given cap from the parent to the given child
/// component.
pub fn offer_runner_cap_to_child(runner_cap: &str, child: &str) -> cm_rust::OfferDecl {
cm_rust::OfferDecl::Runner(cm_rust::OfferRunnerDecl {
source: cm_rust::OfferRunnerSource::Parent,
source_name: runner_cap.into(),
target: cm_rust::OfferTarget::Child(child.to_string()),
target_name: runner_cap.into(),
})
}
/// Create a fsys::OfferRunnerDecl offering the given cap from the parent to the given child
/// collection.
pub fn offer_runner_cap_to_collection(runner_cap: &str, child: &str) -> cm_rust::OfferDecl {
cm_rust::OfferDecl::Runner(cm_rust::OfferRunnerDecl {
source: cm_rust::OfferRunnerSource::Parent,
source_name: runner_cap.into(),
target: cm_rust::OfferTarget::Collection(child.to_string()),
target_name: runner_cap.into(),
})
}
pub async fn dir_contains<'a>(
root_proxy: &'a DirectoryProxy,
path: &'a str,
entry_name: &'a str,
) -> bool {
let dir = io_util::open_directory(&root_proxy, &Path::new(path), OPEN_RIGHT_READABLE)
.expect("Failed to open directory");
let entries = files_async::readdir(&dir).await.expect("readdir failed");
let listing = entries.iter().map(|entry| entry.name.clone()).collect::<Vec<String>>();
listing.contains(&String::from(entry_name))
}
pub async fn list_directory<'a>(root_proxy: &'a DirectoryProxy) -> Vec<String> {
let entries = files_async::readdir(&root_proxy).await.expect("readdir failed");
let mut items = entries.iter().map(|entry| entry.name.clone()).collect::<Vec<String>>();
items.sort();
items
}
pub async fn list_directory_recursive<'a>(root_proxy: &'a DirectoryProxy) -> Vec<String> {
let dir = io_util::clone_directory(&root_proxy, CLONE_FLAG_SAME_RIGHTS)
.expect("Failed to clone DirectoryProxy");
let entries = files_async::readdir_recursive(&dir, /*timeout=*/ None);
let mut items = entries
.map(|result| result.map(|entry| entry.name.clone()))
.try_collect::<Vec<_>>()
.await
.expect("readdir failed");
items.sort();
items
}
pub async fn read_file<'a>(root_proxy: &'a DirectoryProxy, path: &'a str) -> String {
let file_proxy = io_util::open_file(&root_proxy, &Path::new(path), OPEN_RIGHT_READABLE)
.expect("Failed to open file.");
let res = io_util::read_file(&file_proxy).await;
res.expect("Unable to read file.")
}
pub async fn write_file<'a>(root_proxy: &'a DirectoryProxy, path: &'a str, contents: &'a str) {
let file_proxy = io_util::open_file(
&root_proxy,
&Path::new(path),
OPEN_RIGHT_READABLE | OPEN_RIGHT_WRITABLE | OPEN_FLAG_CREATE,
)
.expect("Failed to open file.");
let (s, _) = file_proxy.write(contents.as_bytes()).await.expect("Unable to write file.");
let s = zx::Status::from_raw(s);
assert_eq!(s, zx::Status::OK, "Write failed");
}
pub async fn call_echo<'a>(root_proxy: &'a DirectoryProxy, path: &'a str) -> String {
let node_proxy =
io_util::open_node(&root_proxy, &Path::new(path), OPEN_RIGHT_READABLE, MODE_TYPE_SERVICE)
.expect("failed to open echo service");
let echo_proxy = echo::EchoProxy::new(node_proxy.into_channel().unwrap());
let res = echo_proxy.echo_string(Some("hippos")).await;
res.expect("failed to use echo service").expect("no result from echo")
}
/// Create a `DirectoryEntry` and `Channel` pair. The created `DirectoryEntry`
/// provides the service `S`, sending all requests to the returned channel.
pub fn create_service_directory_entry<S>(
) -> (Arc<dyn DirectoryEntry>, futures::channel::mpsc::Receiver<fidl::endpoints::Request<S>>)
where
S: fidl::endpoints::ServiceMarker,
fidl::endpoints::Request<S>: Send,
{
use futures::sink::SinkExt;
let (sender, receiver) = futures::channel::mpsc::channel(0);
let entry = directory_broker::DirectoryBroker::new(Box::new(
move |_flags: u32,
_mode: u32,
_relative_path: String,
server_end: ServerEnd<fidl_fuchsia_io::NodeMarker>| {
let mut sender = sender.clone();
fasync::Task::spawn(async move {
// Convert the stream into a channel of the correct service.
let server_end: ServerEnd<S> = ServerEnd::new(server_end.into_channel());
let mut stream: S::RequestStream = server_end.into_stream().unwrap();
// Keep handling requests until the stream closes or the handler returns an error.
while let Ok(Some(request)) = stream.try_next().await {
sender.send(request).await.unwrap();
}
})
.detach();
},
));
(entry, receiver)
}
/// Wait for a ComponentRunnerStart request, acknowledge it, and return
/// the start info.
///
/// Panics if the channel closes before we receive a request.
pub async fn wait_for_runner_request(
recv: &mut Receiver<fcrunner::ComponentRunnerRequest>,
) -> fcrunner::ComponentStartInfo {
let fcrunner::ComponentRunnerRequest::Start { start_info, .. } =
recv.next().await.expect("Channel closed before request was received.");
start_info
}
/// Contains test model and ancillary objects.
pub struct TestModelResult {
pub model: Arc<Model>,
pub builtin_environment: Arc<BuiltinEnvironment>,
pub mock_runner: Arc<MockRunner>,
}
/// Returns a `Model` and `BuiltinEnvironment` suitable for most tests.
// TODO: Consider turning this into a builder.
pub async fn new_test_model(
root_component: &str,
components: Vec<(&str, ComponentDecl)>,
runtime_config: RuntimeConfig,
) -> TestModelResult {
let mock_runner = Arc::new(MockRunner::new());
let mut mock_resolver = MockResolver::new();
for (name, decl) in &components {
mock_resolver.add_component(name, decl.clone());
}
let args = Arguments {
root_component_url: Some(Url::new(format!("test:///{}", root_component)).unwrap()),
..Default::default()
};
let builtin_environment = Arc::new(
BuiltinEnvironmentBuilder::new()
.set_args(args)
.add_resolver("test".to_string(), Box::new(mock_resolver))
.unwrap()
.add_runner(TEST_RUNNER_NAME.into(), mock_runner.clone())
.set_runtime_config(runtime_config)
.build()
.await
.expect("builtin environment setup failed"),
);
TestModelResult { model: builtin_environment.model.clone(), builtin_environment, mock_runner }
}
/// A test harness for tests that wish to register or verify actions.
pub struct ActionsTest {
pub model: Arc<Model>,
pub builtin_environment: Arc<BuiltinEnvironment>,
pub test_hook: Arc<TestHook>,
pub realm_proxy: Option<fsys::RealmProxy>,
pub runner: Arc<MockRunner>,
}
impl ActionsTest {
pub async fn new(
root_component: &'static str,
components: Vec<(&'static str, ComponentDecl)>,
realm_moniker: Option<AbsoluteMoniker>,
) -> Self {
Self::new_with_hooks(root_component, components, realm_moniker, vec![]).await
}
pub async fn new_with_hooks(
root_component: &'static str,
components: Vec<(&'static str, ComponentDecl)>,
realm_moniker: Option<AbsoluteMoniker>,
extra_hooks: Vec<HooksRegistration>,
) -> Self {
// Ensure that kernel logging has been set up
let _ = klog::KernelLogger::init();
let TestModelResult { model, builtin_environment, mock_runner } =
new_test_model(root_component, components, RuntimeConfig::default()).await;
// TODO(fsamuel): Don't install the Hub's hooks because the Hub expects components
// to start and stop in a certain lifecycle ordering. In particular, some unit
// tests will destroy component instances before binding to their parents.
let test_hook = Arc::new(TestHook::new());
model.root_realm.hooks.install(test_hook.hooks()).await;
model.root_realm.hooks.install(extra_hooks).await;
// Host framework service for root realm, if requested.
let builtin_environment_inner = builtin_environment.clone();
let realm_proxy = if let Some(realm_moniker) = realm_moniker {
let (realm_proxy, stream) =
endpoints::create_proxy_and_stream::<fsys::RealmMarker>().unwrap();
let realm = WeakRealm::from(
&model
.look_up_realm(&realm_moniker)
.await
.expect(&format!("could not look up {}", realm_moniker)),
);
fasync::Task::spawn(async move {
builtin_environment_inner
.realm_capability_host
.serve(realm, stream)
.await
.expect("failed serving realm service");
})
.detach();
Some(realm_proxy)
} else {
None
};
Self { model, builtin_environment, test_hook, realm_proxy, runner: mock_runner }
}
pub async fn look_up(&self, moniker: AbsoluteMoniker) -> Arc<Realm> {
self.model.look_up_realm(&moniker).await.expect(&format!("could not look up {}", moniker))
}
/// Add a dynamic child to the given collection, with the given name to the
/// realm that our proxy member variable corresponds to.
pub async fn create_dynamic_child(&self, coll: &str, name: &str) {
let mut collection_ref = fsys::CollectionRef { name: coll.to_string() };
let child_decl = ChildDecl {
name: name.to_string(),
url: format!("test:///{}", name),
startup: fsys::StartupMode::Lazy,
environment: None,
}
.native_into_fidl();
let res = self
.realm_proxy
.as_ref()
.expect("realm service not started")
.create_child(&mut collection_ref, child_decl)
.await;
res.expect("failed to create child").expect("failed to create child");
}
}