blob: 949ee612e1d95ce5e391fb41b3bf121250411a71 [file] [log] [blame] [view]
# Realm builder
The realm builder library exists to facilitate integration testing of
components by allowing for the run-time construction of [realms][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. This document uses
Rust in its example code, but the concepts shown exist in all versions of the
library. For a comprehensive list of features and which languages they are
supported in, see the [feature matrix at the end of this
document](#language-feature-matrix).
## Set up realm builder {#set-up}
Add the [realm builder CML shard][realm-builder-shard] as an [`include`][shard-includes]
in the test's manifest:
```json5
{
include: [
"//src/lib/fuchsia-component-test/meta/fuchsia_component_test.shard.cml",
],
...
}
```
This shard declares a [component collection][collection] called
`fuchsia_component_test_collection` where the constructed realms run,
and [offers][offer] a set of default capabilities to those realms:
```json5
{% includecode gerrit_repo="fuchsia/fuchsia" gerrit_path="src/lib/fuchsia-component-test/meta/fuchsia_component_test.shard.cml" region_tag="collection_offers" adjust_indentation="auto" %}
```
## Construct the component topology {#construct-realm}
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.
Use the `RealmBuilder` instance to add child components to the realm with the
`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`][children] section of the component manifest.
1. **Component source:** Defines how the component is created when the realm is
built. For static components, this should be a `ComponentSource::url` with a
valid [component URL][component-urls]. This maps to the `url` attribute of
an instance listed in the [`children`][children] section of a component
manifest.
The example below adds two static child components to the created realm:
* Component `a` loads from `fuchsia-pkg://fuchsia.com/foo#meta/foo.cm`
* Component `b` loads from `fuchsia-pkg://fuchsia.com/bar#meta/bar.cm`
```rust
{% includecode gerrit_repo="fuchsia/fuchsia" gerrit_path="src/lib/fuchsia-component-test/tests/src/lib.rs" region_tag="add_a_and_b_example" adjust_indentation="auto" %}
```
This creates the following component topology:
```none
<root>
/ \
a b
```
Note: Realm builder interprets component sources defined using a relative URL
to be contained in the same package as the test controller.
### Adding a mock component {#mock-components}
Mock components allow tests to supply a local function that behaves as a
dedicated component.
Realm builder implements the protocols that enables the component framework to
treat the local function as a component and handle incoming FIDL connections.
The local function 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 serves the `fidl.examples.routing.echo.Echo` protocol
from a mock component that handles protocol requests from a local
`echo_server_mock()` function:
```rust
{% includecode gerrit_repo="fuchsia/fuchsia" gerrit_path="src/lib/fuchsia-component-test/tests/src/lib.rs" region_tag="mock_component_example" adjust_indentation="auto" %}
```
The `echo_server_mock()` creates a new `ServiceFs` to handle incoming FIDL
connections. When the `echo_string()` protocol function is called, the mock
sends a message to the test controller.
```rust
{% includecode gerrit_repo="fuchsia/fuchsia" gerrit_path="src/lib/fuchsia-component-test/tests/src/lib.rs" region_tag="echo_server_mock" adjust_indentation="auto" %}
```
## Add capability routes {#add-routes}
By default there are no [capability routes][cap-routes] in the created realm.
To route capabilities to components using `RealmBuilder`, call the `add_route()`
function with the appropriate `CapabilityRoute`.
The following example adds a `CapabilityRoute` to [offer][offer] component `b`
the `fidl.examples.routing.echo.Echo` protocol from component `a`.
```rust
{% includecode gerrit_repo="fuchsia/fuchsia" gerrit_path="src/lib/fuchsia-component-test/tests/src/lib.rs" region_tag="route_from_a_to_b_example" adjust_indentation="auto" %}
```
### Exposing realm capabilities {#routes-to-test}
To route capabilities provided from inside the created realm to the test controller,
set the target of the `CapabilityRoute` using `RouteEndpoint::above_root()`.
The created realm will automatically [`expose`][expose] the capability to its
parent. This allows the `RealmBuilder` instance to access the exposed capability.
The following example exposes a `fidl.examples.routing.echo.EchoClientStats`
protocol to the parent test component:
```rust
{% includecode gerrit_repo="fuchsia/fuchsia" gerrit_path="src/lib/fuchsia-component-test/tests/src/lib.rs" region_tag="route_to_above_root_example" adjust_indentation="auto" %}
```
### Offering external capabilities {#routes-from-outside}
To route capabilities from the test controller to components inside the created
realm, set the source of the `CapabilityRoute` using `RouteEndpoint::above_root()`.
Consider the following example to make the `fuchsia.logger.LogSink` protocol from
the parent's realm available to components `a` and `b`:
```rust
{% includecode gerrit_repo="fuchsia/fuchsia" gerrit_path="src/lib/fuchsia-component-test/tests/src/lib.rs" region_tag="route_logsink_example" adjust_indentation="auto" %}
```
The `fuchsia.logger.LogSink` protocol is offered by default to the created realm
through the [realm builder shard][realm-builder-shard].
To route a capability that isn't in the realm builder shard,
[offer][offer] it directly:
```json5
{
include: [
"//src/lib/fuchsia-component-test/meta/fuchsia_component_test.shard.cml",
],
children: [
{
name: "some-child",
url: "...",
},
],
offer: [
{
protocol: "fuchsia.example.Foo",
from: "#some-child",
to: [ "#fuchsia_component_test_collection" ],
},
],
...
}
```
## Creating the realm {#create-realm}
After you have added all the components and routes needed for the test case,
use `realm.create()` to create the realm and make its components ready to
execute.
```rust
{% includecode gerrit_repo="fuchsia/fuchsia" gerrit_path="src/lib/fuchsia-component-test/tests/src/lib.rs" region_tag="create_realm" adjust_indentation="auto" %}
```
Note: The constructed realm instance is immutable. You cannot change components
or routes after calling `create()`.
Use the `realm_instance` returned by `create()` to perform additional tasks.
Any eager components in the realm will immediately execute, and any
capabilities routed using `above_root` are now accessible by the test.
```rust
{% includecode gerrit_repo="fuchsia/fuchsia" gerrit_path="src/lib/fuchsia-component-test/tests/src/lib.rs" region_tag="connect_to_protocol" adjust_indentation="auto" %}
```
## Destroying the realm {#destroy-realm}
When the test no longer needs the realm, it can be destroyed by destroying the
realm instance returned by `create()`:
```rust
// As per rust semantics, this also happens when `realm_instance` goes out of
// scope.
drop(realm_instance);
```
This action instructs Component Manager to destroy the realm and all its
children.
## Advanced configuration
### Modifying generated manifests
For cases where the capability routing features supported by `add_route()` 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 controller.
After [constructing the realm](#construct-realm):
1. Use the `get_decl()` function of the constructed realm to obtain a specific
child's manifest.
1. Modify the appropriate manifest attributes.
1. Substitute the updated manifest for the component by calling the
`set_decl()` function.
See the following example:
```rust
{% includecode gerrit_repo="fuchsia/fuchsia" gerrit_path="src/lib/fuchsia-component-test/tests/src/lib.rs" region_tag="mutate_generated_manifest_example" adjust_indentation="auto" %}
```
When [adding routes](#add-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](#create-realm).
### Determining a moniker {#test-component-moniker}
The moniker for a `RealmBuilder` child component looks like the following:
```none
fuchsia_component_test_collection:{{ '<var>' }}child-name{{ '</var>' }}/{{ '<var>' }}component-name{{ '</var>' }}
```
The moniker consists of the following elements:
* `child-name`: Obtained by calling the `child_name()` function of the constructed realm.
* `component-name`: The "Component name" parameter provided to `add_component()` when
[constructing the realm](#construct-realm).
## Troubleshooting
### Invalid capability routes
The `add_route()` function cannot validate if a capability is properly offered
to the created realm from the test controller.
If you attempt to route capabilities with a source of `above_root` without a
corresponding [offer][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:0/test_manager:0/tests:auto-10238282593681900609:4/test_wrapper:0/test_root:0/fuchsia_component_test_
[86842.197][klog][I] collection:auto-4046836611955440668:16/echo-client:0`: An `offer from parent` declaration was found at `/core:0/test_manager:0/tests:auto-10238282593681900609:4/test_wrapper:0/test_root:0/fuchsia_component_test_colle
[86842.197][klog][I] ction:auto-4046836611955440668:16` 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 external capabilities](#routes-from-outside).
## Language feature matrix {#language-feature-matrix}
| | Rust |
| ------------------------ |:----:|
| Legacy components | N |
| Mock components | Y |
| Strong capability routes | Y |
| Weak capability routes | N |
| Custom environments | N |
| Setting subdirectories | N |
[cap-routes]: /docs/concepts/components/v2/component_manifests.md#capability-routing
[children]: /docs/concepts/components/v2/component_manifests.md#children
[collection]: /docs/concepts/components/v2/component_manifests.md#collections
[component-urls]: /docs/concepts/components/component_urls.md
[environment]: /docs/concepts/components/v2/component_manifests.md#environments
[expose]: /docs/concepts/components/v2/component_manifests.md#expose
[namespaces]: /docs/concepts/process/namespaces.md
[offer]: /docs/concepts/components/v2/component_manifests.md#offer
[realm-builder-shard]: /src/lib/fuchsia-component-test/meta/fuchsia_component_test.shard.cml
[realms]: /docs/concepts/components/v2/realms.md
[resolver]: /docs/concepts/components/v2/capabilities/resolvers.md
[runner]: /docs/concepts/components/v2/capabilities/runners.md
[shard-includes]: /docs/concepts/components/v2/component_manifests.md#include
[test-runner]: /docs/concepts/testing/v2/test_runner_framework.md#test-runners
[use]: /docs/concepts/components/v2/component_manifests.md#use