[samples] Add Realm Builder testing example
Add a sample project that demonstrates integration testing using the
Realm Builder library.
Bug: 87492
Change-Id: I7135fcd24fdd797504b0d0662ab7d057071dd63c
Reviewed-on: https://fuchsia-review.googlesource.com/c/samples/+/648323
Reviewed-by: Yaneury Fermin <yaneury@google.com>
Commit-Queue: Dave Smith <smithdave@google.com>
diff --git a/BUILD.gn b/BUILD.gn
index b8f084b..9fab7f3 100644
--- a/BUILD.gn
+++ b/BUILD.gn
@@ -31,12 +31,15 @@
deps = [
"//src/calculator:tests",
"//src/hello_world:tests",
+ "//src/realm_builder:tests",
"//src/rot13:tests",
]
sources = [
"${target_gen_dir}/src/hello_world/hello_world_test/hello_world_test.far",
"${target_gen_dir}/src/calculator/engine/engine_unittest/engine_unittest.far",
"${target_gen_dir}/src/rot13/server/rot13_unittests/rot13_unittests.far",
+ "${target_gen_dir}/src/realm_builder/greeting_service/greeting_service.far",
+ "${target_gen_dir}/src/realm_builder/greeter_test/greeter_test.far",
]
outputs = [ "${root_out_dir}/{{source_file_part}}" ]
}
diff --git a/README.md b/README.md
index 242bdd0..f0be182 100644
--- a/README.md
+++ b/README.md
@@ -68,6 +68,12 @@
To get started, see the [README](src/rot13/README.md).
+#### realm_builder
+
+Integration test component using the Realm Builder library.
+
+To get started, see the [README](src/realm_builder/README.md).
+
### Repository structure
#### build
The `build` directory contains the build configuration for the sample including
diff --git a/src/realm_builder/BUILD.gn b/src/realm_builder/BUILD.gn
new file mode 100644
index 0000000..8dc26db
--- /dev/null
+++ b/src/realm_builder/BUILD.gn
@@ -0,0 +1,84 @@
+# Copyright 2022 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.
+
+import("//third_party/fuchsia-sdk/build/component.gni")
+import("//third_party/fuchsia-sdk/build/package.gni")
+import("//third_party/fuchsia-sdk/build/test.gni")
+
+# tests group
+group("tests") {
+ testonly = true
+ public_deps = [
+ ":greeting_service_pkg",
+ ":greeter_test_pkg",
+ ]
+}
+
+# Build the greeter component implementation
+executable("greeter_exe") {
+ sources = [ "greeter.cc" ]
+ deps = [
+ "//src/realm_builder/fidl:fuchsia.sdk.examples.fidl",
+ "//third_party/fuchsia-sdk/pkg/async-loop-cpp",
+ "//third_party/fuchsia-sdk/pkg/async-loop-default",
+ "//third_party/fuchsia-sdk/pkg/fidl_cpp",
+ "//third_party/fuchsia-sdk/pkg/sys_cpp",
+ ]
+}
+
+fuchsia_component("greeting_service") {
+ manifest = "meta/greeting_service.cml"
+ data_deps = [ ":greeter_exe" ]
+}
+
+fuchsia_component("greeting_service_v1") {
+ manifest = "meta/greeting_service.cmx"
+ data_deps = [ ":greeter_exe" ]
+}
+
+# Component package contains both a CML and CMX implementation of greeter
+fuchsia_package("greeting_service_pkg") {
+ testonly = true
+ package_name = "greeting_service"
+ deps = [
+ ":greeting_service",
+ ":greeting_service_v1",
+ ]
+}
+
+# Build the greeter integration test component
+executable("greeter_test_exe") {
+ testonly = true
+ sources = [ "test/realm_builder_test.cc" ]
+ deps = [
+ "//src/realm_builder/fidl:fuchsia.sdk.examples.fidl",
+ "//third_party/fuchsia-sdk/fidl/fuchsia.logger",
+ "//third_party/fuchsia-sdk/pkg/async",
+ "//third_party/fuchsia-sdk/pkg/async-default",
+ "//third_party/fuchsia-sdk/pkg/async-loop-cpp",
+ "//third_party/fuchsia-sdk/pkg/async-loop-default",
+ "//third_party/fuchsia-sdk/pkg/fidl_cpp",
+ "//third_party/fuchsia-sdk/pkg/fit",
+ "//third_party/fuchsia-sdk/pkg/sys_component_cpp_testing",
+ "//third_party/fuchsia-sdk/pkg/zx",
+ "//third_party/googletest:gtest",
+ "//third_party/googletest:gtest_main",
+ ]
+}
+
+fuchsia_component("greeter_test") {
+ testonly = true
+ manifest = "meta/greeter_test.cml"
+ data_deps = [ ":greeter_test_exe" ]
+}
+
+# Test package that also bundles the CML implementation of greeter
+fuchsia_package("greeter_test_pkg") {
+ testonly = true
+ package_name = "greeter_test"
+ deps = [
+ ":greeter_test",
+ ":greeting_service",
+ ]
+}
diff --git a/src/realm_builder/README.md b/src/realm_builder/README.md
new file mode 100644
index 0000000..a842344
--- /dev/null
+++ b/src/realm_builder/README.md
@@ -0,0 +1,87 @@
+# Realm Builder Example
+
+The Realm Builder sample demonstrates an integration test component that
+exercises the `fuchsia.sdk.examples.GreetingService` FIDL protocol implemented
+by another component using the Realm Builder testing library.
+
+## Source layout
+
+- `fidl/`: Contains the definition of a Fuchsia interprocess communication (IPC)
+ protocol (`fuchsia.sdk.examples.GreetingService`) defined as a
+ Fuchsia Interface Definition Language (FIDL) library.
+- `greeter.cc`: Source code for a component under test that implements and
+ serves the `fuchsia.sdk.examples.GreetingService` protocol.
+- `test/`: Source code for an integration test component that exercises the
+ `fuchsia.sdk.examples.GreetingService` using Realm Builder.
+
+## Before you begin
+
+1. Start a FEMU instance:
+
+ ```
+ $ ./third_party/fuchsia-sdk/bin/femu.sh -N
+ ```
+
+1. Start a local package repository instance:
+
+ ```
+ $ ./third_party/fuchsia-sdk/tools/x64/fserve --image qemu-x64
+ ```
+
+## Build the sample
+
+1. Run the build script to compile and package the sample:
+
+ ```
+ $ ./scripts/build.sh
+ ```
+
+1. Publish the FAR component package to your local package repository:
+
+ ```
+ $ ./third_party/fuchsia-sdk/tools/x64/fpublish out/x64/greeting_service.far
+ ```
+
+1. Publish the FAR test package to your local package repository:
+
+ ```
+ $ ./third_party/fuchsia-sdk/tools/x64/fpublish out/x64/greeter_test.far
+ ```
+
+## Run the integration tests
+
+1. Execute the integration test suite using `ffx test run`. This resolves the
+ component from the package repository and executes the tests:
+
+ ```
+ $ ffx test run fuchsia-pkg://fuchsia.com/greeter_test#meta/greeter_test.cm
+ ```
+
+1. Verify that the test cases pass:
+
+ ```
+ Running test 'fuchsia-pkg://fuchsia.com/greeter_test#meta/greeter_test.cm'
+ [RUNNING] main
+ [00114.271318][65078][65080][realm_builder_server] INFO: started
+ [00114.383240][1179][1303][realm_builder:auto-10293192455784001876/greeting_service] INFO: Running GreetingService server
+ [stdout - main]
+ Running main() from ../../third_party/googletest/src/googletest/src/gtest_main.cc
+ [==========] Running 3 tests from 1 test suite.
+ [----------] Global test environment set-up.
+ [----------] 3 tests from RealmBuilderTest
+ [ RUN ] RealmBuilderTest.RoutesFromComponent
+ [ OK ] RealmBuilderTest.RoutesFromComponent (245 ms)
+ [ RUN ] RealmBuilderTest.RoutesFromLegacyComponent
+ [ OK ] RealmBuilderTest.RoutesFromLegacyComponent (224 ms)
+ [ RUN ] RealmBuilderTest.RoutesFromMockComponent
+ [ OK ] RealmBuilderTest.RoutesFromMockComponent (67 ms)
+ [----------] 3 tests from RealmBuilderTest (536 ms total)
+
+ [----------] Global test environment tear-down
+ [==========] 3 tests from 1 test suite ran. (541 ms total)
+ [ PASSED ] 3 tests.
+ [PASSED] main
+
+ 1 out of 1 tests passed...
+ fuchsia-pkg://fuchsia.com/greeter_test#meta/greeter_test.cm completed with result: PASSED
+ ```
diff --git a/src/realm_builder/fidl/BUILD.gn b/src/realm_builder/fidl/BUILD.gn
new file mode 100644
index 0000000..40c2ac3
--- /dev/null
+++ b/src/realm_builder/fidl/BUILD.gn
@@ -0,0 +1,11 @@
+# Copyright 2022 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.
+
+assert(is_fuchsia)
+
+import("//third_party/fuchsia-sdk/build/fidl_library.gni")
+
+fidl_library("fuchsia.sdk.examples.fidl") {
+ sources = [ "greeter.fidl" ]
+}
\ No newline at end of file
diff --git a/src/realm_builder/fidl/greeter.fidl b/src/realm_builder/fidl/greeter.fidl
new file mode 100644
index 0000000..1fcbbe0
--- /dev/null
+++ b/src/realm_builder/fidl/greeter.fidl
@@ -0,0 +1,14 @@
+library fuchsia.sdk.examples.fidl;
+
+type Greeting = struct {
+ text string;
+};
+
+@discoverable
+protocol GreetingService {
+ Greet(struct {
+ greeting box<Greeting>;
+ }) -> (struct {
+ greeting box<Greeting>;
+ });
+};
\ No newline at end of file
diff --git a/src/realm_builder/greeter.cc b/src/realm_builder/greeter.cc
new file mode 100644
index 0000000..aa6b552
--- /dev/null
+++ b/src/realm_builder/greeter.cc
@@ -0,0 +1,36 @@
+// Copyright 2022 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.
+
+#include <fuchsia/sdk/examples/fidl/cpp/fidl.h>
+#include <lib/async-loop/cpp/loop.h>
+#include <lib/async-loop/default.h>
+#include <lib/fidl/cpp/binding.h>
+#include <lib/sys/cpp/component_context.h>
+#include <stdio.h>
+
+// This GreetingService implementation echoes back the Greeting sent in calls
+// to Greet. This is provided to facilitate testing.
+class GreetingServiceImpl : public fuchsia::sdk::examples::fidl::GreetingService {
+ public:
+ void Greet(std::unique_ptr<fuchsia::sdk::examples::fidl::Greeting> value,
+ GreetCallback callback) override {
+ callback(std::move(value));
+ }
+};
+
+int main(int argc, const char** argv) {
+ async::Loop loop(&kAsyncLoopConfigAttachToCurrentThread);
+
+ GreetingServiceImpl impl;
+ fidl::Binding<fuchsia::sdk::examples::fidl::GreetingService> binding(&impl);
+ fidl::InterfaceRequestHandler<fuchsia::sdk::examples::fidl::GreetingService> handler =
+ [&](fidl::InterfaceRequest<fuchsia::sdk::examples::fidl::GreetingService> request) {
+ binding.Bind(std::move(request));
+ };
+ auto context = sys::ComponentContext::CreateAndServeOutgoingDirectory();
+ context->outgoing()->AddPublicService(std::move(handler));
+
+ printf("Running GreetingService server\n");
+ return loop.Run();
+}
\ No newline at end of file
diff --git a/src/realm_builder/meta/greeter_test.cml b/src/realm_builder/meta/greeter_test.cml
new file mode 100644
index 0000000..cb706f1
--- /dev/null
+++ b/src/realm_builder/meta/greeter_test.cml
@@ -0,0 +1,15 @@
+// Copyright 2022 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.
+{
+ include: [
+ "syslog/client.shard.cml",
+ "sys/component/realm_builder_absolute.shard.cml",
+ "sys/testing/elf_test_runner.shard.cml",
+ ],
+
+ // Information about the test program.
+ program: {
+ binary: "greeter_test_exe",
+ },
+}
\ No newline at end of file
diff --git a/src/realm_builder/meta/greeting_service.cml b/src/realm_builder/meta/greeting_service.cml
new file mode 100644
index 0000000..219f0d4
--- /dev/null
+++ b/src/realm_builder/meta/greeting_service.cml
@@ -0,0 +1,30 @@
+// Copyright 2022 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.
+{
+ include: [
+ "syslog/elf_stdio.shard.cml",
+ ],
+
+ // Information about the program to run.
+ program: {
+ // Use the built-in ELF runner.
+ runner: "elf",
+
+ // The binary to run for this component.
+ binary: "greeter_exe",
+ },
+
+ // Capabilities provided by this component.
+ capabilities: [
+ {
+ protocol: "fuchsia.sdk.examples.fidl.GreetingService",
+ }
+ ],
+ expose: [
+ {
+ from: "self",
+ protocol: "fuchsia.sdk.examples.fidl.GreetingService",
+ }
+ ]
+}
\ No newline at end of file
diff --git a/src/realm_builder/meta/greeting_service.cmx b/src/realm_builder/meta/greeting_service.cmx
new file mode 100644
index 0000000..dd9e317
--- /dev/null
+++ b/src/realm_builder/meta/greeting_service.cmx
@@ -0,0 +1,5 @@
+{
+ "program": {
+ "binary": "greeter_exe"
+ }
+}
\ No newline at end of file
diff --git a/src/realm_builder/test/realm_builder_test.cc b/src/realm_builder/test/realm_builder_test.cc
new file mode 100644
index 0000000..c6d0493
--- /dev/null
+++ b/src/realm_builder/test/realm_builder_test.cc
@@ -0,0 +1,183 @@
+// Copyright 2022 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.
+
+#include <fuchsia/logger/cpp/fidl.h>
+#include <fuchsia/sdk/examples/fidl/cpp/fidl.h>
+#include <lib/async-loop/cpp/loop.h>
+#include <lib/async-loop/default.h>
+#include <lib/async/default.h>
+#include <lib/async/dispatcher.h>
+#include <lib/fidl/cpp/binding_set.h>
+#include <lib/fidl/cpp/string.h>
+#include <lib/fit/function.h>
+#include <lib/sys/component/cpp/testing/realm_builder.h>
+#include <zircon/status.h>
+
+#include <memory>
+
+#include <gtest/gtest.h>
+
+constexpr static char kGreetingService[] = "greeting_service";
+
+// Create a test fixture to manage the async loop required by Realm Builder.
+class RealmBuilderTest : public ::testing::Test {
+ protected:
+ RealmBuilderTest() : loop_(&kAsyncLoopConfigAttachToCurrentThread) {}
+
+ async_dispatcher_t* dispatcher() { return loop_.dispatcher(); }
+
+ void RunLoop() {
+ loop_.Run();
+ loop_.ResetQuit();
+ }
+
+ void QuitLoop() { loop_.Quit(); }
+
+ fit::closure QuitLoopClosure() {
+ return [this] { loop_.Quit(); };
+ }
+
+ private:
+ async::Loop loop_;
+};
+
+// This first test constructs a realm with a single component and tests that the
+// component correctly implements the
+// fuchsia.sdk.examples.fidl.GreetingService protocol.
+TEST_F(RealmBuilderTest, RoutesFromComponent) {
+ // Component URL of the component to test.
+ static constexpr char kGreetingServiceServerUrl[] = "#meta/greeting_service.cm";
+
+ // Use Realm::Builder factory method to initiate builder object.
+ auto realm_builder = component_testing::RealmBuilder::Create();
+
+ // Add the Greeting Service component as child of the Realm.
+ realm_builder.AddChild(kGreetingService, kGreetingServiceServerUrl);
+
+ // Route the fuchsia.logger.LogSink protocol from the test to the
+ // Greeting Service component.
+ realm_builder.AddRoute(component_testing::Route{
+ .capabilities = {component_testing::Protocol{fuchsia::logger::LogSink::Name_}},
+ .source = component_testing::ParentRef(),
+ .targets = {component_testing::ChildRef{kGreetingService}}});
+
+ // Route the fuchsia.sdk.examples.fidl.GreetingService protocol from
+ // the Greeting Service component to this component.
+ realm_builder.AddRoute(
+ component_testing::Route{.capabilities = {component_testing::Protocol{
+ fuchsia::sdk::examples::fidl::GreetingService::Name_}},
+ .source = component_testing::ChildRef{kGreetingService},
+ .targets = {component_testing::ParentRef()}});
+
+ // Build the realm once the topology has been determined.
+ auto realm = realm_builder.Build(dispatcher());
+
+ // Since we routed the fuchsia.sdk.examples.fidl.GreetingService
+ // protocol to this component, we should be able to connect to it through
+ // the exposed directory of the created Realm.
+ auto greeting_service = realm.ConnectSync<fuchsia::sdk::examples::fidl::GreetingService>();
+ auto greeting = fuchsia::sdk::examples::fidl::Greeting::New();
+
+ static constexpr char kGreeting[] = "Hello World!";
+ greeting->text = kGreeting;
+ fuchsia::sdk::examples::fidl::GreetingPtr reply = nullptr;
+ ASSERT_EQ(greeting_service->Greet(std::move(greeting), &reply), ZX_OK);
+ ASSERT_NE(reply, nullptr);
+ EXPECT_EQ(reply->text, kGreeting);
+}
+
+// This test is the same as the one above, except that the implementation
+// of fuchsia.sdk.examples.fidl.GreetingService comes from a
+// legacy (CMX) component.
+TEST_F(RealmBuilderTest, RoutesFromLegacyComponent) {
+ static constexpr char kGreetingServiceServerLegacyUrl[] =
+ "fuchsia-pkg://fuchsia.com/greeting_service#meta/greeting_service.cmx";
+
+ auto realm_builder = component_testing::RealmBuilder::Create();
+ realm_builder.AddLegacyChild(kGreetingService, kGreetingServiceServerLegacyUrl);
+ realm_builder.AddRoute(component_testing::Route{
+ .capabilities = {component_testing::Protocol{fuchsia::logger::LogSink::Name_}},
+ .source = component_testing::ParentRef(),
+ .targets = {component_testing::ChildRef{kGreetingService}}});
+ realm_builder.AddRoute(
+ component_testing::Route{.capabilities = {component_testing::Protocol{
+ fuchsia::sdk::examples::fidl::GreetingService::Name_}},
+ .source = component_testing::ChildRef{kGreetingService},
+ .targets = {component_testing::ParentRef()}});
+
+ auto realm = realm_builder.Build(dispatcher());
+
+ auto greeting_service = realm.ConnectSync<fuchsia::sdk::examples::fidl::GreetingService>();
+ auto greeting = fuchsia::sdk::examples::fidl::Greeting::New();
+
+ static constexpr char kGreeting[] = "Hello World!";
+ greeting->text = kGreeting;
+ fuchsia::sdk::examples::fidl::GreetingPtr reply = nullptr;
+ ASSERT_EQ(greeting_service->Greet(std::move(greeting), &reply), ZX_OK);
+ ASSERT_NE(reply, nullptr);
+ EXPECT_EQ(reply->text, kGreeting);
+}
+
+// The next test mocks a component in the Realm instead of using a prebuilt one.
+// When component_manager issues a Start request for this component, the
+// `Start` method will be called.
+class MockGreetingServiceServer : public component_testing::LocalComponent,
+ public fuchsia::sdk::examples::fidl::GreetingService {
+ public:
+ MockGreetingServiceServer(async_dispatcher_t* dispatcher)
+ : dispatcher_(dispatcher), called_(false) {}
+
+ void Greet(std::unique_ptr<fuchsia::sdk::examples::fidl::Greeting> value,
+ GreetCallback callback) override {
+ called_ = true;
+ callback(std::move(value));
+ }
+
+ void Start(std::unique_ptr<component_testing::LocalComponentHandles> mock_handles) override {
+ mock_handles_ = std::move(mock_handles);
+ ASSERT_EQ(mock_handles_->outgoing()->AddPublicService(bindings_.GetHandler(this, dispatcher_)),
+ ZX_OK);
+ }
+
+ bool WasCalled() const { return called_; }
+
+ private:
+ async_dispatcher_t* dispatcher_;
+ fidl::BindingSet<fuchsia::sdk::examples::fidl::GreetingService> bindings_;
+ bool called_;
+ std::unique_ptr<component_testing::LocalComponentHandles> mock_handles_;
+};
+
+// This test differs from the other two in that instead of using a prebuilt
+// component added to this test's package, it mocks an implementation inside
+// a local C++ class via the overridden `Start` method.
+TEST_F(RealmBuilderTest, RoutesFromMockComponent) {
+ auto realm_builder = component_testing::RealmBuilder::Create();
+
+ MockGreetingServiceServer mock_server(dispatcher());
+ realm_builder.AddLocalChild(kGreetingService, &mock_server);
+ realm_builder.AddRoute(
+ component_testing::Route{.capabilities = {component_testing::Protocol{
+ fuchsia::sdk::examples::fidl::GreetingService::Name_}},
+ .source = component_testing::ChildRef{kGreetingService},
+ .targets = {component_testing::ParentRef()}});
+
+ auto realm = realm_builder.Build(dispatcher());
+
+ fuchsia::sdk::examples::fidl::GreetingServicePtr greeting_service;
+ ASSERT_EQ(realm.Connect(greeting_service.NewRequest()), ZX_OK);
+ auto greeting = fuchsia::sdk::examples::fidl::Greeting::New();
+ greeting->text = "hello";
+ greeting_service->Greet(std::move(greeting),
+ [&](fuchsia::sdk::examples::fidl::GreetingPtr response) {
+ // Use EXPECT here so the loop can still quit if the test fails
+ EXPECT_EQ(response->text, "hello");
+ QuitLoop();
+ });
+
+ // Wait for async callback to complete
+ RunLoop();
+
+ EXPECT_TRUE(mock_server.WasCalled());
+}
\ No newline at end of file