blob: fdce93c4696071611a5329ad25d903daaa120d16 [file] [log] [blame]
// Copyright 2022 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 <lib/elfldltl/layout.h>
#include <lib/fit/defer.h>
#include <lib/fit/function.h>
#include <lib/stdcompat/string_view.h>
#include <zircon/types.h>
#include <cerrno>
#include <cstdlib>
#include <ctime>
#include <string>
#include <vector>
#include "job-archive.h"
#include "test-tool-process.h"
#ifdef __Fuchsia__
#include "dump-tests.h"
#endif
#include <gtest/gtest.h>
namespace {
using namespace std::literals;
#ifdef __Fuchsia__ // TODO(mcgrathr): See below
constexpr const char* kOutputSwitch = "-o";
constexpr const char* kExcludeMemorySwitch = "--exclude-memory";
constexpr const char* kNoThreadsSwitch = "--no-threads";
constexpr const char* kNoChildrenSwitch = "--no-children";
constexpr const char* kNoProcessesSwitch = "--no-processes";
constexpr const char* kJobsSwitch = "--jobs";
constexpr const char* kJobArchiveSwitch = "--job-archive";
constexpr const char* kZstdSwitch = "--zstd";
constexpr const char* kSystemSwitch = "--system";
constexpr const char* kKernelSwitch = "--kernel";
constexpr const char* kNoDateSwitch = "--no-date";
constexpr const char* kRemarksSwitch = "--remarks=";
constexpr const char* kRawRemarksSwitch = "--remarks-raw=";
constexpr const char* kJsonRemarksSwitch = "--remarks-json=";
constexpr const char* kLiveSwitch = "--live";
constexpr const char* kStreamingSwitch = "--streaming";
constexpr std::string_view kArchiveSuffix = ".a";
struct OutputFile {
zxdump::testing::TestToolProcess::File& file;
std::string prefix;
std::string pid_string;
};
OutputFile GetOutputFile(zxdump::testing::TestToolProcess& child, std::string_view name,
zx_koid_t koid, bool archive = false, std::string_view final_suffix = {}) {
std::string pid_string = std::to_string(koid);
std::string suffix = "." + pid_string;
suffix += final_suffix;
if (archive) {
suffix += kArchiveSuffix;
}
auto& file = child.MakeFile(name, std::move(suffix));
std::string prefix = file.name();
prefix.resize(prefix.size() - pid_string.size() - (archive ? kArchiveSuffix.size() : 0) -
final_suffix.size());
return {file, prefix, pid_string};
}
#endif // __Fuchsia__
void UsageTest(int expected_status, const std::vector<std::string>& args = {}) {
zxdump::testing::TestToolProcess child;
ASSERT_NO_FATAL_FAILURE(child.Init());
ASSERT_NO_FATAL_FAILURE(child.Start("gcore", args));
ASSERT_NO_FATAL_FAILURE(child.CollectStdout());
ASSERT_NO_FATAL_FAILURE(child.CollectStderr());
int status;
ASSERT_NO_FATAL_FAILURE(child.Finish(status));
EXPECT_EQ(status, expected_status);
EXPECT_EQ(child.collected_stdout(), "");
std::string text = child.collected_stderr();
EXPECT_TRUE(cpp20::starts_with(std::string_view(text), "Usage: ")) << text;
EXPECT_TRUE(cpp20::ends_with(std::string_view(text), '\n')) << text;
}
TEST(ZxdumpTests, GcoreHelp) { UsageTest(EXIT_SUCCESS, {"--help"}); }
TEST(ZxdumpTests, GcoreUsage) { UsageTest(EXIT_FAILURE); }
// TODO(mcgrathr): Process-launching tests are only possible on Fuchsia.
// Each of these could have a variant that dumps via reading from a golden
// job archive in testdata.
#ifdef __Fuchsia__
TEST(ZxdumpTests, GcoreProcessDumpIsElfCore) {
zxdump::testing::TestProcess process;
ASSERT_NO_FATAL_FAILURE(process.StartChild());
zxdump::testing::TestToolProcess child;
ASSERT_NO_FATAL_FAILURE(child.Init());
const auto& [dump_file, prefix, pid_string] =
GetOutputFile(child, "process-dump", process.koid());
std::vector<std::string> args({
// Don't dump memory since we don't need it and it is large.
kExcludeMemorySwitch,
// Don't bother dumping threads since this test doesn't check for them.
kNoThreadsSwitch,
kOutputSwitch,
prefix,
pid_string,
});
ASSERT_NO_FATAL_FAILURE(child.Start("gcore", args));
ASSERT_NO_FATAL_FAILURE(child.CollectStdout());
ASSERT_NO_FATAL_FAILURE(child.CollectStderr());
int status;
ASSERT_NO_FATAL_FAILURE(child.Finish(status));
EXPECT_EQ(status, EXIT_SUCCESS);
EXPECT_EQ(child.collected_stdout(), "");
EXPECT_EQ(child.collected_stderr(), "");
fbl::unique_fd fd = dump_file.OpenOutput();
ASSERT_TRUE(fd);
elfldltl::Elf<>::Ehdr ehdr;
ASSERT_EQ(read(fd.get(), &ehdr, sizeof(ehdr)), static_cast<ssize_t>(sizeof(ehdr)))
<< strerror(errno);
EXPECT_TRUE(ehdr.Valid());
EXPECT_EQ(ehdr.type, elfldltl::ElfType::kCore);
}
// Without --jobs, `gcore JOB_KOID` is an error.
TEST(ZxdumpTests, GcoreJobRequiresSwitch) {
zxdump::testing::TestProcess process;
// We don't even need to spawn a process for this test.
// Just create an empty job and (fail to) dump it.
ASSERT_NO_FATAL_FAILURE(process.HermeticJob());
zxdump::testing::TestToolProcess child;
ASSERT_NO_FATAL_FAILURE(child.Init());
const auto& [dump_file, prefix, pid_string] =
GetOutputFile(child, "job-dump", process.job_koid(), true);
dump_file.NoFile();
std::vector<std::string> args({
kNoChildrenSwitch,
kNoProcessesSwitch,
kOutputSwitch,
prefix,
pid_string,
});
ASSERT_NO_FATAL_FAILURE(child.Start("gcore", args));
ASSERT_NO_FATAL_FAILURE(child.CollectStdout());
ASSERT_NO_FATAL_FAILURE(child.CollectStderr());
int status;
ASSERT_NO_FATAL_FAILURE(child.Finish(status));
EXPECT_EQ(status, EXIT_FAILURE);
EXPECT_EQ(child.collected_stdout(), "");
std::string error_text = child.collected_stderr();
EXPECT_TRUE(cpp20::ends_with(std::string_view(error_text), ": KOID is not a process\n"))
<< error_text;
}
// With --jobs, you still just get an ET_CORE file (for each process).
TEST(ZxdumpTests, GcoreProcessDumpViaJob) {
zxdump::testing::TestProcess process;
ASSERT_NO_FATAL_FAILURE(process.HermeticJob());
ASSERT_NO_FATAL_FAILURE(process.StartChild());
zxdump::testing::TestToolProcess child;
ASSERT_NO_FATAL_FAILURE(child.Init());
const auto& [dump_file, prefix, pid_string] =
GetOutputFile(child, "process-dump-via-job", process.koid());
std::vector<std::string> args({
kJobsSwitch,
// Don't dump memory since we don't need it and it is large.
kExcludeMemorySwitch,
// Don't bother dumping threads since this test doesn't check for them.
kNoThreadsSwitch,
kOutputSwitch,
prefix,
std::to_string(process.job_koid()),
});
ASSERT_NO_FATAL_FAILURE(child.Start("gcore", args));
ASSERT_NO_FATAL_FAILURE(child.CollectStdout());
ASSERT_NO_FATAL_FAILURE(child.CollectStderr());
int status;
ASSERT_NO_FATAL_FAILURE(child.Finish(status));
EXPECT_EQ(status, EXIT_SUCCESS);
EXPECT_EQ(child.collected_stdout(), "");
EXPECT_EQ(child.collected_stderr(), "");
fbl::unique_fd fd = dump_file.OpenOutput();
ASSERT_TRUE(fd);
elfldltl::Elf<>::Ehdr ehdr;
ASSERT_EQ(read(fd.get(), &ehdr, sizeof(ehdr)), static_cast<ssize_t>(sizeof(ehdr)))
<< strerror(errno);
EXPECT_TRUE(ehdr.Valid());
EXPECT_EQ(ehdr.type, elfldltl::ElfType::kCore);
}
TEST(ZxdumpTests, GcoreJobDumpIsArchive) {
zxdump::testing::TestProcess process;
// We don't even need to spawn a process for this test.
// Just create an empty job and dump it.
ASSERT_NO_FATAL_FAILURE(process.HermeticJob());
zxdump::testing::TestToolProcess child;
ASSERT_NO_FATAL_FAILURE(child.Init());
const auto& [dump_file, prefix, pid_string] =
GetOutputFile(child, "job-dump", process.job_koid(), true);
std::vector<std::string> args({
kJobArchiveSwitch,
kNoChildrenSwitch,
kNoProcessesSwitch,
kOutputSwitch,
prefix,
pid_string,
});
ASSERT_NO_FATAL_FAILURE(child.Start("gcore", args));
ASSERT_NO_FATAL_FAILURE(child.CollectStdout());
ASSERT_NO_FATAL_FAILURE(child.CollectStderr());
int status;
ASSERT_NO_FATAL_FAILURE(child.Finish(status));
EXPECT_EQ(status, EXIT_SUCCESS);
EXPECT_EQ(child.collected_stdout(), "");
EXPECT_EQ(child.collected_stderr(), "");
fbl::unique_fd fd = dump_file.OpenOutput();
ASSERT_TRUE(fd) << dump_file.name() << ": " << strerror(errno);
char buffer[zxdump::kMinimumArchive];
ASSERT_EQ(read(fd.get(), buffer, sizeof(buffer)), static_cast<ssize_t>(sizeof(buffer)))
<< strerror(errno);
EXPECT_TRUE(cpp20::starts_with(std::string_view(buffer, sizeof(buffer)), zxdump::kArchiveMagic));
}
TEST(ZxdumpTests, GcoreProcessDumpPropertiesAndInfo) {
zxdump::testing::TestProcessForPropertiesAndInfo process;
ASSERT_NO_FATAL_FAILURE(process.StartChild());
zxdump::testing::TestToolProcess child;
ASSERT_NO_FATAL_FAILURE(child.Init());
const auto& [dump_file, prefix, pid_string] =
GetOutputFile(child, "process-dump-no-threads", process.koid());
std::vector<std::string> args({
// Don't include threads.
kNoThreadsSwitch,
// Don't dump memory since we don't need it and it is large.
kExcludeMemorySwitch,
kOutputSwitch,
prefix,
pid_string,
});
ASSERT_NO_FATAL_FAILURE(child.Start("gcore", args));
ASSERT_NO_FATAL_FAILURE(child.CollectStdout());
ASSERT_NO_FATAL_FAILURE(child.CollectStderr());
int status;
ASSERT_NO_FATAL_FAILURE(child.Finish(status));
EXPECT_EQ(status, EXIT_SUCCESS);
EXPECT_EQ(child.collected_stdout(), "");
EXPECT_EQ(child.collected_stderr(), "");
fbl::unique_fd fd = dump_file.OpenOutput();
ASSERT_TRUE(fd) << dump_file.name() << ": " << strerror(errno);
zxdump::TaskHolder holder;
auto read_result = holder.Insert(std::move(fd));
ASSERT_TRUE(read_result.is_ok()) << read_result.error_value();
ASSERT_NO_FATAL_FAILURE(process.CheckDump(holder, false));
}
// There are versions of this test reading a file already decompressed by the
// zstd tool; reading a compressed file directly; and reading a compressed dump
// stream from a pipe. The post_process function turns the compressed dump
// file written by gcore into an fd to pass to the reader.
void GcoreProcessDumpZstdTest(
fit::function<void(zxdump::testing::TestToolProcess::File& compressed_dump_file,
fbl::unique_fd& read_fd)>
post_process) {
zxdump::testing::TestProcessForPropertiesAndInfo process;
ASSERT_NO_FATAL_FAILURE(process.StartChild());
zxdump::testing::TestToolProcess child;
ASSERT_NO_FATAL_FAILURE(child.Init());
const auto& [dump_file, prefix, pid_string] =
GetOutputFile(child, "gcore-process-zstd", process.koid(), false,
zxdump::testing::TestToolProcess::File::kZstdSuffix);
std::vector<std::string> args({
// Compress the output.
kZstdSwitch,
// Don't include threads.
kNoThreadsSwitch,
// Don't dump memory since we don't need it and it is large.
kExcludeMemorySwitch,
kOutputSwitch,
prefix,
pid_string,
});
ASSERT_NO_FATAL_FAILURE(child.Start("gcore", args));
ASSERT_NO_FATAL_FAILURE(child.CollectStdout());
ASSERT_NO_FATAL_FAILURE(child.CollectStderr());
int status;
ASSERT_NO_FATAL_FAILURE(child.Finish(status));
EXPECT_EQ(status, EXIT_SUCCESS);
EXPECT_EQ(child.collected_stdout(), "");
EXPECT_EQ(child.collected_stderr(), "");
fbl::unique_fd fd;
ASSERT_NO_FATAL_FAILURE(post_process(dump_file, fd));
zxdump::TaskHolder holder;
auto read_result = holder.Insert(std::move(fd));
ASSERT_TRUE(read_result.is_ok()) << read_result.error_value();
ASSERT_NO_FATAL_FAILURE(process.CheckDump(holder, false));
}
// Decompress the file using the zstd tool. This ensures that the compressed
// output from gcore is compatible with canonical decompression, not just with
// the reader's decompression.
TEST(ZxdumpTests, GcoreProcessDumpZstd) {
constexpr auto decompress = [](zxdump::testing::TestToolProcess::File& file,
fbl::unique_fd& read_fd) {
auto& decompressed_file = file.ZstdDecompress();
read_fd = decompressed_file.OpenOutput();
ASSERT_TRUE(read_fd) << decompressed_file.name() << ": " << strerror(errno);
};
ASSERT_NO_FATAL_FAILURE(GcoreProcessDumpZstdTest(decompress));
}
// Let the reader automatically decompress the file.
TEST(ZxdumpTests, GcoreProcessDumpZstdReader) {
constexpr auto open_as_is = [](zxdump::testing::TestToolProcess::File& file,
fbl::unique_fd& read_fd) {
read_fd = file.OpenOutput();
ASSERT_TRUE(read_fd) << file.name() << ": " << strerror(errno);
};
ASSERT_NO_FATAL_FAILURE(GcoreProcessDumpZstdTest(open_as_is));
}
// Let the reader automatically decompress the file but via a pipe so it has to
// do streaming input for the decompressor.
TEST(ZxdumpTests, GcoreProcessDumpZstdPipeReader) {
zxdump::testing::TestToolProcess cat;
auto open_via_pipe = [&cat](zxdump::testing::TestToolProcess::File& file,
fbl::unique_fd& read_fd) mutable {
ASSERT_NO_FATAL_FAILURE(cat.Init(file.tmp_path()));
std::vector<std::string> args({file.name()});
ASSERT_NO_FATAL_FAILURE(cat.Start("cat"s, args));
read_fd = std::move(cat.tool_stdout());
};
ASSERT_NO_FATAL_FAILURE(GcoreProcessDumpZstdTest(open_via_pipe));
}
TEST(ZxdumpTests, GcoreProcessDumpSystemInfo) {
zxdump::testing::TestProcessForSystemInfo process;
ASSERT_NO_FATAL_FAILURE(process.StartChild());
zxdump::testing::TestToolProcess child;
ASSERT_NO_FATAL_FAILURE(child.Init());
const auto& [dump_file, prefix, pid_string] =
GetOutputFile(child, "process-dump-system", process.koid());
std::vector<std::string> args({
kSystemSwitch,
// Don't include threads.
kNoThreadsSwitch,
// Don't dump memory since we don't need it and it is large.
kExcludeMemorySwitch,
kOutputSwitch,
prefix,
pid_string,
});
ASSERT_NO_FATAL_FAILURE(child.Start("gcore", args));
ASSERT_NO_FATAL_FAILURE(child.CollectStdout());
ASSERT_NO_FATAL_FAILURE(child.CollectStderr());
int status;
ASSERT_NO_FATAL_FAILURE(child.Finish(status));
EXPECT_EQ(status, EXIT_SUCCESS);
EXPECT_EQ(child.collected_stdout(), "");
EXPECT_EQ(child.collected_stderr(), "");
fbl::unique_fd fd = dump_file.OpenOutput();
ASSERT_TRUE(fd) << dump_file.name() << ": " << strerror(errno);
zxdump::TaskHolder holder;
auto read_result = holder.Insert(std::move(fd));
ASSERT_TRUE(read_result.is_ok()) << read_result.error_value();
ASSERT_NO_FATAL_FAILURE(process.CheckDump(holder));
}
TEST(ZxdumpTests, GcoreProcessDumpKernelInfo) {
zxdump::testing::TestProcessForKernelInfo process;
ASSERT_NO_FATAL_FAILURE(process.StartChild());
zxdump::testing::TestToolProcess child;
ASSERT_NO_FATAL_FAILURE(child.Init());
const auto& [dump_file, prefix, pid_string] =
GetOutputFile(child, "process-dump-kernel", process.koid());
std::vector<std::string> args({
kKernelSwitch,
// Don't include threads.
kNoThreadsSwitch,
// Don't dump memory since we don't need it and it is large.
kExcludeMemorySwitch,
kOutputSwitch,
prefix,
pid_string,
});
// The gcore process needs to get the RootResource protocol, which
// StartChild() already fetched for us.
EXPECT_TRUE(process.root_resource());
child.set_resource(zx::unowned_resource{process.root_resource().get()});
ASSERT_NO_FATAL_FAILURE(child.Start("gcore", args));
ASSERT_NO_FATAL_FAILURE(child.CollectStdout());
ASSERT_NO_FATAL_FAILURE(child.CollectStderr());
int status;
ASSERT_NO_FATAL_FAILURE(child.Finish(status));
EXPECT_EQ(status, EXIT_SUCCESS);
EXPECT_EQ(child.collected_stdout(), "");
EXPECT_EQ(child.collected_stderr(), "");
fbl::unique_fd fd = dump_file.OpenOutput();
ASSERT_TRUE(fd) << dump_file.name() << ": " << strerror(errno);
zxdump::TaskHolder holder;
auto read_result = holder.Insert(std::move(fd));
ASSERT_TRUE(read_result.is_ok()) << read_result.error_value();
ASSERT_NO_FATAL_FAILURE(process.CheckDump(holder));
}
TEST(ZxdumpTests, GcoreProcessDumpNoDate) {
zxdump::testing::TestProcessForPropertiesAndInfo process;
ASSERT_NO_FATAL_FAILURE(process.StartChild());
zxdump::testing::TestToolProcess child;
ASSERT_NO_FATAL_FAILURE(child.Init());
const auto& [dump_file, prefix, pid_string] =
GetOutputFile(child, "process-dump-no-date", process.koid());
std::vector<std::string> args({
kNoDateSwitch,
// Don't include threads.
kNoThreadsSwitch,
// Don't dump memory since we don't need it and it is large.
kExcludeMemorySwitch,
kOutputSwitch,
prefix,
pid_string,
});
ASSERT_NO_FATAL_FAILURE(child.Start("gcore", args));
ASSERT_NO_FATAL_FAILURE(child.CollectStdout());
ASSERT_NO_FATAL_FAILURE(child.CollectStderr());
int status;
ASSERT_NO_FATAL_FAILURE(child.Finish(status));
EXPECT_EQ(status, EXIT_SUCCESS);
EXPECT_EQ(child.collected_stdout(), "");
EXPECT_EQ(child.collected_stderr(), "");
fbl::unique_fd fd = dump_file.OpenOutput();
ASSERT_TRUE(fd) << dump_file.name() << ": " << strerror(errno);
zxdump::TaskHolder holder;
auto read_result = holder.Insert(std::move(fd));
ASSERT_TRUE(read_result.is_ok()) << read_result.error_value();
auto find_result = holder.root_job().find(process.koid());
ASSERT_TRUE(find_result.is_ok()) << find_result.error_value();
ASSERT_EQ(find_result->get().type(), ZX_OBJ_TYPE_PROCESS);
zxdump::Process& read_process = static_cast<zxdump::Process&>(find_result->get());
EXPECT_EQ(read_process.date(), zxdump::testing::kNoDate);
}
TEST(ZxdumpTests, GcoreProcessDumpDate) {
zxdump::testing::TestProcessForPropertiesAndInfo process;
ASSERT_NO_FATAL_FAILURE(process.StartChild());
// gcore includes a date note by default, but offers no way to fudge a
// synthetic date. So it will use the current time when it starts the dump,
// which will be in the future (possibly rounded to the same current second).
const time_t before_dump = time(nullptr);
ASSERT_GT(before_dump, zxdump::testing::kNoDate);
zxdump::testing::TestToolProcess child;
ASSERT_NO_FATAL_FAILURE(child.Init());
const auto& [dump_file, prefix, pid_string] =
GetOutputFile(child, "process-dump-date", process.koid());
std::vector<std::string> args({
// Don't include threads.
kNoThreadsSwitch,
// Don't dump memory since we don't need it and it is large.
kExcludeMemorySwitch,
kOutputSwitch,
prefix,
pid_string,
});
ASSERT_NO_FATAL_FAILURE(child.Start("gcore", args));
ASSERT_NO_FATAL_FAILURE(child.CollectStdout());
ASSERT_NO_FATAL_FAILURE(child.CollectStderr());
int status;
ASSERT_NO_FATAL_FAILURE(child.Finish(status));
EXPECT_EQ(status, EXIT_SUCCESS);
EXPECT_EQ(child.collected_stdout(), "");
EXPECT_EQ(child.collected_stderr(), "");
fbl::unique_fd fd = dump_file.OpenOutput();
ASSERT_TRUE(fd) << dump_file.name() << ": " << strerror(errno);
zxdump::TaskHolder holder;
auto read_result = holder.Insert(std::move(fd));
ASSERT_TRUE(read_result.is_ok()) << read_result.error_value();
auto find_result = holder.root_job().find(process.koid());
ASSERT_TRUE(find_result.is_ok()) << find_result.error_value();
ASSERT_EQ(find_result->get().type(), ZX_OBJ_TYPE_PROCESS);
zxdump::Process& read_process = static_cast<zxdump::Process&>(find_result->get());
EXPECT_GE(read_process.date(), before_dump);
}
TEST(ZxdumpTests, GcoreProcessDumpRemarks) {
using Process = zxdump::testing::TestProcessForRemarks;
Process process;
ASSERT_NO_FATAL_FAILURE(process.StartChild());
zxdump::testing::TestToolProcess child;
ASSERT_NO_FATAL_FAILURE(child.Init());
const auto& [dump_file, prefix, pid_string] =
GetOutputFile(child, "process-dump-remarks", process.koid());
std::vector<std::string> args({
std::string(kRemarksSwitch) + std::string(Process::kTextRemarksData),
std::string(kRemarksSwitch) + std::string(Process::kTestRemarksNameNoSuffix) + '=' +
std::string(Process::kTextRemarksData),
std::string(kRawRemarksSwitch) + std::string(Process::kBinaryRemarksName) + '=' +
std::string(Process::AsString(Process::kBinaryRemarksData)),
std::string(kJsonRemarksSwitch) + std::string(Process::kRawJsonRemarksData),
std::string(kJsonRemarksSwitch) + std::string(Process::kTestRemarksNameNoSuffix) + '=' +
std::string(Process::kRawJsonRemarksData),
// Don't include threads.
kNoThreadsSwitch,
// Don't dump memory since we don't need it and it is large.
kExcludeMemorySwitch,
kOutputSwitch,
prefix,
pid_string,
});
ASSERT_NO_FATAL_FAILURE(child.Start("gcore", args));
ASSERT_NO_FATAL_FAILURE(child.CollectStdout());
ASSERT_NO_FATAL_FAILURE(child.CollectStderr());
int status;
ASSERT_NO_FATAL_FAILURE(child.Finish(status));
EXPECT_EQ(status, EXIT_SUCCESS);
EXPECT_EQ(child.collected_stdout(), "");
EXPECT_EQ(child.collected_stderr(), "");
fbl::unique_fd fd = dump_file.OpenOutput();
ASSERT_TRUE(fd) << dump_file.name() << ": " << strerror(errno);
zxdump::TaskHolder holder;
auto read_result = holder.Insert(std::move(fd));
ASSERT_TRUE(read_result.is_ok()) << read_result.error_value();
ASSERT_NO_FATAL_FAILURE(process.CheckDump(holder));
}
TEST(ZxdumpTests, GcoreProcessDumpRemarksFromFile) {
using Process = zxdump::testing::TestProcessForRemarks;
Process process;
ASSERT_NO_FATAL_FAILURE(process.StartChild());
zxdump::testing::TestToolProcess child;
ASSERT_NO_FATAL_FAILURE(child.Init());
const auto& [dump_file, prefix, pid_string] =
GetOutputFile(child, "process-dump-remarks-from-file", process.koid());
auto& input_text_file = child.MakeFile("text-remarks.txt");
input_text_file.CreateInput(Process::kTextRemarksData);
auto& input_binary_file = child.MakeFile("binary-remarks.bin");
input_binary_file.CreateInput(Process::AsString(Process::kBinaryRemarksData));
auto& input_json_file = child.MakeFile("json-remarks.json");
input_json_file.CreateInput(Process::kRawJsonRemarksData);
std::vector<std::string> args({
std::string(kRemarksSwitch) + '@' + input_text_file.name(),
std::string(kRemarksSwitch) + std::string(Process::kTestRemarksNameNoSuffix) + "=@" +
input_text_file.name(),
std::string(kRawRemarksSwitch) + std::string(Process::kBinaryRemarksName) + "=@" +
input_binary_file.name(),
std::string(kJsonRemarksSwitch) + '@' + input_json_file.name(),
std::string(kJsonRemarksSwitch) + std::string(Process::kTestRemarksNameNoSuffix) + "=@" +
input_json_file.name(),
// Don't include threads.
kNoThreadsSwitch,
// Don't dump memory since we don't need it and it is large.
kExcludeMemorySwitch,
kOutputSwitch,
prefix,
pid_string,
});
ASSERT_NO_FATAL_FAILURE(child.Start("gcore", args));
ASSERT_NO_FATAL_FAILURE(child.CollectStdout());
ASSERT_NO_FATAL_FAILURE(child.CollectStderr());
int status;
ASSERT_NO_FATAL_FAILURE(child.Finish(status));
EXPECT_EQ(status, EXIT_SUCCESS);
EXPECT_EQ(child.collected_stdout(), "");
EXPECT_EQ(child.collected_stderr(), "");
fbl::unique_fd fd = dump_file.OpenOutput();
ASSERT_TRUE(fd) << dump_file.name() << ": " << strerror(errno);
zxdump::TaskHolder holder;
auto read_result = holder.Insert(std::move(fd));
ASSERT_TRUE(read_result.is_ok()) << read_result.error_value();
ASSERT_NO_FATAL_FAILURE(process.CheckDump(holder));
}
TEST(ZxdumpTests, GcoreProcessDumpRemarksBadJson) {
using Process = zxdump::testing::TestProcessForRemarks;
Process process;
ASSERT_NO_FATAL_FAILURE(process.StartChild());
zxdump::testing::TestToolProcess child;
ASSERT_NO_FATAL_FAILURE(child.Init());
const auto& [dump_file, prefix, pid_string] =
GetOutputFile(child, "process-dump-remarks-bad-json", process.koid());
std::vector<std::string> args({
std::string(kJsonRemarksSwitch) + "not valid json syntax",
// Don't include threads.
kNoThreadsSwitch,
// Don't dump memory since we don't need it and it is large.
kExcludeMemorySwitch,
kOutputSwitch,
prefix,
pid_string,
});
dump_file.NoFile(); // Won't actually get written.
ASSERT_NO_FATAL_FAILURE(child.Start("gcore", args));
ASSERT_NO_FATAL_FAILURE(child.CollectStdout());
ASSERT_NO_FATAL_FAILURE(child.CollectStderr());
int status;
ASSERT_NO_FATAL_FAILURE(child.Finish(status));
EXPECT_EQ(status, EXIT_FAILURE);
EXPECT_EQ(child.collected_stdout(), "");
EXPECT_NE(child.collected_stderr(), "");
}
TEST(ZxdumpTests, GcoreProcessDumpMemory) {
zxdump::testing::TestProcessForMemory process;
ASSERT_NO_FATAL_FAILURE(process.StartChild());
zxdump::testing::TestToolProcess child;
ASSERT_NO_FATAL_FAILURE(child.Init());
const auto& [dump_file, prefix, pid_string] =
GetOutputFile(child, "process-dump-memory-no-threads", process.koid());
std::vector<std::string> args({
// Don't include threads.
kNoThreadsSwitch,
kOutputSwitch,
prefix,
pid_string,
});
ASSERT_NO_FATAL_FAILURE(child.Start("gcore", args));
ASSERT_NO_FATAL_FAILURE(child.CollectStdout());
ASSERT_NO_FATAL_FAILURE(child.CollectStderr());
int status;
ASSERT_NO_FATAL_FAILURE(child.Finish(status));
EXPECT_EQ(status, EXIT_SUCCESS);
EXPECT_EQ(child.collected_stdout(), "");
EXPECT_EQ(child.collected_stderr(), "");
fbl::unique_fd fd = dump_file.OpenOutput();
ASSERT_TRUE(fd) << dump_file.name() << ": " << strerror(errno);
zxdump::TaskHolder holder;
auto read_result = holder.Insert(std::move(fd));
ASSERT_TRUE(read_result.is_ok()) << read_result.error_value();
ASSERT_NO_FATAL_FAILURE(process.CheckDump(holder));
}
TEST(ZxdumpTests, GcoreProcessDumpViaLiveSwitch) {
zxdump::testing::TestProcessForPropertiesAndInfo process;
ASSERT_NO_FATAL_FAILURE(process.StartChild());
zxdump::testing::TestToolProcess child;
ASSERT_NO_FATAL_FAILURE(child.Init());
const auto& [dump_file, prefix, pid_string] =
GetOutputFile(child, "process-dump-no-threads", process.koid());
std::vector<std::string> args({
kLiveSwitch,
// Don't include threads.
kNoThreadsSwitch,
// Don't dump memory since we don't need it and it is large.
kExcludeMemorySwitch,
kOutputSwitch,
prefix,
pid_string,
});
ASSERT_NO_FATAL_FAILURE(child.Start("gcore", args));
ASSERT_NO_FATAL_FAILURE(child.CollectStdout());
ASSERT_NO_FATAL_FAILURE(child.CollectStderr());
int status;
ASSERT_NO_FATAL_FAILURE(child.Finish(status));
EXPECT_EQ(status, EXIT_SUCCESS);
EXPECT_EQ(child.collected_stdout(), "");
EXPECT_EQ(child.collected_stderr(), "");
fbl::unique_fd fd = dump_file.OpenOutput();
ASSERT_TRUE(fd) << dump_file.name() << ": " << strerror(errno);
zxdump::TaskHolder holder;
auto read_result = holder.Insert(std::move(fd));
ASSERT_TRUE(read_result.is_ok()) << read_result.error_value();
ASSERT_NO_FATAL_FAILURE(process.CheckDump(holder, false));
}
TEST(ZxdumpTests, GcoreProcessDumpStreamingToPipe) {
zxdump::testing::TestProcessForPropertiesAndInfo process;
ASSERT_NO_FATAL_FAILURE(process.StartChild());
zxdump::testing::TestToolProcess child;
ASSERT_NO_FATAL_FAILURE(child.Init());
std::vector<std::string> args({
kStreamingSwitch,
// Don't include threads.
kNoThreadsSwitch,
// Don't dump memory since we don't need it and it is large.
kExcludeMemorySwitch,
std::to_string(process.koid()),
});
ASSERT_NO_FATAL_FAILURE(child.Start("gcore", args));
ASSERT_NO_FATAL_FAILURE(child.CollectStderr());
// Inserting the stdout pipe fd below will read from the pipe until EOF.
// After that, check and report the child status and stderr even if reading
// or checking the dump fails.
auto finish = fit::defer([&child]() {
int status;
ASSERT_NO_FATAL_FAILURE(child.Finish(status));
EXPECT_EQ(status, EXIT_SUCCESS);
EXPECT_EQ(child.collected_stderr(), "");
});
zxdump::TaskHolder holder;
auto read_result = holder.Insert(std::move(child.tool_stdout()));
ASSERT_TRUE(read_result.is_ok()) << read_result.error_value();
ASSERT_NO_FATAL_FAILURE(process.CheckDump(holder, false));
}
TEST(ZxdumpTests, GcoreProcessDumpStreamingToFile) {
zxdump::testing::TestProcessForPropertiesAndInfo process;
ASSERT_NO_FATAL_FAILURE(process.StartChild());
zxdump::testing::TestToolProcess child;
ASSERT_NO_FATAL_FAILURE(child.Init());
auto& dump_file = child.MakeFile("process-dump-streaming-to-file");
std::vector<std::string> args({
kStreamingSwitch,
kOutputSwitch,
dump_file.name(),
// Don't include threads.
kNoThreadsSwitch,
// Don't dump memory since we don't need it and it is large.
kExcludeMemorySwitch,
std::to_string(process.koid()),
});
ASSERT_NO_FATAL_FAILURE(child.Start("gcore", args));
ASSERT_NO_FATAL_FAILURE(child.CollectStdout());
ASSERT_NO_FATAL_FAILURE(child.CollectStderr());
int status;
ASSERT_NO_FATAL_FAILURE(child.Finish(status));
EXPECT_EQ(status, EXIT_SUCCESS);
EXPECT_EQ(child.collected_stdout(), "");
EXPECT_EQ(child.collected_stderr(), "");
fbl::unique_fd fd = dump_file.OpenOutput();
ASSERT_TRUE(fd) << dump_file.name() << ": " << strerror(errno);
zxdump::TaskHolder holder;
auto read_result = holder.Insert(std::move(fd));
ASSERT_TRUE(read_result.is_ok()) << read_result.error_value();
ASSERT_NO_FATAL_FAILURE(process.CheckDump(holder, false));
}
TEST(ZxdumpTests, GcoreProcessDumpStreamingArchive) {
zxdump::testing::TestProcessForPropertiesAndInfo process1, process2;
ASSERT_NO_FATAL_FAILURE(process1.StartChild());
ASSERT_NO_FATAL_FAILURE(process2.StartChild());
zxdump::testing::TestToolProcess child;
ASSERT_NO_FATAL_FAILURE(child.Init());
std::vector<std::string> args({
kStreamingSwitch,
// Don't include threads.
kNoThreadsSwitch,
// Don't dump memory since we don't need it and it is large.
kExcludeMemorySwitch,
std::to_string(process1.koid()),
std::to_string(process2.koid()),
});
ASSERT_NO_FATAL_FAILURE(child.Start("gcore", args));
ASSERT_NO_FATAL_FAILURE(child.CollectStderr());
// Inserting the stdout pipe fd below will read from the pipe until EOF.
// After that, check and report the child status and stderr even if reading
// or checking the dump fails.
auto finish = fit::defer([&child]() {
int status;
ASSERT_NO_FATAL_FAILURE(child.Finish(status));
EXPECT_EQ(status, EXIT_SUCCESS);
EXPECT_EQ(child.collected_stderr(), "");
});
zxdump::TaskHolder holder;
auto read_result = holder.Insert(std::move(child.tool_stdout()));
ASSERT_TRUE(read_result.is_ok()) << read_result.error_value();
// Both processes were dumped but no jobs, so there should be a fake root.
EXPECT_EQ(holder.root_job().koid(), ZX_KOID_INVALID);
ASSERT_NO_FATAL_FAILURE(process1.CheckDump(holder, false));
ASSERT_NO_FATAL_FAILURE(process2.CheckDump(holder, false));
}
TEST(ZxdumpTests, GcoreElfSearch) {
zxdump::testing::TestProcessForElfSearch process;
ASSERT_NO_FATAL_FAILURE(process.StartChild());
zxdump::testing::TestToolProcess child;
ASSERT_NO_FATAL_FAILURE(child.Init());
const auto& [dump_file, prefix, pid_string] =
GetOutputFile(child, "gcore-elf-search", process.koid());
std::vector<std::string> args({
// Don't include threads.
kNoThreadsSwitch,
kOutputSwitch,
prefix,
pid_string,
});
ASSERT_NO_FATAL_FAILURE(child.Start("gcore", args));
ASSERT_NO_FATAL_FAILURE(child.CollectStdout());
ASSERT_NO_FATAL_FAILURE(child.CollectStderr());
int status;
ASSERT_NO_FATAL_FAILURE(child.Finish(status));
EXPECT_EQ(status, EXIT_SUCCESS);
EXPECT_EQ(child.collected_stdout(), "");
EXPECT_EQ(child.collected_stderr(), "");
fbl::unique_fd fd = dump_file.OpenOutput();
ASSERT_TRUE(fd) << dump_file.name() << ": " << strerror(errno);
// Check the PT_NOTEs produced for build IDs.
ASSERT_NO_FATAL_FAILURE(process.CheckNotes(fd.get()));
ASSERT_EQ(0, lseek(fd.get(), SEEK_SET, 0));
zxdump::TaskHolder holder;
auto read_result = holder.Insert(std::move(fd));
ASSERT_TRUE(read_result.is_ok()) << read_result.error_value();
ASSERT_NO_FATAL_FAILURE(process.CheckDump(holder));
}
#endif // __Fuchsia__
} // namespace