blob: 3857bd25a27b7a53fa397107674bea1487af32e8 [file] [log] [blame]
// 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 <fidl/examples/echo/cpp/fidl.h>
#include <fuchsia/io/cpp/fidl.h>
#include <lib/async/dispatcher.h>
#include <lib/fidl/cpp/interface_handle.h>
#include <lib/sys/cpp/service_directory.h>
#include <lib/sys/cpp/testing/test_with_environment.h>
#include <src/lib/fxl/strings/string_printf.h>
#include <unistd.h>
#include <memory>
#include "garnet/bin/appmgr/integration_tests/mock_runner_registry.h"
#include "lib/async-loop/cpp/loop.h"
#include "lib/async-loop/loop.h"
#include "src/lib/files/glob.h"
#include "src/lib/files/path.h"
namespace component {
namespace {
using fuchsia::sys::TerminationReason;
using sys::testing::EnclosingEnvironment;
using sys::testing::TestWithEnvironment;
using test::component::mockrunner::MockComponentPtr;
using test::component::mockrunner::MockComponentSyncPtr;
using testing::MockRunnerRegistry;
const char kRealm[] = "realmrunnerintegrationtest";
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 = sys::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 = RunLoopUntil([&] { return runner_registry->runner(); });
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 = RunLoopUntil([&] { return !runner_registry_.runner(); });
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 = RunLoopUntil([&] {
return runner->components().size() == expected_components_count;
});
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(RunLoopUntil([&] { return return_code != INT64_MIN; }));
// 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(RunLoopUntil([runner_path = runner_path_in_hub.c_str()] {
struct stat s;
return stat(runner_path, &s) != 0;
}));
// 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(
RunLoopUntil([&] { return reason == TerminationReason::EXITED; }));
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(
RunLoopUntil([&] { return reason == TerminationReason::EXITED; }));
}
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/"
"echo_server_cpp#meta/echo_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(RunLoopUntil([&] { return ret_msg.get() == message; }));
}
TEST_F(RealmRunnerTest, ComponentCanPublishServices) {
constexpr char dummy_service_name[] = "dummy_service";
// launch component with service.
zx::channel request;
auto services = sys::ServiceDirectory::CreateWithRequest(&request);
auto launch_info = CreateLaunchInfo(kComponentForRunner);
launch_info.directory_request = std::move(request);
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
vfs::PseudoDir fake_service_dir;
bool connect_called = false;
fake_service_dir.AddEntry(
dummy_service_name,
std::make_unique<vfs::Service>(
[&](zx::channel channel, async_dispatcher_t* dispatcher) {
connect_called = true;
}));
MockComponentSyncPtr component_ptr;
runner_registry_.runner()->runner_ptr()->ConnectToComponent(
runner_registry_.runner()->components()[0].unique_id,
component_ptr.NewRequest());
fidl::InterfaceHandle<fuchsia::io::Directory> dir_handle;
fake_service_dir.Serve(
fuchsia::io::OPEN_RIGHT_READABLE | fuchsia::io::OPEN_RIGHT_WRITABLE,
dir_handle.NewRequest().TakeChannel());
ASSERT_EQ(ZX_OK,
component_ptr->SetServiceDirectory(dir_handle.TakeChannel()));
ASSERT_EQ(ZX_OK, component_ptr->PublishService(dummy_service_name));
// try to connect to fake service
fidl::examples::echo::EchoPtr echo;
services->Connect(echo.NewRequest(), dummy_service_name);
ASSERT_TRUE(RunLoopUntil([&] { return connect_called; }));
}
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