blob: 2fbbf998b09ff725061708babaaa7a50300d9d1b [file] [log] [blame]
// 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/process_info_iterator.h"
#include <gtest/gtest.h>
#include "src/developer/debug/debug_agent/component_manager.h"
#include "src/developer/debug/debug_agent/debug_agent.h"
#include "src/developer/debug/debug_agent/debugged_process.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/shared/test_with_loop.h"
namespace debug_agent {
namespace {
// Corresponding to processes in MockSystemInterface::CreateWithData.
constexpr zx_koid_t kProcess1JobKoid = 25;
constexpr zx_koid_t kProcess1Koid = 26;
constexpr zx_koid_t kProcess1Thread1Koid = 27;
constexpr zx_koid_t kProcess2JobKoid = 28;
constexpr zx_koid_t kProcess2Koid = 29;
constexpr zx_koid_t kProcess2Thread1Koid = 30;
constexpr zx_koid_t kProcess2Thread2Koid = 31;
} // namespace
class ProcessInfoIteratorTest : public debug::TestWithLoop {
public:
// Individual tests may ignore this default set up by constructing an iterator without using
// |all_procs_|.
void SetUp() override {
debug::TestWithLoop::SetUp();
auto mock_process = harness_.AddProcess(kProcess1Koid);
mock_process->AddThread(kProcess1Thread1Koid);
mock_process->mock_process_handle().set_job_koid(kProcess1JobKoid);
all_procs_.push_back(mock_process);
mock_process = harness_.AddProcess(kProcess2Koid);
mock_process->AddThread(kProcess2Thread1Koid);
mock_process->AddThread(kProcess2Thread2Koid);
mock_process->mock_process_handle().set_job_koid(kProcess2JobKoid);
all_procs_.push_back(mock_process);
}
DebugAgent* GetDebugAgent() { return harness_.debug_agent(); }
const std::vector<DebuggedProcess*>& all_procs() const { return all_procs_; }
// Getters for iterator internals. This class doesn't own the iterator so the tests can configure
// what is passed to the constructor.
static size_t GetProcessIndex(const ProcessInfoIterator& iter) { return iter.process_index_; }
static size_t GetThreadIndex(const ProcessInfoIterator& iter) { return iter.thread_index_; }
static DebuggedProcess* GetCurrentProcess(const ProcessInfoIterator& iter) {
return iter.current_process_;
}
static DebuggedThread* GetCurrentThread(const ProcessInfoIterator& iter) {
return iter.current_thread_;
}
private:
MockDebugAgentHarness harness_;
std::vector<DebuggedProcess*> all_procs_;
};
TEST_F(ProcessInfoIteratorTest, NoProcesses) {
// Doesn't use the processes set up in SetUp().
ProcessInfoIterator iter(GetDebugAgent()->GetWeakPtr(), {}, std::nullopt);
// In reality this should always produce an error to the FIDL client and never call this method,
// but it's good to verify the edge cases anyway.
EXPECT_FALSE(iter.Advance());
}
TEST_F(ProcessInfoIteratorTest, Advance) {
ProcessInfoIterator iter(GetDebugAgent()->GetWeakPtr(), all_procs(), std::nullopt);
EXPECT_EQ(GetProcessIndex(iter), 0u);
EXPECT_EQ(GetThreadIndex(iter), 0u);
// This is nullptr because we have not called iter.Advance() yet.
EXPECT_EQ(GetCurrentThread(iter), nullptr);
ASSERT_NE(GetCurrentProcess(iter), nullptr);
auto last_proc = GetCurrentProcess(iter);
EXPECT_EQ(GetCurrentProcess(iter)->koid(), kProcess1Koid);
// Now advance the iterator. This will return true, the current process should remain the same and
// current thread should be filled in now, while the thread index has advanced.
EXPECT_TRUE(iter.Advance());
EXPECT_EQ(GetProcessIndex(iter), 0u);
EXPECT_EQ(GetThreadIndex(iter), 1u);
EXPECT_EQ(GetCurrentProcess(iter), last_proc);
ASSERT_NE(GetCurrentThread(iter), nullptr);
EXPECT_EQ(GetCurrentThread(iter)->koid(), kProcess1Thread1Koid);
// Advance again. This time we should have updated to the next process and the first thread in
// that process. Note that the thread index is actually 1, not 0. This is so that the caller isn't
// required to call Advance twice at the boundary between the end of one process's threads and
// another process's.
EXPECT_TRUE(iter.Advance());
EXPECT_EQ(GetProcessIndex(iter), 1u);
EXPECT_EQ(GetThreadIndex(iter), 1u);
EXPECT_NE(GetCurrentProcess(iter), last_proc);
EXPECT_EQ(GetCurrentProcess(iter)->koid(), kProcess2Koid);
ASSERT_NE(GetCurrentThread(iter), nullptr);
EXPECT_EQ(GetCurrentThread(iter)->koid(), kProcess2Thread1Koid);
// Update last_proc to reflect the change.
last_proc = GetCurrentProcess(iter);
// Advance to the second thread.
EXPECT_TRUE(iter.Advance());
EXPECT_EQ(GetProcessIndex(iter), 1u);
EXPECT_EQ(GetThreadIndex(iter), 2u);
EXPECT_EQ(GetCurrentProcess(iter), last_proc);
EXPECT_EQ(GetCurrentProcess(iter)->koid(), kProcess2Koid);
ASSERT_NE(GetCurrentThread(iter), nullptr);
EXPECT_EQ(GetCurrentThread(iter)->koid(), kProcess2Thread2Koid);
// Advance one final time. This will return false because we've hit every thread in every process.
EXPECT_FALSE(iter.Advance());
// The iterator is now invalid. Callers must ensure that their work is finished.
}
TEST_F(ProcessInfoIteratorTest, Interest) {
// No interest means nothing is captured.
ProcessInfoIterator iter(GetDebugAgent()->GetWeakPtr(), all_procs(), std::nullopt);
EXPECT_FALSE(iter.CaptureBacktrace());
// An empty interest also means nothing is captured.
fuchsia_debugger::ThreadDetailsInterest interest;
iter = ProcessInfoIterator(GetDebugAgent()->GetWeakPtr(), all_procs(), interest);
EXPECT_FALSE(iter.CaptureBacktrace());
interest.backtrace(true);
iter = ProcessInfoIterator(GetDebugAgent()->GetWeakPtr(), all_procs(), interest);
EXPECT_TRUE(iter.CaptureBacktrace());
}
} // namespace debug_agent