287 changes: 287 additions & 0 deletions lldb/unittests/tools/lldb-server/tests/TestClient.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,287 @@
//===-- TestClient.cpp ------------------------------------------*- C++ -*-===//
//
// The LLVM Compiler Infrastructure
//
// This file is distributed under the University of Illinois Open Source
// License. See LICENSE.TXT for details.
//
//===----------------------------------------------------------------------===//

#include "TestClient.h"
#include "lldb/Core/ArchSpec.h"
#include "lldb/Host/HostInfo.h"
#include "lldb/Host/common/TCPSocket.h"
#include "lldb/Host/posix/ConnectionFileDescriptorPosix.h"
#include "lldb/Host/posix/ProcessLauncherPosix.h"
#include "lldb/Interpreter/Args.h"
#include "lldb/Target/ProcessLaunchInfo.h"
#include "llvm/ADT/StringExtras.h"
#include "gtest/gtest.h"
#include <cstdlib>
#include <future>
#include <sstream>
#include <string>

using namespace lldb;
using namespace lldb_private;
using namespace llvm;

namespace llgs_tests {
void TestClient::Initialize() { HostInfo::Initialize(); }

TestClient::TestClient(const std::string &test_name,
const std::string &test_case_name)
: m_test_name(test_name), m_test_case_name(test_case_name),
m_pc_register(UINT_MAX) {}

TestClient::~TestClient() {}

bool TestClient::StartDebugger() {
const ArchSpec &arch_spec = HostInfo::GetArchitecture();
Args args;
args.AppendArgument(LLDB_SERVER);
args.AppendArgument("gdbserver");
args.AppendArgument("--log-channels=gdb-remote packets");
args.AppendArgument("--reverse-connect");
std::string log_file_name = GenerateLogFileName(arch_spec);
if (log_file_name.size()) {
args.AppendArgument("--log-file=" + log_file_name);
}

Status error;
TCPSocket listen_socket(true, false);
error = listen_socket.Listen("127.0.0.1:0", 5);
if (error.Fail()) {
GTEST_LOG_(ERROR) << "Unable to open listen socket.";
return false;
}

char connect_remote_address[64];
snprintf(connect_remote_address, sizeof(connect_remote_address),
"localhost:%u", listen_socket.GetLocalPortNumber());

args.AppendArgument(connect_remote_address);

m_server_process_info.SetArchitecture(arch_spec);
m_server_process_info.SetArguments(args, true);
Status status = Host::LaunchProcess(m_server_process_info);
if (status.Fail()) {
GTEST_LOG_(ERROR)
<< formatv("Failure to launch lldb server: {0}.", status).str();
return false;
}

char connect_remote_uri[64];
snprintf(connect_remote_uri, sizeof(connect_remote_uri), "connect://%s",
connect_remote_address);
Socket *accept_socket;
listen_socket.Accept(accept_socket);
SetConnection(new ConnectionFileDescriptor(accept_socket));

SendAck(); // Send this as a handshake.
return true;
}

bool TestClient::StopDebugger() {
std::string response;
return SendMessage("k", response, PacketResult::ErrorDisconnected);
}

bool TestClient::SetInferior(llvm::ArrayRef<std::string> inferior_args) {
std::stringstream command;
command << "A";
for (size_t i = 0; i < inferior_args.size(); i++) {
if (i > 0)
command << ',';
std::string hex_encoded = toHex(inferior_args[i]);
command << hex_encoded.size() << ',' << i << ',' << hex_encoded;
}

if (!SendMessage(command.str()))
return false;
if (!SendMessage("qLaunchSuccess"))
return false;
std::string response;
if (!SendMessage("qProcessInfo", response))
return false;
auto create_or_error = ProcessInfo::Create(response);
if (auto create_error = create_or_error.takeError()) {
GTEST_LOG_(ERROR) << toString(std::move(create_error));
return false;
}

m_process_info = *create_or_error;
return true;
}

bool TestClient::ListThreadsInStopReply() {
return SendMessage("QListThreadsInStopReply");
}

bool TestClient::SetBreakpoint(unsigned long address) {
std::stringstream command;
command << "Z0," << std::hex << address << ",1";
return SendMessage(command.str());
}

bool TestClient::ContinueAll() { return Continue("vCont;c"); }

bool TestClient::ContinueThread(unsigned long thread_id) {
return Continue(formatv("vCont;c:{0:x-}", thread_id).str());
}

const ProcessInfo &TestClient::GetProcessInfo() { return *m_process_info; }

Optional<JThreadsInfo> TestClient::GetJThreadsInfo() {
std::string response;
if (!SendMessage("jThreadsInfo", response))
return llvm::None;
auto creation = JThreadsInfo::Create(response, m_process_info->GetEndian());
if (auto create_error = creation.takeError()) {
GTEST_LOG_(ERROR) << toString(std::move(create_error));
return llvm::None;
}

return std::move(*creation);
}

const StopReply &TestClient::GetLatestStopReply() {
return m_stop_reply.getValue();
}

bool TestClient::SendMessage(StringRef message) {
std::string dummy_string;
return SendMessage(message, dummy_string);
}

bool TestClient::SendMessage(StringRef message, std::string &response_string) {
if (!SendMessage(message, response_string, PacketResult::Success))
return false;
else if (response_string[0] == 'E') {
GTEST_LOG_(ERROR) << "Error " << response_string
<< " while sending message: " << message.str();
return false;
}

return true;
}

bool TestClient::SendMessage(StringRef message, std::string &response_string,
PacketResult expected_result) {
StringExtractorGDBRemote response;
GTEST_LOG_(INFO) << "Send Packet: " << message.str();
PacketResult result = SendPacketAndWaitForResponse(message, response, false);
response.GetEscapedBinaryData(response_string);
GTEST_LOG_(INFO) << "Read Packet: " << response_string;
if (result != expected_result) {
GTEST_LOG_(ERROR) << FormatFailedResult(message, result);
return false;
}

return true;
}

unsigned int TestClient::GetPcRegisterId() {
if (m_pc_register != UINT_MAX)
return m_pc_register;

for (unsigned int register_id = 0;; register_id++) {
std::string message = formatv("qRegisterInfo{0:x-}", register_id).str();
std::string response;
if (!SendMessage(message, response)) {
GTEST_LOG_(ERROR) << "Unable to query register ID for PC register.";
return UINT_MAX;
}

auto elements_or_error = SplitPairList("GetPcRegisterId", response);
if (auto split_error = elements_or_error.takeError()) {
GTEST_LOG_(ERROR) << "GetPcRegisterId: Error splitting response: "
<< response;
return UINT_MAX;
}

auto elements = *elements_or_error;
if (elements["alt-name"] == "pc" || elements["generic"] == "pc") {
m_pc_register = register_id;
break;
}
}

return m_pc_register;
}

bool TestClient::Continue(StringRef message) {
if (!m_process_info.hasValue()) {
GTEST_LOG_(ERROR) << "Continue() called before m_process_info initialized.";
return false;
}

std::string response;
if (!SendMessage(message, response))
return false;
auto creation = StopReply::Create(response, m_process_info->GetEndian());
if (auto create_error = creation.takeError()) {
GTEST_LOG_(ERROR) << toString(std::move(create_error));
return false;
}

m_stop_reply = std::move(*creation);
return true;
}

std::string TestClient::GenerateLogFileName(const ArchSpec &arch) const {
char *log_directory = getenv("LOG_FILE_DIRECTORY");
if (!log_directory)
return "";

if (!llvm::sys::fs::is_directory(log_directory)) {
GTEST_LOG_(WARNING) << "Cannot access log directory: " << log_directory;
return "";
}

std::string log_file_name;
raw_string_ostream log_file(log_file_name);
log_file << log_directory << "/lldb-" << m_test_case_name << '-'
<< m_test_name << '-' << arch.GetArchitectureName() << ".log";
return log_file.str();
}

std::string TestClient::FormatFailedResult(const std::string &message,
PacketResult result) {
std::string formatted_error;
raw_string_ostream error_stream(formatted_error);
error_stream << "Failure sending message: " << message << " Result: ";

switch (result) {
case PacketResult::ErrorSendFailed:
error_stream << "ErrorSendFailed";
break;
case PacketResult::ErrorSendAck:
error_stream << "ErrorSendAck";
break;
case PacketResult::ErrorReplyFailed:
error_stream << "ErrorReplyFailed";
break;
case PacketResult::ErrorReplyTimeout:
error_stream << "ErrorReplyTimeout";
break;
case PacketResult::ErrorReplyInvalid:
error_stream << "ErrorReplyInvalid";
break;
case PacketResult::ErrorReplyAck:
error_stream << "ErrorReplyAck";
break;
case PacketResult::ErrorDisconnected:
error_stream << "ErrorDisconnected";
break;
case PacketResult::ErrorNoSequenceLock:
error_stream << "ErrorNoSequenceLock";
break;
default:
error_stream << "Unknown Error";
}

error_stream.str();
return formatted_error;
}
} // namespace llgs_tests
61 changes: 61 additions & 0 deletions lldb/unittests/tools/lldb-server/tests/TestClient.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
//===-- TestClient.h --------------------------------------------*- C++ -*-===//
//
// The LLVM Compiler Infrastructure
//
// This file is distributed under the University of Illinois Open Source
// License. See LICENSE.TXT for details.
//
//===----------------------------------------------------------------------===//

#include "MessageObjects.h"
#include "Plugins/Process/gdb-remote/GDBRemoteCommunicationClient.h"
#include "lldb/Core/ArchSpec.h"
#include "lldb/Target/ProcessLaunchInfo.h"
#include "llvm/ADT/Optional.h"
#include <memory>
#include <string>

namespace llgs_tests {
// TODO: Make the test client an abstract base class, with different children
// for different types of connections: llgs v. debugserver
class TestClient
: public lldb_private::process_gdb_remote::GDBRemoteCommunicationClient {
public:
static void Initialize();
TestClient(const std::string &test_name, const std::string &test_case_name);
virtual ~TestClient();
LLVM_NODISCARD bool StartDebugger();
LLVM_NODISCARD bool StopDebugger();
LLVM_NODISCARD bool SetInferior(llvm::ArrayRef<std::string> inferior_args);
LLVM_NODISCARD bool ListThreadsInStopReply();
LLVM_NODISCARD bool SetBreakpoint(unsigned long address);
LLVM_NODISCARD bool ContinueAll();
LLVM_NODISCARD bool ContinueThread(unsigned long thread_id);
const ProcessInfo &GetProcessInfo();
llvm::Optional<JThreadsInfo> GetJThreadsInfo();
const StopReply &GetLatestStopReply();
LLVM_NODISCARD bool SendMessage(llvm::StringRef message);
LLVM_NODISCARD bool SendMessage(llvm::StringRef message,
std::string &response_string);
LLVM_NODISCARD bool SendMessage(llvm::StringRef message,
std::string &response_string,
PacketResult expected_result);
unsigned int GetPcRegisterId();

private:
LLVM_NODISCARD bool Continue(llvm::StringRef message);
LLVM_NODISCARD bool GenerateConnectionAddress(std::string &address);
std::string GenerateLogFileName(const lldb_private::ArchSpec &arch) const;
std::string FormatFailedResult(
const std::string &message,
lldb_private::process_gdb_remote::GDBRemoteCommunication::PacketResult
result);

llvm::Optional<ProcessInfo> m_process_info;
llvm::Optional<StopReply> m_stop_reply;
lldb_private::ProcessLaunchInfo m_server_process_info;
std::string m_test_name;
std::string m_test_case_name;
unsigned int m_pc_register;
};
} // namespace llgs_tests
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
//===-- ThreadsInJstopinfoTest.cpp ------------------------------*- C++ -*-===//
//
// The LLVM Compiler Infrastructure
//
// This file is distributed under the University of Illinois Open Source
// License. See LICENSE.TXT for details.
//
//===----------------------------------------------------------------------===//

#include "TestClient.h"
#include "gtest/gtest.h"
#include <string>

using namespace llgs_tests;

class ThreadsInJstopinfoTest : public ::testing::Test {
protected:
virtual void SetUp() { TestClient::Initialize(); }
};

TEST_F(ThreadsInJstopinfoTest, TestStopReplyContainsThreadPcsLlgs) {
std::vector<std::string> inferior_args;
// This inferior spawns N threads, then forces a break.
inferior_args.push_back(THREAD_INFERIOR);
inferior_args.push_back("4");

auto test_info = ::testing::UnitTest::GetInstance()->current_test_info();

TestClient client(test_info->name(), test_info->test_case_name());
ASSERT_TRUE(client.StartDebugger());
ASSERT_TRUE(client.SetInferior(inferior_args));
ASSERT_TRUE(client.ListThreadsInStopReply());
ASSERT_TRUE(client.ContinueAll());
unsigned int pc_reg = client.GetPcRegisterId();
ASSERT_NE(pc_reg, UINT_MAX);

auto jthreads_info = client.GetJThreadsInfo();
ASSERT_TRUE(jthreads_info);

auto stop_reply = client.GetLatestStopReply();
auto stop_reply_pcs = stop_reply.GetThreadPcs();
auto thread_infos = jthreads_info->GetThreadInfos();
ASSERT_EQ(stop_reply_pcs.size(), thread_infos.size())
<< "Thread count mismatch.";

for (auto stop_reply_pc : stop_reply_pcs) {
unsigned long tid = stop_reply_pc.first;
ASSERT_TRUE(thread_infos.find(tid) != thread_infos.end())
<< "Thread ID: " << tid << " not in JThreadsInfo.";
uint64_t pc_value;
ASSERT_TRUE(thread_infos[tid].ReadRegisterAsUint64(pc_reg, pc_value))
<< "Failure reading ThreadInfo register " << pc_reg;
ASSERT_EQ(stop_reply_pcs[tid], pc_value)
<< "Mismatched PC for thread: " << tid;
}

ASSERT_TRUE(client.StopDebugger());
}