blob: 838691aa390d0d67eb710b67b8bd3785b14b087e [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 anyhow::Error;
use fidl::{endpoints, AsHandleRef, HandleBased};
use fuchsia_component::client;
use fuchsia_runtime::{HandleInfo, HandleType};
use std::ffi::CString;
use {
fidl_fidl_test_components as ftest, fidl_fuchsia_component as fcomponent,
fidl_fuchsia_component_decl as fdecl, fidl_fuchsia_io as fio, fidl_fuchsia_process as fprocess,
fuchsia_async as fasync, fuchsia_zircon as zx,
};
#[fasync::run_singlethreaded(test)]
async fn collections() {
let realm = client::connect_to_protocol::<fcomponent::RealmMarker>()
.expect("could not connect to Realm service");
// Create a couple child components.
for name in vec!["a", "b"] {
let collection_ref = fdecl::CollectionRef { name: "coll".to_string() };
let child_decl = fdecl::Child {
name: Some(name.to_string()),
url: Some(format!("#meta/trigger_{}.cm", name)),
startup: Some(fdecl::StartupMode::Lazy),
environment: None,
..Default::default()
};
realm
.create_child(&collection_ref, &child_decl, fcomponent::CreateChildArgs::default())
.await
.unwrap_or_else(|e| panic!("create_child {} failed: {:?}", name, e))
.unwrap_or_else(|e| panic!("failed to create child {}: {:?}", name, e));
}
let children = list_children(&realm).await.expect("failed to list children");
assert_eq!("coll:a,coll:b", &children);
// Start the children.
for name in vec!["a", "b"] {
let child_ref = new_child_ref(name, "coll");
let (dir, server_end) = endpoints::create_proxy::<fio::DirectoryMarker>().unwrap();
realm
.open_exposed_dir(&child_ref, server_end)
.await
.unwrap_or_else(|e| panic!("open_exposed_dir {} failed: {:?}", name, e))
.unwrap_or_else(|e| panic!("failed to open exposed dir of child {}: {:?}", name, e));
let trigger = client::connect_to_protocol_at_dir_root::<ftest::TriggerMarker>(&dir)
.expect("failed to open trigger service");
let out =
trigger.run().await.unwrap_or_else(|e| panic!("trigger {} failed: {:?}", name, e));
assert_eq!(out, format!("Triggered {}", name));
}
// Destroy one.
{
let child_ref = new_child_ref("a", "coll");
realm
.destroy_child(&child_ref)
.await
.expect("destroy_child a failed")
.expect("failed to destroy child");
}
// Binding to destroyed child should fail.
{
let (_, server_end) = endpoints::create_proxy::<fio::DirectoryMarker>().unwrap();
let child_ref = new_child_ref("a", "coll");
let res = realm
.open_exposed_dir(&child_ref, server_end)
.await
.expect("second open_exposed_dir a failed");
let err = res.expect_err("expected open_exposed_dir a to fail");
assert_eq!(err, fcomponent::Error::InstanceNotFound);
}
let children = list_children(&realm).await.expect("failed to list children");
assert_eq!("coll:b", &children);
// Recreate child (with different URL), and start it. Should work.
{
let collection_ref = fdecl::CollectionRef { name: "coll".to_string() };
let child_decl = fdecl::Child {
name: Some("a".to_string()),
url: Some("#meta/trigger_realm.cm".to_string()),
startup: Some(fdecl::StartupMode::Lazy),
environment: None,
..Default::default()
};
realm
.create_child(&collection_ref, &child_decl, fcomponent::CreateChildArgs::default())
.await
.expect("second create_child a failed")
.expect("failed to create second child a");
}
{
let (dir, server_end) = endpoints::create_proxy::<fio::DirectoryMarker>().unwrap();
let child_ref = new_child_ref("a", "coll");
realm
.open_exposed_dir(&child_ref, server_end)
.await
.expect("open_exposed_dir a failed")
.expect("failed to open exposed dir of child a");
let trigger = client::connect_to_protocol_at_dir_root::<ftest::TriggerMarker>(&dir)
.expect("failed to open trigger service");
let out = trigger.run().await.expect("second trigger a failed");
assert_eq!(&out, "Triggered a");
}
let children = list_children(&realm).await.expect("failed to list children");
assert_eq!("coll:a,coll:b", &children);
}
#[fasync::run_singlethreaded(test)]
async fn child_args() {
let realm = client::connect_to_protocol::<fcomponent::RealmMarker>()
.expect("could not connect to Realm service");
// Providing numbered handles to a component that is not in a single run collection should fail.
{
let name = "a";
let collection_ref = fdecl::CollectionRef { name: "not_single_run".to_string() };
let child_decl = fdecl::Child {
name: Some(name.to_string()),
url: Some(format!("#meta/trigger_{}.cm", name)),
startup: Some(fdecl::StartupMode::Lazy),
environment: None,
..Default::default()
};
let (_, socket) = zx::Socket::create_stream();
let numbered_handles = vec![fprocess::HandleInfo { handle: socket.into_handle(), id: 0 }];
let child_args = fcomponent::CreateChildArgs {
numbered_handles: Some(numbered_handles),
..Default::default()
};
let res = realm
.create_child(&collection_ref, &child_decl, child_args)
.await
.unwrap_or_else(|e| panic!("create_child {} failed: {:?}", name, e));
let err = res.expect_err("expected create_child a to fail");
assert_eq!(err, fcomponent::Error::Unsupported);
}
// Providing numbered handles to a component that is in a single run collection should succeed.
{
let collection_ref = fdecl::CollectionRef { name: "single_run".to_owned() };
let child_decl = fdecl::Child {
name: Some("write_startup_socket".to_owned()),
url: Some("#meta/write_startup_socket.cm".to_owned()),
startup: Some(fdecl::StartupMode::Lazy),
environment: None,
..Default::default()
};
let (their_socket, our_socket) = zx::Socket::create_stream();
let numbered_handles = vec![fprocess::HandleInfo {
handle: their_socket.into_handle(),
// Must correspond to the same Handle Id in write_startup_socket.rs.
id: HandleInfo::new(HandleType::User0, 0).as_raw(),
}];
let child_args = fcomponent::CreateChildArgs {
numbered_handles: Some(numbered_handles),
..Default::default()
};
realm
.create_child(&collection_ref, &child_decl, child_args)
.await
.expect("fidl error in create_child")
.expect("failed to create_child");
// NOTE: Although we specified StartupMode::Lazy above, |create_child| will actually start
// the component immediately without the need to manually bind it, because "the instances
// in a single run collection are started when they are created" (see
// //docs/concepts/components/v2/realms.md). https://fxbug.dev/42171498 discusses the idea
// to remove the startup parameter.
fasync::OnSignals::new(&our_socket, zx::Signals::SOCKET_READABLE)
.await
.expect("SOCKET_READABLE should be signaled after the child starts");
let mut bytes = [0u8; 32];
let length = our_socket.read(&mut bytes).expect("read should succeed");
assert_eq!(bytes[..length], b"Hello, World!"[..]);
// Checking OBJECT_PEER_CLOSED ensures that |their_socket| is not leaked.
fasync::OnSignals::new(&our_socket, zx::Signals::OBJECT_PEER_CLOSED)
.await
.expect("OBJECT_PEER_CLOSED should be signaled after the child exits");
}
// Providing numbered handles with invalid id should fail.
{
let collection_ref = fdecl::CollectionRef { name: "single_run".to_owned() };
let child_decl = fdecl::Child {
name: Some("write_startup_socket_2".to_owned()),
url: Some("#meta/write_startup_socket.cm".to_owned()),
startup: Some(fdecl::StartupMode::Lazy),
environment: None,
..Default::default()
};
let (their_socket, our_socket) = zx::Socket::create_stream();
let job =
fuchsia_runtime::job_default().create_child_job().expect("fail to create_child_job");
job.set_name(&CString::new("new-job-new-name").unwrap()).expect("fail to set_name");
let numbered_handles = vec![
fprocess::HandleInfo {
handle: job.into_handle(),
// Only PA_FD and PA_USER* handles are valid arguments (//sdk/fidl/fuchsia.component/realm.fidl).
id: HandleInfo::new(HandleType::DefaultJob, 0).as_raw(),
},
fprocess::HandleInfo {
handle: their_socket.into_handle(),
// Must correspond to the same Handle Id in write_startup_socket.rs.
id: HandleInfo::new(HandleType::User0, 0).as_raw(),
},
];
let child_args = fcomponent::CreateChildArgs {
numbered_handles: Some(numbered_handles),
..Default::default()
};
let result = realm
.create_child(&collection_ref, &child_decl, child_args)
.await
.expect("fidl error in create_child");
// TODO(https://fxbug.dev/42181105): it should fail to create_child, but it succeeds instead. Also
// the default_job received in write_startup_socket.rs is NOT the job we created above.
result.unwrap();
// Wait for the component to exit (to mute the log).
fasync::OnSignals::new(&our_socket, zx::Signals::OBJECT_PEER_CLOSED)
.await
.expect("OBJECT_PEER_CLOSED should be signaled after the child exits");
}
}
#[fasync::run_singlethreaded(test)]
async fn eager() {
let realm = client::connect_to_protocol::<fcomponent::RealmMarker>().unwrap();
let collection_ref = fdecl::CollectionRef { name: "eager".to_string() };
let child_decl = fdecl::Child {
name: Some("a".into()),
url: Some("#meta/trigger_a.cm".into()),
startup: Some(fdecl::StartupMode::Eager),
..Default::default()
};
let (controller_proxy, server_end) =
endpoints::create_proxy::<fcomponent::ControllerMarker>().unwrap();
let child_args =
fcomponent::CreateChildArgs { controller: Some(server_end), ..Default::default() };
realm.create_child(&collection_ref, &child_decl, child_args).await.unwrap().unwrap();
// [create_child] blocks for eager children to start so it should be started already.
assert_eq!(controller_proxy.is_started().await.unwrap().unwrap(), true);
let child_ref = new_child_ref("a", "eager");
realm.destroy_child(&child_ref).await.unwrap().unwrap();
}
fn new_child_ref(name: &str, collection: &str) -> fdecl::ChildRef {
fdecl::ChildRef { name: name.to_string(), collection: Some(collection.to_string()) }
}
async fn list_children(realm: &fcomponent::RealmProxy) -> Result<String, Error> {
let (iterator_proxy, server_end) = endpoints::create_proxy().unwrap();
let collection_ref = fdecl::CollectionRef { name: "coll".to_string() };
realm
.list_children(&collection_ref, server_end)
.await
.expect("list_children failed")
.expect("failed to list children");
let res = iterator_proxy.next().await;
let children = res.expect("failed to iterate over children");
let children: Vec<_> = children
.iter()
.map(|c| format!("{}:{}", c.collection.as_ref().expect("no collection"), &c.name))
.collect();
Ok(children.join(","))
}