blob: 0ad83b08ccc911998e9d719788240517a1177e65 [file] [log] [blame]
// Copyright 2022 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_types::{LongName, Name, Path};
use component_events::events::*;
use component_events::matcher::*;
use fidl::endpoints::Proxy;
use fuchsia_component_test::{Capability, ChildOptions, RealmBuilder, Ref, Route};
use futures::channel::mpsc;
use futures::{FutureExt, SinkExt, StreamExt};
use std::collections::BTreeMap;
use zx::AsHandleRef;
use {fidl_fuchsia_component as fcomponent, fidl_fuchsia_io as fio, fuchsia_async as fasync};
// TODO(https://fxbug.dev/42172627): Deduplicate this function. It is used in other CM integration tests
async fn start_nested_cm_and_wait_for_clean_stop(root_url: &str, moniker_to_wait_on: &str) {
let builder = RealmBuilder::new().await.unwrap();
let root = builder.add_child("root", root_url, ChildOptions::new().eager()).await.unwrap();
builder
.add_route(
Route::new()
.capability(Capability::protocol_by_name("fuchsia.logger.LogSink"))
.capability(Capability::protocol_by_name("fuchsia.process.Launcher"))
.capability(Capability::event_stream("started").with_scope(&root))
.capability(Capability::event_stream("stopped").with_scope(&root))
.capability(Capability::event_stream("destroyed").with_scope(&root))
.capability(Capability::event_stream("capability_requested").with_scope(&root))
.from(Ref::parent())
.to(&root),
)
.await
.unwrap();
let instance =
builder.build_in_nested_component_manager("#meta/component_manager.cm").await.unwrap();
let proxy = instance.root.connect_to_protocol_at_exposed_dir().unwrap();
let mut event_stream = EventStream::new(proxy);
instance.start_component_tree().await.unwrap();
// Expect the component to stop
EventMatcher::ok()
.stop(Some(ExitStatusMatcher::Clean))
.moniker(moniker_to_wait_on)
.wait::<Stopped>(&mut event_stream)
.await
.unwrap();
}
#[fasync::run_singlethreaded(test)]
async fn from_framework_should_not_work() {
let root_url = "#meta/async_reporter.cm";
let moniker_to_wait_on = "./root";
let builder = RealmBuilder::new().await.unwrap();
let root = builder.add_child("root", root_url, ChildOptions::new().eager()).await.unwrap();
builder
.add_route(
Route::new()
.capability(Capability::protocol_by_name("fuchsia.logger.LogSink"))
.capability(Capability::event_stream("started").with_scope(&root))
.capability(Capability::event_stream("stopped").with_scope(&root))
.capability(Capability::event_stream("destroyed").with_scope(&root))
.capability(Capability::event_stream("capability_requested").with_scope(&root))
.from(Ref::framework())
.to(&root),
)
.await
.unwrap();
let instance =
builder.build_in_nested_component_manager("#meta/component_manager.cm").await.unwrap();
let proxy = instance.root.connect_to_protocol_at_exposed_dir().unwrap();
let mut event_stream = EventStream::new(proxy);
instance.start_component_tree().await.unwrap();
// Expect the component to stop
EventMatcher::ok()
.stop(Some(ExitStatusMatcher::AnyCrash))
.moniker(moniker_to_wait_on)
.wait::<Stopped>(&mut event_stream)
.await
.unwrap();
}
#[fasync::run_singlethreaded(test)]
async fn async_event_source_test() {
start_nested_cm_and_wait_for_clean_stop("#meta/async_reporter.cm", "./root").await;
}
#[fasync::run_singlethreaded(test)]
async fn scoped_events_test() {
start_nested_cm_and_wait_for_clean_stop("#meta/echo_realm.cm", "./root/echo_reporter").await;
}
#[fasync::run_singlethreaded(test)]
async fn realm_offered_event_source_test() {
start_nested_cm_and_wait_for_clean_stop(
"#meta/realm_offered_root.cm",
"./root/nested_realm/reporter",
)
.await;
}
#[fasync::run_singlethreaded(test)]
async fn nested_event_source_test() {
start_nested_cm_and_wait_for_clean_stop("#meta/nested_reporter.cm", "./root").await;
}
#[fasync::run_singlethreaded(test)]
async fn event_capability_requested() {
start_nested_cm_and_wait_for_clean_stop("#meta/capability_requested_root.cm", "./root").await;
}
#[derive(Debug)]
enum EventOrDirRequest {
DirRequest(fio::DirectoryRequest),
Event(fcomponent::Event),
}
/// Adds a child to the given realm builder. The child's manifest contains the given use
/// declaration, and the realm's manifest contains the given offer declaration. The child will
/// connect to its event stream, and then report any incoming events and any incoming requests to
/// its outgoing directory over the returned mpsc receiver.
async fn set_up_capability_requested_realm(
builder: &RealmBuilder,
offer: cm_rust::OfferDecl,
use_: cm_rust::UseDecl,
) -> mpsc::UnboundedReceiver<EventOrDirRequest> {
let (events_sender, events_receiver) = mpsc::unbounded();
let event_receiver = builder
.add_local_child(
"event_receiver",
move |h| {
let mut events_sender = events_sender.clone();
async move {
let event_stream_proxy =
h.connect_to_protocol::<fcomponent::EventStreamProxy>().unwrap();
let mut outgoing_dir_request_stream = h.outgoing_dir.into_stream();
let mut events_sender_clone = events_sender.clone();
let scope = fasync::Scope::new();
let _task = scope.spawn(async move {
while let Some(Ok(dir_request)) = outgoing_dir_request_stream.next().await {
events_sender_clone
.send(EventOrDirRequest::DirRequest(dir_request))
.await
.unwrap();
}
});
loop {
let next_events = event_stream_proxy.get_next().await.unwrap();
for event in next_events {
events_sender.send(EventOrDirRequest::Event(event)).await.unwrap();
}
}
}
.boxed()
},
ChildOptions::new(),
)
.await
.unwrap();
let mut realm_decl = builder.get_realm_decl().await.unwrap();
cm_rust::push_box(&mut realm_decl.offers, offer);
builder.replace_realm_decl(realm_decl).await.unwrap();
let mut child_decl = builder.get_component_decl(&event_receiver).await.unwrap();
cm_rust::push_box(&mut child_decl.uses, use_);
builder.replace_component_decl(&event_receiver, child_decl).await.unwrap();
events_receiver
}
/// Confirms that a component that's configured to receive capability requests through its event
/// stream will get those requests through the event stream.
#[fuchsia::test]
async fn receive_protocol_through_capability_requested() {
let mut filter = BTreeMap::new();
filter.insert("name".to_string(), cm_rust::DictionaryValue::Str("example-name".to_string()));
let builder = RealmBuilder::new().await.unwrap();
let mut events_receiver = set_up_capability_requested_realm(
&builder,
cm_rust::OfferDecl::EventStream(cm_rust::OfferEventStreamDecl {
source: cm_rust::OfferSource::Parent,
scope: None,
source_name: Name::new("capability_requested").unwrap(),
target: cm_rust::OfferTarget::Child(cm_rust::ChildRef {
name: LongName::new("event_receiver").unwrap(),
collection: None,
}),
target_name: Name::new("capability_requested").unwrap(),
availability: cm_rust::Availability::Required,
}),
cm_rust::UseDecl::EventStream(cm_rust::UseEventStreamDecl {
source_name: Name::new("capability_requested").unwrap(),
source: cm_rust::UseSource::Parent,
scope: None,
target_path: Path::new("/svc/fuchsia.component.EventStream").unwrap(),
filter: Some(filter),
availability: cm_rust::Availability::Required,
}),
)
.await;
builder
.add_route(
Route::new()
.capability(Capability::protocol_by_name("example-name"))
.from(Ref::child("event_receiver"))
.to(Ref::parent()),
)
.await
.unwrap();
let instance =
builder.build_in_nested_component_manager("#meta/component_manager.cm").await.unwrap();
let proxy = instance
.root
.connect_to_named_protocol_at_exposed_dir::<fcomponent::RealmMarker>("example-name")
.unwrap();
let what_happened = events_receiver.next().await.unwrap();
match what_happened {
EventOrDirRequest::Event(fcomponent::Event {
header: Some(header),
payload:
Some(fcomponent::EventPayload::CapabilityRequested(
fcomponent::CapabilityRequestedPayload {
name: Some(name),
capability: Some(channel),
..
},
)),
..
}) if header.event_type == Some(fcomponent::EventType::CapabilityRequested)
&& header.moniker == Some(".".to_string())
&& name == "example-name".to_string() =>
{
assert_eq!(
channel.get_koid().unwrap(),
proxy.as_channel().basic_info().unwrap().related_koid,
);
}
something_else => panic!("something unexpected happened: {something_else:?}"),
}
}
/// When a component is configured to receive one capability through its event stream, other
/// capability requests should still be delivered over its outgoing directory.
#[fuchsia::test]
async fn receive_protocol_through_outgoing_dir_when_outside_filter() {
let mut filter = BTreeMap::new();
filter.insert(
"name".to_string(),
cm_rust::DictionaryValue::Str("different-example-name".to_string()),
);
let builder = RealmBuilder::new().await.unwrap();
let mut events_receiver = set_up_capability_requested_realm(
&builder,
cm_rust::OfferDecl::EventStream(cm_rust::OfferEventStreamDecl {
source: cm_rust::OfferSource::Parent,
scope: None,
source_name: Name::new("capability_requested").unwrap(),
target: cm_rust::OfferTarget::Child(cm_rust::ChildRef {
name: LongName::new("event_receiver").unwrap(),
collection: None,
}),
target_name: Name::new("capability_requested").unwrap(),
availability: cm_rust::Availability::Required,
}),
cm_rust::UseDecl::EventStream(cm_rust::UseEventStreamDecl {
source_name: Name::new("capability_requested").unwrap(),
source: cm_rust::UseSource::Parent,
scope: None,
target_path: Path::new("/svc/fuchsia.component.EventStream").unwrap(),
filter: Some(filter),
availability: cm_rust::Availability::Required,
}),
)
.await;
builder
.add_route(
Route::new()
.capability(Capability::protocol_by_name("example-name"))
.from(Ref::child("event_receiver"))
.to(Ref::parent()),
)
.await
.unwrap();
let instance =
builder.build_in_nested_component_manager("#meta/component_manager.cm").await.unwrap();
let _proxy = instance
.root
.connect_to_named_protocol_at_exposed_dir::<fcomponent::RealmMarker>("example-name")
.unwrap();
let what_happened = events_receiver.next().await.unwrap();
match what_happened {
EventOrDirRequest::DirRequest(fio::DirectoryRequest::Open { path, .. }) => {
assert_eq!(path, "svc/example-name");
}
something_else => panic!("something unexpected happened: {something_else:?}"),
}
}
/// When a component receives a capability through an event stream that has a scope applied to it,
/// the target moniker of the route should have the scope's prefix stripped from it.
#[fuchsia::test]
async fn smaller_scope_impacts_moniker() {
let mut filter = BTreeMap::new();
filter.insert("name".to_string(), cm_rust::DictionaryValue::Str("example-name".to_string()));
let builder = RealmBuilder::new().await.unwrap();
let protocol_consumer_parent = builder
.add_child_realm("protocol_consumer_parent", ChildOptions::new().eager())
.await
.unwrap();
protocol_consumer_parent
.add_local_child(
"protocol_consumer",
|h| {
async move {
let _proxy =
h.connect_to_named_protocol::<fcomponent::RealmProxy>("example-name");
Ok(())
}
.boxed()
},
ChildOptions::new().eager(),
)
.await
.unwrap();
let mut events_receiver = set_up_capability_requested_realm(
&builder,
cm_rust::OfferDecl::EventStream(cm_rust::OfferEventStreamDecl {
source: cm_rust::OfferSource::Parent,
scope: Some(Box::new([cm_rust::EventScope::Child(cm_rust::ChildRef {
name: LongName::new("protocol_consumer_parent").unwrap(),
collection: None,
})])),
source_name: Name::new("capability_requested").unwrap(),
target: cm_rust::OfferTarget::Child(cm_rust::ChildRef {
name: LongName::new("event_receiver").unwrap(),
collection: None,
}),
target_name: Name::new("capability_requested").unwrap(),
availability: cm_rust::Availability::Required,
}),
cm_rust::UseDecl::EventStream(cm_rust::UseEventStreamDecl {
source_name: Name::new("capability_requested").unwrap(),
source: cm_rust::UseSource::Parent,
scope: None,
target_path: Path::new("/svc/fuchsia.component.EventStream").unwrap(),
filter: Some(filter),
availability: cm_rust::Availability::Required,
}),
)
.await;
builder
.add_route(
Route::new()
.capability(Capability::protocol_by_name("example-name"))
.from(Ref::child("event_receiver"))
.to(Ref::child("protocol_consumer_parent")),
)
.await
.unwrap();
protocol_consumer_parent
.add_route(
Route::new()
.capability(Capability::protocol_by_name("example-name"))
.from(Ref::parent())
.to(Ref::child("protocol_consumer")),
)
.await
.unwrap();
let _instance =
builder.build_in_nested_component_manager("#meta/component_manager.cm").await.unwrap();
let what_happened = events_receiver.next().await.unwrap();
match what_happened {
EventOrDirRequest::Event(fcomponent::Event {
header: Some(header),
payload:
Some(fcomponent::EventPayload::CapabilityRequested(
fcomponent::CapabilityRequestedPayload {
name: Some(name),
capability: Some(_channel),
..
},
)),
..
}) if header.event_type == Some(fcomponent::EventType::CapabilityRequested)
&& header.moniker == Some("protocol_consumer".to_string())
&& name == "example-name".to_string() =>
{
// Success!
}
something_else => panic!("something unexpected happened: {something_else:?}"),
}
}
/// When one component attempts to access a capability provided by another, the provider is
/// configured to accept capability requests over an event stream, and the requesting component is
/// out of scope according to the event stream, then the capability request should not be delivered
/// to the provider.
#[fuchsia::test]
async fn out_of_scope_is_not_delivered() {
let mut filter = BTreeMap::new();
filter.insert("name".to_string(), cm_rust::DictionaryValue::Str("example-name".to_string()));
let builder = RealmBuilder::new().await.unwrap();
builder
.add_local_child(
"protocol_consumer",
|h| {
async move {
let _proxy =
h.connect_to_named_protocol::<fcomponent::RealmProxy>("example-name");
Ok(())
}
.boxed()
},
ChildOptions::new().eager(),
)
.await
.unwrap();
let mut events_receiver = set_up_capability_requested_realm(
&builder,
cm_rust::OfferDecl::EventStream(cm_rust::OfferEventStreamDecl {
source: cm_rust::OfferSource::Parent,
scope: Some(Box::new([cm_rust::EventScope::Child(cm_rust::ChildRef {
name: LongName::new("event_receiver").unwrap(),
collection: None,
})])),
source_name: Name::new("capability_requested").unwrap(),
target: cm_rust::OfferTarget::Child(cm_rust::ChildRef {
name: LongName::new("event_receiver").unwrap(),
collection: None,
}),
target_name: Name::new("capability_requested").unwrap(),
availability: cm_rust::Availability::Required,
}),
cm_rust::UseDecl::EventStream(cm_rust::UseEventStreamDecl {
source_name: Name::new("capability_requested").unwrap(),
source: cm_rust::UseSource::Parent,
scope: None,
target_path: Path::new("/svc/fuchsia.component.EventStream").unwrap(),
filter: Some(filter),
availability: cm_rust::Availability::Required,
}),
)
.await;
builder
.add_route(
Route::new()
.capability(Capability::protocol_by_name("example-name"))
.from(Ref::child("event_receiver"))
.to(Ref::child("protocol_consumer")),
)
.await
.unwrap();
let _instance =
builder.build_in_nested_component_manager("#meta/component_manager.cm").await.unwrap();
let mut next_fut = events_receiver.next().fuse();
let mut timer_fut = std::pin::pin!(fasync::Timer::new(std::time::Duration::from_millis(5000)));
futures::select!(
what_happened = next_fut => panic!("something unexpected happened: {what_happened:?}"),
_ = timer_fut => (),
);
}