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") {
  {{ '<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.

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:

    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",
        }
    }
    
  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
      {{ '<strong>' }}manifest = "meta/my_component_test.cml"{{ '</strong>' }}
      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?

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:

Note: For more details on the services and capabilities provided to components by the Test Runner Framework, see the test manager documentation.

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:

ServiceDescription
fuchsia.scheduler.ProfileProviderProfile provider for scheduler
fuchsia.sysmem.AllocatorAllocates system memory buffers
fuchsia.tracing.provider.RegistryRegister to trace provider
fuchsia.vulkan.loader.LoaderVulkan library provider
fuchsia.sys.LoaderCFv1 loader service to help with
: : migration. :
fuchsia.sys.EnvironmentCFv1 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>' }}
}

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:

    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: [
            ...
        ],
    }
    
  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",
              },
          ],
      }
      

      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.

  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 = [ {{ '<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.

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

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

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
    

    Note: If tools or scripts invoke your tests component using fx shell run-test-component, migrate this usage to fx shell run-test-suite or ffx test run.

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