blob: 8576245cc4bc8d4d3191e796b6465198678d88da [file] [log] [blame]
// Copyright 2019 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/zxdb/client/system_impl.h"
#include "gtest/gtest.h"
#include "src/developer/debug/shared/zx_status.h"
#include "src/developer/debug/zxdb/client/frame.h"
#include "src/developer/debug/zxdb/client/job_context_impl.h"
#include "src/developer/debug/zxdb/client/mock_remote_api.h"
#include "src/developer/debug/zxdb/client/process.h"
#include "src/developer/debug/zxdb/client/process_observer.h"
#include "src/developer/debug/zxdb/client/remote_api_test.h"
#include "src/developer/debug/zxdb/client/session.h"
#include "src/developer/debug/zxdb/client/system_observer.h"
#include "src/developer/debug/zxdb/client/target_observer.h"
#include "src/developer/debug/zxdb/client/thread.h"
namespace zxdb {
namespace {
struct ProcessInfo {
uint64_t koid;
std::string name;
};
class APISink : public MockRemoteAPI {
public:
void Attach(const debug_ipc::AttachRequest& request,
fit::callback<void(const Err&, debug_ipc::AttachReply)> cb) override {
attach_requests_.push_back(request);
FXL_DCHECK(info_count_ < next_infos_.size());
auto info = next_infos_[info_count_++];
debug_ipc::AttachReply reply;
reply.status = debug_ipc::kZxOk;
reply.koid = info.koid;
reply.name = info.name;
cb(Err(), std::move(reply));
}
const std::vector<debug_ipc::AttachRequest>& attach_requests() const { return attach_requests_; }
// Sets the list of replys the sink will return.
void SetNextInfos(std::vector<ProcessInfo> infos) {
next_infos_ = std::move(infos);
info_count_ = 0;
}
private:
size_t info_count_ = 0;
std::vector<ProcessInfo> next_infos_;
std::vector<debug_ipc::AttachRequest> attach_requests_;
};
class SystemImplTest : public RemoteAPITest {
public:
SystemImplTest() = default;
~SystemImplTest() override = default;
APISink* sink() { return sink_; }
private:
std::unique_ptr<RemoteAPI> GetRemoteAPIImpl() override {
auto sink = std::make_unique<APISink>();
// The session will own the sink.
sink_ = sink.get();
return sink;
}
APISink* sink_;
};
// We need a RAII-esque wrapper because observers are supposed to outlive the system.
class MockSystemObserver : public TargetObserver, public ProcessObserver {
public:
explicit MockSystemObserver(Session* session) : session_(session) {
session->target_observers().AddObserver(this);
session->process_observers().AddObserver(this);
}
~MockSystemObserver() {
session_->process_observers().RemoveObserver(this);
session_->target_observers().RemoveObserver(this);
}
// TargetObserver.
void DidCreateTarget(Target*) override { target_create_count_++; }
// ProcessObserver.
void DidCreateProcess(Process*, bool) override { process_create_count_++; }
int target_create_count() const { return target_create_count_; }
int process_create_count() const { return process_create_count_; }
private:
Session* session_ = nullptr;
int target_create_count_ = 0;
int process_create_count_ = 0;
};
} // namespace
// Tests that thread state is updated when doing a system-wide continue.
TEST_F(SystemImplTest, GlobalContinue) {
// Make a process and thread for notifying about.
constexpr uint64_t kProcessKoid = 1234;
InjectProcess(kProcessKoid);
constexpr uint64_t kThread1Koid = 5678;
Thread* thread1 = InjectThread(kProcessKoid, kThread1Koid);
constexpr uint64_t kThread2Koid = 9012;
Thread* thread2 = InjectThread(kProcessKoid, kThread2Koid);
sink()->GetAndResetResumeCount(); // Clear from thread init.
constexpr uint64_t kAddress = 0x12345678;
constexpr uint64_t kStack = 0x7890;
// Notify of thread stop on thread 1.
debug_ipc::NotifyException break_notification;
break_notification.type = debug_ipc::ExceptionType::kSoftware;
break_notification.thread.process_koid = kProcessKoid;
break_notification.thread.thread_koid = kThread1Koid;
break_notification.thread.state = debug_ipc::ThreadRecord::State::kBlocked;
break_notification.thread.frames.emplace_back(kAddress, kStack, kStack);
InjectException(break_notification);
EXPECT_EQ(0, sink()->GetAndResetResumeCount());
// Same on thread 2.
break_notification.thread.thread_koid = kThread2Koid;
InjectException(break_notification);
// Continue globally. This should in turn update the thread.
session().system().Continue();
// Both threads should have been resumed in the backend.
EXPECT_EQ(2, sink()->GetAndResetResumeCount());
// The threads should have no stack.
EXPECT_FALSE(thread1->GetStack().has_all_frames());
ASSERT_EQ(0u, thread1->GetStack().size());
EXPECT_FALSE(thread2->GetStack().has_all_frames());
ASSERT_EQ(0u, thread2->GetStack().size());
}
TEST_F(SystemImplTest, FilterMatchesAndRematching) {
SystemImpl& system = session().system_impl();
MockSystemObserver system_observer(&session());
constexpr uint64_t kJobKoid = 0x1234;
JobContextImpl job(&session().system_impl(), false);
job.AddJobImplForTesting(kJobKoid, "job-name");
ASSERT_TRUE(sink()->attach_requests().empty());
// There should be only one empty target.
auto targets = system.GetTargets();
ASSERT_EQ(targets.size(), 1u);
EXPECT_FALSE(targets[0]->GetProcess());
// We match on a new process.
constexpr uint64_t kProcessKoid = 0x5678;
std::string kProcessName = "some-process";
ProcessInfo info = {kProcessKoid, kProcessName};
sink()->SetNextInfos({info});
session().system_impl().OnFilterMatches(&job, {kProcessKoid});
// There should be an attach request.
auto& requests = sink()->attach_requests();
ASSERT_EQ(requests.size(), 1u);
EXPECT_EQ(requests[0].type, debug_ipc::TaskType::kProcess);
EXPECT_EQ(requests[0].koid, kProcessKoid);
// The system should've reused the empty target.
ASSERT_EQ(system_observer.target_create_count(), 0);
targets = system.GetTargets();
ASSERT_EQ(targets.size(), 1u);
// Should've created the process.
ASSERT_EQ(system_observer.process_create_count(), 1);
Process* process = targets[0]->GetProcess();
ASSERT_TRUE(process);
EXPECT_EQ(process->GetKoid(), kProcessKoid);
EXPECT_EQ(process->GetName(), kProcessName);
// Rematching should not create a new target.
sink()->SetNextInfos({info});
session().system_impl().OnFilterMatches(&job, {kProcessKoid});
// The system should've reused the empty target.
ASSERT_EQ(system_observer.target_create_count(), 0);
targets = system.GetTargets();
ASSERT_EQ(targets.size(), 1u);
// Should've created the process.
ASSERT_EQ(system_observer.process_create_count(), 1);
process = targets[0]->GetProcess();
ASSERT_TRUE(process);
EXPECT_EQ(process->GetKoid(), kProcessKoid);
EXPECT_EQ(process->GetName(), kProcessName);
}
TEST_F(SystemImplTest, ExistenProcessShouldCreateTarget) {
SystemImpl& system = session().system_impl();
MockSystemObserver system_observer(&session());
constexpr uint64_t kJobKoid = 0x1234;
JobContextImpl job(&session().system_impl(), false);
job.AddJobImplForTesting(kJobKoid, "job-name");
ASSERT_TRUE(sink()->attach_requests().empty());
// Before injecting the process there should not be an event, after it there should be one.
ASSERT_EQ(system_observer.process_create_count(), 0);
constexpr uint64_t kProcessKoid1 = 0x1;
InjectProcess(kProcessKoid1);
ASSERT_EQ(system_observer.process_create_count(), 1);
// There should be a target with a process.
auto targets = system.GetTargets();
ASSERT_EQ(targets.size(), 1u);
Process* process = targets[0]->GetProcess();
ASSERT_TRUE(process);
ASSERT_EQ(process->GetKoid(), kProcessKoid1);
// We match on a new process.
constexpr uint64_t kProcessKoid2 = 0x2;
std::string kProcessName = "some-process";
ProcessInfo info = {kProcessKoid2, kProcessName};
sink()->SetNextInfos({info});
session().system_impl().OnFilterMatches(&job, {kProcessKoid2});
// There should be an attach request.
auto& requests = sink()->attach_requests();
ASSERT_EQ(requests.size(), 1u);
EXPECT_EQ(requests[0].type, debug_ipc::TaskType::kProcess);
EXPECT_EQ(requests[0].koid, kProcessKoid2);
// The system should've created a new target.
ASSERT_EQ(system_observer.target_create_count(), 1);
targets = system.GetTargets();
ASSERT_EQ(targets.size(), 2u);
// Should've created the process.
ASSERT_EQ(system_observer.process_create_count(), 2);
process = targets[1]->GetProcess();
ASSERT_TRUE(process);
EXPECT_EQ(process->GetKoid(), kProcessKoid2);
EXPECT_EQ(process->GetName(), kProcessName);
}
} // namespace zxdb