blob: 08eda77ded2194270f9a6e2363de234cc9e20d0f [file] [log] [blame]
// Copyright 2020 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/console/console_context.h"
#include <gtest/gtest.h>
#include "src/developer/debug/ipc/protocol.h"
#include "src/developer/debug/ipc/records.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/setting_schema_definition.h"
#include "src/developer/debug/zxdb/client/target_impl.h"
#include "src/developer/debug/zxdb/client/thread.h"
#include "src/developer/debug/zxdb/console/console_test.h"
namespace zxdb {
namespace {
class ConsoleContextTest : public ConsoleTest {};
} // namespace
// Verifies that the breakpoint is set to the current one when it's hit.
TEST_F(ConsoleContextTest, CurrentBreakpoint) {
// Breakpoint 1.
console().ProcessInputLine("b 0x10000");
auto event = console().GetOutputEvent();
ASSERT_NE(std::string::npos, event.output.AsString().find("Created Breakpoint 1"))
<< event.output.AsString();
int breakpoint_1_backend_id = mock_remote_api()->last_breakpoint_id();
// Breakpoint 2.
console().ProcessInputLine("b 0x20000");
event = console().GetOutputEvent();
ASSERT_NE(std::string::npos, event.output.AsString().find("Created Breakpoint 2"))
<< event.output.AsString();
// Breakpoint 2 should be active, so its address should be returned when we ask for the location.
console().ProcessInputLine("bp get location");
event = console().GetOutputEvent();
ASSERT_NE(std::string::npos, event.output.AsString().find("location = 0x20000"))
<< event.output.AsString();
// Provide a stop at breakpoint 1.
debug_ipc::NotifyException notify;
notify.type = debug_ipc::ExceptionType::kSoftwareBreakpoint;
notify.thread.id = {.process = kProcessKoid, .thread = kThreadKoid};
notify.thread.state = debug_ipc::ThreadRecord::State::kBlocked;
notify.thread.frames.emplace_back(0x10000, 0);
notify.hit_breakpoints.emplace_back();
notify.hit_breakpoints[0].id = breakpoint_1_backend_id;
notify.hit_breakpoints[0].hit_count = 1;
InjectException(notify);
// Should have issued a stop at the first location.
event = console().GetOutputEvent();
ASSERT_NE(std::string::npos, event.output.AsString().find("0x10000 (no symbol info)"))
<< event.output.AsString();
// Breakpoint 1 should now be active.
console().ProcessInputLine("bp get location");
event = console().GetOutputEvent();
ASSERT_NE(std::string::npos, event.output.AsString().find("location = 0x10000"))
<< event.output.AsString();
}
// Testing EmbeddedMode requires us to do some configuration before creating the Console object,
// which we can't do if we use the ConsoleTest base class.
class EmbeddedModeConsoleContext : public RemoteAPITest {
public:
void SetUp() override {
RemoteAPITest::SetUp();
session().system().settings().SetString(ClientSettings::System::kConsoleMode,
ClientSettings::System::kConsoleMode_Embedded);
console_ = std::make_unique<MockConsole>(&session());
console_->context().InitConsoleMode();
}
void TearDown() override {
console_.reset();
RemoteAPITest::TearDown();
}
MockConsole& console() const { return *console_; }
private:
std::unique_ptr<MockConsole> console_;
};
TEST_F(EmbeddedModeConsoleContext, ReturnToEmbeddedModeContinueThenQuit) {
auto& context = console().context();
// We should output nothing for the initial attach event.
console().ProcessInputLine("attach xyz");
EXPECT_FALSE(console().HasOutputEvent());
constexpr uint64_t kProcessKoid = 1234;
// Inject a process and break out of embedded mode.
auto target = InjectProcess(kProcessKoid)->GetTarget();
context.SetConsoleMode(ClientSettings::System::kConsoleMode_EmbeddedInteractive);
// Do some inspection commands that should never leave interactive mode.
console().ProcessInputLine("list");
EXPECT_TRUE(console().HasOutputEvent());
auto event = console().GetOutputEvent(); // Consume the event.
EXPECT_EQ(context.GetConsoleMode(), ClientSettings::System::kConsoleMode_EmbeddedInteractive);
console().ProcessInputLine("b test");
EXPECT_TRUE(console().HasOutputEvent());
event = console().GetOutputEvent();
EXPECT_EQ(context.GetConsoleMode(), ClientSettings::System::kConsoleMode_EmbeddedInteractive);
console().ProcessInputLine("continue");
// We should still be in interactive mode.
EXPECT_EQ(context.GetConsoleMode(), ClientSettings::System::kConsoleMode_EmbeddedInteractive);
EXPECT_EQ(target->GetState(), Target::State::kRunning);
// Now we're really done, use "quit" to detach from everything.
console().ProcessInputLine("quit");
loop().RunUntilNoTasks();
// Now we should be back in embedded mode because there are no other processes running.
EXPECT_EQ(context.GetConsoleMode(), ClientSettings::System::kConsoleMode_Embedded);
}
TEST_F(EmbeddedModeConsoleContext, StayInteractiveWithContextualContinue) {
auto& context = console().context();
// We should output nothing for the initial attach event.
console().ProcessInputLine("attach xyz");
EXPECT_FALSE(console().HasOutputEvent());
constexpr uint64_t kProcessKoid = 1234;
constexpr uint64_t kThreadKoid = 4321;
// Inject a process and break out of embedded mode.
auto process = InjectProcess(kProcessKoid);
auto target = process->GetTarget();
auto thread = InjectThread(kProcessKoid, kThreadKoid);
context.SetConsoleMode(ClientSettings::System::kConsoleMode_EmbeddedInteractive);
context.SetActiveTarget(target);
context.SetActiveThreadForTarget(thread);
// Do some inspection commands that should never leave interactive mode.
console().ProcessInputLine("b test");
EXPECT_TRUE(console().HasOutputEvent());
auto event = console().GetOutputEvent(); // Consume the event.
EXPECT_EQ(context.GetConsoleMode(), ClientSettings::System::kConsoleMode_EmbeddedInteractive);
// Set up basic thread information so we can call "frame"
debug_ipc::ThreadStatusReply reply;
reply.record.id.process = kProcessKoid;
reply.record.id.thread = kThreadKoid;
reply.record.name = "thread1";
reply.record.frames.emplace_back(0x1234, 0x5678);
reply.record.frames.emplace_back(0x2345, 0x6789);
reply.record.stack_amount = debug_ipc::ThreadRecord::StackAmount::kFull;
reply.record.state = debug_ipc::ThreadRecord::State::kSuspended;
mock_remote_api()->set_thread_status_reply(reply);
// Now actually sync the stack with the backend.
console().ProcessInputLine("frame");
// Let all the remote api calls go through to propagate all the notifications.
loop().RunUntilNoTasks();
EXPECT_TRUE(console().HasOutputEvent());
event = console().GetOutputEvent();
EXPECT_EQ(context.GetConsoleMode(), ClientSettings::System::kConsoleMode_EmbeddedInteractive);
// A process specific continue, this may hit the breakpoint set during the interactive mode, or
// could exit the process, but we should stay in interactive mode regardless.
console().ProcessInputLine("process continue");
EXPECT_EQ(context.GetConsoleMode(), ClientSettings::System::kConsoleMode_EmbeddedInteractive);
EXPECT_EQ(target->GetState(), Target::State::kRunning);
// We should see the same result with a thread level continue.
console().ProcessInputLine("thread continue");
EXPECT_EQ(context.GetConsoleMode(), ClientSettings::System::kConsoleMode_EmbeddedInteractive);
EXPECT_EQ(target->GetState(), Target::State::kRunning);
// Now end the process, which should bring us back to embedded mode since there are no other
// running targets.
target->Kill([](fxl::WeakPtr<Target> target, const Err& err) {
debug::MessageLoop::Current()->QuitNow();
});
// Let the notifications propagate.
loop().Run();
EXPECT_EQ(context.GetConsoleMode(), ClientSettings::System::kConsoleMode_Embedded);
EXPECT_EQ(target->GetState(), Target::State::kNone);
}
TEST_F(EmbeddedModeConsoleContext, ReturnToEmbeddedModeOnQuitWithMultipleProcesses) {
auto& context = console().context();
// We should output nothing for the initial attach event.
console().ProcessInputLine("attach xyz");
EXPECT_FALSE(console().HasOutputEvent());
constexpr uint64_t kProcess1Koid = 1234;
constexpr uint64_t kProcess2Koid = 1235;
// Inject two processes and break out of embedded mode.
InjectProcess(kProcess1Koid)->GetTarget()->GetWeakPtr();
InjectProcess(kProcess2Koid)->GetTarget()->GetWeakPtr();
context.SetConsoleMode(ClientSettings::System::kConsoleMode_EmbeddedInteractive);
// Do some inspection commands that should never leave interactive mode.
console().ProcessInputLine("list");
EXPECT_TRUE(console().HasOutputEvent());
auto event = console().GetOutputEvent(); // Consume the event.
EXPECT_EQ(context.GetConsoleMode(), ClientSettings::System::kConsoleMode_EmbeddedInteractive);
console().ProcessInputLine("b test");
EXPECT_TRUE(console().HasOutputEvent());
event = console().GetOutputEvent();
EXPECT_EQ(context.GetConsoleMode(), ClientSettings::System::kConsoleMode_EmbeddedInteractive);
// Now quit to mark that we're done.
console().ProcessInputLine("quit");
loop().RunUntilNoTasks();
// We should be back in embedded mode and attached to nothing.
EXPECT_EQ(context.GetConsoleMode(), ClientSettings::System::kConsoleMode_Embedded);
EXPECT_EQ(session().system().GetTargets().size(), 1u);
}
TEST_F(EmbeddedModeConsoleContext, ReturnToEmbeddedOnProcessExit) {
auto& context = console().context();
constexpr uint64_t kProcessKoid = 0x1234;
constexpr uint64_t kThreadKoid = 0x4321;
auto process = InjectProcess(kProcessKoid);
auto thread = InjectThread(kProcessKoid, kThreadKoid);
context.SetConsoleMode(ClientSettings::System::kConsoleMode_EmbeddedInteractive);
context.SetActiveTarget(process->GetTarget());
context.SetActiveThreadForTarget(thread);
// Even using a process or thread specific continue will return to embedded mode when the process
// exits.
console().ProcessInputLine("process continue");
// Should still be in interactive mode for now.
EXPECT_EQ(context.GetConsoleMode(), ClientSettings::System::kConsoleMode_EmbeddedInteractive);
// Send the notification that the process exited successfully.
debug_ipc::NotifyProcessExiting notify;
notify.return_code = 0;
notify.process_koid = kProcessKoid;
session().DispatchNotifyProcessExiting(notify);
loop().RunUntilNoTasks();
// Now we should be back in embedded mode because there are no other processes running.
EXPECT_EQ(context.GetConsoleMode(), ClientSettings::System::kConsoleMode_Embedded);
}
} // namespace zxdb