blob: da94815ed85e8f6767865d322d367165bcf4e0c3 [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. For a comprehensive
list of features and supported languages, see the
[feature matrix at the end of this document](#language-feature-matrix).
## Add Dependencies {#add-deps}
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:
```json5
{% includecode gerrit_repo="fuchsia/fuchsia" gerrit_path="examples/components/realm_builder/rust/meta/sample_realm.cml" region_tag="include_shard_rust" adjust_indentation="auto" %}
```
Afterwards, you should add the GN dependency for your test's language:
* {Rust}
**Add the Rust Realm Builder library to your `BUILD.gn` file**
```gn
{% includecode gerrit_repo="fuchsia/fuchsia" gerrit_path="examples/components/realm_builder/rust/BUILD.gn" region_tag="realm_builder_dep_rust" adjust_indentation="auto" %}
```
* {C++}
**Add the C++ Realm Builder library to your `BUILD.gn` file**
```gn
{% includecode gerrit_repo="fuchsia/fuchsia" gerrit_path="examples/components/realm_builder/cpp/BUILD.gn" region_tag="realm_builder_dep_cpp" adjust_indentation="auto" %}
```
## Initialize Realm Builder {#init-realm}
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:
```rust
#[fuchsia::test]
async fn test() -> Result<(), Error> {
// ...
}
```
**Import Realm Builder library**
```rust
{% includecode gerrit_repo="fuchsia/fuchsia" gerrit_path="examples/components/realm_builder/rust/src/lib.rs" region_tag="import_statement_rust" adjust_indentation="auto" %}
```
**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.
```rust
{% includecode gerrit_repo="fuchsia/fuchsia" gerrit_path="examples/components/realm_builder/rust/src/lib.rs" region_tag="init_realm_builder_rust" adjust_indentation="auto" %}
```
* {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:
```cpp
#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**
```cpp
{% includecode gerrit_repo="fuchsia/fuchsia" gerrit_path="examples/components/realm_builder/cpp/sample.cc" region_tag="import_statement_cpp" adjust_indentation="auto" %}
```
**Use library namespace**
This step is optional. It imports the entire library's namespace,
for convenience when writing and reading tests.
```cpp
{% includecode gerrit_repo="fuchsia/fuchsia" gerrit_path="examples/components/realm_builder/cpp/sample.cc" region_tag="use_namespace_cpp" adjust_indentation="auto" %}
```
**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.
```cpp
{% includecode gerrit_repo="fuchsia/fuchsia" gerrit_path="examples/components/realm_builder/cpp/sample.cc" region_tag="init_realm_builder_cpp" adjust_indentation="auto" %}
```
## Construct Realm {#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`][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 `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 `#meta/bar.cm`
* {Rust}
```rust
{% includecode gerrit_repo="fuchsia/fuchsia" gerrit_path="examples/components/realm_builder/rust/src/lib.rs" region_tag="add_component_rust" adjust_indentation="auto" %}
```
* {C++}
```cpp
{% includecode gerrit_repo="fuchsia/fuchsia" gerrit_path="examples/components/realm_builder/cpp/sample.cc" region_tag="add_component_cpp" adjust_indentation="auto" %}
```
Note: Realm Builder interprets component sources defined using a relative URL
to be contained in the same package as the test component.
### Adding Legacy Components {#legacy-components}
Realm Builder also supports adding Legacy Components to your realm:
* {Rust}
```rust
{% includecode gerrit_repo="fuchsia/fuchsia" gerrit_path="examples/components/realm_builder/rust/src/lib.rs" region_tag="add_legacy_component_rust" adjust_indentation="auto" %}
```
* {C++}
```cpp
{% includecode gerrit_repo="fuchsia/fuchsia" gerrit_path="examples/components/realm_builder/cpp/sample.cc" region_tag="add_legacy_component_cpp" adjust_indentation="auto" %}
```
### Adding Mock Components {#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 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:
```rust
{% includecode gerrit_repo="fuchsia/fuchsia" gerrit_path="src/lib/fuchsia-component-test/src/mock.rs" region_tag="mock_interface_rust" adjust_indentation="auto" %}
```
`MockHandles` is a struct containing handles to the component's incoming
and outgoing capabilities:
```rust
{% includecode gerrit_repo="fuchsia/fuchsia" gerrit_path="src/lib/fuchsia-component-test/src/mock.rs" region_tag="mock_handles_rust" adjust_indentation="auto" %}
```
An implementation for a mock component would look like:
```rust
{% includecode gerrit_repo="fuchsia/fuchsia" gerrit_path="examples/components/realm_builder/rust/src/lib.rs" region_tag="mock_component_impl_rust" adjust_indentation="auto" %}
```
* {C++}
In C++, a mock component is implemented by creating a class that inherits from
MockComponent interface and overrides the `Start` method.
```cpp
{% includecode gerrit_repo="fuchsia/fuchsia" gerrit_path="sdk/lib/sys/component/cpp/testing/realm_builder_types.h" region_tag="mock_interface_cpp" adjust_indentation="auto" %}
```
`MockHandles` is a class containing handles to the component's incoming
and outgoing capabilities:
```cpp
{% includecode gerrit_repo="fuchsia/fuchsia" gerrit_path="sdk/lib/sys/component/cpp/testing/realm_builder_types.h" region_tag="mock_handles_cpp" adjust_indentation="auto" %}
```
An implementation for a mock component would look like:
```cpp
{% includecode gerrit_repo="fuchsia/fuchsia" gerrit_path="examples/components/realm_builder/cpp/sample.cc" region_tag="mock_component_impl_cpp" adjust_indentation="auto" %}
```
After your mock implementation is complete, you may add it your realm:
* {Rust}
```rust
{% includecode gerrit_repo="fuchsia/fuchsia" gerrit_path="examples/components/realm_builder/rust/src/lib.rs" region_tag="add_mock_component_rust" adjust_indentation="auto" %}
```
* {C++}
```cpp
{% includecode gerrit_repo="fuchsia/fuchsia" gerrit_path="examples/components/realm_builder/cpp/sample.cc" region_tag="add_mock_component_cpp" adjust_indentation="auto" %}
```
## Route Capabilities {#routing}
By default there are no [capability routes][cap-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 {#routing-between-children}
The following example adds a capability route to [offer][offer] components `a`,
`b`, and `c` the `fidl.examples.routing.echo.Echo` protocol from component `d`.
* {Rust}
```rust
{% includecode gerrit_repo="fuchsia/fuchsia" gerrit_path="examples/components/realm_builder/rust/src/lib.rs" region_tag="route_between_children_rust" adjust_indentation="auto" %}
```
* {C++}
```cpp
{% includecode gerrit_repo="fuchsia/fuchsia" gerrit_path="examples/components/realm_builder/cpp/sample.cc" region_tag="route_between_children_cpp" adjust_indentation="auto" %}
```
### Exposing realm capabilities {#routing-from-realm}
To route capabilities provided from inside the created realm to the test component,
set the target of the capability route using above root endpoint.
The created realm automatically [`exposes`][expose] 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}
```rust
{% includecode gerrit_repo="fuchsia/fuchsia" gerrit_path="examples/components/realm_builder/rust/src/lib.rs" region_tag="route_to_test_rust" adjust_indentation="auto" %}
```
* {C++}
```cpp
{% includecode gerrit_repo="fuchsia/fuchsia" gerrit_path="examples/components/realm_builder/cpp/sample.cc" region_tag="route_to_test_cpp" adjust_indentation="auto" %}
```
### Offering test capabilities {#routing-from-test}
To route capabilities from the test component to components inside the created
realm, set the source of the capability route using above root endpoint.
Consider the following example to make the `fuchsia.logger.LogSink` protocol from
the parent's realm available all the child components of the Realm.
The `fuchsia.logger.LogSink` protocol is offered by default to the created realm
through the [Realm Builder shard][realm-builder-shard].
* {Rust}
```rust
{% includecode gerrit_repo="fuchsia/fuchsia" gerrit_path="examples/components/realm_builder/rust/src/lib.rs" region_tag="route_from_test_rust" adjust_indentation="auto" %}
```
* {C++}
```cpp
{% includecode gerrit_repo="fuchsia/fuchsia" gerrit_path="examples/components/realm_builder/cpp/sample.cc" region_tag="route_to_test_cpp" adjust_indentation="auto" %}
```
This shard offers the following capabilities to the test component:
```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" %}
```
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" ],
},
],
...
}
```
Once routed to the parent component, it can be offered to child components
in the test realm:
* {Rust}
```rust
{% includecode gerrit_repo="fuchsia/fuchsia" gerrit_path="examples/components/realm_builder/rust/src/lib.rs" region_tag="route_from_test_sibling_rust" adjust_indentation="auto" %}
```
* {C++}
```cpp
{% includecode gerrit_repo="fuchsia/fuchsia" gerrit_path="examples/components/realm_builder/cpp/sample.cc" region_tag="route_from_test_sibling_cpp" adjust_indentation="auto" %}
```
## Creating the realm {#create-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}
```rust
{% includecode gerrit_repo="fuchsia/fuchsia" gerrit_path="examples/components/realm_builder/rust/src/lib.rs" region_tag="build_realm_rust" adjust_indentation="auto" %}
```
* {C++}
```cpp
{% includecode gerrit_repo="fuchsia/fuchsia" gerrit_path="examples/components/realm_builder/cpp/sample.cc" region_tag="build_realm_cpp" adjust_indentation="auto" %}
```
Note: The constructed realm instance is immutable. You cannot change components
or routes after calling the build method.
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 the above root endpoint are now accessible by the test.
* {Rust}
```rust
{% includecode gerrit_repo="fuchsia/fuchsia" gerrit_path="examples/components/realm_builder/rust/src/lib.rs" region_tag="call_echo_rust" adjust_indentation="auto" %}
```
* {C++}
```cpp
{% includecode gerrit_repo="fuchsia/fuchsia" gerrit_path="examples/components/realm_builder/cpp/sample.cc" region_tag="call_echo_cpp" adjust_indentation="auto" %}
```
When the realm object goes out of scope, Component Manager destroys the realm
and its children.
## Advanced Configuration {#advanced}
### Modifying generated manifests (Rust only) {#modifying-manifests}
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](#construct-realm):
1. Use the get decl method 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 method.
* {Rust}
```rust
let mut root_manifest = builder.get_decl(Moniker::root()).await?;
// root_manifest is mutated in whatever way is needed
builder.set_decl(Moniker::root(), root_manifest).await?;
let mut a_manifest = builder.get_decl("a").await?;
// a_manifest is mutated in whatever way is needed
builder.set_decl("a", a_manifest).await?;
```
When [adding routes](#routing) 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 Realm Builder 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`: 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](#construct-realm).
To obtain the child name invoke the following method on the constructed Realm:
* {Rust}
```rust
{% includecode gerrit_repo="fuchsia/fuchsia" gerrit_path="examples/components/realm_builder/rust/src/lib.rs" region_tag="get_child_name_rust" adjust_indentation="auto" %}
```
* {C++}
```cpp
{% includecode gerrit_repo="fuchsia/fuchsia" gerrit_path="examples/components/realm_builder/cpp/sample.cc" region_tag="get_child_name_cpp" adjust_indentation="auto" %}
```
## Troubleshooting {#troubleshoot}
### Invalid capability routes {#invalid-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 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 | C++ |
| --------------------------- |:----:|:----:|
| Legacy components | Y | Y |
| Mock components | Y | Y |
| Manipulating component decl | Y | 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