Files
CMake/Tests/CMakeLib/testDebuggerAdapter.cxx
Jonathan Phippen 41621c3afb Debugger: Add Value Formatting support for StackTrace request
Add support for the "format" property of the Debug Adapter Protocol
StackTrace request to fulfill the host's request to format the resulting
StackFrame name differently.
2024-10-29 13:29:00 -07:00

199 lines
6.4 KiB
C++

/* Distributed under the OSI-approved BSD 3-Clause License. See accompanying
file Copyright.txt or https://cmake.org/licensing for details. */
#include <chrono>
#include <cstdio>
#include <future>
#include <memory>
#include <string>
#include <cm3p/cppdap/future.h>
#include <cm3p/cppdap/io.h>
#include <cm3p/cppdap/optional.h>
#include <cm3p/cppdap/protocol.h>
#include <cm3p/cppdap/session.h>
#include <cm3p/cppdap/types.h>
#include "cmDebuggerAdapter.h"
#include "cmDebuggerProtocol.h"
#include "cmVersionConfig.h"
#include "testCommon.h"
#include "testDebugger.h"
class DebuggerLocalConnection : public cmDebugger::cmDebuggerConnection
{
public:
DebuggerLocalConnection()
: ClientToDebugger(dap::pipe())
, DebuggerToClient(dap::pipe())
{
}
bool StartListening(std::string& errorMessage) override
{
errorMessage = "";
return true;
}
void WaitForConnection() override {}
std::shared_ptr<dap::Reader> GetReader() override
{
return ClientToDebugger;
};
std::shared_ptr<dap::Writer> GetWriter() override
{
return DebuggerToClient;
}
std::shared_ptr<dap::ReaderWriter> ClientToDebugger;
std::shared_ptr<dap::ReaderWriter> DebuggerToClient;
};
bool runTest(std::function<bool(dap::Session&)> onThreadExitedEvent)
{
std::promise<bool> debuggerAdapterInitializedPromise;
std::future<bool> debuggerAdapterInitializedFuture =
debuggerAdapterInitializedPromise.get_future();
std::promise<bool> initializedEventReceivedPromise;
std::future<bool> initializedEventReceivedFuture =
initializedEventReceivedPromise.get_future();
std::promise<bool> exitedEventReceivedPromise;
std::future<bool> exitedEventReceivedFuture =
exitedEventReceivedPromise.get_future();
std::promise<bool> terminatedEventReceivedPromise;
std::future<bool> terminatedEventReceivedFuture =
terminatedEventReceivedPromise.get_future();
std::promise<bool> threadStartedPromise;
std::future<bool> threadStartedFuture = threadStartedPromise.get_future();
std::promise<bool> threadExitedPromise;
std::future<bool> threadExitedFuture = threadExitedPromise.get_future();
std::promise<bool> disconnectResponseReceivedPromise;
std::future<bool> disconnectResponseReceivedFuture =
disconnectResponseReceivedPromise.get_future();
auto futureTimeout = std::chrono::seconds(60);
auto connection = std::make_shared<DebuggerLocalConnection>();
std::unique_ptr<dap::Session> client = dap::Session::create();
client->registerHandler([&](const dap::InitializedEvent& e) {
(void)e;
initializedEventReceivedPromise.set_value(true);
});
client->registerHandler([&](const dap::ExitedEvent& e) {
(void)e;
exitedEventReceivedPromise.set_value(true);
});
client->registerHandler([&](const dap::TerminatedEvent& e) {
(void)e;
terminatedEventReceivedPromise.set_value(true);
});
client->registerHandler([&](const dap::ThreadEvent& e) {
if (e.reason == "started") {
threadStartedPromise.set_value(true);
} else if (e.reason == "exited") {
threadExitedPromise.set_value(true);
}
});
client->bind(connection->DebuggerToClient, connection->ClientToDebugger);
ScopedThread debuggerThread([&]() -> int {
std::shared_ptr<cmDebugger::cmDebuggerAdapter> debuggerAdapter =
std::make_shared<cmDebugger::cmDebuggerAdapter>(
connection, dap::file(stdout, false));
debuggerAdapterInitializedPromise.set_value(true);
debuggerAdapter->ReportExitCode(0);
// Ensure the disconnectResponse has been received before
// destructing debuggerAdapter.
ASSERT_TRUE(disconnectResponseReceivedFuture.wait_for(futureTimeout) ==
std::future_status::ready);
return 0;
});
dap::CMakeInitializeRequest initializeRequest;
auto initializeResponse = client->send(initializeRequest).get();
ASSERT_TRUE(initializeResponse.response.cmakeVersion.full == CMake_VERSION);
ASSERT_TRUE(initializeResponse.response.cmakeVersion.major ==
CMake_VERSION_MAJOR);
ASSERT_TRUE(initializeResponse.response.cmakeVersion.minor ==
CMake_VERSION_MINOR);
ASSERT_TRUE(initializeResponse.response.cmakeVersion.patch ==
CMake_VERSION_PATCH);
ASSERT_TRUE(initializeResponse.response.supportsExceptionInfoRequest);
ASSERT_TRUE(
initializeResponse.response.exceptionBreakpointFilters.has_value());
ASSERT_TRUE(initializeResponse.response.supportsValueFormattingOptions);
dap::LaunchRequest launchRequest;
auto launchResponse = client->send(launchRequest).get();
ASSERT_TRUE(!launchResponse.error);
dap::ConfigurationDoneRequest configurationDoneRequest;
auto configurationDoneResponse =
client->send(configurationDoneRequest).get();
ASSERT_TRUE(!configurationDoneResponse.error);
ASSERT_TRUE(debuggerAdapterInitializedFuture.wait_for(futureTimeout) ==
std::future_status::ready);
ASSERT_TRUE(initializedEventReceivedFuture.wait_for(futureTimeout) ==
std::future_status::ready);
ASSERT_TRUE(threadStartedFuture.wait_for(futureTimeout) ==
std::future_status::ready);
ASSERT_TRUE(threadExitedFuture.wait_for(futureTimeout) ==
std::future_status::ready);
if (onThreadExitedEvent) {
ASSERT_TRUE(onThreadExitedEvent(*client));
}
ASSERT_TRUE(exitedEventReceivedFuture.wait_for(futureTimeout) ==
std::future_status::ready);
ASSERT_TRUE(terminatedEventReceivedFuture.wait_for(futureTimeout) ==
std::future_status::ready);
dap::DisconnectRequest disconnectRequest;
auto disconnectResponse = client->send(disconnectRequest).get();
disconnectResponseReceivedPromise.set_value(true);
ASSERT_TRUE(!disconnectResponse.error);
return true;
}
bool testBasicProtocol()
{
return runTest(nullptr);
}
bool testThreadsRequestAfterThreadExitedEvent()
{
return runTest([](dap::Session& session) -> bool {
// Try requesting threads again after receiving the thread exited event.
// Some clients do this to ensure that their thread list is up-to-date.
dap::ThreadsRequest threadsRequest;
auto threadsResponse = session.send(threadsRequest).get();
ASSERT_TRUE(!threadsResponse.error);
// CMake only has one DAP thread. Once that thread exits, there should be
// no threads left.
ASSERT_TRUE(threadsResponse.response.threads.empty());
return true;
});
}
int testDebuggerAdapter(int, char*[])
{
return runTests(
{ testBasicProtocol, testThreadsRequestAfterThreadExitedEvent });
}