[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