Honoring Asian Pacific American Heritage Month. See how.

Realm Builder

The Realm Builder library exists to facilitate integration testing of components by allowing for the run-time construction of realms and mocked components specific to individual test cases.

If a test wishes to launch a child component, then Realm Builder is likely a good fit for assisting the test.

If a test does not benefit from having either realms tailor made to each test case or realms containing mocked components unique to each test case, then the test can likely be made simpler to implement, understand, and maintain by using static component manifests. If a test does call for either (or both) of these things, then Realm Builder is a good fit for assisting the test.

The Realm Builder library is available in multiple languages, and the exact semantics and abilities available in each language may vary. For a comprehensive list of features and supported languages, see the feature matrix at the end of this document.

Add Dependencies

The Realm Builder client libraries rely on special capabilities to work. Therefore, tests using this library must include the necessary shard in their test component's manifest:

include: [
    "sys/component/realm_builder.shard.cml",

    // ...
],

Afterwards, you should add the GN dependency for your test's language:

Rust

Add the Rust Realm Builder library to your BUILD.gn file

deps = [
  "//src/lib/fuchsia-component-test",

  # ...
]

C++

Add the C++ Realm Builder library to your BUILD.gn file

deps = [
  "//sdk/lib/sys/component/cpp/testing:cpp",

  # ...
]

Dart

Add the Dart Realm Builder library to your BUILD.gn file

deps = [
  "//sdk/dart/fuchsia_component_test",

  # ...
]

Initialize Realm Builder

After adding the necessary dependencies, initialize Realm Builder inside your test component.

Rust

This section assumes that you are writing an asynchronous test and that some part of your component looks similar to this:

#[fuchsia::test]
async fn test() -> Result<(), Error> {
    // ...
}

Import Realm Builder library

use {
    // ...
    fuchsia_component_test::{
        Capability, ChildOptions, LocalComponentHandles, RealmBuilder, Ref, Route,
    },
};

Initialize RealmBuilder struct

Create a new RealmBuilder instance for each test case in your test. This creates a unique, isolated, child realm that ensures that the side-effects of one test case do not affect the others.

let builder = RealmBuilder::new().await?;

C++

This section assumes that you are writing an asynchronous test and that your testing is executing inside a message loop. Typically, such cases look like this:

#include <lib/async-loop/cpp/loop.h>
#include <lib/async-loop/cpp/default.h>

TEST(SampleTest, CallEcho) {
    async::Loop loop(&kAsyncLoopConfigAttachToCurrentThread);
    // Test code below
}

Import Realm Builder library

#include <lib/sys/component/cpp/testing/realm_builder.h>

Use library namespace This step is optional. It imports the entire library's namespace, for convenience when writing and reading tests.

// NOLINTNEXTLINE
using namespace component_testing;

Initialize Realm::Builder class

Create a new Realm::Builder instance for each test case in your test. This creates a unique, isolated, child realm that ensures that the side-effects of one test case do not affect the others.

auto builder = RealmBuilder::Create();

Dart

This section assumes that you are using the Dart test framework, and that some part of your component looks similar to this:

import 'package:test/test.dart';

void main() {
  // This test demonstrates constructing a realm with two child components
  // and verifying the `fidl.examples.routing.Echo` protocol.
  test('routes_from_echo', () async {
    // ...
  });
}

Import Realm Builder library

import 'package:fuchsia_component_test/realm_builder.dart';

Initialize RealmBuilder struct

Create a new RealmBuilder instance for each test case in your test. This creates a unique, isolated, child realm that ensures that the side-effects of one test case do not affect the others.

final builder = await RealmBuilder.create();

Construct Realm

With the constructed Realm Builder object for your target, you can now begin assembling the realm.

Use the Realm Builder instance to add child components to the realm with the language's add component function. Each child component requires the following:

  1. Component name: Unique identifier for the component inside the realm. For static components, this maps to the name attribute of an instance listed in the children section of the component manifest.
  2. Component source: Defines how the component is created when the realm is built. For static components, this should be a URL with a valid component URL. This maps to the url attribute of an instance listed in the children section of a component manifest.

The example below adds two static child components to the created realm:

  • Component echo_server loads from an absolute component URL
  • Component echo_client loads from a relative component URL

Rust

// Add component to the realm, which is fetched using a URL.
let echo_server = builder
    .add_child(
        "echo_server",
        "fuchsia-pkg://fuchsia.com/realm-builder-examples#meta/echo_server.cm",
        ChildOptions::new(),
    )
    .await?;
// Add component to the realm, which is fetched using a relative URL. The
// child is not exposing a service, so the `eager` option ensures the child
// starts when the realm is built.
let echo_client = builder
    .add_child("echo_client", "#meta/echo_client.cm", ChildOptions::new().eager())
    .await?;

C++

// Add component server to the realm, which is fetched using a URL.
builder.AddChild("echo_server",
                 "fuchsia-pkg://fuchsia.com/realm-builder-examples#meta/echo_server.cm");
// Add component to the realm, which is fetched using a relative URL. The
// child is not exposing a service, so the `EAGER` option ensures the child
// starts when the realm is built.
builder.AddChild("echo_client", "#meta/echo_client.cm",
                 ChildOptions{.startup_mode = StartupMode::EAGER});

Dart

// Add a server component to the realm, which is fetched using an
// absolute `fuchsia-pkg://` URL.
final echoServer = await builder.addChild(
  'echo_server',
  'fuchsia-pkg://fuchsia.com/realm-builder-examples#meta/echo_server.cm',
);

// Add a child component to the realm using a relative URL. The child is
// not exposing a service, so the `eager` option ensures the child starts
// when the realm is built.
final echoClient = await builder.addChild(
  'echo_client',
  '#meta/echo_client.cm',
  ChildOptions()..eager(),
);

Adding Legacy Components

Realm Builder also supports adding Legacy Components to your realm:

Rust

// Add component `c` to the realm, which is fetched using a legacy URL.
let echo_server = builder
    .add_legacy_child(
        "echo_server",
        "fuchsia-pkg://fuchsia.com/realm-builder-examples#meta/echo_server.cmx",
        ChildOptions::new(),
    )
    .await?;

C++

// Add component to the realm, which is fetched using a legacy URL.
builder.AddLegacyChild("echo_server",
                       "fuchsia-pkg://fuchsia.com/realm-builder-examples#meta/echo_server.cmx");

Dart

// Add component to the realm, which is fetched using a legacy URL.
final echoServer = await builder.addLegacyChild(
  'echo_server',
  'fuchsia-pkg://fuchsia.com/realm-builder-examples#meta/echo_server.cmx',
);

Adding Mock Components

Mock components allow tests to supply a local implementation that behaves as a dedicated component. Realm Builder implements the protocols that enables the component framework to treat the local implementation as a component and handle incoming FIDL connections. The local implementation can hold state specific to the test case where it is used, allowing each constructed realm to have a mock for its specific use case.

The following example demonstrates a mock component that implements the fidl.examples.routing.echo.Echo protocol.

First, you must implement your mock component.

Rust

In Rust, a mock component is implemented via function that has the following signature:

/// The implementation for a mock component. The contained function is called when the framework
/// asks the mock to run, and the function is given the component's handles for its namespace and
/// outgoing directory. The mock component may then use this handles to run a ServiceFs, access
/// capabilities as the mock, or perform other such actions.
#[derive(Clone)]
pub struct Mock(
    Arc<dyn Fn(MockHandles) -> BoxFuture<'static, Result<(), Error>> + Sync + Send + 'static>,
);

MockHandles is a struct containing handles to the component's incoming and outgoing capabilities:

/// The handles from the framework over which the mock should interact with other components.
pub struct MockHandles {
    namespace: HashMap<String, fio::DirectoryProxy>,

    /// The outgoing directory handle for a mock component. This can be used to run a ServiceFs for
    /// the mock.
    pub outgoing_dir: ServerEnd<fio::DirectoryMarker>,
}

An implementation for a mock component would look like:

async fn echo_server_mock(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| {
        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")
            {
                responder.send(value.as_ref().map(|s| &**s)).expect("failed to send echo response");
            }
        }));
    });

    // Run the ServiceFs on the outgoing directory handle from the mock handles
    fs.serve_connection(handles.outgoing_dir.into_channel())?;
    fs.collect::<()>().await;

    Ok(())
}

C++

In C++, a mock component is implemented by creating a class that inherits from LocalComponent interface and overrides the Start method.

// The interface for backing implementations of components with a Source of Mock.
class LocalComponent {
 public:
  virtual ~LocalComponent();

  // Invoked when the Component Manager issues a Start request to the component.
  // |mock_handles| contains the outgoing directory and namespace of
  // the component.
  virtual void Start(std::unique_ptr<LocalComponentHandles> mock_handles) = 0;
};

LocalComponentHandles is a class containing handles to the component's incoming and outgoing capabilities:

// Handles provided to mock component.
class LocalComponentHandles final {
 public:
  // ...

  // Returns the namespace provided to the mock component. The returned pointer
  // will be invalid once *this is destroyed.
  fdio_ns_t* ns();

  // Returns a wrapper around the component's outgoing directory. The mock
  // component may publish capabilities using the returned object. The returned
  // pointer will be invalid once *this is destroyed.
  sys::OutgoingDirectory* outgoing();

  // Convenience method to construct a ServiceDirectory by opening a handle to
  // "/svc" in the namespace object returned by `ns()`.
  sys::ServiceDirectory svc();

  // ...
};

An implementation for a mock component would look like:

class LocalEchoServerImpl : public fidl::examples::routing::echo::Echo, public LocalComponent {
 public:
  explicit LocalEchoServerImpl(async_dispatcher_t* dispatcher) : dispatcher_(dispatcher) {}

  // Override `EchoString` from `Echo` protocol.
  void EchoString(::fidl::StringPtr value, EchoStringCallback callback) override {
    callback(std::move(value));
  }

  // Override `Start` from `LocalComponent` class.
  void Start(std::unique_ptr<LocalComponentHandles> handles) override {
    // Keep reference to `handles` in member variable.
    // This class contains handles to the component's incoming
    // and outgoing capabilities.
    handles_ = std::move(handles);

    ASSERT_EQ(handles_->outgoing()->AddPublicService(bindings_.GetHandler(this, dispatcher_)),
              ZX_OK);
  }

 private:
  async_dispatcher_t* dispatcher_;
  fidl::BindingSet<fidl::examples::routing::echo::Echo> bindings_;
  std::unique_ptr<LocalComponentHandles> handles_;
};

Dart

In Dart, a mock component starts from an onRun function with the following signature:

typedef OnRun = Future<void> Function(
  LocalComponentHandles handles,
  Completer onStop,
);

When called, the function will typically expose one or more services with handlers, to bind a given interface request to an implementation of the corresponding service.

The onRun function can then await onStop.future to keep the component alive until stopped or destroyed. LocalComponentHandles is a class containing handles to the component's controller, and its incoming and outgoing capabilities:

class LocalComponentHandles {
  final fcrunner.ComponentControllerBinding controllerBinding;

  final namespaceDirs = <String, fio.DirectoryProxy>{};

  /// The outgoing directory handle for a local component. This can be used to
  /// run a [ServiceFs] for the component.
  final fidl.InterfaceRequest<fio.Directory> outgoingDir;

An implementation for a mock component would look like:

class EchoServerMock extends fecho.Echo {
  final LocalComponentHandles handles;

  final echoBinding = fecho.EchoBinding();

  EchoServerMock(this.handles) {
    // Serve the provided outgoing directory for the mock
    services.Outgoing()
      ..serve(
          fidl.InterfaceRequest<fio.Node>(handles.outgoingDir.passChannel()!))

      // Expose the Echo protocol as a public service
      ..addPublicService(
        (fidl.InterfaceRequest<fecho.Echo> connector) {
          echoBinding.bind(this, connector);
        },
        fecho.Echo.$serviceName,
      );
  }

  @override
  Future<String?> echoString(String? str) async {
    return str;
  }
}

After your mock implementation is complete, you may add it your realm:

Rust

let echo_server = builder
    .add_local_child(
        "echo_server",
        move |handles: LocalComponentHandles| Box::pin(echo_server_mock(handles)),
        ChildOptions::new(),
    )
    .await?;

C++

auto mock_echo_server = LocalEchoServerImpl(dispatcher());
// Add component to the realm, providing a mock implementation
builder.AddLocalChild("echo_server", &mock_echo_server);

Dart

final echoServer = await builder.addLocalChild(
  'echo_server',
  onRun: (handles, onStop) async {
    EchoServerMock(handles);

    // Keep the component alive until the test is complete
    await onStop.future;
  },
);

Route Capabilities

By default there are no capability routes in the created realm. To route capabilities to components using Realm Builder, call the add route function with the appropriate capability route.

Routing between child components

The following example adds a capability route to offer component echo_client the fidl.examples.routing.echo.Echo protocol from component echo_server.

Rust

builder
    .add_route(
        Route::new()
            .capability(Capability::protocol_by_name("fidl.examples.routing.echo.Echo"))
            .from(&echo_server)
            .to(&echo_client),
    )
    .await?;

C++

builder.AddRoute(Route{.capabilities = {Protocol{"fidl.examples.routing.echo.Echo"}},
                       .source = ChildRef{"echo_server"},
                       .targets = {ChildRef{"echo_client"}}});

Dart

await builder.addRoute(Route()
  ..capability(ProtocolCapability(fecho.Echo.$serviceName))
  ..from(Ref.child(echoServer))
  ..to(Ref.child(echoClient)));

Exposing realm capabilities

To route capabilities provided from inside the created realm to the test component, set the target of the capability route to parent. The created realm automatically exposes the capability to its parent. This allows the Realm Builder instance to access the exposed capability.

The following example exposes a fidl.examples.routing.echo.Echo protocol to the parent test component:

Rust

builder
    .add_route(
        Route::new()
            .capability(Capability::protocol_by_name("fidl.examples.routing.echo.Echo"))
            .from(&echo_server)
            .to(Ref::parent()),
    )
    .await?;

C++

builder.AddRoute(Route{.capabilities = {Protocol{"fidl.examples.routing.echo.Echo"}},
                       .source = ChildRef{"echo_server"},
                       .targets = {ParentRef()}});

Dart

await builder.addRoute(Route()
  ..capability(ProtocolCapability(fecho.Echo.$serviceName))
  ..from(Ref.child(echoServer))
  ..to(Ref.parent()));

Offering test capabilities

To route capabilities from the test component to components inside the created realm, set the source of the capability route to parent. This includes the capabilities provided to tests by the Realm Builder shard:

{
    protocol: [
        "fuchsia.diagnostics.ArchiveAccessor",
        "fuchsia.logger.LogSink",
        "fuchsia.sys2.EventSource",
    ],
    from: "parent",
    to: [ "#realm_builder" ],
},

Consider the following example to route the fuchsia.logger.LogSink protocol from the test component to the child components of the realm:

Rust

builder
    .add_route(
        Route::new()
            .capability(Capability::protocol_by_name("fuchsia.logger.LogSink"))
            .from(Ref::parent())
            .to(&echo_server)
            .to(&echo_client),
    )
    .await?;

C++

builder.AddRoute(Route{.capabilities = {Protocol{"fuchsia.logger.LogSink"}},
                       .source = ParentRef(),
                       .targets = {ChildRef{"echo_server"}, ChildRef{"echo_client"}}});

Dart

await builder.addRoute(Route()
  ..capability(ProtocolCapability(flogger.LogSink.$serviceName))
  ..from(Ref.parent())
  ..to(Ref.child(echoServer))
  ..to(Ref.child(echoClient)));

Creating the realm

After you have added all the components and routes needed for the test case, use build method to create the realm and make its components ready to execute.

Rust

let realm = builder.build().await?;

C++

auto realm = builder.Build(dispatcher());

Dart

realm = await builder.build();

Use the realm returned by the build method to perform additional tasks. Any eager components in the realm execute immediately, and any capabilities routed using parent are now accessible by the test.

Rust

let echo = realm.root.connect_to_protocol_at_exposed_dir::<fecho::EchoMarker>()?;
assert_eq!(echo.echo_string(Some("hello")).await?, Some("hello".to_owned()));

When the realm object goes out of scope, Component Manager destroys the realm and its children.

C++

auto echo = realm.ConnectSync<fidl::examples::routing::echo::Echo>();
fidl::StringPtr response;
echo->EchoString("hello", &response);
ASSERT_EQ(response, "hello");

When the realm object goes out of scope, Component Manager destroys the realm and its children.

Dart

final echo = realm.root.connectToProtocolAtExposedDir(fecho.EchoProxy());
expect(await echo.echoString('hello'), 'hello');

To ensure Component Manager destroys the realm and its children when the realm object is no longer needed, call close() on the realm root.

} finally {
  if (realm != null) {
    realm.root.close();
  }
}

Advanced Configuration

Modifying generated manifests (Rust and Dart only)

For cases where the capability routing features supported by the add route method are not sufficient, you can manually adjust the manifest declarations. Realm Builder supports this for the following component types:

  • Mock components created by Realm Builder.
  • URL components contained in the same package as the test component.

After constructing the realm:

  1. Use the get decl method of the constructed realm to obtain a specific child's manifest.
  2. Modify the appropriate manifest attributes.
  3. Substitute the updated manifest for the component by calling the replace decl method.

Rust

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("a").await?;
// a_manifest is mutated in whatever way is needed
builder.replace_component_decl("a", a_manifest).await?;

Dart

var rootManifest = await builder.getRealmDecl();
// ...
// Clone and modify the rootManifest as needed, for example, after updating
// the `children` list:
rootManifest = rootManifest.$cloneWith(children: fidl.Some(children));
await builder.replaceRealmDecl(rootManifest);

var aManifest = await builder.getComponentDecl("a");
// ...
// Clone and modify the aManifest as needed, for example, after updating
// exposed capabilities:
aManifest = aManifest.$cloneWith(exposes: fidl.Some(exposes));
await builder.replaceComponentDecl("a", aManifest);

When adding routes for modified components, add them directly to the constructed realm where you obtained the manifest instead of using the builder instance. This ensures the routes are properly validated against the modified component when the realm is created.

Determining a moniker

The moniker for a Realm Builder child component looks like the following:

fuchsia_component_test_collection:child-name/component-name

The moniker consists of the following elements:

  • child-name: An auto-generated name for the realm's collection, created by the Realm Builder library. Obtained by calling the child_name() function of the constructed realm.
  • component-name: The "Component name" parameter provided to the Add Component component when constructing the realm.

To obtain the child name invoke the following method on the constructed Realm:

Rust

println!("Child Name: {}", realm.root.child_name());

C++

std::cout << "Child Name: " << realm.GetChildName() << std::endl;

Dart

print('Child Name: ${realm.root.childName}');

Troubleshooting

Invalid capability routes

The add route function cannot validate if a capability is properly offered to the created realm from the test component.

If you attempt to route capabilities with a source of parent without a corresponding offer, requests to open the capability will not resolve and you will see error messages similar to the following:

[86842.196][klog][E] [component_manager] ERROR: Failed to route protocol `fidl.examples.routing.echo.Echo` with target component `/core/test_manager/tests:auto-10238282593681900609/test_wrapper/test_root/fuchsia_component_test_
[86842.197][klog][I] collection:auto-4046836611955440668/echo-client`: An `offer from parent` declaration was found at `/core/test_manager/tests:auto-10238282593681900609/test_wrapper/test_root/fuchsia_component_test_colle
[86842.197][klog][I] ction:auto-4046836611955440668` for `fidl.examples.routing.echo.Echo`, but no matching `offer` declaration was found in the parent

For more information on how to properly offer capabilities from the test controller, see offering test capabilities.

Language feature matrix

Rust C++ Dart
Legacy components Y Y Y
Mock components Y Y Y
Overriding config values Y Y Y
Manipulating component decl Y N Y