| // Copyright 2018 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 "src/sys/sysmgr/package_updating_loader.h" |
| |
| #include <fidl/examples/echo/cpp/fidl.h> |
| #include <fuchsia/io/cpp/fidl.h> |
| #include <fuchsia/pkg/cpp/fidl.h> |
| #include <fuchsia/sys/cpp/fidl.h> |
| #include <lib/fdio/directory.h> |
| #include <lib/fdio/fd.h> |
| #include <lib/fdio/fdio.h> |
| #include <lib/fidl/cpp/binding_set.h> |
| #include <lib/sys/cpp/testing/enclosing_environment.h> |
| #include <lib/sys/cpp/testing/test_with_environment_fixture.h> |
| #include <lib/syslog/cpp/macros.h> |
| #include <lib/zx/channel.h> |
| #include <lib/zx/time.h> |
| #include <zircon/errors.h> |
| #include <zircon/types.h> |
| |
| #include <memory> |
| #include <string> |
| #include <tuple> |
| #include <utility> |
| #include <vector> |
| |
| #include <gtest/gtest.h> |
| |
| namespace sysmgr { |
| namespace { |
| |
| const char kEchoServerURL[] = |
| "fuchsia-pkg://fuchsia.com/sysmgr-integration-tests#meta/" |
| "echo_server.cmx"; |
| |
| class PackageResolverMock : public fuchsia::pkg::PackageResolver { |
| public: |
| explicit PackageResolverMock(std::optional<fuchsia::pkg::ResolveError> error) : error_(error) {} |
| |
| virtual void Resolve(::std::string package_uri, |
| ::fidl::InterfaceRequest<fuchsia::io::Directory> dir, |
| ResolveCallback callback) override { |
| args_ = std::make_tuple(package_uri); |
| fdio_open("/pkg", |
| static_cast<uint32_t>(fuchsia::io::OpenFlags::RIGHT_READABLE | |
| fuchsia::io::OpenFlags::RIGHT_EXECUTABLE), |
| dir.TakeChannel().release()); |
| if (error_) { |
| callback(fuchsia::pkg::PackageResolver_Resolve_Result::WithErr( |
| std::forward<fuchsia::pkg::ResolveError>(error_.value()))); |
| } else { |
| callback(fuchsia::pkg::PackageResolver_Resolve_Result::WithResponse({})); |
| } |
| } |
| |
| virtual void GetHash(fuchsia::pkg::PackageUrl package_url, GetHashCallback callback) override { |
| callback(fuchsia::pkg::PackageResolver_GetHash_Result::WithErr(ZX_ERR_UNAVAILABLE)); |
| } |
| |
| void AddBinding(fidl::InterfaceRequest<fuchsia::pkg::PackageResolver> req) { |
| bindings_.AddBinding(this, std::move(req)); |
| } |
| |
| void Unbind() { bindings_.CloseAll(); } |
| |
| typedef std::tuple<std::string> ArgsTuple; |
| const ArgsTuple& args() const { return args_; } |
| |
| private: |
| std::optional<fuchsia::pkg::ResolveError> error_; |
| ArgsTuple args_; |
| fidl::BindingSet<fuchsia::pkg::PackageResolver> bindings_; |
| }; |
| |
| class ServiceProviderMock : fuchsia::sys::ServiceProvider { |
| public: |
| explicit ServiceProviderMock(PackageResolverMock* resolver_service) |
| : num_connections_made_(0), resolver_service_(resolver_service) {} |
| |
| void ConnectToService(::std::string service_name, ::zx::channel channel) override { |
| if (service_name != fuchsia::pkg::PackageResolver::Name_) { |
| FX_LOGS(FATAL) << "ServiceProviderMock asked to connect to '" << service_name |
| << "' but we can only connect to the package resolver."; |
| return; |
| } |
| |
| FX_DLOGS(INFO) << "Adding a binding for the package resolver"; |
| resolver_service_->AddBinding( |
| fidl::InterfaceRequest<fuchsia::pkg::PackageResolver>(std::move(channel))); |
| num_connections_made_++; |
| } |
| |
| void DisconnectAll() { |
| FX_DLOGS(INFO) << "Disconnecting package resolver mock clients."; |
| resolver_service_->Unbind(); |
| } |
| |
| fuchsia::sys::ServiceProviderPtr Bind() { |
| fuchsia::sys::ServiceProviderPtr env_services; |
| bindings_.AddBinding(this, env_services.NewRequest()); |
| return env_services; |
| } |
| |
| int num_connections_made_; |
| |
| private: |
| PackageResolverMock* resolver_service_; |
| fidl::BindingSet<fuchsia::sys::ServiceProvider> bindings_; |
| }; |
| |
| constexpr char kRealm[] = "package_updating_loader_env"; |
| |
| class PackageUpdatingLoaderTest : public gtest::TestWithEnvironmentFixture { |
| protected: |
| void Init(ServiceProviderMock* provider_service) { |
| loader_ = std::make_unique<PackageUpdatingLoader>( |
| std::unordered_set<std::string>{"my_resolver"}, provider_service->Bind(), dispatcher()); |
| loader_service_ = |
| std::make_shared<vfs::Service>([this](zx::channel channel, async_dispatcher_t* dispatcher) { |
| loader_->AddBinding(fidl::InterfaceRequest<fuchsia::sys::Loader>(std::move(channel))); |
| }); |
| sys::testing::EnvironmentServices::ParentOverrides parent_overides; |
| parent_overides.loader_service_ = loader_service_; |
| auto services = CreateServicesWithParentOverrides(std::move(parent_overides)); |
| env_ = CreateNewEnclosingEnvironment(kRealm, std::move(services)); |
| } |
| |
| fuchsia::sys::LaunchInfo CreateLaunchInfo(const std::string& url, zx::channel dir) { |
| fuchsia::sys::LaunchInfo launch_info; |
| launch_info.url = url; |
| launch_info.directory_request = std::move(dir); |
| return launch_info; |
| } |
| |
| template <typename RequestType> |
| void ConnectToServiceAt(zx::channel dir, fidl::InterfaceRequest<RequestType> req) { |
| ASSERT_EQ(ZX_OK, fdio_service_connect_at(dir.release(), RequestType::Name_, |
| req.TakeChannel().release())); |
| } |
| |
| std::unique_ptr<sys::testing::EnclosingEnvironment> env_; |
| |
| private: |
| std::unique_ptr<PackageUpdatingLoader> loader_; |
| std::shared_ptr<vfs::Service> loader_service_; |
| }; |
| |
| TEST_F(PackageUpdatingLoaderTest, Success) { |
| PackageResolverMock resolver_service(std::nullopt); |
| ServiceProviderMock provider_service(&resolver_service); |
| Init(&provider_service); |
| |
| // Launch a component in the environment, and prove it started successfully |
| // by trying to use a service offered by it. |
| zx::channel h1, h2; |
| ASSERT_EQ(ZX_OK, zx::channel::create(0, &h1, &h2)); |
| auto launch_info = CreateLaunchInfo(kEchoServerURL, std::move(h2)); |
| auto controller = env_->CreateComponent(std::move(launch_info)); |
| fidl::examples::echo::EchoPtr echo; |
| ConnectToServiceAt(std::move(h1), echo.NewRequest()); |
| const std::string message = "component launched"; |
| std::string ret_msg = ""; |
| echo->EchoString(message, [&](fidl::StringPtr retval) { ret_msg = retval.value_or(""); }); |
| RunLoopUntil([&] { return ret_msg == message; }); |
| |
| // Verify that Resolve was called with the expected arguments. |
| constexpr char kResolvedUrl[] = "fuchsia-pkg://fuchsia.com/sysmgr-integration-tests/0"; |
| const auto& args = resolver_service.args(); |
| EXPECT_EQ(std::get<0>(args), std::string(kResolvedUrl)); |
| } |
| |
| TEST_F(PackageUpdatingLoaderTest, Failure) { |
| PackageResolverMock resolver_service( |
| (std::optional<fuchsia::pkg::ResolveError>(fuchsia::pkg::ResolveError::PACKAGE_NOT_FOUND))); |
| ServiceProviderMock provider_service(&resolver_service); |
| Init(&provider_service); |
| |
| // Launch a component in the environment, and prove it started successfully |
| // by trying to use a service offered by it. Launching the component will |
| // succeed if the test is in base, as PackageUpdateLoader will fall back |
| // to loading from pkgfs. However, if the test is in universe, the package |
| // cannot be loaded from pkgfs because we don't support loading non-static |
| // packages from pkgfs, so we expect CreateComponent to fail. |
| zx::channel h1, h2; |
| ASSERT_EQ(ZX_OK, zx::channel::create(0, &h1, &h2)); |
| auto launch_info = CreateLaunchInfo(kEchoServerURL, std::move(h2)); |
| auto controller = env_->CreateComponent(std::move(launch_info)); |
| fidl::examples::echo::EchoPtr echo; |
| ConnectToServiceAt(std::move(h1), echo.NewRequest()); |
| const std::string message = "component launched"; |
| std::string ret_msg = ""; |
| bool terminated = false; |
| controller.events().OnTerminated = |
| [&terminated](int64_t code, fuchsia::sys::TerminationReason reason) { terminated = true; }; |
| echo->EchoString(message, [&](fidl::StringPtr retval) { ret_msg = retval.value_or(""); }); |
| RunLoopUntil([&] { return ret_msg == message || terminated; }); |
| } |
| |
| TEST_F(PackageUpdatingLoaderTest, HandleResolverDisconnectCorrectly) { |
| PackageResolverMock resolver_service(std::nullopt); |
| ServiceProviderMock service_provider(&resolver_service); |
| Init(&service_provider); |
| |
| auto launch_url = kEchoServerURL; |
| { |
| // Launch a component in the environment, and prove it started successfully |
| // by trying to use a service offered by it. |
| zx::channel h1, h2; |
| ASSERT_EQ(ZX_OK, zx::channel::create(0, &h1, &h2)); |
| auto launch_info = CreateLaunchInfo(launch_url, std::move(h2)); |
| auto controller = env_->CreateComponent(std::move(launch_info)); |
| |
| fidl::examples::echo::EchoPtr echo; |
| ConnectToServiceAt(std::move(h1), echo.NewRequest()); |
| |
| const std::string message = "component launched"; |
| std::string ret_msg = ""; |
| |
| echo->EchoString(message, [&](fidl::StringPtr retval) { ret_msg = retval.value_or(""); }); |
| RunLoopUntil([&] { return ret_msg == message; }); |
| } |
| |
| // since the connection to the package resolver is initiated lazily, we need |
| // to make sure that after a first successful connection we can still recover |
| // by reconnecting |
| service_provider.DisconnectAll(); |
| |
| { |
| zx::channel h1, h2; |
| ASSERT_EQ(ZX_OK, zx::channel::create(0, &h1, &h2)); |
| FX_LOGS(INFO) << "serviceprovider disconnected, new echo channels created"; |
| auto launch_info = CreateLaunchInfo(launch_url, std::move(h2)); |
| auto controller = env_->CreateComponent(std::move(launch_info)); |
| |
| FX_LOGS(INFO) << "connecting to echo service the second."; |
| fidl::examples::echo::EchoPtr echo; |
| ConnectToServiceAt(std::move(h1), echo.NewRequest()); |
| |
| const std::string message = "component launched"; |
| std::string ret_msg = ""; |
| |
| FX_LOGS(INFO) << "sending echo message."; |
| echo->EchoString(message, [&](fidl::StringPtr retval) { ret_msg = retval.value_or(""); }); |
| RunLoopUntil([&] { return ret_msg == message; }); |
| } |
| |
| // an initial connection and a retry |
| ASSERT_EQ(service_provider.num_connections_made_, 2); |
| |
| // we'll go through one more time to make sure we're behaving as expected |
| service_provider.DisconnectAll(); |
| |
| { |
| zx::channel h1, h2; |
| ASSERT_EQ(ZX_OK, zx::channel::create(0, &h1, &h2)); |
| FX_LOGS(INFO) << "serviceprovider disconnected, new echo channels created"; |
| auto launch_info = CreateLaunchInfo(launch_url, std::move(h2)); |
| auto controller = env_->CreateComponent(std::move(launch_info)); |
| |
| FX_LOGS(INFO) << "connecting to echo service the second."; |
| fidl::examples::echo::EchoPtr echo; |
| ConnectToServiceAt(std::move(h1), echo.NewRequest()); |
| |
| const std::string message = "component launched"; |
| std::string ret_msg = ""; |
| |
| FX_LOGS(INFO) << "sending echo message."; |
| echo->EchoString(message, [&](fidl::StringPtr retval) { ret_msg = retval.value_or(""); }); |
| RunLoopUntil([&] { return ret_msg == message; }); |
| } |
| |
| // one more connection |
| ASSERT_EQ(service_provider.num_connections_made_, 3); |
| } |
| |
| } // namespace |
| } // namespace sysmgr |