blob: 045b50603c45f2b5c8ed01f4058d43301959df43 [file]
// Copyright 2024 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 "src/developer/debug/debug_agent/debug_agent_server.h"
#include <algorithm>
#include <memory>
#include <gtest/gtest.h>
#include "lib/async/default.h"
#include "src/developer/debug/debug_agent/mock_component_manager.h"
#include "src/developer/debug/debug_agent/mock_debug_agent_harness.h"
#include "src/developer/debug/debug_agent/mock_process.h"
#include "src/developer/debug/debug_agent/mock_process_handle.h"
#include "src/developer/debug/debug_agent/mock_thread.h"
#include "src/developer/debug/debug_agent/process_info_iterator.h"
#include "src/developer/debug/ipc/protocol.h"
#include "src/developer/debug/ipc/records.h"
#include "src/developer/debug/shared/message_loop.h"
#include "src/developer/debug/shared/result.h"
#include "src/developer/debug/shared/test_with_loop.h"
namespace debug_agent {
// This class is a friend of DebugAgentServer so that we may test the private, non-FIDL APIs
// directly.
class DebugAgentServerTest : public debug::TestWithLoop {
public:
DebugAgentServerTest()
: server_(harness_.debug_agent()->GetWeakPtr(),
debug::MessageLoopFuchsia::Current()->dispatcher()) {}
DebugAgentServer::AddFilterResult AddFilter(const fuchsia_debugger::Filter& filter) {
auto server_filters_before = server_.filters_;
auto result = server_.AddFilter(filter);
std::map<debug_ipc::Filter::Identifier, debug_ipc::Filter> diff;
// Take the previous list of filters known by the server and compare it to the new list. The
// resulting map of filters will contain all of the new filters that were added by this call.
// There should always be at least one, and potentially two, added filters, depending on the
// settings of |filter|.
std::set_difference(server_.filters_.begin(), server_.filters_.end(),
server_filters_before.begin(), server_filters_before.end(),
std::inserter(diff, diff.begin()),
[](const auto& p1, const auto& p2) { return p1.first < p2.first; });
if (result.ok()) {
last_added_filters_.clear();
last_added_filters_.insert(diff.begin(), diff.end());
}
return result;
}
const std::vector<debug_ipc::NotifyFilterCreated>& notify_filter_created() {
return harness_.stream_backend()->filters();
}
uint32_t AttachToMatchingKoids(const debug_ipc::UpdateFilterReply& reply) {
return server_.AttachToFilterMatches(reply.matched_processes_for_filter);
}
auto GetMatchingProcesses(std::optional<fuchsia_debugger::Filter> filter) {
return server_.GetMatchingProcesses(std::move(filter));
}
debug_ipc::StatusReply GetAgentStatus() {
debug_ipc::StatusReply reply;
harness_.debug_agent()->OnStatus({}, &reply);
return reply;
}
const std::map<debug_ipc::Filter::Identifier, debug_ipc::Filter>& last_added_filters() const {
return last_added_filters_;
}
MockDebugAgentHarness* harness() { return &harness_; }
DebugAgent* GetDebugAgent() { return harness_.debug_agent(); }
DebugAgentServer* server() { return &server_; }
private:
// This is a map to both reflect that sending one filter in an UpdateFilter request may result in
// more than one filter actually being installed, and for easy lookup by filter identifier.
std::map<debug_ipc::Filter::Identifier, debug_ipc::Filter> last_added_filters_;
MockDebugAgentHarness harness_;
DebugAgentServer server_;
};
TEST_F(DebugAgentServerTest, AddNewFilter) {
DebugAgent* agent = GetDebugAgent();
auto status_reply = GetAgentStatus();
// There shouldn't be any installed filters yet.
ASSERT_EQ(status_reply.filters.size(), 0u);
fuchsia_debugger::Filter first;
// This will match job koid 25 from mock_system_interface.
first.pattern("fixed/moniker");
first.type(fuchsia_debugger::FilterType::kMonikerSuffix);
auto result = AddFilter(first);
EXPECT_TRUE(result.ok());
auto reply = result.take_value();
// There should be one reported match.
EXPECT_EQ(reply.matched_processes_for_filter.size(), 1u);
EXPECT_EQ(reply.matched_processes_for_filter[0].matches.size(), 1u);
EXPECT_EQ(debug_ipc::TaskType::kProcess, reply.matched_processes_for_filter[0].matches[0].type);
// Now attach to the matching koid.
EXPECT_EQ(AttachToMatchingKoids(reply), 1u);
status_reply = GetAgentStatus();
EXPECT_EQ(status_reply.filters.size(), 1u);
EXPECT_EQ(status_reply.filters[0].pattern, first.pattern());
EXPECT_EQ(status_reply.filters[0].type, debug_ipc::Filter::Type::kComponentMonikerSuffix);
// The recursive flag was left unspecified, which should leave the default value of false in the
// debug_ipc filter.
EXPECT_EQ(status_reply.filters[0].config.recursive, false);
EXPECT_EQ(status_reply.processes.size(), 1u);
EXPECT_EQ(status_reply.processes[0].process_koid,
reply.matched_processes_for_filter[0].matches[0].koid);
// Run the loop so the process has a chance to update its thread list.
loop().RunUntilNoTasks();
auto proc = agent->GetDebuggedProcess(reply.matched_processes_for_filter[0].matches[0].koid);
auto thread_records = proc->GetThreadRecords();
ASSERT_FALSE(thread_records.empty());
// No threads should be suspended because we should have attached weakly.
for (auto& record : thread_records) {
EXPECT_EQ(record.state, debug_ipc::ThreadRecord::State::kRunning);
}
// Corresponds to the koid of the process under the "fixed/moniker" component.
constexpr zx_koid_t kProcessKoid = 26;
EXPECT_NE(agent->GetDebuggedProcess(kProcessKoid), nullptr);
// Simulate a test environment rooted in the collection "test" with name "test_root". A recursive
// moniker suffix filter on "test:test_root" will implicitly install a second moniker prefix
// filter for the entire moniker up to and including "test:test_root" so that any child components
// spawned within its realm will be attached to. We don't need to know the moniker of any child
// components in order to attach to any processes they contain. This is modeled after the real
// moniker structure used for tests in TestManager.
constexpr char kFullRootMoniker[] = "/moniker/generated/test:test_root";
constexpr zx_koid_t kJob4Process1Koid = 33;
fuchsia_debugger::Filter second;
second.pattern("test:test_root");
second.type(fuchsia_debugger::FilterType::kMonikerSuffix);
second.options().recursive(true);
result = AddFilter(second);
EXPECT_TRUE(result.ok());
reply = result.take_value();
// When the recursive filter matches at installation time, then we also install and match against
// the moniker prefix filter that's sent to the client via NotifyFilterCreated. At this level of
// the interface, we can't get the filter ID for the filter moniker suffix filter, so we can't
// check the originating_filter_id field of the notification, but we can assert our expectations
// on the filter settings.
const auto& new_filters = harness()->stream_backend()->filters();
ASSERT_FALSE(new_filters.empty());
ASSERT_EQ(new_filters.size(), 3u);
// We are not required to send another UpdateFilter request to the Agent since it matched eagerly.
EXPECT_TRUE(new_filters[0].participated_in_matching);
const auto& job4_moniker_prefix_filter = std::ranges::find_if(
new_filters, [&kFullRootMoniker](const debug_ipc::NotifyFilterCreated& notify) {
return notify.filter.pattern == kFullRootMoniker;
});
ASSERT_NE(job4_moniker_prefix_filter, new_filters.end());
EXPECT_EQ(job4_moniker_prefix_filter->filter.id.Decode().originator,
debug_ipc::Filter::Originator::kAgent);
EXPECT_EQ(job4_moniker_prefix_filter->filter.pattern, kFullRootMoniker);
EXPECT_EQ(job4_moniker_prefix_filter->filter.type,
debug_ipc::Filter::Type::kComponentMonikerPrefix);
EXPECT_EQ(job4_moniker_prefix_filter->filter.config.recursive, false);
// We will get back the process that we matched and attached to above with the first filter we
// installed, and we should also get back the process under another component in
// |kFullRootMoniker|'s realm.
EXPECT_EQ(reply.matched_processes_for_filter.size(), 5u);
const auto& job4p1_match = std::ranges::find_if(
reply.matched_processes_for_filter, [](const debug_ipc::FilterMatch& match) {
return std::ranges::any_of(
match.matches, [](const auto& match) { return match.koid == kJob4Process1Koid; });
});
ASSERT_NE(job4p1_match, reply.matched_processes_for_filter.end());
ASSERT_EQ(job4p1_match->matches.size(), 1u);
EXPECT_EQ(job4p1_match->matches[0].koid, kJob4Process1Koid);
EXPECT_EQ(job4p1_match->matches[0].type, debug_ipc::TaskType::kProcess);
// We can issue an attach now. Since we're not doing any pre-filtering of koids we're already
// attached to, this will also return the koid 26 attached above as well as the new one.
EXPECT_EQ(AttachToMatchingKoids(reply), 5u);
EXPECT_NE(agent->GetDebuggedProcess(kJob4Process1Koid), nullptr);
// Now we install a job-only filter that will attach DebugAgent directly to a matching job's
// exception channel.
fuchsia_debugger::Filter third;
third.type(fuchsia_debugger::FilterType::kMonikerPrefix);
// Component moniker associated with job5 in mock_system_interface.
third.pattern("/some");
third.options().job_only(true);
result = AddFilter(third);
EXPECT_TRUE(result.ok());
reply = result.take_value();
auto third_filter_match = std::ranges::find_if(reply.matched_processes_for_filter,
[this](const debug_ipc::FilterMatch& match) {
return last_added_filters().contains(match.id);
});
ASSERT_NE(third_filter_match, reply.matched_processes_for_filter.end());
EXPECT_EQ(third_filter_match->matches.size(), 2u);
constexpr zx_koid_t kJob5Koid = 35;
constexpr zx_koid_t kJob51Koid = 38;
// The order of the matches will always be in ascending order.
EXPECT_EQ(third_filter_match->matches[0].koid, kJob5Koid);
EXPECT_EQ(third_filter_match->matches[0].type, debug_ipc::TaskType::kJob);
EXPECT_EQ(third_filter_match->matches[1].koid, kJob51Koid);
EXPECT_EQ(third_filter_match->matches[1].type, debug_ipc::TaskType::kJob);
// Now we test explicit attach requests. This will be the case if the filter is installed after
// the component that matches is already launched and we have been notified of it. See the
// job_only tests in debug_agent_unittests to see the case where the filter is matched upon a
// notification that a component is starting.
debug_ipc::AttachRequest attach_request;
attach_request.koid = kJob5Koid;
attach_request.config.target = debug_ipc::TaskType::kJob;
debug_ipc::AttachReply attach_reply;
agent->OnAttach(attach_request, &attach_reply);
ASSERT_TRUE(attach_reply.status.ok()) << attach_reply.status.message();
attach_request.koid = kJob51Koid;
agent->OnAttach(attach_request, &attach_reply);
ASSERT_TRUE(attach_reply.status.has_error());
ASSERT_EQ(attach_reply.status.type(), debug::Status::kAlreadyExists);
EXPECT_TRUE(agent->GetDebuggedJob(kJob5Koid));
// Should not be attached to the child job.
EXPECT_FALSE(agent->GetDebuggedJob(kJob51Koid));
}
TEST_F(DebugAgentServerTest, AddFilterErrors) {
fuchsia_debugger::Filter f;
auto result = AddFilter(f);
EXPECT_TRUE(result.has_error());
EXPECT_EQ(result.err(), fuchsia_debugger::FilterError::kNoPattern);
// Set pattern but not type.
f.pattern("test");
result = AddFilter(f);
EXPECT_TRUE(result.has_error());
EXPECT_EQ(result.err(), fuchsia_debugger::FilterError::kUnknownType);
// Some filter type from the future.
f.type(static_cast<fuchsia_debugger::FilterType>(1234));
result = AddFilter(f);
EXPECT_TRUE(result.has_error());
EXPECT_EQ(result.err(), fuchsia_debugger::FilterError::kUnknownType);
// Set both recursive and job_only options.
f.type(fuchsia_debugger::FilterType::kMonikerPrefix);
f.options().recursive(true);
f.options().job_only(true);
result = AddFilter(f);
EXPECT_TRUE(result.ok());
}
TEST_F(DebugAgentServerTest, GetMatchingProcesses) {
auto agent = GetDebugAgent();
// Not passing a filter will return all attached processes. There aren't any of those yet, so the
// return value is empty.
auto result = GetMatchingProcesses(std::nullopt);
EXPECT_TRUE(result.ok());
EXPECT_TRUE(result.value().empty());
// If provided, the filter must be valid.
result = GetMatchingProcesses({{{.pattern = ""}}});
EXPECT_TRUE(result.has_error());
EXPECT_EQ(result.err(), fuchsia_debugger::FilterError::kNoPattern);
result = GetMatchingProcesses({{{.pattern = "some/pattern"}}});
EXPECT_TRUE(result.has_error());
EXPECT_EQ(result.err(), fuchsia_debugger::FilterError::kUnknownType);
constexpr char kFullRootMoniker[] = "/moniker/generated/test:test_root";
constexpr char kUrl[] = "fuchsia-pkg://devhost/root_package#meta/root_component.cm";
// This filter is intended to match |kFullRootMoniker| and all child components.
fuchsia_debugger::Filter f1;
f1.pattern("test:test_root");
f1.type(fuchsia_debugger::FilterType::kMonikerSuffix);
f1.options({{.recursive = true}});
AddFilter(f1);
// Create the subfilter.
harness()->system_interface()->mock_component_manager().InjectComponentEvent(
FakeEventType::kDebugStarted, kFullRootMoniker, kUrl);
loop().RunUntilNoTasks();
// Koid of job4 from MockSystemInterface.
constexpr zx_koid_t kJob4Koid = 32;
// Inject a process starting event for the ELF process running under some child component of the
// root component above.
constexpr zx_koid_t kProcessKoid = 33;
auto handle = std::make_unique<MockProcessHandle>(kProcessKoid);
// Set the job koid so that we can look up the corresponding component information.
handle->set_job_koid(kJob4Koid);
agent->OnProcessChanged(DebugAgent::ProcessChangedHow::kStarting, std::move(handle));
// We are now attached to something. Omitting the filter should give us back a process.
result = GetMatchingProcesses(std::nullopt);
EXPECT_TRUE(result.ok());
EXPECT_EQ(result.value().size(), 1u);
EXPECT_EQ(result.value()[0]->koid(), kProcessKoid);
}
TEST_F(DebugAgentServerTest, AttachToJobOnComponentStarting) {
constexpr zx_koid_t kJobKoid = 101;
constexpr std::string kComponentMoniker = "some/fake/moniker";
constexpr std::string kComponentUrl = "url";
fuchsia_debugger::Filter filter;
filter.pattern("moniker");
filter.type(fuchsia_debugger::FilterType::kMonikerSuffix);
filter.options().job_only(true);
AddFilter(filter);
harness()->system_interface()->mock_component_manager().InjectComponentEvent(
FakeEventType::kDebugStarted, kComponentMoniker, kComponentUrl, kJobKoid);
loop().RunUntilNoTasks();
EXPECT_NE(GetDebugAgent()->GetDebuggedJob(kJobKoid), nullptr);
}
// A client of ProcessInfoIterator that collects all processes returned by the iterator. Errors will
// raise an assertion.
class ProcessInfoCollector {
public:
ProcessInfoCollector(fidl::ClientEnd<fuchsia_debugger::ProcessInfoIterator> client_end,
async_dispatcher_t* dispatcher)
: client_(std::move(client_end), dispatcher) {}
void Collect(fit::callback<void(std::vector<fuchsia_debugger::ProcessInfo>)> on_done) {
client_->GetNext().Then(
[this, on_done = std::move(on_done)](
fidl::Result<fuchsia_debugger::ProcessInfoIterator::GetNext>& result) mutable {
ASSERT_TRUE(result.is_ok());
const auto& new_infos = result->info();
if (new_infos.empty()) {
return on_done(std::move(collected_infos_));
}
collected_infos_.insert(collected_infos_.end(), new_infos.begin(), new_infos.end());
debug::MessageLoop::Current()->PostTask(
FROM_HERE,
[this, on_done = std::move(on_done)]() mutable { Collect(std::move(on_done)); });
});
}
private:
fidl::Client<fuchsia_debugger::ProcessInfoIterator> client_;
std::vector<fuchsia_debugger::ProcessInfo> collected_infos_;
};
class FakeFidlClient : public fidl::AsyncEventHandler<fuchsia_debugger::DebugAgent> {
public:
explicit FakeFidlClient(fidl::ClientEnd<fuchsia_debugger::DebugAgent> client_end,
async_dispatcher_t* dispatcher)
: client_(std::move(client_end), dispatcher, this), dispatcher_(dispatcher) {}
void AttachTo(const fuchsia_debugger::Filter& filter,
fit::callback<void(fidl::Result<fuchsia_debugger::DebugAgent::AttachTo>&)> cb) {
client_->AttachTo(filter).Then(
[cb = std::move(cb)](fidl::Result<fuchsia_debugger::DebugAgent::AttachTo>& reply) mutable {
ASSERT_TRUE(cb);
cb(reply);
});
}
// This method is allowed to be synchronous, clients may begin feeding messages into the returned
// iterator immediately.
ProcessInfoCollector MakeProcessInfoCollector(
std::optional<const fuchsia_debugger::Filter> filter,
std::optional<const fuchsia_debugger::ThreadDetailsInterest> interest) {
auto [client_end, server_end] = *fidl::CreateEndpoints<fuchsia_debugger::ProcessInfoIterator>();
fuchsia_debugger::GetProcessInfoOptions options(
{.filter = std::move(filter), .interest = std::move(interest)});
client_
->GetProcessInfo(fuchsia_debugger::DebugAgentGetProcessInfoRequest(std::move(options),
std::move(server_end)))
.Then([](fidl::Result<fuchsia_debugger::DebugAgent::GetProcessInfo>& reply) {
ASSERT_TRUE(reply.is_ok());
});
return ProcessInfoCollector(std::move(client_end), dispatcher_);
}
void OnFatalException(
fidl::Event<fuchsia_debugger::DebugAgent::OnFatalException>& event) override {
exceptions_.emplace_back(event);
debug::MessageLoop::Current()->QuitNow();
}
const auto& GetExceptions() const { return exceptions_; }
void handle_unknown_event(
fidl::UnknownEventMetadata<fuchsia_debugger::DebugAgent> metadata) override {
FX_LOGS(WARNING) << "Unknown event: " << metadata.event_ordinal;
}
private:
std::vector<fidl::Event<fuchsia_debugger::DebugAgent::OnFatalException>> exceptions_;
fidl::Client<fuchsia_debugger::DebugAgent> client_;
async_dispatcher_t* dispatcher_;
};
class DebugAgentServerTestWithClient : public debug::TestWithLoop {
public:
void SetUp() override {
auto [client_end, server_end] = *fidl::CreateEndpoints<fuchsia_debugger::DebugAgent>();
client_ =
std::make_unique<FakeFidlClient>(std::move(client_end), async_get_default_dispatcher());
// The server is owned by the message loop, but we hold on to a pointer here so we can actually
// poke at the server to debug ourselves.
server_ = DebugAgentServer::BindServer(async_get_default_dispatcher(), std::move(server_end),
harness()->debug_agent()->GetWeakPtr());
}
void TearDown() override { client_.reset(); }
FakeFidlClient& client() { return *client_; }
MockDebugAgentHarness* harness() { return &harness_; }
DebugAgent* agent() { return harness_.debug_agent(); }
private:
std::unique_ptr<FakeFidlClient> client_ = nullptr;
DebugAgentServer* server_;
MockDebugAgentHarness harness_;
};
TEST_F(DebugAgentServerTestWithClient, OnFatalException) {
constexpr zx_koid_t kProcessKoid = 0x1234;
constexpr zx_koid_t kThreadKoid = 0x2345;
auto mock_process = harness()->AddProcess(kProcessKoid);
auto mock_thread = mock_process->AddThread(kThreadKoid);
// Now that the server is bound to the message loop with a client, we can send the notification.
// Note that there may be an error from inspector complaining about a process koid that doesn't
// exist, but that's not important for this test.
mock_thread->SendException(0x12345678, debug_ipc::ExceptionType::kGeneral);
loop().Run();
ASSERT_EQ(client().GetExceptions().size(), 1u);
EXPECT_TRUE(client().GetExceptions()[0].thread());
EXPECT_EQ(*client().GetExceptions()[0].thread(), kThreadKoid);
}
// Debug exception types should not send notifications to clients, e.g. single step, software
// breakpoints, etc.
TEST_F(DebugAgentServerTestWithClient, DebugExceptionDoesNotSendEvent) {
constexpr zx_koid_t kProcessKoid = 0x1234;
constexpr zx_koid_t kThreadKoid = 0x2345;
auto mock_process = harness()->AddProcess(kProcessKoid);
auto mock_thread = mock_process->AddThread(kThreadKoid);
// clang-format off
constexpr std::array<debug_ipc::ExceptionType, 5> debug_exceptions = {
// These are taken from the same set that populates the IsDebug function in ipc/records.cc. We
// don't need to worry about the process and thread lifetime exceptions that will return true in
// that function because separate debug_ipc notifications will be sent for those and if we
// decide to make them !IsDebug then it shouldn't affect this FIDL event.
debug_ipc::ExceptionType::kHardwareBreakpoint,
debug_ipc::ExceptionType::kWatchpoint,
debug_ipc::ExceptionType::kSingleStep,
debug_ipc::ExceptionType::kSoftwareBreakpoint,
debug_ipc::ExceptionType::kSynthetic,
};
// clang-format on
constexpr uint64_t kExceptionAddress = 0x12345678;
for (auto exception_type : debug_exceptions) {
// Watchpoints need some special set up.
if (exception_type == debug_ipc::ExceptionType::kWatchpoint) {
DebugRegisters debug_registers;
auto wp_info = debug_registers.SetWatchpoint(debug_ipc::BreakpointType::kReadWrite,
{kExceptionAddress, kExceptionAddress + 1}, 4);
ASSERT_TRUE(wp_info);
debug_registers.SetForHitWatchpoint(wp_info->slot);
mock_thread->mock_thread_handle().SetDebugRegisters(debug_registers);
}
mock_thread->SendException(kExceptionAddress, exception_type);
// This should return immediately.
loop().RunUntilNoTasks();
ASSERT_TRUE(client().GetExceptions().empty());
}
}
TEST_F(DebugAgentServerTestWithClient, AttachToJobOnFilterInstalled) {
// The non-exhaustive job structure with matching component information will look like this from
// MockSystemInterface:
// ..
// root
// ├─j: 8 "/moniker"
// ├─j: 25 "/a/long/generated_to_here/fixed/moniker"
// └─j: 35 "/some/moniker"
// └─j: 38 "/some/other/moniker"
// ..
// Since job 38 is a child of 35, DebugAgent shouldn't attach to it, but it will appear as a match
// to the filter. Therefore, there will be four matches for the filter, and three expected
// attaches.
constexpr std::array kExpectedJobKoids = {8, 25, 35};
// See the default pre-populated system in mock_system_interface.h to see which monikers will be
// matched.
fuchsia_debugger::Filter filter;
filter.pattern("moniker");
filter.type(fuchsia_debugger::FilterType::kMonikerSuffix);
filter.options().job_only(true);
client().AttachTo(
filter, [=, this](fidl::Result<fuchsia_debugger::DebugAgent::AttachTo>& result) mutable {
ASSERT_TRUE(result.is_ok());
EXPECT_EQ(result->num_matches(), 4u);
for (auto koid : kExpectedJobKoids) {
EXPECT_NE(agent()->GetDebuggedJob(koid), nullptr);
}
loop().QuitNow();
});
loop().Run();
}
TEST_F(DebugAgentServerTestWithClient, ProcessInfoIterator) {
fuchsia_debugger::Filter filter;
filter.pattern("/moniker");
filter.type(fuchsia_debugger::FilterType::kMoniker);
client().AttachTo(
filter, [=, this](fidl::Result<fuchsia_debugger::DebugAgent::AttachTo>& result) mutable {
ASSERT_TRUE(result.is_ok());
loop().QuitNow();
});
loop().Run();
// Don't need to wait for the message loop to start sending messages.
auto collector = client().MakeProcessInfoCollector(std::nullopt, std::nullopt);
std::vector<fuchsia_debugger::ProcessInfo> infos;
collector.Collect([&, this](std::vector<fuchsia_debugger::ProcessInfo> new_infos) {
infos = std::move(new_infos);
loop().QuitNow();
});
loop().Run();
// From mock_system_interface.
constexpr std::array kExpectedMonikers = {"/moniker"};
constexpr std::array kExpectedProcessIds = {9, 11, 14, 19, 21};
constexpr std::array kExpectedThreadIds = {10, 12, 15, 16, 20, 22, 23, 24};
ASSERT_FALSE(infos.empty());
for (const auto& info : infos) {
EXPECT_NE(std::ranges::find(kExpectedProcessIds, info.process()), kExpectedProcessIds.end());
EXPECT_NE(std::ranges::find(kExpectedThreadIds, info.thread()), kExpectedThreadIds.end());
EXPECT_NE(std::ranges::find(kExpectedMonikers, info.moniker()), kExpectedMonikers.end());
// We didn't ask for any interest, so we don't get back any backtraces.
EXPECT_EQ(info.details().backtrace(), std::nullopt);
}
}
TEST_F(DebugAgentServerTestWithClient, RecursiveJobOnlyProcessInfoIterator) {
fuchsia_debugger::Filter filter;
filter.pattern("test_root");
filter.type(fuchsia_debugger::FilterType::kMonikerSuffix);
filter.options().job_only(true);
filter.options().recursive(true);
client().AttachTo(
filter, [=, this](fidl::Result<fuchsia_debugger::DebugAgent::AttachTo>& result) mutable {
ASSERT_TRUE(result.is_ok());
loop().QuitNow();
});
loop().Run();
// Don't need to wait for the message loop to start sending messages.
auto collector = client().MakeProcessInfoCollector(std::nullopt, std::nullopt);
std::vector<fuchsia_debugger::ProcessInfo> infos;
collector.Collect([&, this](std::vector<fuchsia_debugger::ProcessInfo> new_infos) {
infos = std::move(new_infos);
loop().QuitNow();
});
loop().Run();
// From mock_system_interface.
constexpr std::array kExpectedMonikers = {
"/moniker/generated/test:test_root/driver", "moniker/abcdef/test:test_root/test_driver",
"moniker/abcdef/test:test_root/under_test", "moniker/abc123/test:test_root"};
constexpr std::array kExpectedProcessIds = {33, 42, 45, 49};
constexpr std::array kExpectedThreadIds = {34, 43, 46, 47, 50};
ASSERT_FALSE(infos.empty());
for (const auto& info : infos) {
EXPECT_NE(std::ranges::find(kExpectedProcessIds, info.process()), kExpectedProcessIds.end());
EXPECT_NE(std::ranges::find(kExpectedThreadIds, info.thread()), kExpectedThreadIds.end());
EXPECT_NE(std::ranges::find(kExpectedMonikers, info.moniker()), kExpectedMonikers.end());
// We didn't ask for any interest, so we don't get back any backtraces.
EXPECT_EQ(info.details().backtrace(), std::nullopt);
}
}
TEST_F(DebugAgentServerTestWithClient, RecursiveJobOnlyProcessInfoIteratorWithFilter) {
// Attach to all test realms.
fuchsia_debugger::Filter filter;
filter.pattern("test_root");
filter.type(fuchsia_debugger::FilterType::kMonikerSuffix);
filter.options().job_only(true);
filter.options().recursive(true);
client().AttachTo(
filter, [=, this](fidl::Result<fuchsia_debugger::DebugAgent::AttachTo>& result) mutable {
ASSERT_TRUE(result.is_ok());
loop().QuitNow();
});
loop().Run();
// Filters that are given to the iterator are typically going to be package urls e.g. from
// TestManager. This is tricky, because we may get a request to recursively match against a
// realm's component URL recursively, without installing any additional filters.
fuchsia_debugger::Filter iterator_filter;
iterator_filter.pattern("fuchsia-pkg://devhost/test_root_package#meta/root_test_component.cm");
iterator_filter.type(fuchsia_debugger::FilterType::kUrl);
iterator_filter.options().recursive(true);
// Don't need to wait for the message loop to start sending messages.
auto collector = client().MakeProcessInfoCollector(std::move(iterator_filter), std::nullopt);
std::vector<fuchsia_debugger::ProcessInfo> infos;
collector.Collect([&, this](std::vector<fuchsia_debugger::ProcessInfo> new_infos) {
infos = std::move(new_infos);
loop().QuitNow();
});
loop().Run();
// From mock_system_interface.
constexpr std::array kExpectedMonikers = {
"moniker/abcdef/test:test_root/test_driver",
"moniker/abcdef/test:test_root/under_test",
};
constexpr std::array kExpectedProcessIds = {42, 45};
constexpr std::array kExpectedThreadIds = {43, 46, 47};
ASSERT_FALSE(infos.empty());
for (const auto& info : infos) {
EXPECT_NE(std::ranges::find(kExpectedProcessIds, info.process()), kExpectedProcessIds.end());
EXPECT_NE(std::ranges::find(kExpectedThreadIds, info.thread()), kExpectedThreadIds.end());
EXPECT_NE(std::ranges::find(kExpectedMonikers, info.moniker()), kExpectedMonikers.end());
// We didn't ask for any interest, so we don't get back any backtraces.
EXPECT_EQ(info.details().backtrace(), std::nullopt);
}
}
} // namespace debug_agent