blob: b3fce112fffce53e9e95f5eb0dc00365feb1cf23 [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 "src/developer/debug/zxdb/client/target_impl.h"
#include <gtest/gtest.h>
#include "src/developer/debug/shared/platform_message_loop.h"
#include "src/developer/debug/shared/test_stream_buffer.h"
#include "src/developer/debug/shared/zx_status.h"
#include "src/developer/debug/zxdb/client/process.h"
#include "src/developer/debug/zxdb/client/remote_api.h"
#include "src/developer/debug/zxdb/client/session.h"
#include "src/developer/debug/zxdb/common/test_with_loop.h"
namespace zxdb {
namespace {
using debug::MessageLoop;
class TargetSink : public RemoteAPI {
public:
TargetSink() = default;
~TargetSink() = default;
void set_launch_err(const Err& err) { launch_err_ = err; }
void set_launch_reply(const debug_ipc::LaunchReply& reply) { launch_reply_ = reply; }
void set_attach_err(const Err& err) { attach_err_ = err; }
void set_attach_reply(const debug_ipc::AttachReply& reply) { attach_reply_ = reply; }
void Launch(const debug_ipc::LaunchRequest& request,
fit::callback<void(const Err&, debug_ipc::LaunchReply)> cb) override {
MessageLoop::Current()->PostTask(
FROM_HERE, [this, cb = std::move(cb)]() mutable { cb(launch_err_, launch_reply_); });
}
void Kill(const debug_ipc::KillRequest& request,
fit::callback<void(const Err&, debug_ipc::KillReply)> cb) override {
MessageLoop::Current()->PostTask(FROM_HERE, [cb = std::move(cb)]() mutable {
// For now, always report success.
cb(Err(), debug_ipc::KillReply());
});
}
void Attach(const debug_ipc::AttachRequest& request,
fit::callback<void(const Err&, debug_ipc::AttachReply)> cb) override {
MessageLoop::Current()->PostTask(
FROM_HERE, [this, cb = std::move(cb)]() mutable { cb(attach_err_, attach_reply_); });
}
void ProcessStatus(
const debug_ipc::ProcessStatusRequest& request,
fit::callback<void(const Err& err, debug_ipc::ProcessStatusReply)> cb) override {
process_status_requests_.push_back(request);
MessageLoop::Current()->PostTask(
FROM_HERE,
[this, cb = std::move(cb)]() mutable { cb(process_status_err_, process_status_reply_); }
);
}
void Detach(const debug_ipc::DetachRequest& request,
fit::callback<void(const Err&, debug_ipc::DetachReply)> cb) override {
MessageLoop::Current()->PostTask(FROM_HERE, [cb = std::move(cb)]() mutable {
// For now, always report success.
cb(Err(), debug_ipc::DetachReply());
});
}
const std::vector<debug_ipc::ProcessStatusRequest>& process_status_requests() const {
return process_status_requests_;
}
private:
// These two variables are returned from Launch().
Err launch_err_;
debug_ipc::LaunchReply launch_reply_;
// These two variables are returned from Attach().
Err attach_err_;
debug_ipc::AttachReply attach_reply_;
// These variables are received/returned from ProcessStatus().
Err process_status_err_;
std::vector<debug_ipc::ProcessStatusRequest> process_status_requests_;
debug_ipc::ProcessStatusReply process_status_reply_;
};
class TargetImplTest : public TestWithLoop {
public:
TargetImplTest() {
sink_ = new TargetSink;
session_ = std::make_unique<Session>(std::unique_ptr<RemoteAPI>(sink_), debug::Arch::kX64);
}
~TargetImplTest() { session_.reset(); }
debug::TestStreamBuffer& stream() { return stream_; }
TargetSink& sink() { return *sink_; }
Session& session() { return *session_; }
private:
debug::TestStreamBuffer stream_;
TargetSink* sink_; // Owned by the session_.
std::unique_ptr<Session> session_;
};
} // namespace
// Tests that the process state is updated when trying to launch with no
// connection to the remote system.
TEST_F(TargetImplTest, LaunchNoConnection) {
auto target_impls = session().system().GetTargetImpls();
ASSERT_EQ(1u, target_impls.size());
TargetImpl* target = target_impls[0];
Err expected_err(ErrType::kNoConnection, "No connection.");
sink().set_launch_err(expected_err);
Err out_err;
target->SetArgs(std::vector<std::string>({"foo_test", "--arg"}));
target->Launch([&out_err](fxl::WeakPtr<Target> target, const Err& err, uint64_t timestamp) {
out_err = err;
MessageLoop::Current()->QuitNow();
});
// Should be in the process of launching.
EXPECT_EQ(Target::State::kStarting, target->GetState());
loop().Run();
// Expect the launch to have failed and be in a non-running state.
EXPECT_EQ(expected_err, out_err);
EXPECT_EQ(Target::State::kNone, target->GetState());
}
// Tests a successful launch and kill.
TEST_F(TargetImplTest, LaunchKill) {
auto target_impls = session().system().GetTargetImpls();
ASSERT_EQ(1u, target_impls.size());
TargetImpl* target = target_impls[0];
// Specify a successful reply.
const uint64_t kKoid = 1234;
sink().set_launch_err(Err());
debug_ipc::LaunchReply requested_reply;
requested_reply.process_id = kKoid;
requested_reply.process_name = "my name";
sink().set_launch_reply(requested_reply);
Err out_err;
target->SetArgs(std::vector<std::string>({"foo_test", "--arg"}));
target->Launch([&out_err](fxl::WeakPtr<Target> target, const Err& err, uint64_t timestamp) {
out_err = err;
MessageLoop::Current()->QuitNow();
});
// Should be in the process of launching.
EXPECT_EQ(Target::State::kStarting, target->GetState());
// Try to launch another one in the pending state.
Err second_launch_err;
target->Launch(
[&second_launch_err](fxl::WeakPtr<Target> target, const Err& err, uint64_t timestamp) {
second_launch_err = err;
MessageLoop::Current()->QuitNow();
});
// We should have two tasks posted the first pending attach and the second pending launch (that we
// expect to fail). They will both quit so the loop need to be run twice.
loop().Run();
loop().Run();
// Expect good launch, it should have made a process.
EXPECT_FALSE(out_err.has_error());
EXPECT_EQ(Target::State::kRunning, target->GetState());
ASSERT_TRUE(target->GetProcess());
EXPECT_EQ(kKoid, target->GetProcess()->GetKoid());
// The second launch should have failed.
EXPECT_TRUE(second_launch_err.has_error());
// Should not be able to launch another one. It should error out before trying to send IPC.
target->Launch([&out_err](fxl::WeakPtr<Target> target, const Err& err, uint64_t timestamp) {
out_err = err;
MessageLoop::Current()->QuitNow();
});
loop().Run();
EXPECT_TRUE(out_err.has_error());
// Should not be able to attach.
target->Attach(1234, [&out_err](fxl::WeakPtr<Target> target, const Err& err, uint64_t timestamp) {
out_err = err;
MessageLoop::Current()->QuitNow();
});
loop().Run();
EXPECT_TRUE(out_err.has_error());
// Kill the process.
target->Kill([&out_err](fxl::WeakPtr<Target> target, const Err& err) {
out_err = err;
MessageLoop::Current()->QuitNow();
});
loop().Run();
// Should have succeeded.
EXPECT_FALSE(out_err.has_error());
EXPECT_EQ(Target::State::kNone, target->GetState());
}
// Tests a successful attach and detach.
TEST_F(TargetImplTest, AttachDetach) {
auto target_impls = session().system().GetTargetImpls();
ASSERT_EQ(1u, target_impls.size());
TargetImpl* target = target_impls[0];
// Specify a successful reply.
const uint64_t kKoid = 1234;
sink().set_attach_err(Err());
Err out_err;
target->Attach(kKoid,
[&out_err](fxl::WeakPtr<Target> target, const Err& err, uint64_t timestamp) {
out_err = err;
MessageLoop::Current()->QuitNow();
});
// Should be in the process of launching.
EXPECT_EQ(Target::State::kAttaching, target->GetState());
// Try to launch another one in the pending state.
Err second_launch_err;
target->Launch(
[&second_launch_err](fxl::WeakPtr<Target> target, const Err& err, uint64_t timestamp) {
second_launch_err = err;
MessageLoop::Current()->QuitNow();
});
// We should have two tasks posted the first pending attach and the second pending launch (that we
// expect to fail). They will both quit so the loop need to be run twice.
loop().Run();
loop().Run();
// Expect good attach, it should have made a process.
EXPECT_FALSE(out_err.has_error());
EXPECT_EQ(Target::State::kRunning, target->GetState());
ASSERT_TRUE(target->GetProcess());
EXPECT_EQ(kKoid, target->GetProcess()->GetKoid());
// The second we launch should have failed.
EXPECT_TRUE(second_launch_err.has_error());
// Detach.
target->Detach([&out_err](fxl::WeakPtr<Target> target, const Err& err) {
out_err = err;
MessageLoop::Current()->QuitNow();
});
loop().Run();
// Should be in a non-running state.
EXPECT_EQ(Target::State::kNone, target->GetState());
}
TEST_F(TargetImplTest, AttachToAlreadyAttached) {
auto target_impls = session().system().GetTargetImpls();
constexpr uint64_t kProcessKoid = 0x1;
const std::string kProcessName = "process-1";
// Add a reply that says the process is already bound.
debug_ipc::AttachReply reply = {};
reply.koid = kProcessKoid;
reply.name = kProcessName;
reply.status = debug::Status(debug::Status::InternalValues(), debug::Status::kAlreadyExists, 0,
"Already bound");
sink().set_attach_reply(reply);
TargetImpl* target = target_impls[0];
Err out_err("NOT CALLED");
target->Attach(kProcessKoid, [&out_err](fxl::WeakPtr<Target> target, const Err& err,
uint64_t timestamp) { out_err = err; });
loop().RunUntilNoTasks();
ASSERT_FALSE(out_err.has_error()) << out_err.msg();
// Should have called the status report.
ASSERT_EQ(sink().process_status_requests().size(), 1u);
EXPECT_EQ(sink().process_status_requests()[0].process_koid, kProcessKoid);
}
} // namespace zxdb