| // 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 |