| // 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 <fuchsia/sys/internal/cpp/fidl.h> | |
| #include <lib/async/default.h> | |
| #include <lib/fdio/spawn.h> | |
| #include <lib/syslog/cpp/log_settings.h> | |
| #include <lib/syslog/cpp/macros.h> | |
| #include <zircon/syscalls/object.h> | |
| #include <fs/pseudo_dir.h> | |
| #include <fs/pseudo_file.h> | |
| #include <fs/remote_dir.h> | |
| #include <fs/synchronous_vfs.h> | |
| #include <gmock/gmock.h> | |
| #include <gtest/gtest.h> | |
| #include "lib/gtest/real_loop_fixture.h" | |
| #include "src/lib/fsl/handles/object_info.h" | |
| #include "src/lib/fxl/strings/split_string.h" | |
| #include "src/sys/appmgr/component_controller_impl.h" | |
| #include "src/sys/appmgr/util.h" | |
| namespace component { | |
| namespace { | |
| using fuchsia::sys::ServiceList; | |
| using fuchsia::sys::ServiceListPtr; | |
| using fuchsia::sys::TerminationReason; | |
| template <typename T> | |
| class ComponentContainerImpl : public ComponentContainer<T> { | |
| public: | |
| size_t ComponentCount() { return components_.size(); } | |
| const std::string koid() { return "5342"; } | |
| void AddComponent(std::unique_ptr<T> component); | |
| std::shared_ptr<T> ExtractComponent(T* controller) override; | |
| private: | |
| std::unordered_map<T*, std::shared_ptr<T>> components_; | |
| }; | |
| template <typename T> | |
| void ComponentContainerImpl<T>::AddComponent(std::unique_ptr<T> component) { | |
| auto key = component.get(); | |
| components_.emplace(key, std::move(component)); | |
| } | |
| template <typename T> | |
| std::shared_ptr<T> ComponentContainerImpl<T>::ExtractComponent(T* controller) { | |
| auto it = components_.find(controller); | |
| if (it == components_.end()) { | |
| return nullptr; | |
| } | |
| auto component = std::move(it->second); | |
| components_.erase(it); | |
| return component; | |
| } | |
| // Get a list of the default service entries that exist in every namespace. | |
| // See |Namespace::Namespace| constructor. | |
| std::vector<std::string> GetDefaultNamespaceServiceEntries() { | |
| return std::vector<std::string>{ | |
| ".", | |
| Namespace::Launcher::Name_, | |
| fuchsia::logger::LogSink::Name_, | |
| fuchsia::process::Launcher::Name_, | |
| fuchsia::process::Resolver::Name_, | |
| fuchsia::sys::Environment::Name_, | |
| fuchsia::sys::internal::LogConnector::Name_, | |
| }; | |
| } | |
| // Create a new Namespace that contains the default services available to all | |
| // namespaces, plus the given |service_names|. The resulting object is useful | |
| // for listing its service names but not much else. | |
| fxl::RefPtr<Namespace> CreateFakeNamespace(const std::vector<const char*>& extra_service_names) { | |
| ServiceListPtr service_list(new ServiceList()); | |
| for (auto& service : extra_service_names) { | |
| service_list->names.push_back(service); | |
| } | |
| return fxl::MakeRefCounted<Namespace>(nullptr, std::move(service_list), nullptr); | |
| } | |
| std::vector<std::string> SplitPath(const std::string& path) { | |
| return fxl::SplitStringCopy(path, "/", fxl::WhiteSpaceHandling::kKeepWhitespace, | |
| fxl::SplitResult::kSplitWantAll); | |
| } | |
| bool PathExists(const fbl::RefPtr<fs::PseudoDir>& hub_dir, std::string path, | |
| fbl::RefPtr<fs::Vnode>* out = nullptr) { | |
| auto tokens = SplitPath(path); | |
| auto ntokens = tokens.size(); | |
| fbl::RefPtr<fs::Vnode> dir = hub_dir; | |
| fbl::RefPtr<fs::Vnode> pdir; | |
| for (size_t i = 0; i < ntokens; i++) { | |
| auto token = tokens[i]; | |
| pdir = dir; | |
| if (pdir->Lookup(token, &dir) != ZX_OK) { | |
| return false; | |
| } | |
| } | |
| if (out != nullptr) { | |
| *out = dir; | |
| } | |
| return true; | |
| } | |
| // Get a list of names of the entries in a directory. This will generally | |
| // include at least "." (i.e. the current directory). | |
| std::vector<std::string> GetDirectoryEntries(fbl::RefPtr<fs::Vnode> dir) { | |
| std::vector<std::string> entry_names{}; | |
| // Arbitrary size. | |
| uint8_t buffer[4096]; | |
| fs::vdircookie_t cookie{}; | |
| // Actual number of bytes read into the buffer. | |
| size_t real_len = 0; | |
| while (dir->Readdir(&cookie, buffer, sizeof(buffer), &real_len) == ZX_OK && real_len > 0) { | |
| size_t offset = 0; | |
| while (offset < real_len) { | |
| auto dir_entry = reinterpret_cast<vdirent_t*>(buffer + offset); | |
| size_t entry_size = sizeof(vdirent_t) + dir_entry->size; | |
| std::string name(dir_entry->name, dir_entry->size); | |
| entry_names.push_back(name); | |
| offset += entry_size; | |
| } | |
| } | |
| return entry_names; | |
| } | |
| // Assert that the hub for the given component has "in", "in/svc", default | |
| // services, and the given extra service names. | |
| void AssertHubHasIncomingServices(const ComponentControllerBase* component, | |
| const std::vector<std::string>& extra_service_names) { | |
| ASSERT_TRUE(PathExists(component->hub_dir(), "in")); | |
| ASSERT_TRUE(PathExists(component->hub_dir(), "in/pkg")); | |
| fbl::RefPtr<fs::Vnode> in_svc_dir; | |
| ASSERT_TRUE(PathExists(component->hub_dir(), "in/svc", &in_svc_dir)); | |
| for (auto& service : extra_service_names) { | |
| EXPECT_TRUE(PathExists(component->hub_dir(), "in/svc/" + service)); | |
| } | |
| // Default entries from namespace, plus the two we added. | |
| auto expected_entries = GetDefaultNamespaceServiceEntries(); | |
| expected_entries.insert(expected_entries.end(), extra_service_names.begin(), | |
| extra_service_names.end()); | |
| EXPECT_THAT(GetDirectoryEntries(in_svc_dir), | |
| ::testing::UnorderedElementsAreArray(expected_entries)); | |
| } | |
| typedef ComponentContainerImpl<ComponentControllerImpl> FakeRealm; | |
| typedef ComponentContainerImpl<ComponentBridge> FakeRunner; | |
| class ComponentControllerTest : public gtest::RealLoopFixture { | |
| public: | |
| void SetUp() override { | |
| gtest::RealLoopFixture::SetUp(); | |
| vfs_.SetDispatcher(async_get_default_dispatcher()); | |
| pkg_vfs_.SetDispatcher(async_get_default_dispatcher()); | |
| // create child job | |
| zx_status_t status = zx::job::create(*zx::job::default_job(), 0u, &job_); | |
| ASSERT_EQ(status, ZX_OK); | |
| // create process | |
| const char* argv[] = {"sleep", "999999999", NULL}; | |
| char err_msg[FDIO_SPAWN_ERR_MSG_MAX_LENGTH]; | |
| status = fdio_spawn_etc(job_.get(), FDIO_SPAWN_CLONE_ALL, "/bin/sleep", argv, NULL, 0, NULL, | |
| process_.reset_and_get_address(), err_msg); | |
| ASSERT_EQ(status, ZX_OK) << err_msg; | |
| process_koid_ = std::to_string(fsl::GetKoid(process_.get())); | |
| } | |
| void TearDown() override { | |
| if (job_) { | |
| job_.kill(); | |
| } | |
| gtest::RealLoopFixture::TearDown(); | |
| } | |
| protected: | |
| std::unique_ptr<ComponentControllerImpl> CreateComponent( | |
| fuchsia::sys::ComponentControllerPtr& controller, zx::channel export_dir = zx::channel(), | |
| zx::channel pkg_dir = zx::channel(), fxl::RefPtr<Namespace> ns = CreateFakeNamespace({})) { | |
| // job_ is used later in a test to check the job-id, so we need to make a | |
| // clone of it to pass into std::move | |
| zx::job job_clone; | |
| zx_status_t status = job_.duplicate(ZX_RIGHT_SAME_RIGHTS, &job_clone); | |
| if (status != ZX_OK) { | |
| return nullptr; | |
| } | |
| return std::make_unique<ComponentControllerImpl>( | |
| controller.NewRequest(), &realm_, std::move(job_clone), std::move(process_), "test-url", | |
| "test-arg", "test-label", ns, std::move(export_dir), zx::channel(), std::move(pkg_dir)); | |
| } | |
| FakeRealm realm_; | |
| zx::job job_; | |
| std::string process_koid_; | |
| zx::process process_; | |
| fs::SynchronousVfs vfs_; | |
| fs::SynchronousVfs pkg_vfs_; | |
| }; | |
| class ComponentBridgeTest : public gtest::RealLoopFixture, | |
| public fuchsia::sys::ComponentController { | |
| public: | |
| ComponentBridgeTest() : binding_(this), binding_error_handler_called_(false) {} | |
| void SetUp() override { | |
| gtest::RealLoopFixture::SetUp(); | |
| vfs_.SetDispatcher(async_get_default_dispatcher()); | |
| pkg_vfs_.SetDispatcher(async_get_default_dispatcher()); | |
| binding_.Bind(remote_controller_.NewRequest()); | |
| binding_.set_error_handler([this](zx_status_t status) { | |
| binding_error_handler_called_ = true; | |
| Kill(); | |
| }); | |
| } | |
| void Kill() override { | |
| SendReturnCode(); | |
| binding_.Unbind(); | |
| } | |
| void Detach() override { binding_.set_error_handler(nullptr); } | |
| protected: | |
| std::unique_ptr<ComponentBridge> CreateComponentBridge( | |
| fuchsia::sys::ComponentControllerPtr& controller, zx::channel export_dir = zx::channel(), | |
| zx::channel package_handle = zx::channel(), | |
| fxl::RefPtr<Namespace> ns = CreateFakeNamespace({})) { | |
| // only allow creation of one component. | |
| if (!remote_controller_) { | |
| return nullptr; | |
| } | |
| auto component = std::make_unique<ComponentBridge>( | |
| controller.NewRequest(), std::move(remote_controller_), &runner_, "test-url", "test-arg", | |
| "test-label", "1", ns, std::move(export_dir), zx::channel(), std::move(package_handle)); | |
| component->SetParentJobId(runner_.koid()); | |
| return component; | |
| } | |
| void SetReturnCode(int64_t errcode) { errcode_ = errcode; } | |
| void SendReady() { binding_.events().OnDirectoryReady(); } | |
| void SendReturnCode() { binding_.events().OnTerminated(errcode_, TerminationReason::EXITED); } | |
| FakeRunner runner_; | |
| ::fidl::Binding<fuchsia::sys::ComponentController> binding_; | |
| fuchsia::sys::ComponentControllerPtr remote_controller_; | |
| fs::SynchronousVfs vfs_; | |
| fs::SynchronousVfs pkg_vfs_; | |
| int64_t errcode_ = 1; | |
| bool binding_error_handler_called_; | |
| }; | |
| fbl::String get_value(const fbl::RefPtr<fs::PseudoDir>& hub_dir, std::string path) { | |
| auto tokens = SplitPath(path); | |
| auto ntokens = tokens.size(); | |
| fbl::RefPtr<fs::Vnode> node = hub_dir; | |
| fbl::RefPtr<fs::Vnode> pdir; | |
| for (size_t i = 0; i < ntokens; i++) { | |
| auto token = tokens[i]; | |
| pdir = node; | |
| if (pdir->Lookup(token, &node) != ZX_OK) { | |
| EXPECT_FALSE(true) << token << " not found"; | |
| return ""; | |
| } | |
| } | |
| fbl::RefPtr<fs::Vnode> file; | |
| auto validated_options = node->ValidateOptions(fs::VnodeConnectionOptions::ReadOnly()); | |
| if (validated_options.is_error()) { | |
| EXPECT_FALSE(true) << "validate option failed: " << validated_options.error(); | |
| } | |
| if (node->Open(validated_options.value(), &file) != ZX_OK) { | |
| EXPECT_FALSE(true) << "cannot open: " << path; | |
| return ""; | |
| } | |
| char buf[1024]; | |
| size_t read_len; | |
| file->Read(buf, sizeof(buf), 0, &read_len); | |
| return fbl::String(buf, read_len); | |
| } | |
| TEST_F(ComponentControllerTest, CreateAndKill) { | |
| fuchsia::sys::ComponentControllerPtr component_ptr; | |
| auto component = CreateComponent(component_ptr); | |
| auto hub_info = component->HubInfo(); | |
| EXPECT_EQ(hub_info.label(), "test-label"); | |
| EXPECT_EQ(hub_info.koid(), process_koid_); | |
| ASSERT_EQ(realm_.ComponentCount(), 0u); | |
| realm_.AddComponent(std::move(component)); | |
| ASSERT_EQ(realm_.ComponentCount(), 1u); | |
| bool wait = false; | |
| int64_t return_code; | |
| TerminationReason termination_reason; | |
| component_ptr.events().OnTerminated = [&](int64_t err, TerminationReason reason) { | |
| return_code = err; | |
| termination_reason = reason; | |
| wait = true; | |
| }; | |
| component_ptr->Kill(); | |
| RunLoopUntil([&wait] { return wait; }); | |
| // make sure all messages are processed after wait was called | |
| RunLoopUntilIdle(); | |
| EXPECT_EQ(ZX_TASK_RETCODE_SYSCALL_KILL, return_code); | |
| EXPECT_EQ(TerminationReason::EXITED, termination_reason); | |
| EXPECT_EQ(realm_.ComponentCount(), 0u); | |
| } | |
| TEST_F(ComponentControllerTest, CreateAndDeleteWithoutKilling) { | |
| fuchsia::sys::ComponentControllerPtr component_ptr; | |
| int64_t return_code = 0; | |
| TerminationReason termination_reason = TerminationReason::INTERNAL_ERROR; | |
| auto component = CreateComponent(component_ptr); | |
| auto* component_to_remove = component.get(); | |
| realm_.AddComponent(std::move(component)); | |
| component_ptr.events().OnTerminated = [&](int64_t err, TerminationReason reason) { | |
| return_code = err; | |
| termination_reason = reason; | |
| }; | |
| realm_.ExtractComponent(component_to_remove); | |
| RunLoopUntil([&return_code] { return return_code; }); | |
| // make sure all messages are processed after wait was called | |
| RunLoopUntilIdle(); | |
| EXPECT_EQ(-1, return_code); | |
| EXPECT_EQ(TerminationReason::UNKNOWN, termination_reason); | |
| EXPECT_EQ(realm_.ComponentCount(), 0u); | |
| } | |
| TEST_F(ComponentControllerTest, ControllerScope) { | |
| { | |
| fuchsia::sys::ComponentControllerPtr component_ptr; | |
| auto component = CreateComponent(component_ptr); | |
| realm_.AddComponent(std::move(component)); | |
| ASSERT_EQ(realm_.ComponentCount(), 1u); | |
| } | |
| RunLoopUntil([this]() { return realm_.ComponentCount() == 0; }); | |
| } | |
| TEST_F(ComponentControllerTest, DetachController) { | |
| syslog::SetLogSettings({.min_log_level = -2}); | |
| bool wait = false; | |
| { | |
| fuchsia::sys::ComponentControllerPtr component_ptr; | |
| auto component = CreateComponent(component_ptr); | |
| component_ptr.events().OnTerminated = [&](int64_t return_code, | |
| TerminationReason termination_reason) { | |
| FX_LOGS(ERROR) << "OnTerminated called: " << return_code | |
| << ", : " << static_cast<uint32_t>(termination_reason); | |
| wait = true; | |
| }; | |
| realm_.AddComponent(std::move(component)); | |
| ASSERT_EQ(realm_.ComponentCount(), 1u); | |
| // detach controller before it goes out of scope and then test that our | |
| // component did not die. | |
| component_ptr->Detach(); | |
| RunLoopUntilIdle(); | |
| EXPECT_FALSE(wait) << "Please please please report logs from this failure to fxbug.dev/8292."; | |
| } | |
| // make sure all messages are processed if Kill was called. | |
| RunLoopUntilIdle(); | |
| EXPECT_FALSE(wait) << "Please please please report logs from this failure to fxbug.dev/8292."; | |
| EXPECT_EQ(realm_.ComponentCount(), 1u) | |
| << "Please please please report logs from this failure to fxbug.dev/8292."; | |
| } | |
| TEST_F(ComponentControllerTest, Hub) { | |
| zx::channel export_dir, export_dir_req; | |
| ASSERT_EQ(zx::channel::create(0, &export_dir, &export_dir_req), ZX_OK); | |
| vfs_.ServeDirectory(fbl::MakeRefCounted<fs::PseudoDir>(), std::move(export_dir)); | |
| fuchsia::sys::ComponentControllerPtr component_ptr; | |
| zx::channel pkg_dir, pkg_dir_req; | |
| ASSERT_EQ(zx::channel::create(0, &pkg_dir, &pkg_dir_req), ZX_OK); | |
| auto component = | |
| CreateComponent(component_ptr, std::move(export_dir_req), std::move(pkg_dir_req)); | |
| bool ready = false; | |
| component_ptr.events().OnDirectoryReady = [&ready] { ready = true; }; | |
| RunLoopUntil([&ready] { return ready; }); | |
| EXPECT_TRUE(PathExists(component->hub_dir(), "out")); | |
| EXPECT_STREQ(get_value(component->hub_dir(), "name").c_str(), "test-label"); | |
| EXPECT_STREQ(get_value(component->hub_dir(), "args").c_str(), "test-arg"); | |
| EXPECT_STREQ(get_value(component->hub_dir(), "job-id").c_str(), | |
| std::to_string(fsl::GetKoid(job_.get())).c_str()); | |
| EXPECT_STREQ(get_value(component->hub_dir(), "url").c_str(), "test-url"); | |
| EXPECT_STREQ(get_value(component->hub_dir(), "process-id").c_str(), process_koid_.c_str()); | |
| // "in", "in/svc", and default services should exist. | |
| AssertHubHasIncomingServices(component.get(), {}); | |
| fbl::RefPtr<fs::Vnode> out_dir; | |
| ASSERT_TRUE(PathExists(component->hub_dir(), "out", &out_dir)); | |
| ASSERT_TRUE(out_dir->IsRemote()); | |
| } | |
| TEST_F(ComponentControllerTest, HubWithIncomingServices) { | |
| zx::channel export_dir, export_dir_req; | |
| ASSERT_EQ(zx::channel::create(0, &export_dir, &export_dir_req), ZX_OK); | |
| vfs_.ServeDirectory(fbl::MakeRefCounted<fs::PseudoDir>(), std::move(export_dir)); | |
| fuchsia::sys::ComponentControllerPtr component_ptr; | |
| fxl::RefPtr<Namespace> ns = CreateFakeNamespace({"service_a", "service_b"}); | |
| zx::channel pkg_dir, pkg_dir_req; | |
| ASSERT_EQ(zx::channel::create(0, &pkg_dir, &pkg_dir_req), ZX_OK); | |
| auto component = | |
| CreateComponent(component_ptr, std::move(export_dir_req), std::move(pkg_dir_req), ns); | |
| bool ready = false; | |
| component_ptr.events().OnDirectoryReady = [&ready] { ready = true; }; | |
| RunLoopUntil([&ready] { return ready; }); | |
| AssertHubHasIncomingServices(component.get(), {"service_a", "service_b"}); | |
| } | |
| TEST_F(ComponentControllerTest, GetDiagnosticsDirExists) { | |
| auto out_dir = fbl::MakeRefCounted<fs::PseudoDir>(); | |
| zx::channel export_dir, export_dir_req; | |
| ASSERT_EQ(zx::channel::create(0, &export_dir, &export_dir_req), ZX_OK); | |
| vfs_.ServeDirectory(out_dir, std::move(export_dir)); | |
| fuchsia::sys::ComponentControllerPtr component_ptr; | |
| auto component = CreateComponent(component_ptr, std::move(export_dir_req)); | |
| bool ready = false; | |
| component_ptr.events().OnDirectoryReady = [&ready] { ready = true; }; | |
| RunLoopUntil([&ready] { return ready; }); | |
| auto diagnostics_dir = new fs::PseudoDir(); | |
| auto test_file = fbl::AdoptRef<fs::Vnode>(new fs::UnbufferedPseudoFile()); | |
| ASSERT_EQ(ZX_OK, diagnostics_dir->AddEntry("test_file", test_file)); | |
| ASSERT_EQ(ZX_OK, | |
| out_dir->AddEntry("diagnostics", fbl::AdoptRef<fs::Vnode>(std::move(diagnostics_dir)))); | |
| bool done = false; | |
| async::Executor executor(async_get_default_dispatcher()); | |
| fidl::InterfaceHandle<fuchsia::io::Directory> directory_handle; | |
| executor.schedule_task(component->GetDiagnosticsDir().then( | |
| [&](fit::result<fidl::InterfaceHandle<fuchsia::io::Directory>, zx_status_t>& result) { | |
| EXPECT_TRUE(result.is_ok()); | |
| directory_handle = std::move(result.value()); | |
| done = true; | |
| })); | |
| RunLoopUntil([&done] { return done; }); | |
| // Test the directory contains only our test file. | |
| fuchsia::io::DirectoryPtr directory = directory_handle.Bind(); | |
| std::vector<std::string> entry_names; | |
| zx_status_t status; | |
| std::vector<uint8_t> buffer; | |
| done = false; | |
| directory->ReadDirents( | |
| fuchsia::io::MAX_BUF, | |
| [&status, &buffer, &done](zx_status_t read_status, std::vector<uint8_t> read_buffer) { | |
| buffer = std::move(read_buffer); | |
| status = std::move(read_status); | |
| done = true; | |
| }); | |
| RunLoopUntil([&done] { return done; }); | |
| EXPECT_EQ(status, ZX_OK); | |
| EXPECT_FALSE(buffer.empty()); | |
| size_t offset = 0; | |
| auto data_ptr = buffer.data(); | |
| while (sizeof(vdirent_t) < buffer.size() - offset) { | |
| vdirent_t* entry = reinterpret_cast<vdirent_t*>(data_ptr + offset); | |
| std::string name(entry->name, entry->size); | |
| entry_names.push_back(name); | |
| offset += sizeof(vdirent_t) + entry->size; | |
| } | |
| EXPECT_THAT(entry_names, testing::UnorderedElementsAre(".", "test_file")); | |
| } | |
| TEST_F(ComponentControllerTest, GetDiagnosticsDirMissing) { | |
| zx::channel export_dir, export_dir_req; | |
| ASSERT_EQ(zx::channel::create(0, &export_dir, &export_dir_req), ZX_OK); | |
| vfs_.ServeDirectory(fbl::MakeRefCounted<fs::PseudoDir>(), std::move(export_dir)); | |
| fuchsia::sys::ComponentControllerPtr component_ptr; | |
| auto component = CreateComponent(component_ptr, std::move(export_dir_req)); | |
| bool ready = false; | |
| component_ptr.events().OnDirectoryReady = [&ready] { ready = true; }; | |
| RunLoopUntil([&ready] { return ready; }); | |
| bool done = false; | |
| async::Executor executor(async_get_default_dispatcher()); | |
| executor.schedule_task(component->GetDiagnosticsDir().then( | |
| [&](fit::result<fidl::InterfaceHandle<fuchsia::io::Directory>, zx_status_t>& result) { | |
| EXPECT_TRUE(result.is_error()); | |
| done = true; | |
| })); | |
| RunLoopUntil([&done] { return done; }); | |
| } | |
| TEST_F(ComponentBridgeTest, CreateAndKill) { | |
| fuchsia::sys::ComponentControllerPtr component_ptr; | |
| auto component = CreateComponentBridge(component_ptr); | |
| auto hub_info = component->HubInfo(); | |
| EXPECT_EQ(hub_info.label(), "test-label"); | |
| ASSERT_EQ(runner_.ComponentCount(), 0u); | |
| runner_.AddComponent(std::move(component)); | |
| ASSERT_EQ(runner_.ComponentCount(), 1u); | |
| bool wait = false; | |
| bool ready = false; | |
| int64_t retval; | |
| TerminationReason termination_reason; | |
| component_ptr.events().OnTerminated = [&wait, &retval, &termination_reason]( | |
| int64_t errcode, TerminationReason tr) { | |
| wait = true; | |
| retval = errcode; | |
| termination_reason = tr; | |
| }; | |
| component_ptr.events().OnDirectoryReady = [&ready] { ready = true; }; | |
| int64_t expected_retval = (1L << 60); | |
| SendReady(); | |
| SetReturnCode(expected_retval); | |
| component_ptr->Kill(); | |
| RunLoopUntil([&wait] { return wait; }); | |
| EXPECT_TRUE(ready); | |
| EXPECT_EQ(expected_retval, retval); | |
| EXPECT_EQ(TerminationReason::EXITED, termination_reason); | |
| // make sure all messages are processed after wait was called | |
| RunLoopUntilIdle(); | |
| EXPECT_EQ(runner_.ComponentCount(), 0u); | |
| } | |
| TEST_F(ComponentBridgeTest, CreateAndDeleteWithoutKilling) { | |
| fuchsia::sys::ComponentControllerPtr component_ptr; | |
| auto component = CreateComponentBridge(component_ptr); | |
| auto* component_to_remove = component.get(); | |
| component->SetTerminationReason(TerminationReason::INTERNAL_ERROR); | |
| runner_.AddComponent(std::move(component)); | |
| bool terminated = false; | |
| int64_t retval = 0; | |
| TerminationReason termination_reason; | |
| component_ptr.events().OnTerminated = [&](int64_t errcode, TerminationReason tr) { | |
| terminated = true; | |
| retval = errcode; | |
| termination_reason = tr; | |
| }; | |
| // Component controller called OnTerminated before the component is destroyed, | |
| // so we expect the value set above (INTERNAL_ERROR). | |
| runner_.ExtractComponent(component_to_remove); | |
| RunLoopUntil([&terminated] { return terminated; }); | |
| EXPECT_EQ(-1, retval); | |
| EXPECT_EQ(TerminationReason::INTERNAL_ERROR, termination_reason); | |
| // make sure all messages are processed after wait was called | |
| RunLoopUntilIdle(); | |
| EXPECT_EQ(runner_.ComponentCount(), 0u); | |
| } | |
| TEST_F(ComponentBridgeTest, RemoteComponentDied) { | |
| fuchsia::sys::ComponentControllerPtr component_ptr; | |
| auto component = CreateComponentBridge(component_ptr); | |
| component->SetTerminationReason(TerminationReason::EXITED); | |
| runner_.AddComponent(std::move(component)); | |
| bool terminated = false; | |
| int64_t retval = 0; | |
| TerminationReason termination_reason; | |
| component_ptr.events().OnTerminated = [&](int64_t errcode, TerminationReason tr) { | |
| terminated = true; | |
| retval = errcode; | |
| termination_reason = tr; | |
| }; | |
| // Even though the termination reason was set above, unbinding and closing the | |
| // channel will cause the bridge to return UNKNOWN>. | |
| binding_.Unbind(); | |
| RunLoopUntil([&terminated] { return terminated; }); | |
| EXPECT_EQ(-1, retval); | |
| EXPECT_EQ(TerminationReason::UNKNOWN, termination_reason); | |
| EXPECT_EQ(0u, runner_.ComponentCount()); | |
| // make sure all messages are processed after wait was called | |
| RunLoopUntilIdle(); | |
| EXPECT_EQ(runner_.ComponentCount(), 0u); | |
| } | |
| TEST_F(ComponentBridgeTest, ControllerScope) { | |
| bool wait = false; | |
| { | |
| fuchsia::sys::ComponentControllerPtr component_ptr; | |
| auto component = CreateComponentBridge(component_ptr); | |
| component->OnTerminated( | |
| [&wait](int64_t return_code, TerminationReason termination_reason) { wait = true; }); | |
| runner_.AddComponent(std::move(component)); | |
| ASSERT_EQ(runner_.ComponentCount(), 1u); | |
| } | |
| RunLoopUntil([&wait] { return wait; }); | |
| // make sure all messages are processed after wait was called | |
| RunLoopUntilIdle(); | |
| EXPECT_EQ(runner_.ComponentCount(), 0u); | |
| } | |
| TEST_F(ComponentBridgeTest, DetachController) { | |
| bool wait = false; | |
| ComponentBridge* component_bridge_ptr; | |
| { | |
| fuchsia::sys::ComponentControllerPtr component_ptr; | |
| auto component = CreateComponentBridge(component_ptr); | |
| component_bridge_ptr = component.get(); | |
| runner_.AddComponent(std::move(component)); | |
| ASSERT_EQ(runner_.ComponentCount(), 1u); | |
| // detach controller before it goes out of scope and then test that our | |
| // component did not die. | |
| component_ptr->Detach(); | |
| RunLoopUntilIdle(); | |
| } | |
| // make sure all messages are processed if Kill was called. | |
| RunLoopUntilIdle(); | |
| ASSERT_FALSE(wait); | |
| EXPECT_EQ(runner_.ComponentCount(), 1u); | |
| // bridge should be still connected, kill that to see if we are able to kill | |
| // real component. | |
| component_bridge_ptr->OnTerminated( | |
| [&wait](int64_t return_code, TerminationReason termination_reason) { wait = true; }); | |
| component_bridge_ptr->Kill(); | |
| RunLoopUntil([&wait] { return wait; }); | |
| // make sure all messages are processed after wait was called | |
| RunLoopUntilIdle(); | |
| EXPECT_EQ(runner_.ComponentCount(), 0u); | |
| } | |
| TEST_F(ComponentBridgeTest, Hub) { | |
| zx::channel export_dir, export_dir_req; | |
| ASSERT_EQ(zx::channel::create(0, &export_dir, &export_dir_req), ZX_OK); | |
| vfs_.ServeDirectory(fbl::MakeRefCounted<fs::PseudoDir>(), std::move(export_dir)); | |
| fuchsia::sys::ComponentControllerPtr component_ptr; | |
| zx::channel pkg_dir, pkg_dir_req; | |
| ASSERT_EQ(zx::channel::create(0, &pkg_dir, &pkg_dir_req), ZX_OK); | |
| auto component = | |
| CreateComponentBridge(component_ptr, std::move(export_dir_req), std::move(pkg_dir_req)); | |
| RunLoopUntil([&component] { return PathExists(component->hub_dir(), "out"); }); | |
| EXPECT_STREQ(get_value(component->hub_dir(), "name").c_str(), "test-label"); | |
| EXPECT_STREQ(get_value(component->hub_dir(), "args").c_str(), "test-arg"); | |
| EXPECT_STREQ(get_value(component->hub_dir(), "job-id").c_str(), runner_.koid().c_str()); | |
| EXPECT_STREQ(get_value(component->hub_dir(), "url").c_str(), "test-url"); | |
| fbl::RefPtr<fs::Vnode> out_dir; | |
| ASSERT_TRUE(PathExists(component->hub_dir(), "out", &out_dir)); | |
| ASSERT_TRUE(out_dir->IsRemote()); | |
| // "in", "in/svc", and default services should exist. | |
| AssertHubHasIncomingServices(component.get(), {}); | |
| } | |
| TEST_F(ComponentBridgeTest, HubWithIncomingServices) { | |
| zx::channel export_dir, export_dir_req; | |
| ASSERT_EQ(zx::channel::create(0, &export_dir, &export_dir_req), ZX_OK); | |
| vfs_.ServeDirectory(fbl::MakeRefCounted<fs::PseudoDir>(), std::move(export_dir)); | |
| fuchsia::sys::ComponentControllerPtr component_ptr; | |
| fxl::RefPtr<Namespace> ns = CreateFakeNamespace({"service_a", "service_b"}); | |
| zx::channel pkg_dir, pkg_dir_req; | |
| ASSERT_EQ(zx::channel::create(0, &pkg_dir, &pkg_dir_req), ZX_OK); | |
| auto component = | |
| CreateComponentBridge(component_ptr, std::move(export_dir_req), std::move(pkg_dir_req), ns); | |
| RunLoopUntil([&component] { return PathExists(component->hub_dir(), "out"); }); | |
| AssertHubHasIncomingServices(component.get(), {"service_a", "service_b"}); | |
| } | |
| TEST_F(ComponentBridgeTest, BindingErrorHandler) { | |
| zx::channel export_dir, export_dir_req; | |
| ASSERT_EQ(zx::channel::create(0, &export_dir, &export_dir_req), ZX_OK); | |
| fuchsia::sys::ComponentControllerPtr component_ptr; | |
| { | |
| // let it go out of scope, that should trigger binding error handler. | |
| auto component = CreateComponentBridge(component_ptr, std::move(export_dir_req)); | |
| } | |
| RunLoopUntil([this] { return !binding_.is_bound(); }); | |
| EXPECT_TRUE(binding_error_handler_called_); | |
| } | |
| TEST_F(ComponentBridgeTest, BindingErrorHandlerWhenDetached) { | |
| zx::channel export_dir, export_dir_req; | |
| ASSERT_EQ(zx::channel::create(0, &export_dir, &export_dir_req), ZX_OK); | |
| fuchsia::sys::ComponentControllerPtr component_ptr; | |
| { | |
| // let it go out of scope, that should trigger binding error handler. | |
| auto component = CreateComponentBridge(component_ptr, std::move(export_dir_req)); | |
| component_ptr->Detach(); | |
| RunLoopUntilIdle(); | |
| } | |
| RunLoopUntil([this] { return !binding_.is_bound(); }); | |
| EXPECT_TRUE(binding_error_handler_called_); | |
| } | |
| TEST(ComponentControllerUnitTest, GetDirectoryEntries) { | |
| auto dir = fbl::AdoptRef<fs::PseudoDir>(new fs::PseudoDir()); | |
| auto subdir = fbl::AdoptRef<fs::Vnode>(new fs::PseudoDir()); | |
| auto file1 = fbl::AdoptRef<fs::Vnode>(new fs::UnbufferedPseudoFile()); | |
| auto file2 = fbl::AdoptRef<fs::Vnode>(new fs::UnbufferedPseudoFile()); | |
| // add entries | |
| EXPECT_EQ(ZX_OK, dir->AddEntry("subdir", subdir)); | |
| EXPECT_EQ(ZX_OK, dir->AddEntry("file1", file1)); | |
| EXPECT_EQ(ZX_OK, dir->AddEntry("file2", file2)); | |
| const std::vector<std::string> entries = GetDirectoryEntries(dir); | |
| EXPECT_THAT(entries, ::testing::ElementsAre(".", "subdir", "file1", "file2")); | |
| } | |
| } // namespace | |
| } // namespace component |