blob: 8bed4120a088b4c88fd5d1e0d23bce54aac5720d [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 <lib/async/cpp/executor.h>
#include <lib/async/default.h>
#include <lib/fdio/directory.h>
#include <lib/sys/cpp/file_descriptor.h>
#include <lib/sys/cpp/testing/test_with_environment.h>
#include <src/lib/fxl/strings/concatenate.h>
#include <src/lib/fxl/strings/join_strings.h>
#include <src/lib/fxl/strings/string_printf.h>
#include "garnet/bin/sysmgr/config.h"
#include "gmock/gmock.h"
#include "gtest/gtest.h"
#include "src/lib/files/directory.h"
#include "src/lib/files/file.h"
#include "src/lib/files/glob.h"
#include "src/lib/inspect_deprecated/query/discover.h"
#include "src/lib/inspect_deprecated/query/location.h"
#include "src/lib/inspect_deprecated/query/read.h"
#include "src/lib/inspect_deprecated/testing/inspect.h"
using namespace inspect_deprecated::testing;
namespace component {
namespace {
// This test fixture will provide a way to run components in provided launchers
// and check for errors.
class HubTest : public sys::testing::TestWithEnvironment {
protected:
// This would launch component and check that it returns correct
// |expected_return_code|.
void RunComponent(const fuchsia::sys::LauncherPtr& launcher, const std::string& component_url,
const std::vector<std::string>& args, int64_t expected_return_code) {
std::FILE* outf = std::tmpfile();
int out_fd = fileno(outf);
fuchsia::sys::LaunchInfo launch_info;
launch_info.url = component_url;
launch_info.arguments = args;
launch_info.out = sys::CloneFileDescriptor(out_fd);
fuchsia::sys::ComponentControllerPtr controller;
launcher->CreateComponent(std::move(launch_info), controller.NewRequest());
int64_t return_code = INT64_MIN;
controller.events().OnTerminated = [&return_code](int64_t code,
fuchsia::sys::TerminationReason reason) {
return_code = code;
};
RunLoopUntil([&return_code] { return return_code != INT64_MIN; });
std::string output;
ASSERT_TRUE(files::ReadFileDescriptorToString(out_fd, &output));
EXPECT_EQ(expected_return_code, return_code)
<< "failed for: " << fxl::JoinStrings(args, ", ") << "\noutput: " << output;
}
};
TEST(ProbeHub, Component) {
constexpr char kGlob[] = "/hub/c/*/*/out/debug";
files::Glob glob(kGlob);
EXPECT_GE(glob.size(), 1u) << kGlob << " expected to match at least once.";
}
TEST(ProbeHub, Realm) {
constexpr char kGlob[] = "/hub/c/";
files::Glob glob(kGlob);
EXPECT_EQ(glob.size(), 1u) << kGlob << " expected to match once.";
}
TEST(ProbeHub, RealmSvc) {
constexpr char kGlob[] = "/hub/svc/fuchsia.sys.Environment";
files::Glob glob(kGlob);
EXPECT_EQ(glob.size(), 1u);
}
TEST_F(HubTest, Services) {
// Services for sys.
{
constexpr char kGlob[] = "/hub/svc";
files::Glob glob(kGlob);
EXPECT_EQ(glob.size(), 1u) << kGlob << " expected to match once.";
const std::string path = *glob.begin();
// Expected files are built-in services plus sysmgr services.
std::vector<std::string> expected_files = {".",
"fuchsia.boot.FactoryItems",
"fuchsia.boot.ReadOnlyLog",
"fuchsia.boot.RootJob",
"fuchsia.boot.RootJobForInspect",
"fuchsia.boot.RootResource",
"fuchsia.boot.WriteOnlyLog",
"fuchsia.device.NameProvider",
"fuchsia.device.manager.Administrator",
"fuchsia.device.manager.DebugDumper",
"fuchsia.hardware.pty.Device",
"fuchsia.kernel.Counter",
"fuchsia.kernel.DebugBroker",
"fuchsia.kernel.Stats",
"fuchsia.paver.Paver",
"fuchsia.process.Launcher",
"fuchsia.process.Resolver",
"fuchsia.scheduler.ProfileProvider",
"fuchsia.sys.Environment",
"fuchsia.sys.Launcher",
"fuchsia.sys.Loader",
"fuchsia.sys.test.CacheControl",
"fuchsia.virtualconsole.SessionManager"};
sysmgr::Config config;
// The following path is deprecated, and because config-data is component
// name isolated, it will be impossible to continue to do this in future:
ASSERT_TRUE(config.ParseFromDirectory("/pkgfs/packages/config-data/0/data/sysmgr"));
const auto service_map = config.TakeServices();
for (const auto& e : service_map) {
expected_files.push_back(e.first);
}
// readdir should list all services.
std::vector<std::string> files;
ASSERT_TRUE(files::ReadDirContents(path, &files));
EXPECT_THAT(files, ::testing::UnorderedElementsAreArray(expected_files));
// Try looking up an individual service.
const std::string service_path = fxl::Concatenate({path, "/", service_map.begin()->first});
EXPECT_TRUE(files::IsFile(service_path)) << service_path;
const std::string bogus_path = fxl::Concatenate({path, "/does_not_exist"});
EXPECT_FALSE(files::IsFile(bogus_path)) << bogus_path;
}
}
TEST_F(HubTest, ScopePolicy) {
constexpr char kGlobUrl[] = "fuchsia-pkg://fuchsia.com/glob#meta/glob.cmx";
// create nested environment
// test that we can see nested env
auto nested_env = CreateNewEnclosingEnvironment("hubscopepolicytest", CreateServices());
WaitForEnclosingEnvToStart(nested_env.get());
RunComponent(launcher_ptr(), kGlobUrl, {"/hub/r/hubscopepolicytest/"}, 0);
// test that we cannot see nested env using its own launcher
RunComponent(nested_env->launcher_ptr(), kGlobUrl, {"/hub/r/hubscopepolicytest"}, 1);
// test that we can see check_hub_path
RunComponent(nested_env->launcher_ptr(), kGlobUrl, {"/hub/c/glob.cmx"}, 0);
}
TEST_F(HubTest, SystemObjects) {
std::string glob_url = "fuchsia-pkg://fuchsia.com/glob#meta/glob.cmx";
auto nested_env = CreateNewEnclosingEnvironment("hubscopepolicytest", CreateServices());
WaitForEnclosingEnvToStart(nested_env.get());
RunComponent(launcher_ptr(), glob_url, {"/hub/r/hubscopepolicytest/"}, 0);
// test that we can see system objects
RunComponent(nested_env->launcher_ptr(), glob_url, {"/hub/c/glob.cmx/*/system_objects"}, 0);
}
TEST_F(HubTest, SystemObjectsThreads) {
std::string url =
"fuchsia-pkg://fuchsia.com/appmgr_integration_tests#meta/"
"appmgr_integration_tests_inspect_test_app.cmx";
auto env_name = fxl::StringPrintf("test-%lu", time(NULL));
auto nested_env = CreateNewEnclosingEnvironment(env_name, CreateServices());
WaitForEnclosingEnvToStart(nested_env.get());
fuchsia::sys::ComponentControllerPtr controller = nested_env->CreateComponentFromUrl(url);
bool ready = false;
controller.events().OnDirectoryReady = [&] { ready = true; };
RunLoopUntil([&] { return ready; });
auto paths = inspect_deprecated::SyncSearchGlobs(
{fxl::StringPrintf("/hub/r/%s/*/c/appmgr_integration_tests_inspect_test_app.cmx/*/"
"system_objects/*",
env_name.c_str())});
ASSERT_EQ(1U, paths.size());
auto read = inspect_deprecated::ReadLocation(paths[0]);
async::Executor executor_(dispatcher());
fit::result<inspect_deprecated::Source, std::string> result;
executor_.schedule_task(read.then(
[&](fit::result<inspect_deprecated::Source, std::string>& res) { result = std::move(res); }));
RunLoopUntil([&] { return !!result; });
ASSERT_TRUE(result.is_ok()) << result.take_error();
auto* stacks = result.value().GetHierarchy().GetByPath({"threads", "all_thread_stacks"});
ASSERT_NE(nullptr, stacks);
EXPECT_THAT(*stacks, NodeMatches(PropertyList(ElementsAre(StringPropertyIs(
"stacks", "\nERROR (CF-812): Full thread dump disabled")))));
}
TEST_F(HubTest, SystemObjectsThreadsInUseWhileFreed) {
std::string url =
"fuchsia-pkg://fuchsia.com/appmgr_integration_tests#meta/"
"appmgr_integration_tests_inspect_test_app.cmx";
auto env_name = fxl::StringPrintf("test-%lu", time(NULL));
auto nested_env = CreateNewEnclosingEnvironment(env_name, CreateServices());
WaitForEnclosingEnvToStart(nested_env.get());
fuchsia::sys::ComponentControllerPtr controller = nested_env->CreateComponentFromUrl(url);
bool ready = false;
controller.events().OnDirectoryReady = [&] { ready = true; };
RunLoopUntil([&] { return ready; });
auto paths = inspect_deprecated::SyncSearchGlobs(
{fxl::StringPrintf("/hub/r/%s/*/c/appmgr_integration_tests_inspect_test_app.cmx/*/"
"system_objects/*",
env_name.c_str())});
ASSERT_EQ(1U, paths.size());
async::Executor executor_(dispatcher());
fuchsia::inspect::deprecated::InspectPtr inspect;
auto endpoint = paths[0].AbsoluteFilePath();
zx_status_t status =
fdio_service_connect(endpoint.c_str(), inspect.NewRequest().TakeChannel().get());
ASSERT_EQ(ZX_OK, status);
auto reader = std::make_unique<inspect_deprecated::ObjectReader>(std::move(inspect));
bool reader_open = false;
executor_.schedule_task(reader->Read().and_then(
[&](fuchsia::inspect::deprecated::Object& unused) { reader_open = true; }));
RunLoopUntil([&] { return reader_open; });
auto open_child = reader->OpenChild("threads");
bool terminated = false;
controller.events().OnTerminated = [&](uint64_t status, fuchsia::sys::TerminationReason reason) {
terminated = true;
};
controller->Kill();
RunLoopUntil([&] { return terminated; });
controller.Unbind();
std::unique_ptr<inspect_deprecated::ObjectReader> all_stack_reader;
executor_.schedule_task(
open_child
.and_then([&](inspect_deprecated::ObjectReader& next) {
return next.OpenChild("all_thread_stacks");
})
.and_then([&](inspect_deprecated::ObjectReader& next) {
all_stack_reader = std::make_unique<inspect_deprecated::ObjectReader>(std::move(next));
}));
RunLoopUntil([&] { return !!all_stack_reader; });
reader.reset();
// At this point in time we have an open FIDL connection to a node in the
// SystemObjectsDirectory. Accessing that node should not cause a crash and
// will give no visible error.
fit::result<fuchsia::inspect::deprecated::Object> result;
executor_.schedule_task(all_stack_reader->Read().then(
[&](fit::result<fuchsia::inspect::deprecated::Object>& res) { result = std::move(res); }));
RunLoopUntil([&] { return !!result; });
EXPECT_TRUE(result.is_ok());
}
} // namespace
} // namespace component