| // 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/appmgr/namespace.h" |
| |
| #include <fuchsia/sys/cpp/fidl.h> |
| #include <lib/async/dispatcher.h> |
| #include <lib/fdio/directory.h> |
| #include <lib/fdio/fd.h> |
| #include <lib/fdio/fdio.h> |
| #include <lib/gtest/real_loop_fixture.h> |
| #include <lib/sys/cpp/testing/service_directory_provider.h> |
| #include <lib/vfs/cpp/pseudo_dir.h> |
| #include <lib/vfs/cpp/service.h> |
| #include <lib/zx/channel.h> |
| #include <zircon/errors.h> |
| #include <zircon/status.h> |
| |
| #include <map> |
| #include <memory> |
| #include <string> |
| #include <utility> |
| #include <vector> |
| |
| #include <gmock/gmock.h> |
| #include <gtest/gtest.h> |
| |
| #include "lib/fidl/cpp/interface_handle.h" |
| #include "lib/fidl/cpp/interface_request.h" |
| |
| using fuchsia::sys::ServiceList; |
| using fuchsia::sys::ServiceListPtr; |
| using fuchsia::sys::ServiceProvider; |
| using fuchsia::sys::ServiceProviderPtr; |
| |
| namespace component { |
| namespace { |
| |
| class NamespaceGuard { |
| public: |
| explicit NamespaceGuard(fxl::RefPtr<Namespace> ns) : ns_(std::move(ns)) {} |
| NamespaceGuard(std::nullptr_t) : ns_(nullptr) {} |
| ~NamespaceGuard() { Kill(); } |
| Namespace* operator->() const { return ns_.get(); } |
| |
| fxl::RefPtr<Namespace>& ns() { return ns_; } |
| |
| void Kill() { |
| if (ns_) { |
| ns_->FlushAndShutdown(ns_); |
| } |
| ns_ = nullptr; |
| } |
| |
| private: |
| fxl::RefPtr<Namespace> ns_; |
| }; |
| |
| class NamespaceTest : public ::gtest::RealLoopFixture { |
| protected: |
| NamespaceGuard MakeNamespace(ServiceListPtr additional_services, |
| NamespaceGuard parent = nullptr) { |
| if (parent.ns().get() == nullptr) { |
| return NamespaceGuard( |
| fxl::MakeRefCounted<Namespace>(nullptr, std::move(additional_services), nullptr)); |
| } |
| return NamespaceGuard(Namespace::CreateChildNamespace(parent.ns(), nullptr, |
| std::move(additional_services), nullptr)); |
| } |
| |
| zx_status_t ConnectToService(zx_handle_t svc_dir, const std::string& name) { |
| zx::channel h1, h2; |
| zx_status_t r = zx::channel::create(0, &h1, &h2); |
| if (r != ZX_OK) |
| return r; |
| fdio_service_connect_at(svc_dir, name.c_str(), h2.release()); |
| return ZX_OK; |
| } |
| }; |
| |
| class NamespaceHostDirectoryTest : public NamespaceTest { |
| protected: |
| zx::channel OpenAsDirectory() { |
| fidl::InterfaceHandle<fuchsia::io::Directory> dir; |
| directory_.Serve(fuchsia::io::OPEN_RIGHT_READABLE | fuchsia::io::OPEN_RIGHT_WRITABLE, |
| dir.NewRequest().TakeChannel(), dispatcher()); |
| return dir.TakeChannel(); |
| } |
| |
| zx_status_t AddService(const std::string& name) { |
| auto cb = [this, name](zx::channel channel, async_dispatcher_t* dispatcher) { |
| ++connection_ctr_[name]; |
| }; |
| return directory_.AddEntry(name, std::make_unique<vfs::Service>(cb)); |
| } |
| |
| vfs::PseudoDir directory_; |
| std::map<std::string, int> connection_ctr_; |
| }; |
| |
| class NamespaceProviderTest : public NamespaceTest { |
| protected: |
| NamespaceGuard MakeNamespace(ServiceListPtr additional_services) { |
| return NamespaceGuard( |
| fxl::MakeRefCounted<Namespace>(nullptr, std::move(additional_services), nullptr)); |
| } |
| |
| zx_status_t AddService(const std::string& name) { |
| fidl::InterfaceRequestHandler<fuchsia::io::Node> cb = |
| [this, name](fidl::InterfaceRequest<fuchsia::io::Node> request) { |
| ++connection_ctr_[name]; |
| }; |
| provider_.AddService(std::move(cb), name); |
| return ZX_OK; |
| } |
| |
| sys::testing::ServiceDirectoryProvider provider_; |
| std::map<std::string, int> connection_ctr_; |
| }; |
| |
| std::pair<std::string, int> StringIntPair(const std::string& s, int i) { return {s, i}; } |
| |
| TEST_F(NamespaceHostDirectoryTest, AdditionalServices) { |
| static constexpr char kService1[] = "fuchsia.test.TestService1"; |
| static constexpr char kService2[] = "fuchsia.test.TestService2"; |
| ServiceListPtr service_list(new ServiceList); |
| service_list->names.push_back(kService1); |
| service_list->names.push_back(kService2); |
| AddService(kService1); |
| AddService(kService2); |
| ServiceProviderPtr service_provider; |
| service_list->host_directory = OpenAsDirectory(); |
| auto ns = MakeNamespace(std::move(service_list)); |
| |
| zx::channel svc_dir = ns->OpenServicesAsDirectory(); |
| EXPECT_EQ(ZX_OK, ConnectToService(svc_dir.get(), kService1)); |
| EXPECT_EQ(ZX_OK, ConnectToService(svc_dir.get(), kService2)); |
| EXPECT_EQ(ZX_OK, ConnectToService(svc_dir.get(), kService2)); |
| // fdio_service_connect_at does not return an error if connection failed. |
| EXPECT_EQ(ZX_OK, ConnectToService(svc_dir.get(), "fuchsia.test.NotExists")); |
| RunLoopUntilIdle(); |
| std::vector<std::pair<std::string, int>> connection_ctr_vec; |
| for (const auto& e : connection_ctr_) { |
| connection_ctr_vec.push_back(e); |
| } |
| EXPECT_THAT(connection_ctr_vec, |
| ::testing::ElementsAre(StringIntPair(kService1, 1), StringIntPair(kService2, 2))); |
| } |
| |
| TEST_F(NamespaceHostDirectoryTest, AdditionalServices_InheritParent) { |
| static constexpr char kService1[] = "fuchsia.test.TestService1"; |
| static constexpr char kService2[] = "fuchsia.test.TestService2"; |
| ServiceListPtr parent_service_list(new ServiceList); |
| parent_service_list->names.push_back(kService1); |
| ServiceListPtr service_list(new ServiceList); |
| service_list->names.push_back(kService2); |
| AddService(kService1); |
| AddService(kService2); |
| parent_service_list->host_directory = OpenAsDirectory(); |
| service_list->host_directory = OpenAsDirectory(); |
| auto parent_ns = MakeNamespace(std::move(parent_service_list)); |
| auto ns = MakeNamespace(std::move(service_list), parent_ns); |
| |
| zx::channel svc_dir = ns->OpenServicesAsDirectory(); |
| EXPECT_EQ(ZX_OK, ConnectToService(svc_dir.get(), kService1)); |
| EXPECT_EQ(ZX_OK, ConnectToService(svc_dir.get(), kService2)); |
| // fdio_service_connect_at does not return an error if connection failed. |
| EXPECT_EQ(ZX_OK, ConnectToService(svc_dir.get(), "fuchsia.test.NotExists")); |
| RunLoopUntilIdle(); |
| std::vector<std::pair<std::string, int>> connection_ctr_vec; |
| for (const auto& e : connection_ctr_) { |
| connection_ctr_vec.push_back(e); |
| } |
| EXPECT_THAT(connection_ctr_vec, |
| ::testing::ElementsAre(StringIntPair(kService1, 1), StringIntPair(kService2, 1))); |
| } |
| |
| TEST_F(NamespaceProviderTest, AdditionalServices) { |
| static constexpr char kService1[] = "fuchsia.test.TestService1"; |
| static constexpr char kService2[] = "fuchsia.test.TestService2"; |
| ServiceListPtr service_list(new ServiceList); |
| ServiceProviderPtr service_provider; |
| service_list->names.push_back(kService1); |
| service_list->names.push_back(kService2); |
| EXPECT_EQ(ZX_OK, AddService(kService1)); |
| EXPECT_EQ(ZX_OK, AddService(kService2)); |
| |
| service_list->host_directory = provider_.service_directory()->CloneChannel().TakeChannel(); |
| auto ns = MakeNamespace(std::move(service_list)); |
| |
| zx::channel svc_dir = ns->OpenServicesAsDirectory(); |
| EXPECT_EQ(ZX_OK, ConnectToService(svc_dir.get(), kService1)); |
| EXPECT_EQ(ZX_OK, ConnectToService(svc_dir.get(), kService2)); |
| EXPECT_EQ(ZX_OK, ConnectToService(svc_dir.get(), kService2)); |
| // fdio_service_connect_at does not return an error if connection failed. |
| EXPECT_EQ(ZX_OK, ConnectToService(svc_dir.get(), "fuchsia.test.NotExists")); |
| RunLoopUntilIdle(); |
| std::vector<std::pair<std::string, int>> connection_ctr_vec; |
| for (const auto& e : connection_ctr_) { |
| connection_ctr_vec.push_back(e); |
| } |
| EXPECT_THAT(connection_ctr_vec, |
| ::testing::ElementsAre(StringIntPair(kService1, 1), StringIntPair(kService2, 2))); |
| } |
| |
| // test that service is connected even when namespace dies right after connect request. |
| TEST_F(NamespaceHostDirectoryTest, AdditionalServices_NsDies) { |
| static constexpr char kService1[] = "fuchsia.test.TestService1"; |
| static constexpr char kService2[] = "fuchsia.test.TestService2"; |
| ServiceListPtr service_list(new ServiceList); |
| service_list->names.push_back(kService1); |
| service_list->names.push_back(kService2); |
| AddService(kService1); |
| AddService(kService2); |
| ServiceProviderPtr service_provider; |
| service_list->host_directory = OpenAsDirectory(); |
| auto ns = MakeNamespace(std::move(service_list)); |
| |
| zx::channel svc_dir = ns->OpenServicesAsDirectory(); |
| EXPECT_EQ(ZX_OK, ConnectToService(svc_dir.get(), kService1)); |
| EXPECT_EQ(ZX_OK, ConnectToService(svc_dir.get(), kService2)); |
| EXPECT_EQ(ZX_OK, ConnectToService(svc_dir.get(), kService2)); |
| // fdio_service_connect_at does not return an error if connection failed. |
| EXPECT_EQ(ZX_OK, ConnectToService(svc_dir.get(), "fuchsia.test.NotExists")); |
| ns.Kill(); |
| RunLoopUntilIdle(); |
| std::vector<std::pair<std::string, int>> connection_ctr_vec; |
| for (const auto& e : connection_ctr_) { |
| connection_ctr_vec.push_back(e); |
| } |
| EXPECT_THAT(connection_ctr_vec, |
| ::testing::ElementsAre(StringIntPair(kService1, 1), StringIntPair(kService2, 2))); |
| connection_ctr_.clear(); |
| EXPECT_EQ(ZX_OK, ConnectToService(svc_dir.get(), kService1)); // cannot make anymore connections |
| RunLoopUntilIdle(); |
| // we should not see anymore processed conenction requests. |
| EXPECT_EQ(0u, connection_ctr_.size()); |
| } |
| |
| // test that service in parent is connected even when namespace dies right after connect request. |
| TEST_F(NamespaceHostDirectoryTest, AdditionalServices_InheritParent_nsDies) { |
| static constexpr char kService1[] = "fuchsia.test.TestService1"; |
| static constexpr char kService2[] = "fuchsia.test.TestService2"; |
| ServiceListPtr parent_service_list(new ServiceList); |
| parent_service_list->names.push_back(kService1); |
| ServiceListPtr service_list(new ServiceList); |
| service_list->names.push_back(kService2); |
| AddService(kService1); |
| AddService(kService2); |
| parent_service_list->host_directory = OpenAsDirectory(); |
| service_list->host_directory = OpenAsDirectory(); |
| auto parent_ns = MakeNamespace(std::move(parent_service_list)); |
| auto ns = MakeNamespace(std::move(service_list), parent_ns); |
| |
| zx::channel svc_dir = ns->OpenServicesAsDirectory(); |
| EXPECT_EQ(ZX_OK, ConnectToService(svc_dir.get(), kService1)); |
| EXPECT_EQ(ZX_OK, ConnectToService(svc_dir.get(), kService2)); |
| // fdio_service_connect_at does not return an error if connection failed. |
| EXPECT_EQ(ZX_OK, ConnectToService(svc_dir.get(), "fuchsia.test.NotExists")); |
| ns.Kill(); |
| RunLoopUntilIdle(); |
| std::vector<std::pair<std::string, int>> connection_ctr_vec; |
| for (const auto& e : connection_ctr_) { |
| connection_ctr_vec.push_back(e); |
| } |
| EXPECT_THAT(connection_ctr_vec, |
| ::testing::ElementsAre(StringIntPair(kService1, 1), StringIntPair(kService2, 1))); |
| } |
| |
| } // namespace |
| } // namespace component |