| <!-- Copyright 2026 The Fuchsia Authors. All rights reserved. |
| Use of this source code is governed by a BSD-style license that can be |
| found in the LICENSE file. --> |
| |
| # TRF Codegen: Declarative Integration Testing Framework |
| |
| The TRF (Test Realm Factory) Codegen suite automates the boilerplate |
| involved in setting up integration tests using the Realm Factory pattern |
| in Fuchsia. It aims to generate typed Rust harnesses that eliminate |
| manual FIDL routing overhead, providing developers with a streamlined, |
| ergonomic testing experience. |
| |
| It eliminates boilerplate in component integration tests by automatically |
| parsing Rust test definitions at build time to generate: |
| - **FIDL Mock Control Libraries** (`fuchsia.trf.mockcontrol`) for |
| inspecting and manipulating mock component state. |
| - **Injectable Universe (IU) Mock Host Components** serving |
| protocol mocks with isolated state. |
| - **Component Manifests (`.cml`)** for the Test Root, Test Driver, |
| and Injectable Universe components. |
| - **Capability Topology Routing** linking Component Under Test (CUT), |
| mock components, and the test driver automatically. |
| |
| ## Component Architecture |
| |
| This directory is the root of the TRF Codegen infrastructure. Currently, |
| it holds foundational dependencies that the generated code relies upon: |
| |
| - **`fidl/`**: Contains the protocol definitions (e.g., |
| `fuchsia.trf.factory`) that define the contract for configuring and |
| launching a test realm dynamically via a factory component. |
| - **`lib/runtime/`**: Provides the runtime Rust library |
| (`trf_codegen_runtime`). Generated tests use this library's |
| capabilities (such as `TestRealm`) as a common, high-level harness |
| wrapper to manage component capabilities, connect to mock controls, |
| and stream lifecycle events. |
| |
| --- |
| |
| ## User's Guide |
| |
| ### 1. Define the GN Build Target |
| |
| Import `//src/testing/trf_codegen/trf_test.gni` in your `BUILD.gn` file |
| and define a `trf_test` target specifying the Rust test source file and |
| the target Component Under Test (CUT) dependency. |
| |
| ```gn |
| import("//src/testing/trf_codegen/trf_test.gni") |
| |
| if (is_fuchsia) { |
| trf_test("reverser_trf_integration_test") { |
| source = "src/reverser_trf_tester.rs" |
| cut = [ "//examples/components/reverser" ] |
| |
| # Optional: List of source files containing TRF mock implementations |
| mocks = [ "src/mocks.rs" ] |
| |
| # Optional: Archive test in the Compatibility Test Framework (CTF) |
| # preserving it for backwards compatibility testing on branch cuts |
| is_ctf = true |
| } |
| } |
| ``` |
| |
| ### 2. Write the Integration Test |
| |
| In your Rust test source file, import the TRF macros and runtime harness. |
| Annotate test entry points with `#[trf_test]`: |
| |
| ```rust |
| // Copyright 2026 The Fuchsia Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| use anyhow::Error; |
| use fidl_fuchsia_examples_reverser as freverser; |
| use trf_codegen_macro::trf_test; |
| // Note: The `TestRealm` argument type is mapped to a generated wrapper struct. |
| |
| #[trf_test(config(switch_case = false, use_replacer = true), mocks(MockReplacer))] |
| pub async fn test_reverser_trf_mock(realm: &TestRealm) -> Result<(), Error> { |
| // Call control proxy methods directly on the realm |
| // (Generated only if `mocks(MockReplacer)` is specified) |
| realm.set_replacement("Hello".to_string(), "Greetings".to_string()).await?; |
| |
| // Connect to protocols exposed by the Component Under Test (CUT) |
| let client = realm.connect_to_protocol::<freverser::ReverserMarker>()?; |
| |
| let input = "Hello Fuchsia TRF!"; |
| let output = client.reverse(input).await?; |
| assert_eq!(output, "!FRT aishcuF sgniteerG"); |
| |
| Ok(()) |
| } |
| ``` |
| |
| ### 3. Define Protocol Mocks (Optional) |
| |
| If your Component Under Test relies on external protocols, you can author mock implementations to satisfy them. Mocks are placed in dedicated Rust source files (e.g. `src/mocks.rs`) and passed via the `mocks` argument of the `trf_test` GN template. Use `#[trf::mock]` to implement the mocked protocol, and `#[trf::control]` to define a control channel for the test driver to manipulate the mock's local state: |
| |
| ```rust |
| use fidl_fuchsia_examples_reverser as freverser; |
| use futures::StreamExt; |
| use std::sync::Mutex; |
| use trf_codegen_macro as trf; |
| |
| #[trf::mock(protocol = "fuchsia.examples.reverser.Replacer")] |
| pub struct MockReplacer { |
| // Internal mock state must be safely shared across concurrent requests |
| from: Mutex<String>, |
| to: Mutex<String>, |
| } |
| |
| impl MockReplacer { |
| pub fn new() -> Self { |
| Self { from: Mutex::new(String::new()), to: Mutex::new(String::new()) } |
| } |
| |
| // 1. Define the control interface for the Test Driver to drive the mock |
| #[trf::control] |
| pub fn set_replacement(&self, from: String, to: String) { |
| *self.from.lock().unwrap() = from; |
| *self.to.lock().unwrap() = to; |
| } |
| |
| // 2. Define the protocol server loop served to the CUT. |
| pub async fn serve_replacer(&self, mut stream: freverser::ReplacerRequestStream) -> Result<(), anyhow::Error> { |
| while let Some(Ok(req)) = stream.next().await { |
| match req { |
| freverser::ReplacerRequest::Replace { value, responder } => { |
| let from = self.from.lock().unwrap().clone(); |
| let to = self.to.lock().unwrap().clone(); |
| let result = if !from.is_empty() { value.replace(&from, &to) } else { value }; |
| let _ = responder.send(&result); |
| } |
| } |
| } |
| Ok(()) |
| } |
| } |
| ``` |
| |
| ### 4. Run the Test |
| |
| Execute your integration test using standard `fx` workflow commands: |
| |
| ```bash |
| fx add-test //path/to/your/tests:reverser_trf_integration_test |
| fx test //path/to/your/tests |
| ``` |