Honoring Asian Pacific American Heritage Month. See how.

Migrate test components

To migrate your test components, follow these steps:

  1. Migrate the test manifest
  2. Update test dependencies
  3. Migrate component features
  4. Verify the migrated tests

Migrate the test manifest

Find the GN build rules for the tests that exercise your component. Typically this is a fuchsia_test_package() or fuchsia_unittest_package().

Unit test packages

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") {
  manifest = "meta/my_component_test.cmx"
  deps = [ ":my_component_test" ]
}

Your test package is now able to execute using Components v2 and the Test Runner Framework.

Test packages

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:

  1. Create a CML file that points to the test binary that includes the appropriate test runner:

    // 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",
        }
    }
    
  2. 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" ]
    }
    
  3. Update your test component's build rule to reference the new CML file:

    fuchsia_component("my_component_test") {
      testonly = true
      manifest = "meta/my_component_test.cml"
      deps = [ ":bin_test" ]
    }
    
    fuchsia_test_package("my_component_tests") {
      deps = [ ":my_component_test" ]
    }
    

Update test dependencies

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 as injected-services or system-services?
  • Does your test create environments in-process? If so, does it create a separate environment for each test case?

The migration procedure varies depending on the testing framework features in your v1 component:

System service dependencies

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",
    },
    facets: {
        "fuchsia.test": {
            type: "system"
        },
    },
    use: [
        {
            protocol: [ "fuchsia.sysmem.Allocator" ],
        },
    ],
}

Injected service dependencies

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:

  1. Create a CML file for the test component that points to the test binary and includes the appropriate test runner:

    // 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: [
            ...
        ],
    }
    
  2. 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",
            },
        ],
    }
    
  3. 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",
            },
        ],
    }
    
  4. 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 = [ "//path/to/legacy(v1)_component" ]
    }
    
    # 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.

TestWithEnvironment

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.

Test setup

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.
      // ...
}

Add components to a realm

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});

Connect components together

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"}}});

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{}}});

Implement protocols

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.

Migrate component features

Explore the following sections for additional migration guidance on specific features your test components may support:

Verify the migrated tests

Verify that your migrated tests are passing successfully using Components v2.

  1. Build the target for your test package:

    fx build
    
  2. Verify your tests successfully pass with the test:

    fx test my_component_tests
    

If your test doesn't run correctly or doesn't start at all, try following the advice in Troubleshooting test components.