| // 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 <unistd.h> |
| |
| #include <fidl/examples/echo/cpp/fidl.h> |
| |
| #include "garnet/bin/appmgr/integration_tests/mock_runner_registry.h" |
| #include "lib/component/cpp/testing/test_util.h" |
| #include "lib/component/cpp/testing/test_with_environment.h" |
| #include "lib/fxl/files/glob.h" |
| #include "lib/fxl/files/path.h" |
| #include "lib/fxl/strings/string_printf.h" |
| |
| namespace component { |
| namespace { |
| |
| using fuchsia::sys::TerminationReason; |
| using test::component::mockrunner::MockComponentPtr; |
| using test::component::mockrunner::MockComponentSyncPtr; |
| using testing::EnclosingEnvironment; |
| using testing::MockRunnerRegistry; |
| using testing::TestWithEnvironment; |
| |
| const char kRealm[] = "realmrunnerintegrationtest"; |
| const auto kTimeout = zx::sec(5); |
| const char kComponentForRunner[] = "fuchsia-pkg://fuchsia.com/fake_component_for_runner#meta/fake_component_for_runner.cmx"; |
| const char kComponentForRunnerProcessName[] = "fake_component_for_runner.cmx"; |
| |
| class RealmRunnerTest : public TestWithEnvironment { |
| protected: |
| void SetUp() override { |
| TestWithEnvironment::SetUp(); |
| auto services = CreateServices(); |
| ASSERT_EQ(ZX_OK, services->AddService(runner_registry_.GetHandler())); |
| enclosing_environment_ = |
| CreateNewEnclosingEnvironment(kRealm, std::move(services)); |
| ASSERT_TRUE(WaitForEnclosingEnvToStart(enclosing_environment_.get())); |
| } |
| |
| std::pair<std::unique_ptr<EnclosingEnvironment>, |
| std::unique_ptr<MockRunnerRegistry>> |
| MakeNestedEnvironment(const fuchsia::sys::EnvironmentOptions& options) { |
| fuchsia::sys::EnvironmentPtr env; |
| enclosing_environment_->ConnectToService(fuchsia::sys::Environment::Name_, |
| env.NewRequest().TakeChannel()); |
| auto registry = std::make_unique<MockRunnerRegistry>(); |
| auto services = testing::EnvironmentServices::Create(env); |
| EXPECT_EQ(ZX_OK, services->AddService(registry->GetHandler())); |
| auto nested_environment = EnclosingEnvironment::Create( |
| "nested-environment", env, std::move(services), std::move(options)); |
| EXPECT_TRUE(WaitForEnclosingEnvToStart(nested_environment.get())); |
| return std::make_pair(std::move(nested_environment), std::move(registry)); |
| } |
| |
| bool WaitForRunnerToRegister(MockRunnerRegistry* runner_registry = nullptr) { |
| if (!runner_registry) { |
| runner_registry = &runner_registry_; |
| } |
| const bool ret = RunLoopWithTimeoutOrUntil( |
| [&] { return runner_registry->runner(); }, kTimeout); |
| EXPECT_TRUE(ret) << "Waiting for connection timed out: " |
| << runner_registry->connect_count(); |
| return ret; |
| } |
| |
| fuchsia::sys::LaunchInfo CreateLaunchInfo(const std::string& url) { |
| fuchsia::sys::LaunchInfo launch_info; |
| launch_info.url = url; |
| return launch_info; |
| } |
| |
| bool WaitForRunnerToDie() { |
| const bool ret = RunLoopWithTimeoutOrUntil( |
| [&] { return !runner_registry_.runner(); }, kTimeout); |
| EXPECT_TRUE(ret) << "Waiting for connection timed out: " |
| << runner_registry_.dead_runner_count(); |
| return ret; |
| } |
| |
| bool WaitForComponentCount(size_t expected_components_count) { |
| return WaitForComponentCount(&runner_registry_, expected_components_count); |
| } |
| |
| bool WaitForComponentCount(MockRunnerRegistry* runner_registry, |
| size_t expected_components_count) { |
| auto runner = runner_registry->runner(); |
| const bool ret = RunLoopWithTimeoutOrUntil( |
| [&] { |
| return runner->components().size() == expected_components_count; |
| }, |
| kTimeout); |
| EXPECT_TRUE(ret) << "Waiting for component to start/die timed out, got:" |
| << runner->components().size() |
| << ", expected: " << expected_components_count; |
| return ret; |
| } |
| |
| std::unique_ptr<EnclosingEnvironment> enclosing_environment_; |
| MockRunnerRegistry runner_registry_; |
| }; |
| |
| TEST_F(RealmRunnerTest, RunnerLaunched) { |
| auto component = |
| enclosing_environment_->CreateComponentFromUrl(kComponentForRunner); |
| ASSERT_TRUE(WaitForRunnerToRegister()); |
| ASSERT_TRUE(WaitForComponentCount(1)); |
| auto components = runner_registry_.runner()->components(); |
| ASSERT_EQ(components[0].url, kComponentForRunner); |
| } |
| |
| TEST_F(RealmRunnerTest, RunnerLaunchedOnlyOnce) { |
| auto component1 = |
| enclosing_environment_->CreateComponentFromUrl(kComponentForRunner); |
| ASSERT_TRUE(WaitForRunnerToRegister()); |
| // launch again and check that runner was not executed again |
| auto component2 = |
| enclosing_environment_->CreateComponentFromUrl(kComponentForRunner); |
| |
| WaitForComponentCount(2); |
| EXPECT_EQ(1, runner_registry_.connect_count()); |
| } |
| |
| TEST_F(RealmRunnerTest, RunnerLaunchedAgainWhenKilled) { |
| auto component = |
| enclosing_environment_->CreateComponentFromUrl(kComponentForRunner); |
| ASSERT_TRUE(WaitForRunnerToRegister()); |
| |
| auto glob_str = |
| fxl::StringPrintf("/hub/r/%s/*/c/appmgr_mock_runner.cmx/*", kRealm); |
| files::Glob glob(glob_str); |
| ASSERT_EQ(glob.size(), 1u); |
| std::string runner_path_in_hub = *(glob.begin()); |
| |
| int64_t return_code = INT64_MIN; |
| component.events().OnTerminated = |
| [&](int64_t code, TerminationReason reason) { return_code = code; }; |
| runner_registry_.runner()->runner_ptr()->Crash(); |
| ASSERT_TRUE(WaitForRunnerToDie()); |
| // Make sure component is dead. |
| ASSERT_TRUE(RunLoopWithTimeoutOrUntil( |
| [&] { return return_code != INT64_MIN; }, kTimeout)); |
| |
| // Make sure we no longer have runner in hub. This will make sure that appmgr |
| // knows that runner died, before we try to launch component again. |
| ASSERT_TRUE(RunLoopWithTimeoutOrUntil( |
| [runner_path = runner_path_in_hub.c_str()] { |
| struct stat s; |
| return stat(runner_path, &s) != 0; |
| }, |
| kTimeout)); |
| |
| // launch again and check that runner was executed again |
| component = |
| enclosing_environment_->CreateComponentFromUrl(kComponentForRunner); |
| ASSERT_TRUE(WaitForRunnerToRegister()); |
| ASSERT_EQ(2, runner_registry_.connect_count()); |
| // make sure component was also launched |
| ASSERT_TRUE(WaitForComponentCount(1)); |
| auto components = runner_registry_.runner()->components(); |
| ASSERT_EQ(components[0].url, kComponentForRunner); |
| } |
| |
| TEST_F(RealmRunnerTest, RunnerLaunchedForEachEnvironment) { |
| auto component1 = |
| enclosing_environment_->CreateComponentFromUrl(kComponentForRunner); |
| ASSERT_TRUE(WaitForRunnerToRegister()); |
| |
| std::unique_ptr<EnclosingEnvironment> nested_environment; |
| std::unique_ptr<MockRunnerRegistry> nested_registry; |
| std::tie(nested_environment, nested_registry) = MakeNestedEnvironment({}); |
| |
| // launch again and check that runner was created for the nested environment |
| auto component2 = |
| nested_environment->CreateComponentFromUrl(kComponentForRunner); |
| WaitForRunnerToRegister(nested_registry.get()); |
| |
| ASSERT_TRUE(WaitForComponentCount(&runner_registry_, 1)); |
| ASSERT_TRUE(WaitForComponentCount(nested_registry.get(), 1)); |
| EXPECT_EQ(1, runner_registry_.connect_count()); |
| EXPECT_EQ(1, nested_registry->connect_count()); |
| } |
| |
| TEST_F(RealmRunnerTest, RunnerSharedFromParent) { |
| auto component1 = |
| enclosing_environment_->CreateComponentFromUrl(kComponentForRunner); |
| ASSERT_TRUE(WaitForRunnerToRegister()); |
| |
| std::unique_ptr<EnclosingEnvironment> nested_environment; |
| std::unique_ptr<MockRunnerRegistry> nested_registry; |
| { |
| std::tie(nested_environment, nested_registry) = |
| MakeNestedEnvironment({.allow_parent_runners = true}); |
| } |
| |
| // launch again and check that the runner from the parent environment was |
| // shared. |
| auto component2 = |
| nested_environment->CreateComponentFromUrl(kComponentForRunner); |
| |
| ASSERT_TRUE(WaitForComponentCount(&runner_registry_, 2)); |
| EXPECT_EQ(1, runner_registry_.connect_count()); |
| EXPECT_EQ(0, nested_registry->connect_count()); |
| } |
| |
| TEST_F(RealmRunnerTest, ComponentBridgeReturnsRightReturnCode) { |
| auto component = |
| enclosing_environment_->CreateComponentFromUrl(kComponentForRunner); |
| ASSERT_TRUE(WaitForRunnerToRegister()); |
| // make sure component was launched |
| ASSERT_TRUE(WaitForComponentCount(1)); |
| int64_t return_code; |
| TerminationReason reason; |
| component.events().OnTerminated = [&](int64_t code, TerminationReason r) { |
| return_code = code; |
| reason = r; |
| }; |
| auto components = runner_registry_.runner()->components(); |
| const int64_t ret_code = 3; |
| MockComponentPtr component_ptr; |
| runner_registry_.runner()->runner_ptr()->ConnectToComponent( |
| components[0].unique_id, component_ptr.NewRequest()); |
| component_ptr->Kill(ret_code); |
| ASSERT_TRUE(WaitForComponentCount(0)); |
| EXPECT_TRUE(RunLoopWithTimeoutOrUntil( |
| [&] { return reason == TerminationReason::EXITED; }, kTimeout)); |
| EXPECT_EQ(return_code, ret_code); |
| } |
| |
| TEST_F(RealmRunnerTest, DestroyingControllerKillsComponent) { |
| { |
| auto component = |
| enclosing_environment_->CreateComponentFromUrl(kComponentForRunner); |
| ASSERT_TRUE(WaitForRunnerToRegister()); |
| // make sure component was launched |
| ASSERT_TRUE(WaitForComponentCount(1)); |
| // component will go out of scope |
| } |
| ASSERT_TRUE(WaitForComponentCount(0)); |
| } |
| |
| TEST_F(RealmRunnerTest, KillComponentController) { |
| auto component = |
| enclosing_environment_->CreateComponentFromUrl(kComponentForRunner); |
| ASSERT_TRUE(WaitForRunnerToRegister()); |
| // make sure component was launched |
| ASSERT_TRUE(WaitForComponentCount(1)); |
| TerminationReason reason; |
| component.events().OnTerminated = [&](int64_t code, TerminationReason r) { |
| reason = r; |
| }; |
| component->Kill(); |
| ASSERT_TRUE(WaitForComponentCount(0)); |
| EXPECT_TRUE(RunLoopWithTimeoutOrUntil( |
| [&] { return reason == TerminationReason::EXITED; }, kTimeout)); |
| } |
| |
| class RealmRunnerServiceTest : public RealmRunnerTest { |
| protected: |
| void SetUp() override { |
| TestWithEnvironment::SetUp(); |
| auto env_services = CreateServices(); |
| ASSERT_EQ(ZX_OK, env_services->AddService(runner_registry_.GetHandler())); |
| ASSERT_EQ(ZX_OK, env_services->AddServiceWithLaunchInfo( |
| CreateLaunchInfo("fuchsia-pkg://fuchsia.com/echo2_server_cpp#meta/echo2_server_cpp.cmx"), |
| fidl::examples::echo::Echo::Name_)); |
| enclosing_environment_ = |
| CreateNewEnclosingEnvironment(kRealm, std::move(env_services)); |
| } |
| }; |
| |
| TEST_F(RealmRunnerServiceTest, ComponentCanConnectToEnvService) { |
| auto component = |
| enclosing_environment_->CreateComponentFromUrl(kComponentForRunner); |
| ASSERT_TRUE(WaitForRunnerToRegister()); |
| // make sure component was launched |
| ASSERT_TRUE(WaitForComponentCount(1)); |
| |
| fidl::examples::echo::EchoPtr echo; |
| MockComponentPtr component_ptr; |
| runner_registry_.runner()->runner_ptr()->ConnectToComponent( |
| runner_registry_.runner()->components()[0].unique_id, |
| component_ptr.NewRequest()); |
| component_ptr->ConnectToService(fidl::examples::echo::Echo::Name_, |
| echo.NewRequest().TakeChannel()); |
| const std::string message = "ConnectToEnvService"; |
| fidl::StringPtr ret_msg = ""; |
| echo->EchoString(message, |
| [&](::fidl::StringPtr retval) { ret_msg = retval; }); |
| ASSERT_TRUE(RunLoopWithTimeoutOrUntil( |
| [&] { return ret_msg.get() == message; }, kTimeout)); |
| } |
| |
| TEST_F(RealmRunnerTest, ComponentCanPublishServices) { |
| constexpr char dummy_service_name[] = "dummy_service"; |
| |
| // launch component with service. |
| Services services; |
| auto launch_info = CreateLaunchInfo(kComponentForRunner); |
| launch_info.directory_request = services.NewRequest(); |
| auto component = |
| enclosing_environment_->CreateComponent(std::move(launch_info)); |
| |
| ASSERT_TRUE(WaitForRunnerToRegister()); |
| // make sure component was launched |
| ASSERT_TRUE(WaitForComponentCount(1)); |
| |
| // create and publish fake service |
| auto fake_service_dir = fbl::AdoptRef(new fs::PseudoDir()); |
| bool connect_called = false; |
| fake_service_dir->AddEntry( |
| dummy_service_name, |
| fbl::AdoptRef(new fs::Service([&](zx::channel channel) { |
| connect_called = true; |
| return ZX_OK; |
| }))); |
| fs::SynchronousVfs vfs(async_get_default_dispatcher()); |
| MockComponentSyncPtr component_ptr; |
| runner_registry_.runner()->runner_ptr()->ConnectToComponent( |
| runner_registry_.runner()->components()[0].unique_id, |
| component_ptr.NewRequest()); |
| ASSERT_EQ(ZX_OK, component_ptr->SetServiceDirectory( |
| testing::OpenAsDirectory(&vfs, fake_service_dir))); |
| ASSERT_EQ(ZX_OK, component_ptr->PublishService(dummy_service_name)); |
| |
| // try to connect to fake service |
| fidl::examples::echo::EchoPtr echo; |
| services.ConnectToService(echo.NewRequest().TakeChannel(), |
| dummy_service_name); |
| ASSERT_TRUE( |
| RunLoopWithTimeoutOrUntil([&] { return connect_called; }, kTimeout)); |
| } |
| |
| TEST_F(RealmRunnerTest, ProbeHub) { |
| auto glob_str = fxl::StringPrintf("/hub/r/%s/*/c/appmgr_mock_runner.cmx/*/c/%s/*", |
| kRealm, kComponentForRunnerProcessName); |
| // launch two components and make sure both show up in /hub. |
| auto component1 = |
| enclosing_environment_->CreateComponentFromUrl(kComponentForRunner); |
| auto component2 = |
| enclosing_environment_->CreateComponentFromUrl(kComponentForRunner); |
| ASSERT_TRUE(WaitForRunnerToRegister()); |
| WaitForComponentCount(2); |
| |
| files::Glob glob(glob_str); |
| ASSERT_EQ(glob.size(), 2u) << glob_str << " expected 2 matches."; |
| |
| std::vector<std::string> paths = {glob.begin(), glob.end()}; |
| EXPECT_NE(paths[0], paths[1]); |
| EXPECT_EQ(files::GetDirectoryName(paths[0]), |
| files::GetDirectoryName(paths[1])); |
| } |
| |
| } // namespace |
| } // namespace component |