blob: 1d18236ca6781ff8f1086b42c166d2933fa301d0 [file] [log] [blame]
// Copyright 2019 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.
#ifndef LIB_MODULAR_TESTING_CPP_TEST_HARNESS_BUILDER_H_
#define LIB_MODULAR_TESTING_CPP_TEST_HARNESS_BUILDER_H_
#include <fuchsia/modular/testing/cpp/fidl.h>
#include <lib/fit/function.h>
#include <lib/stdcompat/optional.h>
#include <lib/sys/cpp/service_directory.h>
#include <lib/vfs/cpp/pseudo_dir.h>
#include <lib/vfs/cpp/service.h>
namespace modular_testing {
// TestHarnessBuilder is a utility for building a
// |fuchsia.modular.testing.TestHarnessSpec|. This utility provides methods for
// hosting environment services and routing intercepted components.
//
//
// SAMPLE USAGE:
//
// #include <lib/modular/testing/cpp/fake_component.h>
// #include <lib/modular/testing/cpp/test_harness_builder.h>
// #include <lib/modular/testing/cpp/test_harness_launcher.h>
//
// class MyTest : gtest::RealLoopFixture {};
//
// TEST_F(MyTest, TestOne) {
// modular_testing::TestHarnessLauncher test_harness_launcher;
// modular_testing::TestHarnessBuilder builder;
//
// // Instruct the test harness to intercept the launch of a new component
// // within the test harness environment. Specify that the component should
// // include foo.Service within its component manifest.
// modular_testing::FakeComponent component(
// {.url = modular_testing::TestHarnessBuilder::GenerateFakeUrl(),
// .sandbox_services = {"foo.Service"}});
// builder.InterceptComponent(component.BuildInterceptOptions());
//
// // Start an instance of the modular runtime in the test harness
// // environment. As soon as |component_url| is created in
// // this environment |component.on_create| is triggered.
// builder.BuildAndRun(test_harness_launcher.test_harness());
//
// // ... do something that would cause |component_url| to be created ...
// RunLoopUntil([&] { return component.is_running(); });
//
// foo::ServicePtr service_ptr;
// component.component_context()->svc()->Connect(service_ptr.NewRequest());
//
// // ...
// }
class TestHarnessBuilder final {
public:
using LaunchHandler =
fit::function<void(fuchsia::sys::StartupInfo startup_info,
fidl::InterfaceHandle<fuchsia::modular::testing::InterceptedComponent>
intercepted_component)>;
struct InterceptOptions {
// The URL of the component to intercept. Use GenerateFakeUrl() to create a
// random valid URL.
//
// Required: Must not be empty.
std::string url;
// A list of service names to populate the component's manifest
// sandbox.services JSON property
//
// Optional.
std::vector<std::string> sandbox_services;
// Called when this component is launched.
//
// Required.
LaunchHandler launch_handler;
};
// Builds on top of an empty |fuchsia.modular.testing.TestHarnessSpec|.
TestHarnessBuilder();
// Builds on top of the supplied |spec|.
explicit TestHarnessBuilder(fuchsia::modular::testing::TestHarnessSpec spec);
// Movable.
TestHarnessBuilder(TestHarnessBuilder&&) = default;
TestHarnessBuilder& operator=(TestHarnessBuilder&&) = default;
// Not copyable.
TestHarnessBuilder(const TestHarnessBuilder&) = delete;
TestHarnessBuilder& operator=(const TestHarnessBuilder&) = delete;
// Builds the underlying TestHarnessSpec and issues a |TestHarness/Run()|.
// Binds an OnNewComponent event handler to the supplied |test_harness| to
// route the Intercept*() calls issued below.
//
// Can only be called once.
void BuildAndRun(const fuchsia::modular::testing::TestHarnessPtr& test_harness);
// Amends the TestHarnessSpec to include interception instructions specified by
// |options|.
TestHarnessBuilder& InterceptComponent(InterceptOptions options);
// Convenience variant of InterceptComponent() which adds a session shell URL
// to the ModularConfig for |options.url|.
TestHarnessBuilder& InterceptSessionShell(InterceptOptions options);
// Convenience variant of InterceptComponent() which sets the story shell URL
// in the ModularConfig to |options.url|.
TestHarnessBuilder& InterceptStoryShell(InterceptOptions options);
// Convenience variant of InterceptComponent() which sets the `session_launcher` URL
// in the ModularConfig to |options.url|, and optionally, the `session_launcher` args.
TestHarnessBuilder& InterceptSessionLauncherComponent(
InterceptOptions options, cpp17::optional<std::vector<std::string>> args = cpp17::nullopt);
// Make a service named |service_name| available in the test harness
// environment. |connector| is called every time a client requests to
// establish a new connection. This service is hosted for as long as this
// TestHarnessBuilder object is kept alive.
TestHarnessBuilder& AddService(const std::string& service_name,
vfs::Service::Connector connector);
// Make the templated |Interface| service available in the test harness
// environment. |request_handler| is called every time a client requests to
// establish a new connection. This service is hosted for as long as this
// TestHarnessBuilder object is kept alive.
template <typename Interface>
TestHarnessBuilder& AddService(fidl::InterfaceRequestHandler<Interface> request_handler) {
return AddService(Interface::Name_,
[request_handler = std::move(request_handler)](
zx::channel request, async_dispatcher_t* dispatcher) mutable {
request_handler(fidl::InterfaceRequest<Interface>(std::move(request)));
});
}
// Make the specified |service_name| available in the test harness
// environment. The service is provided by |component_url|, which is
// launched and kept alive for the duration of the test harness environment.
// See |TestHarnessSpec.env_services.services_from_components| for more
// details.
TestHarnessBuilder& AddServiceFromComponent(const std::string& service_name,
const std::string& component_url);
// Make the templated service available in the test harness environment.
// The service is provided by the given |component_url|, which is launched and
// kept alive for the duration of the test harness environment. See
// |TestHarnessSpec.env_services.services_from_components| for more details.
template <typename Interface>
TestHarnessBuilder& AddServiceFromComponent(const std::string& component_url) {
return AddServiceFromComponent(Interface::Name_, component_url);
}
// Make the specified |service_name| from |services| available in the test
// harness environment. |services| and the service are both kept alive for the
// duration of this builder object's life time.
TestHarnessBuilder& AddServiceFromServiceDirectory(
const std::string& service_name, std::shared_ptr<sys::ServiceDirectory> services);
TestHarnessBuilder& UseSessionShellForStoryShellFactory();
// Make the templated service from |services| available in the test
// harness environment. |services| and the service are both kept alive for the
// duration of this builder object's life time.
template <typename Interface>
TestHarnessBuilder& AddServiceFromServiceDirectory(
std::shared_ptr<sys::ServiceDirectory> services) {
return AddServiceFromServiceDirectory(Interface::Name_, std::move(services));
}
// Returns a generated fake URL. Subsequent calls to this method will generate
// a different URL. If |name| is provided, adds its contents to the component
// name. Non alpha-num characters (a-zA-Z0-9) are stripped.
static std::string GenerateFakeUrl(std::string name = "");
private:
// Takes the TestHarnessSpec built so far with the builder functions below.
//
// Can only be called once.
fuchsia::modular::testing::TestHarnessSpec BuildSpec();
// Builds a router function which routes calls to the various handlers
// provided to Intercept*() variants. Intended to be used as the handler for
// TestHarness.events.OnNewComponent
//
// Can only be called once.
LaunchHandler BuildOnNewComponentHandler();
fuchsia::modular::testing::TestHarnessSpec spec_;
// Map from url to handler to be called when that url's component
// is created and intercepted.
std::map<std::string, LaunchHandler> handlers_;
// Hosts services injected using AddService() and InheritService().
std::unique_ptr<vfs::PseudoDir> env_services_;
};
} // namespace modular_testing
#endif // LIB_MODULAR_TESTING_CPP_TEST_HARNESS_BUILDER_H_