| // 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}, |
| assert_matches::assert_matches, |
| cm_rust::{self, FidlIntoNative}, |
| cm_types, |
| fidl_fidl_examples_routing_echo::{self as fecho, EchoMarker as EchoClientStatsMarker}, |
| fidl_fuchsia_component as fcomponent, fidl_fuchsia_component_decl as fcdecl, |
| fidl_fuchsia_component_test as ftest, fidl_fuchsia_data as fdata, |
| fidl_fuchsia_examples_services as fex_services, fidl_fuchsia_io as fio, |
| fuchsia_async as fasync, |
| fuchsia_component::server as fserver, |
| fuchsia_component_test::{ |
| error::Error as RealmBuilderError, Capability, ChildOptions, DirectoryContents, |
| LocalComponentHandles, RealmBuilder, Ref, Route, |
| }, |
| fuchsia_fs, |
| futures::{channel::mpsc, future::pending, FutureExt, SinkExt, StreamExt, TryStreamExt}, |
| std::convert::TryInto, |
| }; |
| |
| 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 V2_ECHO_CLIENT_STRUCTURED_CONFIG_RELATIVE_URL: &'static str = "#meta/echo_client_sc.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 ECHO_REALM_RELATIVE_URL: &'static str = "#meta/echo_realm.cm"; |
| |
| const DEFAULT_ECHO_STR: &'static str = "Hello Fuchsia!"; |
| |
| #[fuchsia::test] |
| async fn v1_component_route_to_parent() -> Result<(), Error> { |
| let builder = RealmBuilder::new().await?; |
| let child = builder.add_legacy_child("child", V1_ECHO_SERVER_URL, ChildOptions::new()).await?; |
| builder |
| .add_route( |
| Route::new() |
| .capability(Capability::protocol::<fecho::EchoMarker>()) |
| .from(&child) |
| .to(Ref::parent()), |
| ) |
| .await?; |
| builder |
| .add_route( |
| Route::new() |
| .capability(Capability::protocol_by_name("fuchsia.logger.LogSink")) |
| .from(Ref::parent()) |
| .to(&child), |
| ) |
| .await?; |
| let instance = builder.build().await?; |
| let echo_proxy = instance.root.connect_to_protocol_at_exposed_dir::<fecho::EchoMarker>()?; |
| assert_eq!( |
| Some(DEFAULT_ECHO_STR.to_string()), |
| echo_proxy.echo_string(Some(DEFAULT_ECHO_STR)).await?, |
| ); |
| Ok(()) |
| } |
| |
| #[fuchsia::test] |
| async fn absolute_component_route_to_parent() -> Result<(), Error> { |
| let builder = RealmBuilder::new().await?; |
| let child = |
| builder.add_child("child", V2_ECHO_SERVER_ABSOLUTE_URL, ChildOptions::new()).await?; |
| builder |
| .add_route( |
| Route::new() |
| .capability(Capability::protocol::<fecho::EchoMarker>()) |
| .from(&child) |
| .to(Ref::parent()), |
| ) |
| .await?; |
| builder |
| .add_route( |
| Route::new() |
| .capability(Capability::protocol_by_name("fuchsia.logger.LogSink")) |
| .from(Ref::parent()) |
| .to(&child), |
| ) |
| .await?; |
| let instance = builder.build().await?; |
| let echo_proxy = instance.root.connect_to_protocol_at_exposed_dir::<fecho::EchoMarker>()?; |
| assert_eq!( |
| Some(DEFAULT_ECHO_STR.to_string()), |
| echo_proxy.echo_string(Some(DEFAULT_ECHO_STR)).await?, |
| ); |
| Ok(()) |
| } |
| |
| #[fuchsia::test] |
| async fn relative_component_route_to_parent() -> Result<(), Error> { |
| let builder = RealmBuilder::new().await?; |
| let child = |
| builder.add_child("child", V2_ECHO_SERVER_RELATIVE_URL, ChildOptions::new()).await?; |
| builder |
| .add_route( |
| Route::new() |
| .capability(Capability::protocol::<fecho::EchoMarker>()) |
| .from(&child) |
| .to(Ref::parent()), |
| ) |
| .await?; |
| builder |
| .add_route( |
| Route::new() |
| .capability(Capability::protocol_by_name("fuchsia.logger.LogSink")) |
| .from(Ref::parent()) |
| .to(&child), |
| ) |
| .await?; |
| let instance = builder.build().await?; |
| let echo_proxy = instance.root.connect_to_protocol_at_exposed_dir::<fecho::EchoMarker>()?; |
| assert_eq!( |
| Some(DEFAULT_ECHO_STR.to_string()), |
| echo_proxy.echo_string(Some(DEFAULT_ECHO_STR)).await?, |
| ); |
| Ok(()) |
| } |
| |
| #[fuchsia::test] |
| async fn local_component_route_to_parent() -> Result<(), Error> { |
| let builder = RealmBuilder::new().await?; |
| let (send_echo_server_called, mut receive_echo_server_called) = mpsc::channel(1); |
| let child = builder |
| .add_local_child( |
| "child", |
| move |h| echo_server_mock(DEFAULT_ECHO_STR, send_echo_server_called.clone(), h).boxed(), |
| ChildOptions::new(), |
| ) |
| .await?; |
| builder |
| .add_route( |
| Route::new() |
| .capability(Capability::protocol::<fecho::EchoMarker>()) |
| .from(&child) |
| .to(Ref::parent()), |
| ) |
| .await?; |
| builder |
| .add_route( |
| Route::new() |
| .capability(Capability::protocol_by_name("fuchsia.logger.LogSink")) |
| .from(Ref::parent()) |
| .to(&child), |
| ) |
| .await?; |
| let instance = builder.build().await?; |
| let echo_proxy = instance.root.connect_to_protocol_at_exposed_dir::<fecho::EchoMarker>()?; |
| assert_eq!( |
| Some(DEFAULT_ECHO_STR.to_string()), |
| echo_proxy.echo_string(Some(DEFAULT_ECHO_STR)).await?, |
| ); |
| assert!( |
| receive_echo_server_called.next().await.is_some(), |
| "failed to observe the mock server report a successful connection from a client" |
| ); |
| Ok(()) |
| } |
| |
| #[fuchsia::test] |
| async fn v1_component_route_to_parent_in_sub_realm() -> Result<(), Error> { |
| let builder = RealmBuilder::new().await?; |
| let child_realm = builder.add_child_realm("child-realm", ChildOptions::new()).await?; |
| let child = |
| child_realm.add_legacy_child("child", V1_ECHO_SERVER_URL, ChildOptions::new()).await?; |
| builder |
| .add_route( |
| Route::new() |
| .capability(Capability::protocol::<fecho::EchoMarker>()) |
| .from(&child_realm) |
| .to(Ref::parent()), |
| ) |
| .await?; |
| builder |
| .add_route( |
| Route::new() |
| .capability(Capability::protocol_by_name("fuchsia.logger.LogSink")) |
| .from(Ref::parent()) |
| .to(&child_realm), |
| ) |
| .await?; |
| child_realm |
| .add_route( |
| Route::new() |
| .capability(Capability::protocol::<fecho::EchoMarker>()) |
| .from(&child) |
| .to(Ref::parent()), |
| ) |
| .await?; |
| child_realm |
| .add_route( |
| Route::new() |
| .capability(Capability::protocol_by_name("fuchsia.logger.LogSink")) |
| .from(Ref::parent()) |
| .to(&child), |
| ) |
| .await?; |
| let instance = builder.build().await?; |
| let echo_proxy = instance.root.connect_to_protocol_at_exposed_dir::<fecho::EchoMarker>()?; |
| assert_eq!( |
| Some(DEFAULT_ECHO_STR.to_string()), |
| echo_proxy.echo_string(Some(DEFAULT_ECHO_STR)).await?, |
| ); |
| Ok(()) |
| } |
| |
| #[fuchsia::test] |
| async fn absolute_component_route_to_parent_in_sub_realm() -> Result<(), Error> { |
| let builder = RealmBuilder::new().await?; |
| let child_realm = builder.add_child_realm("child-realm", ChildOptions::new()).await?; |
| let child = |
| child_realm.add_child("child", V2_ECHO_SERVER_ABSOLUTE_URL, ChildOptions::new()).await?; |
| builder |
| .add_route( |
| Route::new() |
| .capability(Capability::protocol::<fecho::EchoMarker>()) |
| .from(&child_realm) |
| .to(Ref::parent()), |
| ) |
| .await?; |
| builder |
| .add_route( |
| Route::new() |
| .capability(Capability::protocol_by_name("fuchsia.logger.LogSink")) |
| .from(Ref::parent()) |
| .to(&child_realm), |
| ) |
| .await?; |
| child_realm |
| .add_route( |
| Route::new() |
| .capability(Capability::protocol::<fecho::EchoMarker>()) |
| .from(&child) |
| .to(Ref::parent()), |
| ) |
| .await?; |
| child_realm |
| .add_route( |
| Route::new() |
| .capability(Capability::protocol_by_name("fuchsia.logger.LogSink")) |
| .from(Ref::parent()) |
| .to(&child), |
| ) |
| .await?; |
| let instance = builder.build().await?; |
| let echo_proxy = instance.root.connect_to_protocol_at_exposed_dir::<fecho::EchoMarker>()?; |
| assert_eq!( |
| Some(DEFAULT_ECHO_STR.to_string()), |
| echo_proxy.echo_string(Some(DEFAULT_ECHO_STR)).await?, |
| ); |
| Ok(()) |
| } |
| |
| #[fuchsia::test] |
| async fn relative_component_route_to_parent_in_sub_realm() -> Result<(), Error> { |
| let builder = RealmBuilder::new().await?; |
| let child_realm = builder.add_child_realm("child-realm", ChildOptions::new()).await?; |
| let child = |
| child_realm.add_child("child", V2_ECHO_SERVER_RELATIVE_URL, ChildOptions::new()).await?; |
| builder |
| .add_route( |
| Route::new() |
| .capability(Capability::protocol::<fecho::EchoMarker>()) |
| .from(&child_realm) |
| .to(Ref::parent()), |
| ) |
| .await?; |
| builder |
| .add_route( |
| Route::new() |
| .capability(Capability::protocol_by_name("fuchsia.logger.LogSink")) |
| .from(Ref::parent()) |
| .to(&child_realm), |
| ) |
| .await?; |
| child_realm |
| .add_route( |
| Route::new() |
| .capability(Capability::protocol::<fecho::EchoMarker>()) |
| .from(&child) |
| .to(Ref::parent()), |
| ) |
| .await?; |
| child_realm |
| .add_route( |
| Route::new() |
| .capability(Capability::protocol_by_name("fuchsia.logger.LogSink")) |
| .from(Ref::parent()) |
| .to(&child), |
| ) |
| .await?; |
| let instance = builder.build().await?; |
| let echo_proxy = instance.root.connect_to_protocol_at_exposed_dir::<fecho::EchoMarker>()?; |
| assert_eq!( |
| Some(DEFAULT_ECHO_STR.to_string()), |
| echo_proxy.echo_string(Some(DEFAULT_ECHO_STR)).await?, |
| ); |
| Ok(()) |
| } |
| |
| #[fuchsia::test] |
| async fn local_component_route_to_parent_in_sub_realm() -> Result<(), Error> { |
| let builder = RealmBuilder::new().await?; |
| let (send_echo_server_called, mut receive_echo_server_called) = mpsc::channel(1); |
| let child_realm = builder.add_child_realm("child-realm", ChildOptions::new()).await?; |
| let child = child_realm |
| .add_local_child( |
| "child", |
| move |h| echo_server_mock(DEFAULT_ECHO_STR, send_echo_server_called.clone(), h).boxed(), |
| ChildOptions::new(), |
| ) |
| .await?; |
| builder |
| .add_route( |
| Route::new() |
| .capability(Capability::protocol::<fecho::EchoMarker>()) |
| .from(&child_realm) |
| .to(Ref::parent()), |
| ) |
| .await?; |
| builder |
| .add_route( |
| Route::new() |
| .capability(Capability::protocol_by_name("fuchsia.logger.LogSink")) |
| .from(Ref::parent()) |
| .to(&child_realm), |
| ) |
| .await?; |
| child_realm |
| .add_route( |
| Route::new() |
| .capability(Capability::protocol::<fecho::EchoMarker>()) |
| .from(&child) |
| .to(Ref::parent()), |
| ) |
| .await?; |
| child_realm |
| .add_route( |
| Route::new() |
| .capability(Capability::protocol_by_name("fuchsia.logger.LogSink")) |
| .from(Ref::parent()) |
| .to(&child), |
| ) |
| .await?; |
| let instance = builder.build().await?; |
| let echo_proxy = instance.root.connect_to_protocol_at_exposed_dir::<fecho::EchoMarker>()?; |
| assert_eq!( |
| Some(DEFAULT_ECHO_STR.to_string()), |
| echo_proxy.echo_string(Some(DEFAULT_ECHO_STR)).await?, |
| ); |
| assert!( |
| receive_echo_server_called.next().await.is_some(), |
| "failed to observe the mock server report a successful connection from a client" |
| ); |
| Ok(()) |
| } |
| |
| #[fuchsia::test] |
| async fn local_component_stop_notifier() -> Result<(), Error> { |
| let (send_stop_notifier_registered, mut receive_stop_notifier_registered) = mpsc::channel(1); |
| let (send_stop_notifier_called, mut receive_stop_notifier_called) = mpsc::channel(1); |
| |
| let builder = RealmBuilder::new().await?; |
| let _child = builder |
| .add_local_child( |
| "child", |
| move |handles| { |
| let mut send_stop_notifier_registered = send_stop_notifier_registered.clone(); |
| let mut send_stop_notifier_called = send_stop_notifier_called.clone(); |
| async move { |
| let stop_notifier = handles.register_stop_notifier().await; |
| send_stop_notifier_registered |
| .send(()) |
| .await |
| .expect("failed to send that the stop notifier was registered"); |
| stop_notifier.await.expect("failed to wait for stop notification"); |
| send_stop_notifier_called |
| .send(()) |
| .await |
| .expect("failed to send that the stop notifier was registered"); |
| Ok(()) |
| } |
| .boxed() |
| }, |
| ChildOptions::new().eager(), |
| ) |
| .await?; |
| let instance = builder.build().await?; |
| assert!( |
| receive_stop_notifier_registered.next().await.is_some(), |
| "failed to observe the local component register a stop notifier" |
| ); |
| drop(instance); |
| assert!( |
| receive_stop_notifier_called.next().await.is_some(), |
| "failed to observe the local component receive notice with the stop notifier" |
| ); |
| Ok(()) |
| } |
| |
| #[fuchsia::test] |
| async fn get_and_replace_realm_decl() -> Result<(), Error> { |
| let builder = RealmBuilder::new().await?; |
| let mut root_decl = builder.get_realm_decl().await?; |
| assert_eq!(root_decl, cm_rust::ComponentDecl::default()); |
| root_decl.children.push(cm_rust::ChildDecl { |
| name: "example-child".to_string(), |
| url: "example://url".to_string(), |
| startup: fcdecl::StartupMode::Eager, |
| on_terminate: None, |
| environment: None, |
| }); |
| builder.replace_realm_decl(root_decl.clone()).await?; |
| assert_eq!(root_decl, builder.get_realm_decl().await?); |
| Ok(()) |
| } |
| |
| #[fuchsia::test] |
| async fn get_and_replace_component_decl() -> Result<(), Error> { |
| let builder = RealmBuilder::new().await?; |
| let child = |
| builder.add_local_child("child", |_| pending().boxed(), ChildOptions::new()).await?; |
| let mut child_decl = builder.get_component_decl(&child).await?; |
| child_decl.children.push(cm_rust::ChildDecl { |
| name: "example-grand-child".to_string(), |
| url: "example://url".to_string(), |
| startup: fcdecl::StartupMode::Eager, |
| on_terminate: None, |
| environment: None, |
| }); |
| builder.replace_component_decl(&child, child_decl.clone()).await?; |
| assert_eq!(child_decl, builder.get_component_decl(&child).await?); |
| Ok(()) |
| } |
| |
| #[fuchsia::test] |
| async fn protocol_with_siblings_test() -> Result<(), Error> { |
| // [START mock_component_example] |
| // Create a new mpsc channel for passing a message from the echo server function |
| let (send_echo_server_called, mut receive_echo_server_called) = mpsc::channel(1); |
| |
| // Build a new realm |
| let builder = RealmBuilder::new().await?; |
| // Add the echo server, which is implemented by the echo_server_mock function (defined below). |
| // Give this function access to the channel created above, along with the mock component's |
| // handles |
| let child_a = builder |
| .add_local_child( |
| "a", |
| move |handles: LocalComponentHandles| { |
| echo_server_mock(DEFAULT_ECHO_STR, send_echo_server_called.clone(), handles).boxed() |
| }, |
| ChildOptions::new(), |
| ) |
| .await?; |
| // Add the echo client with a URL source |
| let child_b = builder |
| .add_child( |
| "b", |
| "fuchsia-pkg://fuchsia.com/fuchsia-component-test-tests#meta/echo_client.cm", |
| ChildOptions::new().eager(), |
| ) |
| .await?; |
| // Route the fidl.examples.routing.echo.Echo protocol from a to b |
| builder |
| .add_route( |
| Route::new() |
| .capability(Capability::protocol::<fecho::EchoMarker>()) |
| .from(&child_a) |
| .to(&child_b), |
| ) |
| .await?; |
| // Route the logsink to `b`, so it can inform us of any issues |
| builder |
| .add_route( |
| Route::new() |
| .capability(Capability::protocol_by_name("fuchsia.logger.LogSink")) |
| .from(Ref::parent()) |
| .to(&child_b), |
| ) |
| .await?; |
| |
| // Create and start the realm |
| let _instance = builder.build().await?; |
| |
| // Wait for the channel we created above to receive a message |
| assert!(receive_echo_server_called.next().await.is_some()); |
| // [END mock_component_example] |
| Ok(()) |
| } |
| |
| #[fuchsia::test] |
| async fn protocol_with_uncle_test() -> Result<(), Error> { |
| let (send_echo_server_called, mut receive_echo_server_called) = mpsc::channel(1); |
| |
| let builder = RealmBuilder::new().await?; |
| |
| let sub_realm = builder.add_child_realm("parent", ChildOptions::new().eager()).await?; |
| |
| let echo_server = builder |
| .add_local_child( |
| "echo-server", |
| move |handles| { |
| echo_server_mock(DEFAULT_ECHO_STR, send_echo_server_called.clone(), handles).boxed() |
| }, |
| ChildOptions::new(), |
| ) |
| .await?; |
| builder |
| .add_route( |
| Route::new() |
| .capability(Capability::protocol::<fecho::EchoMarker>()) |
| .from(&echo_server) |
| .to(&sub_realm), |
| ) |
| .await?; |
| builder |
| .add_route( |
| Route::new() |
| .capability(Capability::protocol_by_name("fuchsia.logger.LogSink")) |
| .from(Ref::parent()) |
| .to(&echo_server) |
| .to(&sub_realm), |
| ) |
| .await?; |
| |
| let echo_client = sub_realm |
| .add_child("echo-client", V2_ECHO_CLIENT_ABSOLUTE_URL, ChildOptions::new().eager()) |
| .await?; |
| sub_realm |
| .add_route( |
| Route::new() |
| .capability(Capability::protocol::<fecho::EchoMarker>()) |
| .capability(Capability::protocol_by_name("fuchsia.logger.LogSink")) |
| .from(Ref::parent()) |
| .to(&echo_client), |
| ) |
| .await?; |
| let _instance = builder.build().await?; |
| |
| assert!(receive_echo_server_called.next().await.is_some()); |
| Ok(()) |
| } |
| |
| #[fuchsia::test] |
| async fn examples() -> Result<(), Error> { |
| // This test exists purely to provide us with live snippets for the realm builder |
| // documentation |
| { |
| // [START add_a_and_b_example] |
| // Create a new RealmBuilder instance, which we will use to define a new realm |
| let builder = RealmBuilder::new().await?; |
| let child_a = builder |
| // Add component `a` to the realm, which will be fetched with a URL |
| .add_child("a", "fuchsia-pkg://fuchsia.com/foo#meta/foo.cm", ChildOptions::new()) |
| .await?; |
| // Add component `b` to the realm, which will be fetched with a URL |
| let child_b = builder |
| .add_child("b", "fuchsia-pkg://fuchsia.com/bar#meta/bar.cm", ChildOptions::new()) |
| .await?; |
| // [END add_a_and_b_example] |
| |
| // [START route_from_a_to_b_example] |
| // Add a new route for the protocol capability `fidl.examples.routing.echo.Echo` |
| // from `a` to `b` |
| builder |
| .add_route( |
| Route::new() |
| .capability(Capability::protocol::<fecho::EchoMarker>()) |
| .from(&child_a) |
| .to(&child_b), |
| ) |
| .await?; |
| // [END route_from_a_to_b_example] |
| } |
| { |
| let builder = RealmBuilder::new().await?; |
| let child_a = |
| builder.add_child("a", V2_ECHO_CLIENT_ABSOLUTE_URL, ChildOptions::new()).await?; |
| let child_b = |
| builder.add_child("b", V2_ECHO_CLIENT_ABSOLUTE_URL, ChildOptions::new()).await?; |
| // [START route_logsink_example] |
| // Routes `fuchsia.logger.LogSink` from above root to `a` and `b` |
| builder |
| .add_route( |
| Route::new() |
| .capability(Capability::protocol_by_name("fuchsia.logger.LogSink")) |
| .from(Ref::parent()) |
| .to(&child_a) |
| .to(&child_b), |
| ) |
| .await?; |
| // [END route_logsink_example] |
| } |
| { |
| let builder = RealmBuilder::new().await?; |
| let child_b = |
| builder.add_child("b", V2_ECHO_CLIENT_ABSOLUTE_URL, ChildOptions::new()).await?; |
| // [START route_to_above_root_example] |
| // Adds a route for the protocol capability |
| // `fidl.examples.routing.echo.EchoClientStats` from `b` to the realm's parent |
| builder |
| .add_route( |
| Route::new() |
| .capability(Capability::protocol_by_name( |
| "fidl.examples.routing.echo.EchoClientStats", |
| )) |
| .from(&child_b) |
| .to(Ref::parent()), |
| ) |
| .await?; |
| |
| // [START create_realm] |
| // Creates the realm, and add it to the collection to start its execution |
| let realm_instance = builder.build().await?; |
| // [END create_realm] |
| |
| // [START connect_to_protocol] |
| // Connects to `fidl.examples.routing.echo.EchoClientStats`, which is provided |
| // by `b` in the created realm |
| let echo_client_stats_proxy = |
| realm_instance.root.connect_to_protocol_at_exposed_dir::<EchoClientStatsMarker>()?; |
| // [END connect_to_protocol] |
| // [END route_to_above_root_example] |
| drop(echo_client_stats_proxy); |
| } |
| #[allow(unused, unused_mut)] |
| { |
| let builder = RealmBuilder::new().await?; |
| let child_realm_a = builder.add_child_realm("a", ChildOptions::new()).await?; |
| let child_b = |
| child_realm_a.add_child("b", V2_ECHO_CLIENT_ABSOLUTE_URL, ChildOptions::new()).await?; |
| |
| // [START mutate_generated_manifest_example] |
| let mut root_manifest = builder.get_realm_decl().await?; |
| // root_manifest is mutated in whatever way is needed |
| builder.replace_realm_decl(root_manifest).await?; |
| |
| let mut a_manifest = builder.get_component_decl(&child_realm_a).await.unwrap(); |
| // a_manifest is mutated in whatever way is needed |
| builder.replace_component_decl(&child_realm_a, a_manifest).await.unwrap(); |
| // [END mutate_generated_manifest_example] |
| } |
| Ok(()) |
| } |
| |
| // This test confirms that dynamic components in the built realm can use URLs that are relative to |
| // the test package (this is a special case the realm builder resolver needs to handle). |
| #[fuchsia::test] |
| async fn mock_component_with_a_relative_dynamic_child() -> Result<(), Error> { |
| let (send_echo_client_results, mut receive_echo_client_results) = mpsc::channel(1); |
| |
| let collection_name = "dynamic-children".to_string(); |
| let collection_name_for_mock = collection_name.clone(); |
| |
| let builder = RealmBuilder::new().await?; |
| let echo_client = builder |
| .add_local_child( |
| "echo-client", |
| move |handles| { |
| let collection_name_for_mock = collection_name_for_mock.clone(); |
| let mut send_echo_client_results = send_echo_client_results.clone(); |
| async move { |
| let realm_proxy = handles.connect_to_protocol::<fcomponent::RealmMarker>()?; |
| realm_proxy |
| .create_child( |
| &mut fcdecl::CollectionRef { name: collection_name_for_mock.clone() }, |
| fcdecl::Child { |
| name: Some("echo-server".to_string()), |
| url: Some(V2_ECHO_SERVER_RELATIVE_URL.to_string()), |
| startup: Some(fcdecl::StartupMode::Lazy), |
| environment: None, |
| on_terminate: None, |
| ..fcdecl::Child::EMPTY |
| }, |
| fcomponent::CreateChildArgs::EMPTY, |
| ) |
| .await? |
| .expect("failed to create child"); |
| let (exposed_dir_proxy, exposed_dir_server_end) = |
| fidl::endpoints::create_proxy()?; |
| realm_proxy |
| .open_exposed_dir( |
| &mut fcdecl::ChildRef { |
| name: "echo-server".to_string(), |
| collection: Some(collection_name_for_mock.clone()), |
| }, |
| exposed_dir_server_end, |
| ) |
| .await? |
| .expect("failed to open exposed dir"); |
| let echo_proxy = fuchsia_component::client::connect_to_protocol_at_dir_root::< |
| fecho::EchoMarker, |
| >(&exposed_dir_proxy)?; |
| let out = echo_proxy.echo_string(Some(DEFAULT_ECHO_STR)).await?; |
| if Some(DEFAULT_ECHO_STR.to_string()) != out { |
| return Err(format_err!("unexpected echo result: {:?}", out)); |
| } |
| send_echo_client_results.send(()).await.expect("failed to send results"); |
| Ok(()) |
| } |
| .boxed() |
| }, |
| ChildOptions::new().eager(), |
| ) |
| .await?; |
| let mut echo_client_decl = builder.get_component_decl(&echo_client).await?; |
| echo_client_decl.collections.push(cm_rust::CollectionDecl { |
| name: collection_name.clone(), |
| durability: fcdecl::Durability::Transient, |
| environment: None, |
| allowed_offers: cm_types::AllowedOffers::StaticOnly, |
| allow_long_names: false, |
| persistent_storage: None, |
| }); |
| echo_client_decl.capabilities.push(cm_rust::CapabilityDecl::Protocol(cm_rust::ProtocolDecl { |
| name: "fidl.examples.routing.echo.Echo".into(), |
| source_path: Some("/svc/fidl.examples.routing.echo.Echo".try_into().unwrap()), |
| })); |
| echo_client_decl.offers.push(cm_rust::OfferDecl::Protocol(cm_rust::OfferProtocolDecl { |
| source: cm_rust::OfferSource::Self_, |
| source_name: "fidl.examples.routing.echo.Echo".into(), |
| target: cm_rust::OfferTarget::Collection(collection_name.clone()), |
| target_name: "fidl.examples.routing.echo.Echo".into(), |
| dependency_type: cm_rust::DependencyType::Strong, |
| availability: cm_rust::Availability::Required, |
| })); |
| echo_client_decl.uses.push(cm_rust::UseDecl::Protocol(cm_rust::UseProtocolDecl { |
| source: cm_rust::UseSource::Framework, |
| source_name: "fuchsia.component.Realm".into(), |
| target_path: "/svc/fuchsia.component.Realm".try_into().unwrap(), |
| dependency_type: cm_rust::DependencyType::Strong, |
| availability: cm_rust::Availability::Required, |
| })); |
| builder.replace_component_decl(&echo_client, echo_client_decl).await?; |
| |
| let _instance = builder.build().await?; |
| |
| assert!(receive_echo_client_results.next().await.is_some()); |
| Ok(()) |
| } |
| |
| #[fuchsia::test] |
| async fn altered_echo_client_args() -> Result<(), Error> { |
| let (send_echo_server_called, mut receive_echo_server_called) = mpsc::channel(1); |
| |
| let builder = RealmBuilder::new().await?; |
| let echo_client = builder |
| .add_child("echo_client", V2_ECHO_CLIENT_RELATIVE_URL, ChildOptions::new().eager()) |
| .await?; |
| let echo_server = builder |
| .add_local_child( |
| "echo_server", |
| move |handles| { |
| echo_server_mock("Whales rule!", send_echo_server_called.clone(), handles).boxed() |
| }, |
| ChildOptions::new().eager(), |
| ) |
| .await?; |
| builder |
| .add_route( |
| Route::new() |
| .capability(Capability::protocol::<fecho::EchoMarker>()) |
| .from(&echo_server) |
| .to(&echo_client), |
| ) |
| .await?; |
| builder |
| .add_route( |
| Route::new() |
| .capability(Capability::protocol_by_name("fuchsia.logger.LogSink")) |
| .from(Ref::parent()) |
| .to(&echo_client), |
| ) |
| .await?; |
| |
| // Change the program.args section of the manifest, to alter the string it will try to echo |
| let mut echo_client_decl = builder.get_component_decl(&echo_client).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 rule!".to_string()]))); |
| } |
| } |
| builder.replace_component_decl(&echo_client, echo_client_decl).await?; |
| let _instance = builder.build().await?; |
| |
| assert!(receive_echo_server_called.next().await.is_some()); |
| |
| Ok(()) |
| } |
| |
| #[fuchsia::test] |
| async fn echo_client_structured_config() -> Result<(), Error> { |
| let (send_echo_server_called, mut receive_echo_server_called) = mpsc::channel(1); |
| |
| let builder = RealmBuilder::new().await?; |
| let echo_client = builder |
| .add_child( |
| "echo_client", |
| V2_ECHO_CLIENT_STRUCTURED_CONFIG_RELATIVE_URL, |
| ChildOptions::new().eager(), |
| ) |
| .await?; |
| let echo_server = builder |
| .add_local_child( |
| "echo_server", |
| move |handles| { |
| echo_server_mock( |
| "Hello Fuchsia!, Hi, There, false, 100", |
| send_echo_server_called.clone(), |
| handles, |
| ) |
| .boxed() |
| }, |
| ChildOptions::new(), |
| ) |
| .await?; |
| builder |
| .add_route( |
| Route::new() |
| .capability(Capability::protocol::<fecho::EchoMarker>()) |
| .from(&echo_server) |
| .to(&echo_client), |
| ) |
| .await?; |
| builder |
| .add_route( |
| Route::new() |
| .capability(Capability::protocol_by_name("fuchsia.logger.LogSink")) |
| .from(Ref::parent()) |
| .to(&echo_client), |
| ) |
| .await?; |
| |
| let _instance = builder.build().await?; |
| |
| assert!(receive_echo_server_called.next().await.is_some()); |
| |
| Ok(()) |
| } |
| |
| #[fuchsia::test] |
| async fn echo_client_structured_config_replace() -> Result<(), Error> { |
| let (send_echo_server_called, mut receive_echo_server_called) = mpsc::channel(1); |
| |
| let builder = RealmBuilder::new().await?; |
| let echo_client = builder |
| .add_child( |
| "echo_client", |
| V2_ECHO_CLIENT_STRUCTURED_CONFIG_RELATIVE_URL, |
| ChildOptions::new().eager(), |
| ) |
| .await?; |
| |
| let echo_server = builder |
| .add_local_child( |
| "echo_server", |
| move |handles| { |
| echo_server_mock( |
| "Foobar!, Hey, Folks, true, 42", |
| send_echo_server_called.clone(), |
| handles, |
| ) |
| .boxed() |
| }, |
| ChildOptions::new(), |
| ) |
| .await?; |
| |
| // fail to replace a config field in a component that doesn't have a config schema |
| assert_matches!( |
| builder.replace_config_value_bool(&echo_server, "echo_bool", false).await, |
| Err(RealmBuilderError::ServerError(ftest::RealmBuilderError::NoConfigSchema)) |
| ); |
| |
| // fail to replace a field that doesn't exist |
| assert_matches!( |
| builder.replace_config_value_string(&echo_client, "doesnt_exist", "test").await, |
| Err(RealmBuilderError::ServerError(ftest::RealmBuilderError::NoSuchConfigField)) |
| ); |
| |
| // fail to replace a field with the wrong type |
| assert_matches!( |
| builder.replace_config_value_string(&echo_client, "echo_bool", "test").await, |
| Err(RealmBuilderError::ServerError(ftest::RealmBuilderError::ConfigValueInvalid)) |
| ); |
| |
| // fail to replace a string that violates max_len |
| let long_string = String::from_utf8(vec![b'F'; 20]).unwrap(); |
| assert_matches!( |
| builder.replace_config_value_string(&echo_client, "echo_string", long_string.clone()).await, |
| Err(RealmBuilderError::ServerError(ftest::RealmBuilderError::ConfigValueInvalid)) |
| ); |
| |
| // fail to replace a vector whose string element violates max_len |
| assert_matches!( |
| builder |
| .replace_config_value_string_vector( |
| &echo_client, |
| "echo_string_vector", |
| vec![long_string] |
| ) |
| .await, |
| Err(RealmBuilderError::ServerError(ftest::RealmBuilderError::ConfigValueInvalid)) |
| ); |
| |
| // fail to replace a vector that violates max_count |
| assert_matches!( |
| builder |
| .replace_config_value_string_vector( |
| &echo_client, |
| "echo_string_vector", |
| vec!["a", "b", "c", "d"], |
| ) |
| .await, |
| Err(RealmBuilderError::ServerError(ftest::RealmBuilderError::ConfigValueInvalid)) |
| ); |
| |
| // succeed at replacing all fields with proper constraints |
| builder.replace_config_value_string(&echo_client, "echo_string", "Foobar!").await.unwrap(); |
| builder |
| .replace_config_value_string_vector(&echo_client, "echo_string_vector", ["Hey", "Folks"]) |
| .await |
| .unwrap(); |
| builder.replace_config_value_bool(&echo_client, "echo_bool", true).await.unwrap(); |
| builder.replace_config_value_uint64(&echo_client, "echo_num", 42).await.unwrap(); |
| builder |
| .add_route( |
| Route::new() |
| .capability(Capability::protocol::<fecho::EchoMarker>()) |
| .from(&echo_server) |
| .to(&echo_client), |
| ) |
| .await?; |
| builder |
| .add_route( |
| Route::new() |
| .capability(Capability::protocol_by_name("fuchsia.logger.LogSink")) |
| .from(Ref::parent()) |
| .to(&echo_client), |
| ) |
| .await?; |
| |
| let _instance = builder.build().await?; |
| |
| assert!(receive_echo_server_called.next().await.is_some()); |
| |
| Ok(()) |
| } |
| |
| async fn setup_echo_client_realm(builder: &RealmBuilder) -> Result<mpsc::Receiver<()>, Error> { |
| let (send_echo_server_called, receive_echo_server_called) = mpsc::channel(1); |
| let echo_server = builder |
| .add_local_child( |
| "echo-server", |
| move |h| echo_server_mock(DEFAULT_ECHO_STR, send_echo_server_called.clone(), h).boxed(), |
| ChildOptions::new(), |
| ) |
| .await?; |
| builder |
| .add_route( |
| Route::new() |
| .capability(Capability::protocol::<fecho::EchoMarker>()) |
| .from(&echo_server) |
| .to(Ref::child("echo-client")), |
| ) |
| .await?; |
| builder |
| .add_route( |
| Route::new() |
| .capability(Capability::protocol_by_name("fuchsia.logger.LogSink")) |
| .from(Ref::parent()) |
| .to(&echo_server) |
| .to(Ref::child("echo-client")), |
| ) |
| .await?; |
| Ok(receive_echo_server_called) |
| } |
| |
| #[fuchsia::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 client_name = "echo-client"; |
| let child_opts = ChildOptions::new().eager(); |
| { |
| let builder = RealmBuilder::new().await?; |
| builder.add_legacy_child(client_name, V1_ECHO_CLIENT_URL, child_opts.clone()).await?; |
| let mut receive_echo_server_called = setup_echo_client_realm(&builder).await?; |
| let realm_instance = builder.build().await?; |
| |
| assert!( |
| receive_echo_server_called.next().await.is_some(), |
| "failed to observe the mock server report a successful connection", |
| ); |
| |
| realm_instance.destroy().await?; |
| } |
| |
| { |
| let builder = RealmBuilder::new().await?; |
| builder.add_child(client_name, V2_ECHO_CLIENT_ABSOLUTE_URL, child_opts.clone()).await?; |
| let mut receive_echo_server_called = setup_echo_client_realm(&builder).await?; |
| let realm_instance = builder.build().await?; |
| |
| assert!( |
| receive_echo_server_called.next().await.is_some(), |
| "failed to observe the mock server report a successful connection", |
| ); |
| |
| realm_instance.destroy().await?; |
| } |
| |
| { |
| let builder = RealmBuilder::new().await?; |
| builder.add_child(client_name, V2_ECHO_CLIENT_RELATIVE_URL, child_opts.clone()).await?; |
| let mut receive_echo_server_called = setup_echo_client_realm(&builder).await?; |
| let realm_instance = builder.build().await?; |
| |
| assert!( |
| receive_echo_server_called.next().await.is_some(), |
| "failed to observe the mock server report a successful connection", |
| ); |
| |
| realm_instance.destroy().await?; |
| } |
| |
| { |
| let (send_echo_client_results, mut receive_echo_client_results) = mpsc::channel(1); |
| |
| let builder = RealmBuilder::new().await?; |
| builder |
| .add_local_child( |
| client_name, |
| move |h| echo_client_mock(send_echo_client_results.clone(), h).boxed(), |
| child_opts.clone(), |
| ) |
| .await?; |
| let mut receive_echo_server_called = setup_echo_client_realm(&builder).await?; |
| let realm_instance = builder.build().await?; |
| |
| assert!( |
| receive_echo_server_called.next().await.is_some(), |
| "failed to observe the mock server report a successful connection", |
| ); |
| |
| assert!( |
| receive_echo_client_results.next().await.is_some(), |
| "failed to observe the mock client report success" |
| ); |
| |
| realm_instance.destroy().await?; |
| } |
| |
| Ok(()) |
| } |
| |
| #[fuchsia::test] |
| async fn echo_clients_in_nested_component_manager() -> 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 builder = RealmBuilder::new().await?; |
| builder |
| .add_legacy_child("echo-client", V1_ECHO_CLIENT_URL, ChildOptions::new().eager()) |
| .await?; |
| |
| // assert_matches wants to pretty-print what went wrong if the assert fails, but neither |
| // sub-type in the returned Result here implements Debug. |
| match builder.build_in_nested_component_manager("#meta/component_manager.cm").await { |
| Err(RealmBuilderError::LegacyChildrenUnsupportedInNestedComponentManager) => (), |
| _ => panic!("legacy children should be unsupported in nested component managers"), |
| } |
| } |
| |
| { |
| let builder = RealmBuilder::new().await?; |
| builder |
| .add_child("echo-client", V2_ECHO_CLIENT_ABSOLUTE_URL, ChildOptions::new().eager()) |
| .await?; |
| let mut receive_echo_server_called = setup_echo_client_realm(&builder).await?; |
| let realm_instance = |
| builder.build_in_nested_component_manager("#meta/component_manager.cm").await?; |
| |
| assert!( |
| receive_echo_server_called.next().await.is_some(), |
| "failed to observe the mock server report a successful connection", |
| ); |
| |
| realm_instance.destroy().await?; |
| } |
| |
| { |
| let builder = RealmBuilder::new().await?; |
| builder |
| .add_child("echo-client", V2_ECHO_CLIENT_RELATIVE_URL, ChildOptions::new().eager()) |
| .await?; |
| let mut receive_echo_server_called = setup_echo_client_realm(&builder).await?; |
| let realm_instance = |
| builder.build_in_nested_component_manager("#meta/component_manager.cm").await?; |
| |
| assert!( |
| receive_echo_server_called.next().await.is_some(), |
| "failed to observe the mock server report a successful connection", |
| ); |
| |
| realm_instance.destroy().await?; |
| } |
| |
| { |
| let (send_echo_client_results, mut receive_echo_client_results) = mpsc::channel(1); |
| |
| let builder = RealmBuilder::new().await?; |
| builder |
| .add_local_child( |
| "echo-client", |
| move |h| echo_client_mock(send_echo_client_results.clone(), h).boxed(), |
| ChildOptions::new().eager(), |
| ) |
| .await?; |
| let mut receive_echo_server_called = setup_echo_client_realm(&builder).await?; |
| let realm_instance = |
| builder.build_in_nested_component_manager("#meta/component_manager.cm").await?; |
| |
| assert!( |
| receive_echo_server_called.next().await.is_some(), |
| "failed to observe the mock server report a successful connection", |
| ); |
| |
| assert!( |
| receive_echo_client_results.next().await.is_some(), |
| "failed to observe the mock client report success" |
| ); |
| |
| realm_instance.destroy().await?; |
| } |
| |
| Ok(()) |
| } |
| |
| async fn setup_echo_server_realm(builder: &RealmBuilder) -> Result<mpsc::Receiver<()>, Error> { |
| let (send_echo_client_results, receive_echo_client_results) = mpsc::channel(1); |
| let echo_client = builder |
| .add_local_child( |
| "echo-client", |
| move |h| echo_client_mock(send_echo_client_results.clone(), h).boxed(), |
| ChildOptions::new().eager(), |
| ) |
| .await?; |
| builder |
| .add_route( |
| Route::new() |
| .capability(Capability::protocol::<fecho::EchoMarker>()) |
| .from(Ref::child("echo-server")) |
| .to(&echo_client), |
| ) |
| .await?; |
| builder |
| .add_route( |
| Route::new() |
| .capability(Capability::protocol_by_name("fuchsia.logger.LogSink")) |
| .from(Ref::parent()) |
| .to(Ref::child("echo-server")) |
| .to(&echo_client), |
| ) |
| .await?; |
| Ok(receive_echo_client_results) |
| } |
| |
| #[fuchsia::test] |
| async fn echo_servers() -> Result<(), Error> { |
| let server_name = "echo-server"; |
| let child_opts = ChildOptions::new().eager(); |
| |
| { |
| let builder = RealmBuilder::new().await?; |
| builder.add_legacy_child(server_name, V1_ECHO_SERVER_URL, child_opts.clone()).await?; |
| let mut receive_echo_client_results = setup_echo_server_realm(&builder).await?; |
| let realm_instance = builder.build().await?; |
| |
| assert!( |
| receive_echo_client_results.next().await.is_some(), |
| "failed to observe the mock client report success", |
| ); |
| |
| realm_instance.destroy().await?; |
| } |
| |
| { |
| let builder = RealmBuilder::new().await?; |
| builder.add_child(server_name, V2_ECHO_SERVER_ABSOLUTE_URL, child_opts.clone()).await?; |
| let mut receive_echo_client_results = setup_echo_server_realm(&builder).await?; |
| let realm_instance = builder.build().await?; |
| |
| assert!( |
| receive_echo_client_results.next().await.is_some(), |
| "failed to observe the mock client report success", |
| ); |
| |
| realm_instance.destroy().await?; |
| } |
| |
| { |
| let builder = RealmBuilder::new().await?; |
| builder.add_child(server_name, V2_ECHO_SERVER_RELATIVE_URL, child_opts.clone()).await?; |
| let mut receive_echo_client_results = setup_echo_server_realm(&builder).await?; |
| let realm_instance = builder.build().await?; |
| |
| assert!( |
| receive_echo_client_results.next().await.is_some(), |
| "failed to observe the mock client report success", |
| ); |
| |
| realm_instance.destroy().await?; |
| } |
| |
| { |
| let (send_echo_server_called, mut receive_echo_server_called) = mpsc::channel(1); |
| |
| let builder = RealmBuilder::new().await?; |
| builder |
| .add_local_child( |
| server_name, |
| move |h| { |
| echo_server_mock(DEFAULT_ECHO_STR, send_echo_server_called.clone(), h).boxed() |
| }, |
| child_opts.clone(), |
| ) |
| .await?; |
| let mut receive_echo_client_results = setup_echo_server_realm(&builder).await?; |
| let realm_instance = builder.build().await?; |
| |
| assert!( |
| receive_echo_client_results.next().await.is_some(), |
| "failed to observe the mock client report success", |
| ); |
| |
| assert!( |
| receive_echo_server_called.next().await.is_some(), |
| "failed to observe the mock server report a successful connection from a client" |
| ); |
| |
| realm_instance.destroy().await?; |
| } |
| |
| Ok(()) |
| } |
| |
| #[fuchsia::test] |
| async fn route_required_fields_for_local_component() { |
| // This test confirms that certain fields are required when routing capabilities to or from a |
| // local component |
| |
| // Given a source, a target, and a flag signifying if either the source or a target are a local |
| // component, checks if the server will return an error for various scenarios in which a flag |
| // required when interfacing with local components is omitted. |
| async fn check_required_status(from: Ref, to: Ref, extra_fields_are_required: bool) { |
| let builder = RealmBuilder::new().await.unwrap(); |
| builder.add_child("non_local", "test://a", ChildOptions::new()).await.unwrap(); |
| builder |
| .add_local_child("local_1", |_| pending().boxed(), ChildOptions::new()) |
| .await |
| .unwrap(); |
| builder |
| .add_local_child("local_2", |_| pending().boxed(), ChildOptions::new()) |
| .await |
| .unwrap(); |
| |
| // Confirms that the result returned by the server matches our expectations |
| let assert_add_route_results = |results| match (extra_fields_are_required, results) { |
| ( |
| true, |
| Err(RealmBuilderError::ServerError(ftest::RealmBuilderError::CapabilityInvalid)), |
| ) => (), |
| (false, Ok(())) => (), |
| (true, Ok(())) => { |
| panic!("the server didn't return an error when a required field was missing") |
| } |
| (false, Err(e)) => { |
| panic!("the server returned an error when it should have succeeded: {:?}", e) |
| } |
| (true, Err(e)) => panic!("we were expecting an error, but not this one: {:?}", e), |
| }; |
| |
| // Routing a directory without the path specified |
| let res = builder |
| .add_route( |
| Route::new() |
| .capability(Capability::directory("2").rights(fio::RX_STAR_DIR)) |
| .from(from.clone()) |
| .to(to.clone()), |
| ) |
| .await; |
| assert_add_route_results(res); |
| |
| // Routing a directory without the rights specified |
| let res = builder |
| .add_route( |
| Route::new() |
| .capability(Capability::directory("2").path("/2")) |
| .from(from.clone()) |
| .to(to.clone()), |
| ) |
| .await; |
| assert_add_route_results(res); |
| |
| // Routing storage without the path specified |
| if to != Ref::parent() { |
| // Storage capabilities cannot be exposed |
| let res = builder |
| .add_route( |
| Route::new() |
| .capability(Capability::storage("data")) |
| .from(from.clone()) |
| .to(to.clone()), |
| ) |
| .await; |
| assert_add_route_results(res); |
| } |
| } |
| |
| let non_local_ref = Ref::child("non_local"); |
| let local_1_ref = Ref::child("local_1"); |
| let local_2_ref = Ref::child("local_2"); |
| |
| // These (from,to) tuples are scenarios where the additional, local-component-specific fields |
| // are _not_ required. |
| let fields_not_required_combinations = |
| vec![(non_local_ref.clone(), Ref::parent()), (Ref::parent(), non_local_ref.clone())]; |
| // These (from,to) tuples are scenarios where the additional, local-component-specific fields |
| // _are_ required. |
| let fields_required_combinations = vec![ |
| (Ref::parent(), local_1_ref.clone()), |
| (local_1_ref.clone(), non_local_ref.clone()), |
| (non_local_ref.clone(), local_1_ref.clone()), |
| (local_1_ref.clone(), local_2_ref.clone()), |
| (local_2_ref.clone(), Ref::parent()), |
| ]; |
| |
| for (from, to) in fields_not_required_combinations { |
| check_required_status(from, to, false).await; |
| } |
| for (from, to) in fields_required_combinations { |
| check_required_status(from, to, true).await; |
| } |
| } |
| |
| #[fuchsia::test] |
| async fn route_storage() -> Result<(), Error> { |
| let builder = RealmBuilder::new().await?; |
| let (send_storage_used, mut receive_storage_used) = mpsc::channel(1); |
| let storage_user = builder |
| .add_local_child( |
| "storage_user", |
| move |handles| { |
| let mut send_storage_used = send_storage_used.clone(); |
| async move { |
| let data_dir = handles.clone_from_namespace("data")?; |
| let example_file = fuchsia_fs::directory::open_file( |
| &data_dir, |
| "example_file", |
| fio::OpenFlags::RIGHT_READABLE |
| | fio::OpenFlags::RIGHT_WRITABLE |
| | fio::OpenFlags::CREATE, |
| ) |
| .await |
| .expect("failed to open example_file"); |
| let example_data = "example data"; |
| fuchsia_fs::write_file(&example_file, example_data).await?; |
| let _: Result<u64, i32> = example_file.seek(fio::SeekOrigin::Start, 0).await?; |
| let file_contents = fuchsia_fs::read_file(&example_file).await?; |
| assert_eq!(example_data, file_contents.as_str()); |
| send_storage_used.send(()).await.expect("failed to send results"); |
| Ok(()) |
| } |
| .boxed() |
| }, |
| ChildOptions::new().eager(), |
| ) |
| .await?; |
| builder |
| .add_route( |
| Route::new() |
| .capability(Capability::storage("data").path("/data")) |
| .from(Ref::parent()) |
| .to(&storage_user), |
| ) |
| .await?; |
| let _realm_instance = builder.build().await?; |
| assert!( |
| receive_storage_used.next().await.is_some(), |
| "failed to observe the local component report a successful usage of its storage" |
| ); |
| Ok(()) |
| } |
| |
| #[fuchsia::test] |
| async fn route_service() -> Result<(), Error> { |
| let builder = RealmBuilder::new().await?; |
| let service_provider = builder |
| .add_local_child( |
| "service_provider", |
| move |handles| { |
| async move { |
| let _ = &handles; |
| let mut fs = fserver::ServiceFs::new(); |
| fs.dir("svc").add_unified_service(|req: fex_services::BankAccountRequest| req); |
| fs.serve_connection(handles.outgoing_dir.into_channel())?; |
| fs.for_each_concurrent(None, move |request| async move { |
| match request { |
| fex_services::BankAccountRequest::ReadOnly(mut stream) => { |
| while let Some(request) = stream |
| .try_next() |
| .await |
| .expect("failed to get next read-only request") |
| { |
| match request { |
| fex_services::ReadOnlyAccountRequest::GetOwner { |
| responder, |
| } => { |
| responder |
| .send("hippos") |
| .expect("failed to send service response"); |
| } |
| _ => panic!("unexpected request"), |
| } |
| } |
| } |
| _ => panic!("unexpected request"), |
| } |
| }) |
| .await; |
| Err(format_err!("should not have exited on its own")) |
| } |
| .boxed() |
| }, |
| ChildOptions::new(), |
| ) |
| .await?; |
| let (send_service_used, mut receive_service_used) = mpsc::channel(1); |
| let service_user = builder |
| .add_local_child( |
| "service_user", |
| move |handles| { |
| let mut send_service_used = send_service_used.clone(); |
| async move { |
| let read_only_account = handles |
| .connect_to_service::<fex_services::BankAccountMarker>()? |
| .read_only()?; |
| let owner = read_only_account.get_owner().await?; |
| assert_eq!("hippos", owner.as_str()); |
| send_service_used.send(()).await.expect("failed to send results"); |
| Ok(()) |
| } |
| .boxed() |
| }, |
| ChildOptions::new().eager(), |
| ) |
| .await?; |
| builder |
| .add_route( |
| Route::new() |
| .capability(Capability::service::<fex_services::BankAccountMarker>()) |
| .from(&service_provider) |
| .to(&service_user), |
| ) |
| .await?; |
| let _realm_instance = builder.build().await?; |
| assert!( |
| receive_service_used.next().await.is_some(), |
| "failed to observe the local component report a successful usage of its service" |
| ); |
| Ok(()) |
| } |
| |
| #[fuchsia::test] |
| async fn fail_to_set_invalid_decls() -> Result<(), Error> { |
| let builder = RealmBuilder::new().await?; |
| |
| let child_a = builder.add_child("a", "test://a", ChildOptions::new()).await?; |
| let child_b = builder.add_local_child("b", |_| pending().boxed(), ChildOptions::new()).await?; |
| |
| // We cannot replace the decl for a child added with an absolute URL |
| assert_matches!( |
| builder.replace_component_decl(&child_a, cm_rust::ComponentDecl::default()).await, |
| Err(RealmBuilderError::ServerError(ftest::RealmBuilderError::ChildDeclNotVisible)) |
| ); |
| |
| // We cannot replace the decl for a local component with one missing the realm-builder |
| // specifics in the program section |
| assert_matches!( |
| builder.replace_component_decl(&child_b, cm_rust::ComponentDecl::default()).await, |
| Err(RealmBuilderError::ServerError(ftest::RealmBuilderError::ImmutableProgram)) |
| ); |
| |
| // We cannot replace the decl for a component with an invalid decl (references a non-existent |
| // child) |
| assert_matches!( |
| builder |
| .replace_component_decl( |
| &child_b, |
| cm_rust::ComponentDecl { |
| exposes: vec![cm_rust::ExposeDecl::Protocol(cm_rust::ExposeProtocolDecl { |
| source: cm_rust::ExposeSource::Child("does-not-exist".to_string()), |
| source_name: "fuchsia.examples.Echo".into(), |
| target: cm_rust::ExposeTarget::Parent, |
| target_name: "fuchsia.examples.Echo".into(), |
| })], |
| ..cm_rust::ComponentDecl::default() |
| } |
| ) |
| .await, |
| Err(RealmBuilderError::ServerError(ftest::RealmBuilderError::InvalidComponentDecl)) |
| ); |
| |
| // We cannot replace the realm's decl with an invalid decl (references a non-existent child) |
| assert_matches!( |
| builder |
| .replace_realm_decl(cm_rust::ComponentDecl { |
| exposes: vec![cm_rust::ExposeDecl::Protocol(cm_rust::ExposeProtocolDecl { |
| source: cm_rust::ExposeSource::Child("does-not-exist".to_string()), |
| source_name: "fuchsia.examples.Echo".into(), |
| target: cm_rust::ExposeTarget::Parent, |
| target_name: "fuchsia.examples.Echo".into(), |
| })], |
| ..cm_rust::ComponentDecl::default() |
| }) |
| .await, |
| Err(RealmBuilderError::ServerError(ftest::RealmBuilderError::InvalidComponentDecl)) |
| ); |
| |
| // We _can_ replace the realm's decl with a decl that references children added with the |
| // 'add_child' calls, and thus don't yet have a valid ChildDecl in the manifest. |
| let mut realm_decl = builder.get_realm_decl().await?; |
| realm_decl.exposes.push(cm_rust::ExposeDecl::Protocol(cm_rust::ExposeProtocolDecl { |
| source: cm_rust::ExposeSource::Child("b".to_string()), |
| source_name: "fuchsia.examples.Echo".into(), |
| target: cm_rust::ExposeTarget::Parent, |
| target_name: "fuchsia.examples.Echo".into(), |
| })); |
| assert_matches!(builder.replace_realm_decl(realm_decl).await, Ok(())); |
| |
| Ok(()) |
| } |
| |
| #[fuchsia::test] |
| async fn read_only_directory() -> Result<(), Error> { |
| let builder = RealmBuilder::new().await?; |
| |
| let local_component_impl = |mut send_file_contents: mpsc::Sender<String>, |
| handles: LocalComponentHandles| async move { |
| let config_dir = handles.clone_from_namespace("config").expect("failed to open /config"); |
| let config_file = fuchsia_fs::directory::open_file( |
| &config_dir, |
| "config.txt", |
| fuchsia_fs::OpenFlags::RIGHT_READABLE, |
| ) |
| .await |
| .expect("failed to open config.txt"); |
| let config_file_contents = |
| fuchsia_fs::read_file(&config_file).await.expect("failed to read config.txt"); |
| send_file_contents |
| .send(config_file_contents) |
| .await |
| .expect("failed to send config.txt contents"); |
| Ok(()) |
| }; |
| |
| let (send_a_file_contents, mut receive_a_file_contents) = mpsc::channel(1); |
| let child_a = builder |
| .add_local_child( |
| "a", |
| move |handles| local_component_impl(send_a_file_contents.clone(), handles).boxed(), |
| ChildOptions::new().eager(), |
| ) |
| .await?; |
| builder |
| .read_only_directory( |
| "config", |
| vec![&child_a], |
| DirectoryContents::new().add_file("config.txt", "a"), |
| ) |
| .await |
| .unwrap(); |
| |
| let (send_b_file_contents, mut receive_b_file_contents) = mpsc::channel(1); |
| let child_b = builder |
| .add_local_child( |
| "b", |
| move |handles| local_component_impl(send_b_file_contents.clone(), handles).boxed(), |
| ChildOptions::new().eager(), |
| ) |
| .await?; |
| builder |
| .read_only_directory( |
| "config", |
| vec![(&child_b).into(), Ref::parent()], |
| DirectoryContents::new().add_file("config.txt", "b"), |
| ) |
| .await |
| .unwrap(); |
| |
| let instance = builder.build().await?; |
| |
| assert_eq!(receive_a_file_contents.next().await, Some("a".to_string()),); |
| assert_eq!(receive_b_file_contents.next().await, Some("b".to_string()),); |
| |
| let exposed_dir = instance.root.get_exposed_dir(); |
| let config_file = fuchsia_fs::directory::open_file( |
| &exposed_dir, |
| "config/config.txt", |
| fuchsia_fs::OpenFlags::RIGHT_READABLE, |
| ) |
| .await |
| .expect("failed to open config.txt"); |
| let config_file_contents = |
| fuchsia_fs::read_file(&config_file).await.expect("failed to read config.txt"); |
| assert_eq!("b".to_string(), config_file_contents); |
| |
| Ok(()) |
| } |
| |
| #[fuchsia::test] |
| async fn from_relative_url() -> Result<(), Error> { |
| let builder = RealmBuilder::from_relative_url(ECHO_REALM_RELATIVE_URL).await?; |
| |
| let echo_client_decl_file = fuchsia_fs::open_file_in_namespace( |
| "/pkg/meta/echo_client.cm", |
| fuchsia_fs::OpenFlags::RIGHT_READABLE, |
| )?; |
| let echo_client_decl: fcdecl::Component = |
| fuchsia_fs::read_file_fidl(&echo_client_decl_file).await?; |
| |
| assert_eq!( |
| builder.get_component_decl("echo_client").await?, |
| echo_client_decl.fidl_into_native() |
| ); |
| |
| let echo_server_decl_file = fuchsia_fs::open_file_in_namespace( |
| "/pkg/meta/echo_server.cm", |
| fuchsia_fs::OpenFlags::RIGHT_READABLE, |
| )?; |
| let echo_server_decl: fcdecl::Component = |
| fuchsia_fs::read_file_fidl(&echo_server_decl_file).await?; |
| |
| assert_eq!( |
| builder.get_component_decl("echo_server").await?, |
| echo_server_decl.fidl_into_native() |
| ); |
| |
| let echo_realm_decl_file = fuchsia_fs::open_file_in_namespace( |
| "/pkg/meta/echo_realm.cm", |
| fuchsia_fs::OpenFlags::RIGHT_READABLE, |
| )?; |
| let mut echo_realm_decl: fcdecl::Component = |
| fuchsia_fs::read_file_fidl(&echo_realm_decl_file).await?; |
| |
| // The realm builder server removes these decls so it can manage them itself |
| echo_realm_decl.children = Some(vec![]); |
| |
| assert_eq!(builder.get_realm_decl().await?, echo_realm_decl.fidl_into_native()); |
| |
| Ok(()) |
| } |
| |
| #[fuchsia::test] |
| async fn from_relative_url_invalid_manifest() -> Result<(), Error> { |
| // The file referenced here is intentionally not a component manifest |
| assert_matches!( |
| RealmBuilder::from_relative_url("#data/component_manager_realm_builder_config").await, |
| Err(RealmBuilderError::ServerError(ftest::RealmBuilderError::DeclReadError)) |
| ); |
| |
| assert_matches!( |
| RealmBuilder::from_relative_url("#meta/does-not-exist.cm").await, |
| Err(RealmBuilderError::ServerError(ftest::RealmBuilderError::DeclNotFound)) |
| ); |
| |
| Ok(()) |
| } |
| |
| // [START echo_server_mock] |
| // 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: mpsc::Sender<()>, |
| handles: LocalComponentHandles, |
| ) -> Result<(), Error> { |
| // Create a new ServiceFs to host FIDL protocols from |
| let mut fs = fserver::ServiceFs::new(); |
| let mut tasks = vec![]; |
| |
| // Add the echo protocol to the ServiceFs |
| fs.dir("svc").add_fidl_service(move |mut stream: fecho::EchoRequestStream| { |
| let mut 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); |
| // Send the received string back to the client |
| responder.send(value.as_ref().map(|s| &**s)).expect("failed to send echo response"); |
| |
| // Use send_echo_server_called to report back that we successfully received a |
| // message and it aligned with our expectations |
| send_echo_server_called.send(()).await.expect("failed to send results"); |
| } |
| })); |
| }); |
| |
| // Run the ServiceFs on the outgoing directory handle from the mock handles |
| fs.serve_connection(handles.outgoing_dir.into_channel())?; |
| fs.collect::<()>().await; |
| Ok(()) |
| } |
| // [END echo_server_mock] |
| |
| async fn echo_client_mock( |
| mut send_echo_client_results: mpsc::Sender<()>, |
| handles: LocalComponentHandles, |
| ) -> Result<(), Error> { |
| let echo = handles.connect_to_protocol::<fecho::EchoMarker>()?; |
| let out = echo.echo_string(Some(DEFAULT_ECHO_STR)).await?; |
| if Some(DEFAULT_ECHO_STR.to_string()) != out { |
| return Err(format_err!("unexpected echo result: {:?}", out)); |
| } |
| send_echo_client_results.send(()).await.expect("failed to send results"); |
| Ok(()) |
| } |