blob: 33e30bfbe05c75086f7669230ccd9ec6dee3a97c [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 <fuchsia/sys/cpp/fidl.h>
#include <fuchsia/sys/internal/cpp/fidl.h>
#include <lib/async-loop/cpp/loop.h>
#include <lib/async-loop/default.h>
#include <lib/async/default.h>
#include <lib/async/dispatcher.h>
#include <lib/fdio/directory.h>
#include <lib/fdio/fd.h>
#include <lib/fdio/fdio.h>
#include <lib/fdio/spawn.h>
#include <lib/fidl/cpp/binding_set.h>
#include <lib/sys/cpp/file_descriptor.h>
#include <lib/sys/cpp/service_directory.h>
#include <lib/sys/cpp/testing/enclosing_environment.h>
#include <lib/sys/cpp/testing/test_with_environment.h>
#include <lib/syslog/cpp/macros.h>
#include <stdint.h>
#include <stdio.h>
#include <unistd.h>
#include <zircon/errors.h>
#include <zircon/process.h>
#include <zircon/rights.h>
#include <zircon/status.h>
#include <fstream>
#include <memory>
#include <string>
#include <vector>
#include <fidl/examples/echo/cpp/fidl.h>
#include <fs/pseudo_dir.h>
#include <fs/synchronous_vfs.h>
#include <gmock/gmock.h>
#include <gtest/gtest.h>
#include <test/appmgr/integration/cpp/fidl.h>
#include "fuchsia/testing/appmgr/cpp/fidl.h"
#include "lib/vfs/cpp/service.h"
#include "src/lib/files/file.h"
#include "src/lib/files/glob.h"
#include "src/lib/files/scoped_temp_dir.h"
#include "src/lib/fsl/handles/object_info.h"
#include "src/lib/fxl/strings/substitute.h"
#include "src/sys/appmgr/component_controller_impl.h"
#include "src/sys/appmgr/integration_tests/util/data_file_reader_writer_util.h"
#include "src/sys/appmgr/realm.h"
#include "src/sys/appmgr/util.h"
namespace component {
namespace {
using fuchsia::sys::TerminationReason;
using ::testing::AnyOf;
using ::testing::Eq;
using sys::testing::EnclosingEnvironment;
using sys::testing::TestWithEnvironment;
using test::appmgr::integration::DataFileReaderWriterPtr;
class RealmTest : virtual public TestWithEnvironment {
protected:
void SetUp() override {
TestWithEnvironment::SetUp();
OpenNewOutFile();
}
void OpenNewOutFile() {
ASSERT_TRUE(tmp_dir_.NewTempFile(&out_file_));
outf_ = fileno(std::fopen(out_file_.c_str(), "w"));
}
std::string ReadOutFile() {
std::string out;
if (!files::ReadFileToString(out_file_, &out)) {
FX_LOGS(ERROR) << "Could not read output file " << out_file_;
return "";
}
return out;
}
fuchsia::sys::LaunchInfo CreateLaunchInfo(const std::string& url,
zx::channel directory_request = zx::channel(),
const std::vector<std::string>& args = {}) {
fuchsia::sys::LaunchInfo launch_info;
launch_info.url = url;
launch_info.arguments = args;
if (directory_request.is_valid()) {
launch_info.directory_request = std::move(directory_request);
}
launch_info.out = sys::CloneFileDescriptor(outf_);
launch_info.err = sys::CloneFileDescriptor(STDERR_FILENO);
return launch_info;
}
fuchsia::sys::ComponentControllerPtr RunComponent(EnclosingEnvironment* enclosing_environment,
const std::string& url,
zx::channel directory_request = zx::channel(),
const std::vector<std::string>& args = {}) {
return enclosing_environment->CreateComponent(
CreateLaunchInfo(url, std::move(directory_request), std::move(args)));
}
private:
files::ScopedTempDir tmp_dir_;
std::string out_file_;
int outf_;
};
constexpr char kRealm[] = "realmintegrationtest";
TEST_F(RealmTest, Resolve) {
auto enclosing_environment = CreateNewEnclosingEnvironment(kRealm, CreateServices());
auto resolver = enclosing_environment->ConnectToService<fuchsia::process::Resolver>();
bool wait = false;
resolver->Resolve(
"fuchsia-pkg://fuchsia.com/appmgr_integration_tests#bin/"
"appmgr_realm_integration_tests",
[&wait](zx_status_t status, zx::vmo binary,
fidl::InterfaceHandle<fuchsia::ldsvc::Loader> loader) {
wait = true;
ASSERT_EQ(ZX_OK, status);
std::string expect;
files::ReadFileToString("/pkg/bin/appmgr_realm_integration_tests", &expect);
ASSERT_FALSE(expect.empty());
std::vector<char> buf(expect.length());
ASSERT_EQ(ZX_OK, binary.read(buf.data(), 0, buf.size()));
std::string actual(buf.begin(), buf.end());
ASSERT_EQ(expect, actual);
});
RunLoopUntil([&wait] { return wait; });
}
TEST_F(RealmTest, LaunchNonExistentComponent) {
auto env_services = CreateServices();
auto enclosing_environment = CreateNewEnclosingEnvironment(kRealm, std::move(env_services));
WaitForEnclosingEnvToStart(enclosing_environment.get());
// try to launch file url.
auto controller1 = RunComponent(enclosing_environment.get(), "does_not_exist");
bool wait = false;
controller1.events().OnTerminated = [&wait](int64_t return_code,
fuchsia::sys::TerminationReason reason) {
wait = true;
EXPECT_EQ(reason, fuchsia::sys::TerminationReason::PACKAGE_NOT_FOUND);
};
RunLoopUntil([&wait] { return wait; });
// try to launch pkg url.
auto controller2 = RunComponent(enclosing_environment.get(),
"fuchsia-pkg://fuchsia.com/does_not_exist#meta/some.cmx");
wait = false;
controller2.events().OnTerminated = [&wait](int64_t return_code,
fuchsia::sys::TerminationReason reason) {
wait = true;
EXPECT_EQ(reason, fuchsia::sys::TerminationReason::PACKAGE_NOT_FOUND);
};
RunLoopUntil([&wait] { return wait; });
}
// This test exercises the fact that two components should be in separate jobs,
// and thus when one component controller kills its job due to a .Kill() call
// the other component should run uninterrupted.
TEST_F(RealmTest, CreateTwoKillOne) {
// launch component as a service.
auto env_services = CreateServices();
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_));
auto enclosing_environment = CreateNewEnclosingEnvironment(kRealm, std::move(env_services));
WaitForEnclosingEnvToStart(enclosing_environment.get());
// launch component normally
auto controller1 =
RunComponent(enclosing_environment.get(),
"fuchsia-pkg://fuchsia.com/echo_server_cpp#meta/echo_server_cpp.cmx");
// make sure echo service is running.
fidl::examples::echo::EchoPtr echo;
enclosing_environment->ConnectToService(echo.NewRequest());
const std::string message = "CreateTwoKillOne";
fidl::StringPtr ret_msg;
echo->EchoString(message, [&](::fidl::StringPtr retval) { ret_msg = retval; });
RunLoopUntil([&] { return ret_msg.has_value(); });
// Kill one of the two components, make sure it's exited via Wait
bool wait = false;
controller1.events().OnTerminated =
[&wait](int64_t return_code, fuchsia::sys::TerminationReason reason) { wait = true; };
controller1->Kill();
RunLoopUntil([&wait] { return wait; });
// Make sure the second component is still running.
ret_msg.reset();
echo->EchoString(message, [&](::fidl::StringPtr retval) { ret_msg = retval; });
RunLoopUntil([&] { return ret_msg.has_value(); });
}
TEST_F(RealmTest, KillRealmKillsComponent) {
auto env_services = CreateServices();
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_));
auto enclosing_environment = CreateNewEnclosingEnvironment(kRealm, std::move(env_services));
WaitForEnclosingEnvToStart(enclosing_environment.get());
// make sure echo service is running.
fidl::examples::echo::EchoPtr echo;
enclosing_environment->ConnectToService(echo.NewRequest());
const std::string message = "CreateTwoKillOne";
fidl::StringPtr ret_msg;
echo->EchoString(message, [&](::fidl::StringPtr retval) { ret_msg = retval; });
RunLoopUntil([&] { return ret_msg.has_value(); });
bool killed = false;
echo.set_error_handler([&](zx_status_t status) { killed = true; });
enclosing_environment->Kill();
RunLoopUntil([&] { return enclosing_environment->is_running(); });
// send a msg, without that error handler won't be called.
echo->EchoString(message, [&](::fidl::StringPtr retval) { ret_msg = retval; });
RunLoopUntil([&] { return killed; });
}
// test that service is connected even when realm dies right after connect request.
TEST_F(RealmTest, ConnectToServiceWhenRealmDies) {
auto env_services = CreateServices();
bool connected = false;
auto fake_service = std::make_shared<vfs::Service>(
[&](zx::channel client_handle, async_dispatcher_t* /*unused*/) mutable { connected = true; });
env_services->AddSharedService(fake_service, fidl::examples::echo::Echo::Name_);
auto enclosing_environment = CreateNewEnclosingEnvironment(kRealm, std::move(env_services));
WaitForEnclosingEnvToStart(enclosing_environment.get());
// first make sure that environment is running and can process requests.
fidl::examples::echo::EchoPtr echo;
enclosing_environment->ConnectToService(echo.NewRequest());
RunLoopUntil([&] { return connected; });
// reset connection and now connect and kill the environment.
connected = false;
enclosing_environment->ConnectToService(echo.NewRequest());
bool killed = false;
// kill enclosing env
enclosing_environment->Kill([&]() { killed = true; });
RunLoopUntil([&] { return killed; });
// make sure we can receive connect request.
RunLoopUntil([&] { return connected; });
}
TEST_F(RealmTest, EnvironmentControllerRequired) {
fuchsia::sys::EnvironmentPtr env;
real_env()->CreateNestedEnvironment(env.NewRequest(), /* controller = */ nullptr, kRealm,
/* additional_services = */ nullptr,
fuchsia::sys::EnvironmentOptions{});
zx_status_t env_status = ZX_OK;
env.set_error_handler([&](zx_status_t status) { env_status = status; });
RunLoopUntil([&] { return env_status != ZX_OK; });
}
TEST_F(RealmTest, EnvironmentLabelMustBeUnique) {
// Create first environment with label kRealm using EnclosingEnvironment since
// that's easy.
auto enclosing_environment = CreateNewEnclosingEnvironment(kRealm, CreateServices());
// Can't use EnclosingEnvironment here since there's no way to discern between
// 'not yet created' and 'failed to create'. This also lets us check the
// specific status returned.
fuchsia::sys::EnvironmentPtr env;
fuchsia::sys::EnvironmentControllerPtr env_controller;
zx_status_t env_status, env_controller_status;
env.set_error_handler([&](zx_status_t status) { env_status = status; });
env_controller.set_error_handler([&](zx_status_t status) { env_controller_status = status; });
// Same environment label as EnclosingEnvironment created above.
real_env()->CreateNestedEnvironment(env.NewRequest(), env_controller.NewRequest(), kRealm,
nullptr, fuchsia::sys::EnvironmentOptions{});
RunLoopUntil([&] { return env_status == ZX_ERR_BAD_STATE; });
RunLoopUntil([&] { return env_controller_status == ZX_ERR_BAD_STATE; });
}
TEST_F(RealmTest, RealmJobProvider) {
fuchsia::sys::JobProviderSyncPtr ptr;
fdio_service_connect("/hub/job", ptr.NewRequest().TakeChannel().release());
zx::job job;
ASSERT_EQ(ZX_OK, ptr->GetJob(&job));
// check that we can get properties.
char name[ZX_MAX_NAME_LEN];
ASSERT_EQ(ZX_OK, job.get_property(ZX_PROP_NAME, name, sizeof(name)));
EXPECT_THAT(name, ::testing::StartsWith("test_env"));
zx_koid_t koid;
size_t actual;
size_t avail_count;
ASSERT_EQ(ZX_OK,
job.get_info(ZX_INFO_JOB_CHILDREN, &koid, sizeof(zx_koid_t), &actual, &avail_count));
ASSERT_EQ(1u, actual);
// check that we can get children
zx::job child_job;
ASSERT_EQ(ZX_OK, job.get_child(koid, ZX_RIGHT_SAME_RIGHTS, &child_job));
}
TEST_F(RealmTest, RealmDiesWhenItsJobDies) {
auto env_services = CreateServices();
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_));
auto enclosing_environment = CreateNewEnclosingEnvironment(kRealm, std::move(env_services));
WaitForEnclosingEnvToStart(enclosing_environment.get());
// make sure echo service is running.
fidl::examples::echo::EchoPtr echo;
enclosing_environment->ConnectToService(echo.NewRequest());
const std::string message = "some_msg";
fidl::StringPtr ret_msg;
echo->EchoString(message, [&](::fidl::StringPtr retval) { ret_msg = retval; });
RunLoopUntil([&] { return ret_msg.has_value(); });
fuchsia::sys::JobProviderSyncPtr ptr;
files::Glob glob(std::string("/hub/r/") + kRealm + "/*/job");
ASSERT_EQ(1u, glob.size());
const std::string path = *glob.begin();
fdio_service_connect(path.c_str(), ptr.NewRequest().TakeChannel().release());
zx::job job;
EXPECT_EQ(ZX_OK, ptr->GetJob(&job));
ASSERT_EQ(ZX_OK, job.kill());
RunLoopUntil([&] { return !enclosing_environment->is_running(); });
}
TEST_F(RealmTest, EmptyRealmDiesWhenItsJobDies) {
auto env_services = CreateServices();
auto enclosing_environment = CreateNewEnclosingEnvironment(kRealm, std::move(env_services));
WaitForEnclosingEnvToStart(enclosing_environment.get());
fuchsia::sys::JobProviderSyncPtr ptr;
files::Glob glob(std::string("/hub/r/") + kRealm + "/*/job");
ASSERT_EQ(1u, glob.size());
const std::string path = *glob.begin();
fdio_service_connect(path.c_str(), ptr.NewRequest().TakeChannel().release());
zx::job job;
EXPECT_EQ(ZX_OK, ptr->GetJob(&job));
ASSERT_EQ(ZX_OK, job.kill());
RunLoopUntil([&] { return !enclosing_environment->is_running(); });
}
TEST_F(RealmTest, KillWorks) {
auto env_services = CreateServices();
auto enclosing_environment = CreateNewEnclosingEnvironment(kRealm, std::move(env_services));
WaitForEnclosingEnvToStart(enclosing_environment.get());
std::string hub_path = std::string("/hub/r/") + kRealm;
// make sure realm was created
files::Glob glob1(hub_path);
ASSERT_EQ(1u, glob1.size());
bool killed = false;
enclosing_environment->Kill([&] { killed = true; });
RunLoopUntil([&] { return killed; });
// make sure realm was really killed
files::Glob glob2(hub_path);
ASSERT_EQ(0u, glob2.size());
}
/// this will remove hash from the url and return un-hashed version.
/// example:
/// `url`:"fuchsia-pkg://fuchsia.com/my-pkg?hash=3204f2f24920e55bfbcb9c3a058ec2869f229b18d00ef1049ec3f47e5b7e4351#meta/my-component.cmx"
/// returns "fuchsia-pkg://fuchsia.com/my-pkg#meta/my-component.cmx"
std::string GetUnhashedUrl(const std::string& url) {
component::FuchsiaPkgUrl furl;
furl.Parse(url);
return fxl::Substitute("fuchsia-pkg://$0/$1#$2", furl.host_name(), furl.package_name(),
furl.resource_path());
}
class RealmCrashIntrospectTest : public RealmTest {
public:
using CrashIntrospect_FindComponentByThreadKoid_Result =
fuchsia::sys::internal::CrashIntrospect_FindComponentByThreadKoid_Result;
void SetUp() override {
RealmTest::SetUp();
real_services()->Connect(introspect_.NewRequest());
ASSERT_TRUE(files::ReadFileToString("hub/name", &current_realm_name_));
}
CrashIntrospect_FindComponentByThreadKoid_Result FindComponent(zx_koid_t thread_koid) {
CrashIntrospect_FindComponentByThreadKoid_Result result;
auto status = introspect_->FindComponentByThreadKoid(thread_koid, &result);
FX_CHECK(ZX_OK == status) << "status: " << status;
return result;
}
static zx_koid_t GetKoid(const zx::object_base& obj) { return fsl::GetKoid(obj.get()); }
static zx_koid_t GetCurrentProcessKoid() {
auto koid = GetKoid(*zx::process::self());
ZX_DEBUG_ASSERT(koid != ZX_KOID_INVALID);
return koid;
}
const std::string& current_realm_name() const { return current_realm_name_; }
private:
fuchsia::sys::internal::CrashIntrospectSyncPtr introspect_;
std::string current_realm_name_;
};
// This tests that the service is not available in environment which do not explicitly include it
// from parent environment.
// This test's cmx includes this service that so we are able to indirectly test that inheriting this
// service works.
TEST_F(RealmCrashIntrospectTest, CrashServiceNotAvailableInAllEnvironments) {
auto env_services = CreateServices();
auto enclosing_environment = CreateNewEnclosingEnvironment(kRealm, std::move(env_services));
WaitForEnclosingEnvToStart(enclosing_environment.get());
fuchsia::sys::internal::CrashIntrospectPtr introspect;
enclosing_environment->ConnectToService(introspect.NewRequest());
bool done = false;
introspect.set_error_handler([&](zx_status_t status) {
done = true;
ASSERT_EQ(ZX_ERR_PEER_CLOSED, status);
});
RunLoopUntil([&]() { return done; });
}
// pass job's koid so that we can test ZX_ERR_NOT_FOUND.
TEST_F(RealmCrashIntrospectTest, InvalidProcessId) {
auto job = zx::job::default_job();
auto koid = GetKoid(*job);
auto result = FindComponent(koid);
ASSERT_TRUE(result.is_err());
EXPECT_EQ(ZX_ERR_NOT_FOUND, result.err());
}
// TODO(fxbug.dev/57032): re-enable once we can intercept the exception after appmgr, but before the
// platform's exception handling.
TEST_F(RealmCrashIntrospectTest, DISABLED_ComponentUrlForNewCrashingProcess) {
const char* command_argv[] = {"/pkg/bin/crashing_process", nullptr};
auto job = zx::job::default_job();
zx::process process;
ASSERT_EQ(ZX_OK, fdio_spawn(job->get(), FDIO_SPAWN_CLONE_ALL,
command_argv[0], // path
command_argv, process.reset_and_get_address()));
auto koid = GetKoid(process);
// TODO(fxbug.dev/51382): Remove these logs. Added for debugging incase process never crashes.
FX_LOGS(INFO) << "Waiting for process to die.";
process.wait_one(ZX_TASK_TERMINATED, zx::time::infinite(), nullptr);
FX_LOGS(INFO) << "Process died.";
auto result = FindComponent(koid);
ASSERT_FALSE(result.is_err()) << zx_status_get_string(result.err());
auto& response = result.response().component_info;
EXPECT_EQ(GetUnhashedUrl(response.component_url()),
"fuchsia-pkg://fuchsia.com/appmgr_integration_tests#meta/"
"appmgr_realm_integration_tests.cmx");
std::vector<std::string> expected = {"app", "sys", current_realm_name()};
EXPECT_EQ(response.realm_path(), expected);
EXPECT_EQ(response.instance_id(), std::to_string(GetCurrentProcessKoid()));
// we should only be able to retrieve it once.
result = FindComponent(koid);
ASSERT_TRUE(result.is_err());
EXPECT_EQ(ZX_ERR_NOT_FOUND, result.err());
}
// TODO(fxbug.dev/57032): re-enable once we can intercept the exception after appmgr, but before the
// platform's exception handling.
TEST_F(RealmCrashIntrospectTest, DISABLED_ComponentUrlForNewComponentInCurrentEnv) {
zx::channel request;
auto component_svc = sys::ServiceDirectory::CreateWithRequest(&request);
auto launch_info = CreateLaunchInfo(
"fuchsia-pkg://fuchsia.com/appmgr_integration_tests#meta/crashing_component.cmx",
std::move(request));
fuchsia::sys::ComponentControllerPtr controller;
launcher_ptr()->CreateComponent(std::move(launch_info), controller.NewRequest());
bool component_started = false;
controller.events().OnDirectoryReady = [&] { component_started = true; };
RunLoopUntil([&] { return component_started; });
files::Glob glob("/hub/c/crashing_component.cmx/*/process-id");
ASSERT_EQ(1u, glob.size());
std::string process_koid;
ASSERT_TRUE(files::ReadFileToString(*glob.begin(), &process_koid));
bool component_died = false;
controller.events().OnTerminated = [&](int64_t err, TerminationReason r) {
component_died = true;
};
fuchsia::testing::appmgr::CrashInducerPtr crash_srv;
component_svc->Connect(crash_srv.NewRequest());
crash_srv->Crash();
// TODO(fxbug.dev/51382): Remove these logs. Added for debugging incase process never crashes.
FX_LOGS(INFO) << "Waiting for component to die.";
RunLoopUntil([&] { return component_died; });
FX_LOGS(INFO) << "Component died.";
auto result = FindComponent(std::stoi(process_koid));
ASSERT_FALSE(result.is_err()) << zx_status_get_string(result.err());
auto& response = result.response().component_info;
EXPECT_EQ(response.component_url(),
"fuchsia-pkg://fuchsia.com/appmgr_integration_tests#meta/crashing_component.cmx");
std::vector<std::string> expected = {"app", "sys", current_realm_name()};
EXPECT_EQ(response.realm_path(), expected);
EXPECT_EQ(response.instance_id(), process_koid);
}
// TODO(fxbug.dev/57032): re-enable once we can intercept the exception after appmgr, but before the
// platform's exception handling.
TEST_F(RealmCrashIntrospectTest, DISABLED_ComponentUrlForNewComponentInEnclosingEnv) {
auto env_services = CreateServices();
std::string realm_label = "RealmCrashIntrospectTest";
auto enclosing_environment = CreateNewEnclosingEnvironment(realm_label, std::move(env_services));
WaitForEnclosingEnvToStart(enclosing_environment.get());
zx::channel request;
auto component_svc = sys::ServiceDirectory::CreateWithRequest(&request);
auto controller =
RunComponent(enclosing_environment.get(),
"fuchsia-pkg://fuchsia.com/appmgr_integration_tests#meta/crashing_component.cmx",
std::move(request));
bool component_started = false;
controller.events().OnDirectoryReady = [&] { component_started = true; };
RunLoopUntil([&] { return component_started; });
files::Glob glob(std::string("/hub/r/") + realm_label +
"/*/c/crashing_component.cmx/*/process-id");
ASSERT_EQ(1u, glob.size());
std::string process_koid;
ASSERT_TRUE(files::ReadFileToString(*glob.begin(), &process_koid));
bool component_died = false;
controller.events().OnTerminated = [&](int64_t err, TerminationReason r) {
component_died = true;
};
fuchsia::testing::appmgr::CrashInducerPtr crash_srv;
component_svc->Connect(crash_srv.NewRequest());
crash_srv->Crash();
// TODO(fxbug.dev/51382): Remove these logs. Added for debugging incase process never crashes.
FX_LOGS(INFO) << "Waiting for component to die.";
RunLoopUntil([&] { return component_died; });
FX_LOGS(INFO) << "Component died.";
auto result = FindComponent(std::stoi(process_koid));
ASSERT_FALSE(result.is_err()) << zx_status_get_string(result.err());
auto& response = result.response().component_info;
EXPECT_EQ(response.component_url(),
"fuchsia-pkg://fuchsia.com/appmgr_integration_tests#meta/crashing_component.cmx");
std::vector<std::string> expected = {"app", "sys", current_realm_name(), std::move(realm_label)};
EXPECT_EQ(response.realm_path(), expected);
EXPECT_EQ(response.instance_id(), process_koid);
EXPECT_EQ(response.component_name(), "crashing_component.cmx");
}
// Create a test environment that includes additional functions for
// reading/writing data.
//
// We use virtual inheritance here to work around the LLVM compiler bug
// https://bugs.llvm.org/show_bug.cgi?id=35902 which generates
// a false-positive alignment alert.
class EnvironmentOptionsTest : public virtual RealmTest,
public virtual component::testing::DataFileReaderWriterUtil {};
TEST_F(EnvironmentOptionsTest, DeleteStorageOnDeath) {
constexpr char kTestFileName[] = "some-test-file";
constexpr char kTestFileContent[] = "the-best-file-content";
// Create an environment with 'delete_storage_on_death' option enabled.
zx::channel request;
auto services = sys::ServiceDirectory::CreateWithRequest(&request);
DataFileReaderWriterPtr util;
auto enclosing_environment = CreateNewEnclosingEnvironment(
kRealm, CreateServices(), fuchsia::sys::EnvironmentOptions{.delete_storage_on_death = true});
auto ctrl = RunComponent(enclosing_environment.get(),
"fuchsia-pkg://fuchsia.com/persistent_storage_test_util#meta/util.cmx",
std::move(request));
services->Connect(util.NewRequest());
// Write some arbitrary file content into the test util's "/data" dir, and
// verify that we can read it back.
ASSERT_EQ(WriteFileSync(util, kTestFileName, kTestFileContent), ZX_OK);
ASSERT_EQ(ReadFileSync(util, kTestFileName).value_or(""), kTestFileContent);
// Kill the environment, which should automatically delete any persistent
// storage it owns.
bool killed = false;
enclosing_environment->Kill([&] { killed = true; });
RunLoopUntil([&] { return killed; });
// Recreate the environment and component using the same environment label.
services = sys::ServiceDirectory::CreateWithRequest(&request);
enclosing_environment = CreateNewEnclosingEnvironment(kRealm, CreateServices());
ctrl = RunComponent(enclosing_environment.get(),
"fuchsia-pkg://fuchsia.com/persistent_storage_test_util#meta/util.cmx",
std::move(request));
services->Connect(util.NewRequest());
// Verify that the file no longer exists.
EXPECT_FALSE(ReadFileSync(util, kTestFileName).has_value());
}
using LabelAndValidity = std::tuple<std::string, bool>;
class EnvironmentLabelTest : public RealmTest,
public ::testing::WithParamInterface<LabelAndValidity> {};
TEST_P(EnvironmentLabelTest, CheckLabelValidity) {
// Can't use EnclosingEnvironment here since there's no way to discern between
// 'not yet created' and 'failed to create'. This also lets use check the
// specific status returned.
fuchsia::sys::EnvironmentPtr env;
fuchsia::sys::EnvironmentControllerPtr env_controller;
zx_status_t env_status = ZX_OK;
zx_status_t env_controller_status = ZX_OK;
bool env_created = false;
env.set_error_handler([&](zx_status_t status) { env_status = status; });
env_controller.set_error_handler([&](zx_status_t status) { env_controller_status = status; });
env_controller.events().OnCreated = [&] { env_created = true; };
auto [label, label_valid] = GetParam();
real_env()->CreateNestedEnvironment(env.NewRequest(), env_controller.NewRequest(), label,
/* additional_services = */ nullptr,
fuchsia::sys::EnvironmentOptions{});
if (label_valid) {
RunLoopUntil([&] { return env_created; });
} else {
RunLoopUntil([&] { return env_status == ZX_ERR_INVALID_ARGS; });
RunLoopUntil([&] { return env_controller_status == ZX_ERR_INVALID_ARGS; });
EXPECT_FALSE(env_created);
}
}
INSTANTIATE_TEST_SUITE_P(
InvalidLabels, EnvironmentLabelTest,
::testing::Combine(::testing::Values("", "a/b", "/", ".", "..", "../..", "\t", "\r", "ab\n",
std::string("123\0", 4), "\10", "\33", "\177", " ",
"my realm", "~", "`", "!", "@", "$", "%", "^", "&", "*",
"(", ")", "=", "+", "{", "}", "[", "]", "|", "?", ";", "'",
"\"", "<", ">", ",",
"fuchsia-pkg://fuchsia.com/abcd#meta/abcd.cmx"),
::testing::Values(false)));
INSTANTIATE_TEST_SUITE_P(
ValidLabels, EnvironmentLabelTest,
::testing::Combine(::testing::Values("abcdefghijklmnopqrstuvwxyz", "ABCDEFGHIJKLMNOPQRSTUVWXYZ",
"0123456789", "#-_:.", "my.realm", "my..realm",
"fuchsia-pkg:::fuchsia.com:abcd#meta:abcd.cmx"),
::testing::Values(true)));
class RealmFakeLoaderTest : public RealmTest, public fuchsia::sys::Loader {
protected:
RealmFakeLoaderTest() {
sys::testing::EnvironmentServices::ParentOverrides parent_overrides;
parent_overrides.loader_service_ =
std::make_shared<vfs::Service>([this](zx::channel channel, async_dispatcher_t* dispatcher) {
bindings_.AddBinding(this,
fidl::InterfaceRequest<fuchsia::sys::Loader>(std::move(channel)));
});
enclosing_environment_ = CreateNewEnclosingEnvironment(
kRealm, CreateServicesWithParentOverrides(std::move(parent_overrides)));
}
void LoadUrl(std::string url, LoadUrlCallback callback) override {
ASSERT_TRUE(component_url_.empty());
component_url_ = url;
}
void WaitForComponentLoad() {
RunLoopUntil([this] { return !component_url_.empty(); });
}
const std::string& component_url() const { return component_url_; }
std::unique_ptr<EnclosingEnvironment> enclosing_environment_;
private:
std::shared_ptr<vfs::Service> loader_service_;
fidl::BindingSet<fuchsia::sys::Loader> bindings_;
std::string component_url_;
};
TEST_F(RealmFakeLoaderTest, CreateInvalidComponent) {
TerminationReason reason = TerminationReason::UNKNOWN;
int64_t return_code = INT64_MAX;
auto controller = RunComponent(enclosing_environment_.get(), "garbage://test");
controller.events().OnTerminated = [&](int64_t err, TerminationReason r) {
return_code = err;
reason = r;
};
RunLoopUntil([&] { return return_code < INT64_MAX; });
EXPECT_EQ(TerminationReason::URL_INVALID, reason);
EXPECT_EQ(-1, return_code);
}
} // namespace
} // namespace component