To migrate your test components, follow these steps:
- Migrate the test manifest
- Update test dependencies
- Migrate component features
- Verify the migrated tests
Find the GN build rules for the tests that exercise your component.
Typically this is a fuchsia_test_package()
or
fuchsia_unittest_package()
.
The preferred practice for tests declared with a fuchsia_unittest_package()
build rule is to use the generated manifest provided by
the Fuchsia build system.
To allow the GN target to generate your manifest, remove the manifest
attribute from the fuchsia_unittest_package()
:
fuchsia_unittest_package("my_component_tests") {
{{ '<strike>' }}manifest = "meta/my_component_test.cmx"{{ '</strike>' }}
deps = [ ":my_component_test" ]
}
Your test package is now able to execute using Components v2 and the Test Runner Framework.
Consider the following example test component manifest:
// my_component_test.cmx
{
"include": [
"syslog/client.shard.cmx"
],
"program": {
"binary": "bin/my_component_test"
}
}
To migrate this test to the Test Runner Framework, do the following:
-
Create a CML file that points to the test binary that includes the appropriate test runner:
Note: See the available test runners provided by the framework.
// my_component_test.cml { include: [ // Select the appropriate test runner shard here: // rust, gtest, go, etc. "//src/sys/test_runners/rust/default.shard.cml", // Enable system logging "syslog/client.shard.cml", ], program: { binary: "bin/my_component_test", } }
-
Locate the GN build rule for your test component referenced by the
fuchsia_test_package()
:fuchsia_component("my_component_test") { testonly = true manifest = "meta/my_component_test.cmx" deps = [ ":bin_test" ] } fuchsia_test_package("my_component_tests") { deps = [ ":my_component_test" ] }
-
Update your test component's build rule to reference the new CML file:
fuchsia_component("my_component_test") { testonly = true {{ '<strong>' }}manifest = "meta/my_component_test.cml"{{ '</strong>' }} deps = [ ":bin_test" ] } fuchsia_test_package("my_component_tests") { deps = [ ":my_component_test" ] }
A test may include or depend on components that are separate from the test component. Here are some things to look for:
- Does your test have a CMX with
fuchsia.test facets
, such asinjected-services
orsystem-services
? - Does your test create environments in-process? If so, does it create a separate environment for each test case?
Note: The Test Runner Framework executes tests within a realm that enforces hermetic component resolution, which means that test components must resolve dependencies from within their own package. For more details, see hermetic component resolution.
The migration procedure varies depending on the testing framework features in your v1 component:
- Test depends on system services: The test has a CMX that
contains
system-services
test facets. - Test depend on injected services: The test has a CMX that
contains
injected-services
test facets.
Note: For more details on the services and capabilities provided to components by the Test Runner Framework, see the test manager documentation.
For tests that use system-services
test facets, consider if
they can be converted to injected services instead.
Injecting services is the preferred method because it promotes hermetic test
behavior.
For certain non-hermetic tests, the Test Runner Framework provides the test realm with the following services:
Service | Description |
---|---|
fuchsia.scheduler.ProfileProvider |
Profile provider for scheduler |
fuchsia.sysmem.Allocator |
Allocates system memory buffers |
fuchsia.tracing.provider.Registry |
Register to trace provider |
fuchsia.vulkan.loader.Loader |
Vulkan library provider |
fuchsia.sys.Loader |
CFv1 loader service to help with |
: : migration. : | |
fuchsia.sys.Environment |
CFv1 environment service to help with |
: : migration. : |
Consider the following example test component that uses a single system service,
fuchsia.sysmem.Allocator
:
// my_component_test.cmx
{
"facets": {
"fuchsia.test": {
"system-services": [
"fuchsia.sysmem.Allocator"
]
}
},
"program": {
"binary": "bin/my_component_test"
},
"sandbox": {
"services": [
"fuchsia.sysmem.Allocator"
]
}
}
To migrate this test to the Test Runner Framework, declare each available system
service with the other required services in your test
component manifest. Since this test uses the fuchsia.sysmem.Allocator
system capability, it also needs to be marked with type: "system"
as shown
below.
// my_component_test.cml
{
include: [
// Select the appropriate test runner shard here:
// rust, gtest, go, etc.
"//src/sys/test_runners/rust/default.shard.cml",
],
program: {
binary: "bin/my_component_test",
},
{{ '<strong>' }}facets: {
"fuchsia.test": {
type: "system"
},
},
use: [
{
protocol: [ "fuchsia.sysmem.Allocator" ],
},
],{{ '</strong>' }}
}
For tests that use other fuchsia.test facets, such as
injected-services
, your test component manifest must declare each dependent
component and route the provided capabilities to the test component.
In the following example, suppose there's a single injected service,
fuchsia.pkg.FontResolver
:
// my_component_test.cmx
{
"facets": {
"fuchsia.test": {
"injected-services": {
"fuchsia.pkg.FontResolver":
"fuchsia-pkg://fuchsia.com/font_provider_test#meta/mock_font_resolver.cmx"
}
}
},
"program": {
"binary": "bin/my_component_test"
},
"sandbox": {
"services": [
"fuchsia.pkg.FontResolver"
]
}
}
To migrate this test to the Test Runner Framework, do the following:
-
Create a CML file for the test component that points to the test binary and includes the appropriate test runner:
Note: See test runners that are provided by the framework.
// my_component_test.cml (test component) { include: [ // Select the appropriate test runner shard here: // rust, gtest, go, etc. "//src/sys/test_runners/rust/default.shard.cml", ], program: { // Binary containing tests binary: "bin/font_provider_test", }, use: [ ... ], }
-
Ensure each component providing capabilities to this test has a CML manifest file. If this manifest does not already exist, consider creating it at this point. You can also temporarily wrap a legacy (CMX) provider component using the
cmx_runner
in your migrated test.-
{CML provider}
// mock_font_resolver.cml (capability provider) { program: { runner: "elf", binary: "bin/mock_font_resolver", }, use: [ // mock_font_resolver's dependencies. { protocol: [ "fuchsia.proto.SomeProtocol" ], }, ], capabilities: [ { protocol: [ "fuchsia.pkg.FontResolver" ], }, ], expose: [ { protocol: "fuchsia.pkg.FontResolver", from: "self", }, ], }
-
{CMX provider}
// mock_font_resolver.cml (capability provider) { include: [ // Use `cmx_runner` to wrap the component. "//src/sys/test_manager/cmx_runner/default.shard.cml", "syslog/client.shard.cml", ], program: { // wrap v1 component legacy_url: "fuchsia-pkg://fuchsia.com/font_provider_test#meta/mock_font_resolver.cmx", }, use: [ // if `mock_font_resolver.cmx` depends on some other protocol. { protocol: [ "fuchsia.proto.SomeProtocol" ], }, ], // expose capability provided by mock component. capabilities: [ { protocol: [ "fuchsia.pkg.FontResolver" ], }, ], expose: [ { protocol: "fuchsia.pkg.FontResolver", from: "self", }, ], }
Note: Component manifests wrapping a legacy component can only
use
protocol capabilities. The.cmx
file of the legacy component defines the remaining non-protocol capabilities (isolated-tmp
,/dev
, etc). These capabilities will come directly from the system and can't be mocked or forwarded from the test to legacy components.
Note: The CML files for the capability providers can be distributed in the same package that contained the v1 test. Follow the same instructions in Migrate the component manifest that you used to package your component.
-
-
Add the capability provider(s) as children of the test component, and route the capabilities from each provider.
-
{CML provider}
// my_component_test.cml (test component) { ... // Add capability providers children: [ { name: "font_resolver", url: "#meta/mock_font_resolver.cm", }, ], // Route capabilities to the test use: [ { protocol: [ "fuchsia.pkg.FontResolver" ], from: "#font_resolver", }, ], offer: [ { // offer dependencies to mock font provider. protocol: [ "fuchsia.proto.SomeProtocol" ], from: "#some_other_child", }, ], }
-
{CMX provider}
// my_component_test.cml (test component) { include: [ // Required for wrapped CMX components "sys/testing/hermetic-tier-2-test.shard.cml", ], ... // Add capability providers children: [ { name: "font_resolver", url: "#meta/mock_font_resolver.cm", }, ], // Route capabilities to the test use: [ { protocol: [ "fuchsia.pkg.FontResolver" ], from: "#font_resolver", }, ], offer: [ { // offer dependencies to mock font provider. protocol: [ "fuchsia.proto.SomeProtocol" ], from: "#some_other_child", }, ], }
-
-
Package the test component and capability provider(s) together into a single hermetic
fuchsia_test_package()
:-
{CML provider}
# Test component fuchsia_component("my_component_test") { testonly = true manifest = "meta/my_component_test.cml" deps = [ ":bin_test" ] } fuchsia_component("mock_font_resolver") { testonly = true manifest = "meta/mock_font_resolver.cml" deps = [ ":mock_font_resolver_bin" ] } # Hermetic test package fuchsia_test_package("my_component_tests") { test_components = [ ":my_component_test" ] deps = [ ":mock_font_resolver" ] }
-
{CMX provider}
# Test component fuchsia_component("my_component_test") { testonly = true manifest = "meta/my_component_test.cml" deps = [ ":bin_test" ] } fuchsia_component("mock_font_resolver") { testonly = true manifest = "meta/mock_font_resolver.cml" deps = [ {{ '<var label="legacy_component">"//path/to/legacy(v1)_component"</var>' }} ] } # Hermetic test package fuchsia_test_package("my_component_tests") { test_components = [ ":my_component_test" ] deps = [ ":mock_font_resolver" ] }
-
For more details on providing external capabilities to tests, see Integration testing topologies.
The legacy Component Framework provided a C++ library named
TestWithEnvironment
that allowed the construction of an isolated environment
within a test. It was often used to serve injected services that are either
implemented in-process or by other components in the test.
Consider migrating legacy tests relying on this functionality to Realm Builder.
Realm Builder creates isolated environments similar to those constructed by
TestWithEnvironment
. For more details on Realm Builder, see the
developer guide.
The remainder of this section covers migrating TestWithEnvironment
use cases
to Realm Builder.
Realm Builder does not provide its own test fixture, so tests that depend on
TextWithEnvironmentFixture
should migrate to a more generic test fixture,
such as gtest::RealLoopFixture
.
class RealmBuilderTest : public gtest::RealLoopFixture {};
During the setup phase of your test, use RealmBuilder::Create()
to initialize a
realm instance. After populating the realm with components and routes, call
Build()
to construct and start the realm instance.
TEST_F(RealmBuilderTest, RoutesProtocolFromChild) {
auto realm_builder = RealmBuilder::Create();
// Configure the realm.
// ...
auto realm = realm_builder.Build(dispatcher());
// Use the constructed realm to assert properties on the components
// under test.
// ...
}
When using TestWithEnvironment
, services are specified before creation of the
EnclosingEnvironment
. After the environment is created, the test may include
components using CreateComponent()
or CreateComponentFromUrl()
.
Consider the following example:
std::unique_ptr<EnvironmentServices> services = CreateServices();
// Add services to the environment
// ...
auto test_env = CreateNewEnclosingEnvironment("test_env", std::move(services));
// Create additional components in the environment
test_env_->CreateComponentFromUrl(
"fuchsia-pkg://fuchsia.com/example-package#meta/example.cmx");
Realm Bulder supports constructing realms that contain both legacy and CML components simultaneously. However, all components must be added to the realm before it is created. Once a realm is created its contents are immutable.
To add a CML component with Realm Builder, use AddChild()
:
realm_builder->AddChild("example_component", "#meta/example_component.cm");
To include a legacy component in the same realm, use AddLegacyChild()
:
realm_builder->AddLegacyChild(
"example_legacy_component",
"fuchsia-pkg://fuchsia.com/example-package#meta/example.cmx");
Realm Builder allows you to provide additional options for each new child
component. The following example marks the child as eager
when
adding it to the realm, indicating the component should start automatically with
its parent:
realm_builder->AddChild(
"example_eager_component",
"#meta/example_eager.cm",
ChildOptions{.startup_mode = StartupMode::EAGER});
When using TestWithEnvironment
, the EnclosedEnvironment
inherits all
services from the parent environment by default. Tests can configure additional
services in their nested environment using the EnvironmentServices
instance.
It is not necessary to route these services from the components providing them
to the test environment.
With Realm Builder, tests must explicitly route all capabilities between
the components in the realm and the parent using AddRoute()
.
The following example makes the fuchsia.logger.LogSink
protocol available
from the parent to example_component
and example_legacy_component
in the
realm:
realm_builder->AddRoute(
Route{.capabilities = {Protocol{"fuchsia.logger.LogSink"}},
.source = ParentRef(),
.targets = {
ChildRef{"example_component"},
ChildRef{"example_legacy_component"}}});
Note: All components should be added to the realm before adding routes.
To route additional capabilities between child components within the realm or
back to the parent, simply adjust the source
and target
properties.
// Route fuchsia.examples.Example from one child to another
realm_builder->AddRoute(
Route{.capabilities = {Protocol{"fuchsia.examples.Example"}},
.source = ChildRef{"example_component"},
.targets = {ChildRef{"example_legacy_component"}}});
//Route fuchsia.examples.Example2 up to the parent
realm_builder->AddRoute(
Route{.capabilities = {Protocol{"fuchsia.examples.Example2"}},
.source = ChildRef{"example_legacy_component"},
.targets = {ParentRef{}}});
The EnvironmentServices
connected to TestWithEnvironment
can be implemented
anywhere, including within the test component itself. The test runner framework
in Components v2 does not allow test components to offer capabilities they
implement directly to components in the test realm. Instead the test component
can create local components using Realm Builder.
Local components are implemented in-process by local objects. When these functions
are added to the realm under construction, they become a valid source
or
target
for capability routes. Once the realm is created, Realm Builder invokes
these functions as dedicated components.
The following example implements a mock for the fuchsia.example.Echo
protocol:
class LocalEchoServer : public test::placeholders::Echo, public LocalComponent {
public:
explicit LocalEchoServer(fit::closure quit_loop, async_dispatcher_t* dispatcher)
: quit_loop_(std::move(quit_loop)), dispatcher_(dispatcher), called_(false) {}
void EchoString(::fidl::StringPtr value, EchoStringCallback callback) override {
callback(std::move(value));
called_ = true;
quit_loop_();
}
void Start(std::unique_ptr<LocalComponentHandles> handles) override {
handles_ = std::move(handles);
ASSERT_EQ(handles_->outgoing()->AddPublicService(bindings_.GetHandler(this, dispatcher_)),
ZX_OK);
}
bool WasCalled() const { return called_; }
private:
fit::closure quit_loop_;
async_dispatcher_t* dispatcher_;
fidl::BindingSet<test::placeholders::Echo> bindings_;
bool called_;
std::unique_ptr<LocalComponentHandles> handles_;
};
You can use Realm Builder to instantiate this class as a local component to
provide fuchsia.example.Echo
to the realm and handle requests:
LocalEchoServer local_echo_server(QuitLoopClosure(), dispatcher());
realm_builder.AddLocalChild(kEchoServer, &local_echo_server);
Since the test has direct access to the local component object, you can inspect
it to determine state. In the above example, the test could assert the value of
LocalEchoServer::WasCalled()
to indicate whether the FIDL protocol method was
accessed.
Explore the following sections for additional migration guidance on specific features your test components may support:
Verify that your migrated tests are passing successfully using Components v2.
-
Build the target for your test package:
fx build
-
Verify your tests successfully pass with the test:
fx test my_component_tests
Note: If tools or scripts invoke your tests component using
fx shell run-test-component
, migrate this usage tofx shell run-test-suite
orffx test run
.
If your test doesn't run correctly or doesn't start at all, try following the advice in Troubleshooting test components.