blob: ef264455971c257669016c87464d5945c08f6fa9 [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 {
anyhow::{format_err, Error},
fidl_fidl_examples_routing_echo as fecho, fidl_fuchsia_data as fdata, fuchsia_async as fasync,
fuchsia_component::server as fserver,
fuchsia_component_test::{builder::*, mock, Moniker},
futures::{channel::oneshot, lock::Mutex, StreamExt, TryStreamExt},
std::sync::Arc,
};
const V1_ECHO_CLIENT_URL: &'static str =
"fuchsia-pkg://fuchsia.com/fuchsia-component-test-tests#meta/echo_client.cmx";
const V2_ECHO_CLIENT_ABSOLUTE_URL: &'static str =
"fuchsia-pkg://fuchsia.com/fuchsia-component-test-tests#meta/echo_client.cm";
const V2_ECHO_CLIENT_RELATIVE_URL: &'static str = "#meta/echo_client.cm";
const V1_ECHO_SERVER_URL: &'static str =
"fuchsia-pkg://fuchsia.com/fuchsia-component-test-tests#meta/echo_server.cmx";
const V2_ECHO_SERVER_ABSOLUTE_URL: &'static str =
"fuchsia-pkg://fuchsia.com/fuchsia-component-test-tests#meta/echo_server.cm";
const V2_ECHO_SERVER_RELATIVE_URL: &'static str = "#meta/echo_server.cm";
const DEFAULT_ECHO_STR: &'static str = "Hippos rule!";
#[fasync::run_singlethreaded(test)]
async fn protocol_with_uncle_test() -> Result<(), Error> {
let (send_echo_server_called, receive_echo_server_called) = oneshot::channel();
let sender = Arc::new(Mutex::new(Some(send_echo_server_called)));
let mut builder = RealmBuilder::new().await?;
builder
.add_component(
"echo-server",
ComponentSource::mock(move |mock_handles: mock::MockHandles| {
Box::pin(echo_server_mock(DEFAULT_ECHO_STR, sender.clone(), mock_handles))
}),
)
.await?
.add_eager_component(
"parent/echo-client",
ComponentSource::url(V2_ECHO_CLIENT_ABSOLUTE_URL),
)
.await?
.add_route(CapabilityRoute {
capability: Capability::protocol("fidl.examples.routing.echo.Echo"),
source: RouteEndpoint::component("echo-server"),
targets: vec![RouteEndpoint::component("parent/echo-client")],
})?
.add_route(CapabilityRoute {
capability: Capability::protocol("fuchsia.logger.LogSink"),
source: RouteEndpoint::AboveRoot,
targets: vec![
RouteEndpoint::component("echo-server"),
RouteEndpoint::component("parent/echo-client"),
],
})?;
let _child_instance = builder.build().create().await?;
receive_echo_server_called.await?;
Ok(())
}
#[fasync::run_singlethreaded(test)]
async fn protocol_with_siblings_test() -> Result<(), Error> {
let (send_echo_server_called, receive_echo_server_called) = oneshot::channel();
let sender = Arc::new(Mutex::new(Some(send_echo_server_called)));
let mut builder = RealmBuilder::new().await?;
builder
.add_eager_component("echo-client", ComponentSource::url(V2_ECHO_CLIENT_ABSOLUTE_URL))
.await?
.add_component(
"echo-server",
ComponentSource::mock(move |mock_handles: mock::MockHandles| {
Box::pin(echo_server_mock(DEFAULT_ECHO_STR, sender.clone(), mock_handles))
}),
)
.await?
.add_route(CapabilityRoute {
capability: Capability::protocol("fidl.examples.routing.echo.Echo"),
source: RouteEndpoint::component("echo-server"),
targets: vec![RouteEndpoint::component("echo-client")],
})?
.add_route(CapabilityRoute {
capability: Capability::protocol("fuchsia.logger.LogSink"),
source: RouteEndpoint::AboveRoot,
targets: vec![
RouteEndpoint::component("echo-client"),
RouteEndpoint::component("echo-server"),
],
})?;
let _child_instance = builder.build().create().await?;
receive_echo_server_called.await?;
Ok(())
}
#[fasync::run_singlethreaded(test)]
async fn protocol_with_cousins_test() -> Result<(), Error> {
let (send_echo_server_called, receive_echo_server_called) = oneshot::channel();
let sender = Arc::new(Mutex::new(Some(send_echo_server_called)));
let mut builder = RealmBuilder::new().await?;
builder
.add_eager_component(
"parent-1/echo-client",
ComponentSource::url(V2_ECHO_CLIENT_ABSOLUTE_URL),
)
.await?
.add_component(
"parent-2/echo-server",
ComponentSource::mock(move |mock_handles: mock::MockHandles| {
Box::pin(echo_server_mock(DEFAULT_ECHO_STR, sender.clone(), mock_handles))
}),
)
.await?
.add_route(CapabilityRoute {
capability: Capability::protocol("fidl.examples.routing.echo.Echo"),
source: RouteEndpoint::component("parent-2/echo-server"),
targets: vec![RouteEndpoint::component("parent-1/echo-client")],
})?
.add_route(CapabilityRoute {
capability: Capability::protocol("fuchsia.logger.LogSink"),
source: RouteEndpoint::AboveRoot,
targets: vec![
RouteEndpoint::component("parent-1/echo-client"),
RouteEndpoint::component("parent-2/echo-server"),
],
})?;
let _child_instance = builder.build().create().await?;
receive_echo_server_called.await?;
Ok(())
}
#[fasync::run_singlethreaded(test)]
async fn mock_component_with_a_child() -> Result<(), Error> {
let (send_echo_server_called, receive_echo_server_called) = oneshot::channel();
let sender = Arc::new(Mutex::new(Some(send_echo_server_called)));
let mut builder = RealmBuilder::new().await?;
builder
.add_component(
"echo-server",
ComponentSource::mock(move |mock_handles: mock::MockHandles| {
Box::pin(echo_server_mock(DEFAULT_ECHO_STR, sender.clone(), mock_handles))
}),
)
.await?
.add_eager_component(
"echo-server/echo-client",
ComponentSource::url(V2_ECHO_CLIENT_ABSOLUTE_URL),
)
.await?
.add_route(CapabilityRoute {
capability: Capability::protocol("fidl.examples.routing.echo.Echo"),
source: RouteEndpoint::component("echo-server"),
targets: vec![RouteEndpoint::component("echo-server/echo-client")],
})?
.add_route(CapabilityRoute {
capability: Capability::protocol("fuchsia.logger.LogSink"),
source: RouteEndpoint::AboveRoot,
targets: vec![
RouteEndpoint::component("echo-server"),
RouteEndpoint::component("echo-server/echo-client"),
],
})?;
let _child_instance = builder.build().create().await?;
receive_echo_server_called.await?;
Ok(())
}
#[fasync::run_singlethreaded(test)]
async fn relative_echo_realm() -> Result<(), Error> {
let mut builder = RealmBuilder::new().await?;
builder
.add_component(Moniker::root(), ComponentSource::url("#meta/echo_realm.cm"))
.await?
// This route will result in the imported echo_realm exposing this protocol, whereas before
// it only offered it to echo_client
.add_route(CapabilityRoute {
capability: Capability::protocol("fidl.examples.routing.echo.Echo"),
source: RouteEndpoint::component("echo_server"),
targets: vec![RouteEndpoint::above_root()],
})?;
let realm_instance = builder.build().create().await?;
let echo_proxy =
realm_instance.root.connect_to_protocol_at_exposed_dir::<fecho::EchoMarker>()?;
assert_eq!(Some("hello".to_string()), echo_proxy.echo_string(Some("hello")).await?);
Ok(())
}
#[fasync::run_singlethreaded(test)]
async fn altered_echo_client_args() -> Result<(), Error> {
let (send_echo_server_called, receive_echo_server_called) = oneshot::channel();
let sender = Arc::new(Mutex::new(Some(send_echo_server_called)));
let mut builder = RealmBuilder::new().await?;
builder
.add_component(Moniker::root(), ComponentSource::url("#meta/echo_realm.cm"))
.await?
.override_component(
"echo_server",
ComponentSource::mock(move |mock_handles: mock::MockHandles| {
Box::pin(echo_server_mock("Whales rule!", sender.clone(), mock_handles))
}),
)
.await?
// echo_realm already has the offer we need, but we still need to add this route so that
// the proper exposes are added to our mock component
.add_route(CapabilityRoute {
capability: Capability::protocol("fidl.examples.routing.echo.Echo"),
source: RouteEndpoint::component("echo_server"),
targets: vec![RouteEndpoint::component("echo_client")],
})?;
// Change the program.args section of the manifest, to alter the string it will try to echo
let mut realm = builder.build();
let mut echo_client_decl = realm.get_decl(&"echo_client".into()).await?;
for entry in echo_client_decl.program.as_mut().unwrap().info.entries.as_mut().unwrap() {
if entry.key.as_str() == "args" {
entry.value = Some(Box::new(fdata::DictionaryValue::StrVec(vec![
"Whales".to_string(),
"rule!".to_string(),
])));
}
}
realm.set_component(&"echo_client".into(), echo_client_decl).await?;
let _realm_instance = realm.create().await?;
receive_echo_server_called.await?;
Ok(())
}
#[fasync::run_singlethreaded(test)]
async fn echo_clients() -> Result<(), Error> {
// This test runs a series of echo clients from different sources against a mock echo server,
// confirming that each client successfully connects to the server.
let (send_echo_client_results, receive_echo_client_results) = oneshot::channel();
let sender = Arc::new(Mutex::new(Some(send_echo_client_results)));
let client_sources = vec![
ComponentSource::legacy_url(V1_ECHO_CLIENT_URL),
ComponentSource::url(V2_ECHO_CLIENT_ABSOLUTE_URL),
ComponentSource::url(V2_ECHO_CLIENT_RELATIVE_URL),
ComponentSource::mock(move |h| Box::pin(echo_client_mock(sender.clone(), h))),
];
for client_source in client_sources {
let (send_echo_server_called, receive_echo_server_called) = oneshot::channel();
let sender = Arc::new(Mutex::new(Some(send_echo_server_called)));
let mut builder = RealmBuilder::new().await?;
builder
.add_component(
"echo-server",
ComponentSource::mock(move |h| {
Box::pin(echo_server_mock(DEFAULT_ECHO_STR, sender.clone(), h))
}),
)
.await?
.add_eager_component("echo-client", client_source)
.await?
.add_route(CapabilityRoute {
capability: Capability::protocol("fidl.examples.routing.echo.Echo"),
source: RouteEndpoint::component("echo-server"),
targets: vec![RouteEndpoint::component("echo-client")],
})?
.add_route(CapabilityRoute {
capability: Capability::protocol("fuchsia.logger.LogSink"),
source: RouteEndpoint::AboveRoot,
targets: vec![
RouteEndpoint::component("echo-server"),
RouteEndpoint::component("echo-client"),
],
})?;
let _child_instance = builder.build().create().await?;
receive_echo_server_called.await?;
}
receive_echo_client_results.await?;
Ok(())
}
#[fasync::run_singlethreaded(test)]
async fn echo_servers() -> Result<(), Error> {
// This test runs a series of echo servers from different sources against a mock echo client,
// confirming that the client can successfully connect to and use each server.
let (send_echo_server_called, receive_echo_server_called) = oneshot::channel();
let sender = Arc::new(Mutex::new(Some(send_echo_server_called)));
let server_sources = vec![
ComponentSource::legacy_url(V1_ECHO_SERVER_URL),
ComponentSource::url(V2_ECHO_SERVER_ABSOLUTE_URL),
ComponentSource::url(V2_ECHO_SERVER_RELATIVE_URL),
ComponentSource::mock(move |h| {
Box::pin(echo_server_mock(DEFAULT_ECHO_STR, sender.clone(), h))
}),
];
for server_source in server_sources {
let (send_echo_client_results, receive_echo_client_results) = oneshot::channel();
let sender = Arc::new(Mutex::new(Some(send_echo_client_results)));
let mut builder = RealmBuilder::new().await?;
builder
.add_component("echo-server", server_source)
.await?
.add_eager_component(
"echo-client",
ComponentSource::mock(move |h| Box::pin(echo_client_mock(sender.clone(), h))),
)
.await?
.add_route(CapabilityRoute {
capability: Capability::protocol("fidl.examples.routing.echo.Echo"),
source: RouteEndpoint::component("echo-server"),
targets: vec![RouteEndpoint::component("echo-client")],
})?
.add_route(CapabilityRoute {
capability: Capability::protocol("fuchsia.logger.LogSink"),
source: RouteEndpoint::AboveRoot,
targets: vec![
RouteEndpoint::component("echo-server"),
RouteEndpoint::component("echo-client"),
],
})?;
let _child_instance = builder.build().create().await?;
receive_echo_client_results.await?;
}
receive_echo_server_called.await?;
Ok(())
}
// A mock echo server implementation, that will crash if it doesn't receive anything other than the
// contents of `expected_echo_str`. It takes and sends a message over `send_echo_server_called`
// once it receives one echo request.
async fn echo_server_mock(
expected_echo_string: &'static str,
send_echo_server_called: Arc<Mutex<Option<oneshot::Sender<()>>>>,
mock_handles: mock::MockHandles,
) -> Result<(), Error> {
let mut fs = fserver::ServiceFs::new();
let mut tasks = vec![];
fs.dir("svc").add_fidl_service(move |mut stream: fecho::EchoRequestStream| {
let send_echo_server_called = send_echo_server_called.clone();
tasks.push(fasync::Task::local(async move {
while let Some(fecho::EchoRequest::EchoString { value, responder }) =
stream.try_next().await.expect("failed to serve echo service")
{
assert_eq!(Some(expected_echo_string.to_string()), value);
responder.send(value.as_ref().map(|s| &**s)).expect("failed to send echo response");
send_echo_server_called
.lock()
.await
.take()
.unwrap()
.send(())
.expect("failed to send results");
}
}));
});
fs.serve_connection(mock_handles.outgoing_dir.into_channel())?;
fs.collect::<()>().await;
Ok(())
}
async fn echo_client_mock(
send_echo_client_results: Arc<Mutex<Option<oneshot::Sender<()>>>>,
mock_handles: mock::MockHandles,
) -> Result<(), Error> {
let echo = mock_handles.connect_to_service::<fecho::EchoMarker>()?;
let out = echo.echo_string(Some(DEFAULT_ECHO_STR)).await?;
send_echo_client_results.lock().await.take().unwrap().send(()).expect("failed to send results");
if Some(DEFAULT_ECHO_STR.to_string()) != out {
return Err(format_err!("unexpected echo result: {:?}", out));
}
Ok(())
}