summaryrefslogtreecommitdiffstats
path: root/Tests
diff options
context:
space:
mode:
authorGlen Chung <kuchung@microsoft.com>2023-03-16 00:50:08 (GMT)
committerBrad King <brad.king@kitware.com>2023-05-30 13:46:12 (GMT)
commita9a592f96e6498da302f8e968be1db0ad3c32123 (patch)
tree0d75f16ee2eae99b1a3f063e575b3f5f8f2ee931 /Tests
parentb0d1ddb7234950374977b83f8dbded806c15b356 (diff)
downloadCMake-a9a592f96e6498da302f8e968be1db0ad3c32123.zip
CMake-a9a592f96e6498da302f8e968be1db0ad3c32123.tar.gz
CMake-a9a592f96e6498da302f8e968be1db0ad3c32123.tar.bz2
cmake: Add debugger
- Depends on cppdap and jsoncpp. - Add --debugger argument to enable the Debugger. - Add --debugger-pipe argument for DAP traffics over named pipes. - Support breakpoints by filenames and line numbers. - Support exception breakpoints. - Call stack shows filenames and line numbers. - Show Cache Variables. - Show the state of currently defined targets, tests and directories with their properties. - Add cmakeVersion to DAP initialize response. - Include unit tests. Co-authored-by: Ben McMorran <bemcmorr@microsoft.com>
Diffstat (limited to 'Tests')
-rw-r--r--Tests/CMakeLib/CMakeLists.txt25
-rw-r--r--Tests/CMakeLib/DebuggerSample/CMakeLists.txt9
-rw-r--r--Tests/CMakeLib/DebuggerSample/script.cmake1
-rw-r--r--Tests/CMakeLib/testCommon.h30
-rw-r--r--Tests/CMakeLib/testDebugger.h99
-rw-r--r--Tests/CMakeLib/testDebuggerAdapter.cxx173
-rw-r--r--Tests/CMakeLib/testDebuggerAdapterPipe.cxx184
-rw-r--r--Tests/CMakeLib/testDebuggerBreakpointManager.cxx172
-rw-r--r--Tests/CMakeLib/testDebuggerNamedPipe.cxx218
-rw-r--r--Tests/CMakeLib/testDebuggerVariables.cxx185
-rw-r--r--Tests/CMakeLib/testDebuggerVariablesHelper.cxx587
-rw-r--r--Tests/CMakeLib/testDebuggerVariablesManager.cxx50
-rw-r--r--Tests/RunCMake/CommandLine/DebuggerArgMissingDapLog-result.txt1
-rw-r--r--Tests/RunCMake/CommandLine/DebuggerArgMissingDapLog-stderr.txt2
-rw-r--r--Tests/RunCMake/CommandLine/DebuggerArgMissingDapLog.cmake1
-rw-r--r--Tests/RunCMake/CommandLine/DebuggerArgMissingPipe-result.txt1
-rw-r--r--Tests/RunCMake/CommandLine/DebuggerArgMissingPipe-stderr.txt2
-rw-r--r--Tests/RunCMake/CommandLine/DebuggerArgMissingPipe.cmake1
-rw-r--r--Tests/RunCMake/CommandLine/DebuggerCapabilityInspect-check.cmake5
-rw-r--r--Tests/RunCMake/CommandLine/DebuggerNotSupported-result.txt1
-rw-r--r--Tests/RunCMake/CommandLine/DebuggerNotSupported-stderr.txt2
-rw-r--r--Tests/RunCMake/CommandLine/DebuggerNotSupported.cmake1
-rw-r--r--Tests/RunCMake/CommandLine/DebuggerNotSupportedDapLog-result.txt1
-rw-r--r--Tests/RunCMake/CommandLine/DebuggerNotSupportedDapLog-stderr.txt2
-rw-r--r--Tests/RunCMake/CommandLine/DebuggerNotSupportedDapLog.cmake1
-rw-r--r--Tests/RunCMake/CommandLine/DebuggerNotSupportedPipe-result.txt1
-rw-r--r--Tests/RunCMake/CommandLine/DebuggerNotSupportedPipe-stderr.txt2
-rw-r--r--Tests/RunCMake/CommandLine/DebuggerNotSupportedPipe.cmake1
-rw-r--r--Tests/RunCMake/CommandLine/E_capabilities-stdout.txt2
-rw-r--r--Tests/RunCMake/CommandLine/RunCMakeTest.cmake11
30 files changed, 1770 insertions, 1 deletions
diff --git a/Tests/CMakeLib/CMakeLists.txt b/Tests/CMakeLib/CMakeLists.txt
index 944b328..5c14de2 100644
--- a/Tests/CMakeLib/CMakeLists.txt
+++ b/Tests/CMakeLib/CMakeLists.txt
@@ -32,6 +32,16 @@ set(CMakeLib_TESTS
testCMExtEnumSet.cxx
testList.cxx
)
+if(CMake_ENABLE_DEBUGGER)
+ list(APPEND CMakeLib_TESTS
+ testDebuggerAdapter.cxx
+ testDebuggerAdapterPipe.cxx
+ testDebuggerBreakpointManager.cxx
+ testDebuggerVariables.cxx
+ testDebuggerVariablesHelper.cxx
+ testDebuggerVariablesManager.cxx
+ )
+endif()
if (CMake_TEST_FILESYSTEM_PATH OR NOT CMake_HAVE_CXX_FILESYSTEM)
list(APPEND CMakeLib_TESTS testCMFilesystemPath.cxx)
endif()
@@ -78,3 +88,18 @@ add_subdirectory(PseudoMemcheck)
add_executable(testAffinity testAffinity.cxx)
target_link_libraries(testAffinity CMakeLib)
+
+if(CMake_ENABLE_DEBUGGER)
+ add_executable(testDebuggerNamedPipe testDebuggerNamedPipe.cxx)
+ target_link_libraries(testDebuggerNamedPipe PRIVATE CMakeLib)
+ set(testDebuggerNamedPipe_Project_ARGS
+ "$<TARGET_FILE:cmake>" ${CMAKE_CURRENT_SOURCE_DIR}/DebuggerSample ${CMAKE_CURRENT_BINARY_DIR}/DebuggerSample
+ )
+ set(testDebuggerNamedPipe_Script_ARGS
+ "$<TARGET_FILE:cmake>" ${CMAKE_CURRENT_SOURCE_DIR}/DebuggerSample/script.cmake
+ )
+ foreach(case Project Script)
+ add_test(NAME CMakeLib.testDebuggerNamedPipe-${case} COMMAND testDebuggerNamedPipe ${testDebuggerNamedPipe_${case}_ARGS})
+ set_property(TEST CMakeLib.testDebuggerNamedPipe-${case} PROPERTY TIMEOUT 300)
+ endforeach()
+endif()
diff --git a/Tests/CMakeLib/DebuggerSample/CMakeLists.txt b/Tests/CMakeLib/DebuggerSample/CMakeLists.txt
new file mode 100644
index 0000000..8f8603a
--- /dev/null
+++ b/Tests/CMakeLib/DebuggerSample/CMakeLists.txt
@@ -0,0 +1,9 @@
+cmake_minimum_required(VERSION 3.26)
+project(DebuggerSample NONE)
+message("Hello CMake Debugger")
+
+# There are concerns that because the debugger uses libuv for pipe
+# communication, libuv may register a SIGCHILD handler that interferes with
+# the existing handler used by kwsys process management. Test this case with a
+# simple external process.
+execute_process(COMMAND "${CMAKE_COMMAND}" -E echo test)
diff --git a/Tests/CMakeLib/DebuggerSample/script.cmake b/Tests/CMakeLib/DebuggerSample/script.cmake
new file mode 100644
index 0000000..4c0c00a
--- /dev/null
+++ b/Tests/CMakeLib/DebuggerSample/script.cmake
@@ -0,0 +1 @@
+message(STATUS "This is an example script")
diff --git a/Tests/CMakeLib/testCommon.h b/Tests/CMakeLib/testCommon.h
new file mode 100644
index 0000000..bd2d54e
--- /dev/null
+++ b/Tests/CMakeLib/testCommon.h
@@ -0,0 +1,30 @@
+/* Distributed under the OSI-approved BSD 3-Clause License. See accompanying
+ file Copyright.txt or https://cmake.org/licensing for details. */
+#pragma once
+
+#include <functional>
+#include <iostream>
+#include <vector>
+
+#define ASSERT_TRUE(x) \
+ do { \
+ if (!(x)) { \
+ std::cout << "ASSERT_TRUE(" #x ") failed on line " << __LINE__ << "\n"; \
+ return false; \
+ } \
+ } while (false)
+
+inline int runTests(std::vector<std::function<bool()>> const& tests)
+{
+ for (auto const& test : tests) {
+ if (!test()) {
+ return 1;
+ }
+ std::cout << ".";
+ }
+
+ std::cout << " Passed" << std::endl;
+ return 0;
+}
+
+#define BOOL_STRING(b) ((b) ? "TRUE" : "FALSE")
diff --git a/Tests/CMakeLib/testDebugger.h b/Tests/CMakeLib/testDebugger.h
new file mode 100644
index 0000000..8ba21f6
--- /dev/null
+++ b/Tests/CMakeLib/testDebugger.h
@@ -0,0 +1,99 @@
+/* Distributed under the OSI-approved BSD 3-Clause License. See accompanying
+ file Copyright.txt or https://cmake.org/licensing for details. */
+#pragma once
+
+#include <memory>
+#include <vector>
+
+#include "cmDebuggerAdapter.h"
+#include "cmDebuggerProtocol.h"
+#include "cmListFileCache.h"
+#include "cmMessenger.h"
+#include <cmcppdap/include/dap/io.h>
+#include <cmcppdap/include/dap/session.h>
+#include <cmcppdap/include/dap/types.h>
+
+#include "testCommon.h"
+
+#define ASSERT_VARIABLE(x, expectedName, expectedValue, expectedType) \
+ do { \
+ ASSERT_TRUE(x.name == expectedName); \
+ ASSERT_TRUE(x.value == expectedValue); \
+ ASSERT_TRUE(x.type.value() == expectedType); \
+ ASSERT_TRUE(x.evaluateName.has_value() == false); \
+ if (std::string(expectedType) == "collection") { \
+ ASSERT_TRUE(x.variablesReference != 0); \
+ } \
+ } while (false)
+
+#define ASSERT_VARIABLE_REFERENCE(x, expectedName, expectedValue, \
+ expectedType, expectedReference) \
+ do { \
+ ASSERT_VARIABLE(x, expectedName, expectedValue, expectedType); \
+ ASSERT_TRUE(x.variablesReference == (expectedReference)); \
+ } while (false)
+
+#define ASSERT_VARIABLE_REFERENCE_NOT_ZERO(x, expectedName, expectedValue, \
+ expectedType) \
+ do { \
+ ASSERT_VARIABLE(x, expectedName, expectedValue, expectedType); \
+ ASSERT_TRUE(x.variablesReference != 0); \
+ } while (false)
+
+#define ASSERT_BREAKPOINT(x, expectedId, expectedLine, sourcePath, \
+ isVerified) \
+ do { \
+ ASSERT_TRUE(x.id.has_value()); \
+ ASSERT_TRUE(x.id.value() == expectedId); \
+ ASSERT_TRUE(x.line.has_value()); \
+ ASSERT_TRUE(x.line.value() == expectedLine); \
+ ASSERT_TRUE(x.source.has_value()); \
+ ASSERT_TRUE(x.source.value().path.has_value()); \
+ ASSERT_TRUE(x.source.value().path.value() == sourcePath); \
+ ASSERT_TRUE(x.verified == isVerified); \
+ } while (false)
+
+class DebuggerTestHelper
+{
+ std::shared_ptr<dap::ReaderWriter> Client2Debugger = dap::pipe();
+ std::shared_ptr<dap::ReaderWriter> Debugger2Client = dap::pipe();
+
+public:
+ std::unique_ptr<dap::Session> Client = dap::Session::create();
+ std::unique_ptr<dap::Session> Debugger = dap::Session::create();
+ void bind()
+ {
+ auto client2server = dap::pipe();
+ auto server2client = dap::pipe();
+ Client->bind(server2client, client2server);
+ Debugger->bind(client2server, server2client);
+ }
+ std::vector<cmListFileFunction> CreateListFileFunctions(const char* str,
+ const char* filename)
+ {
+ cmMessenger messenger;
+ cmListFileBacktrace backtrace;
+ cmListFile listfile;
+ listfile.ParseString(str, filename, &messenger, backtrace);
+ return listfile.Functions;
+ }
+};
+
+class ScopedThread
+{
+public:
+ template <class... Args>
+ explicit ScopedThread(Args&&... args)
+ : Thread(std::forward<Args>(args)...)
+ {
+ }
+
+ ~ScopedThread()
+ {
+ if (Thread.joinable())
+ Thread.join();
+ }
+
+private:
+ std::thread Thread;
+};
diff --git a/Tests/CMakeLib/testDebuggerAdapter.cxx b/Tests/CMakeLib/testDebuggerAdapter.cxx
new file mode 100644
index 0000000..394986b
--- /dev/null
+++ b/Tests/CMakeLib/testDebuggerAdapter.cxx
@@ -0,0 +1,173 @@
+/* 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 <functional>
+#include <future>
+#include <memory>
+#include <string>
+#include <vector>
+
+#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 testBasicProtocol()
+{
+ 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());
+
+ 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);
+ 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;
+}
+
+int testDebuggerAdapter(int, char*[])
+{
+ return runTests(std::vector<std::function<bool()>>{
+ testBasicProtocol,
+ });
+}
diff --git a/Tests/CMakeLib/testDebuggerAdapterPipe.cxx b/Tests/CMakeLib/testDebuggerAdapterPipe.cxx
new file mode 100644
index 0000000..643661d
--- /dev/null
+++ b/Tests/CMakeLib/testDebuggerAdapterPipe.cxx
@@ -0,0 +1,184 @@
+/* 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 <functional>
+#include <future>
+#include <iostream>
+#include <memory>
+#include <stdexcept>
+#include <string>
+#include <vector>
+
+#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 "cmDebuggerPipeConnection.h"
+#include "cmDebuggerProtocol.h"
+#include "cmVersionConfig.h"
+
+#ifdef _WIN32
+# include "cmCryptoHash.h"
+# include "cmSystemTools.h"
+#endif
+
+#include "testCommon.h"
+#include "testDebugger.h"
+
+bool testProtocolWithPipes()
+{
+ std::promise<void> debuggerConnectionCreatedPromise;
+ std::future<void> debuggerConnectionCreatedFuture =
+ debuggerConnectionCreatedPromise.get_future();
+
+ std::future<void> startedListeningFuture;
+
+ 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);
+
+#ifdef _WIN32
+ std::string namedPipe = R"(\\.\pipe\LOCAL\CMakeDebuggerPipe2_)" +
+ cmCryptoHash(cmCryptoHash::AlgoSHA256)
+ .HashString(cmSystemTools::GetCurrentWorkingDirectory());
+#else
+ std::string namedPipe = "CMakeDebuggerPipe2";
+#endif
+
+ 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);
+ }
+ });
+
+ ScopedThread debuggerThread([&]() -> int {
+ try {
+ auto connection =
+ std::make_shared<cmDebugger::cmDebuggerPipeConnection>(namedPipe);
+ startedListeningFuture = connection->StartedListening.get_future();
+ debuggerConnectionCreatedPromise.set_value();
+ 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;
+ } catch (const std::runtime_error& error) {
+ std::cerr << "Error: Failed to create debugger adapter.\n";
+ std::cerr << error.what() << "\n";
+ return -1;
+ }
+ });
+
+ ASSERT_TRUE(debuggerConnectionCreatedFuture.wait_for(futureTimeout) ==
+ std::future_status::ready);
+ ASSERT_TRUE(startedListeningFuture.wait_for(futureTimeout) ==
+ std::future_status::ready);
+
+ auto client2Debugger =
+ std::make_shared<cmDebugger::cmDebuggerPipeClient>(namedPipe);
+ client2Debugger->Start();
+ client2Debugger->WaitForConnection();
+ client->bind(client2Debugger, client2Debugger);
+
+ dap::CMakeInitializeRequest initializeRequest;
+ auto response = client->send(initializeRequest);
+ auto initializeResponse = response.get();
+ ASSERT_TRUE(!initializeResponse.error);
+ 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());
+ 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(terminatedEventReceivedFuture.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);
+ ASSERT_TRUE(exitedEventReceivedFuture.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;
+}
+
+int testDebuggerAdapterPipe(int, char*[])
+{
+ return runTests(std::vector<std::function<bool()>>{
+ testProtocolWithPipes,
+ });
+}
diff --git a/Tests/CMakeLib/testDebuggerBreakpointManager.cxx b/Tests/CMakeLib/testDebuggerBreakpointManager.cxx
new file mode 100644
index 0000000..83734ea
--- /dev/null
+++ b/Tests/CMakeLib/testDebuggerBreakpointManager.cxx
@@ -0,0 +1,172 @@
+/* Distributed under the OSI-approved BSD 3-Clause License. See accompanying
+ file Copyright.txt or https://cmake.org/licensing for details. */
+
+#include <atomic>
+#include <chrono>
+#include <functional>
+#include <future>
+#include <memory>
+#include <string>
+#include <vector>
+
+#include <cm3p/cppdap/future.h>
+#include <cm3p/cppdap/optional.h>
+#include <cm3p/cppdap/protocol.h>
+#include <cm3p/cppdap/session.h>
+#include <cm3p/cppdap/types.h>
+
+#include "cmDebuggerBreakpointManager.h"
+#include "cmDebuggerSourceBreakpoint.h" // IWYU pragma: keep
+#include "cmListFileCache.h"
+
+#include "testCommon.h"
+#include "testDebugger.h"
+
+static bool testHandleBreakpointRequestBeforeFileIsLoaded()
+{
+ // Arrange
+ DebuggerTestHelper helper;
+ cmDebugger::cmDebuggerBreakpointManager breakpointManager(
+ helper.Debugger.get());
+ helper.bind();
+ dap::SetBreakpointsRequest setBreakpointRequest;
+ std::string sourcePath = "C:/CMakeLists.txt";
+ setBreakpointRequest.source.path = sourcePath;
+ dap::array<dap::SourceBreakpoint> sourceBreakpoints(3);
+ sourceBreakpoints[0].line = 1;
+ sourceBreakpoints[1].line = 2;
+ sourceBreakpoints[2].line = 3;
+ setBreakpointRequest.breakpoints = sourceBreakpoints;
+
+ // Act
+ auto got = helper.Client->send(setBreakpointRequest).get();
+
+ // Assert
+ auto& response = got.response;
+ ASSERT_TRUE(!got.error);
+ ASSERT_TRUE(response.breakpoints.size() == sourceBreakpoints.size());
+ ASSERT_BREAKPOINT(response.breakpoints[0], 0, sourceBreakpoints[0].line,
+ sourcePath, false);
+ ASSERT_BREAKPOINT(response.breakpoints[1], 1, sourceBreakpoints[1].line,
+ sourcePath, false);
+ ASSERT_BREAKPOINT(response.breakpoints[2], 2, sourceBreakpoints[2].line,
+ sourcePath, false);
+ return true;
+}
+
+static bool testHandleBreakpointRequestAfterFileIsLoaded()
+{
+ // Arrange
+ DebuggerTestHelper helper;
+ std::atomic<bool> notExpectBreakpointEvents(true);
+ helper.Client->registerHandler([&](const dap::BreakpointEvent&) {
+ notExpectBreakpointEvents.store(false);
+ });
+
+ cmDebugger::cmDebuggerBreakpointManager breakpointManager(
+ helper.Debugger.get());
+ helper.bind();
+ std::string sourcePath = "C:/CMakeLists.txt";
+ std::vector<cmListFileFunction> functions = helper.CreateListFileFunctions(
+ "# Comment1\nset(var1 foo)\n# Comment2\nset(var2\nbar)\n",
+ sourcePath.c_str());
+
+ breakpointManager.SourceFileLoaded(sourcePath, functions);
+ dap::SetBreakpointsRequest setBreakpointRequest;
+ setBreakpointRequest.source.path = sourcePath;
+ dap::array<dap::SourceBreakpoint> sourceBreakpoints(5);
+ sourceBreakpoints[0].line = 1;
+ sourceBreakpoints[1].line = 2;
+ sourceBreakpoints[2].line = 3;
+ sourceBreakpoints[3].line = 4;
+ sourceBreakpoints[4].line = 5;
+ setBreakpointRequest.breakpoints = sourceBreakpoints;
+
+ // Act
+ auto got = helper.Client->send(setBreakpointRequest).get();
+
+ // Assert
+ auto& response = got.response;
+ ASSERT_TRUE(!got.error);
+ ASSERT_TRUE(response.breakpoints.size() == sourceBreakpoints.size());
+ // Line 1 is a comment. Move it to next valid function, which is line 2.
+ ASSERT_BREAKPOINT(response.breakpoints[0], 0, 2, sourcePath, true);
+ ASSERT_BREAKPOINT(response.breakpoints[1], 1, sourceBreakpoints[1].line,
+ sourcePath, true);
+ // Line 3 is a comment. Move it to next valid function, which is line 4.
+ ASSERT_BREAKPOINT(response.breakpoints[2], 2, 4, sourcePath, true);
+ ASSERT_BREAKPOINT(response.breakpoints[3], 3, sourceBreakpoints[3].line,
+ sourcePath, true);
+ // Line 5 is the 2nd part of line 4 function. No valid function after line 5,
+ // show the breakpoint at line 4.
+ ASSERT_BREAKPOINT(response.breakpoints[4], 4, sourceBreakpoints[3].line,
+ sourcePath, true);
+
+ ASSERT_TRUE(notExpectBreakpointEvents.load());
+
+ return true;
+}
+
+static bool testSourceFileLoadedAfterHandleBreakpointRequest()
+{
+ // Arrange
+ DebuggerTestHelper helper;
+ std::vector<dap::BreakpointEvent> breakpointEvents;
+ std::atomic<int> remainingBreakpointEvents(5);
+ std::promise<void> allBreakpointEventsReceivedPromise;
+ std::future<void> allBreakpointEventsReceivedFuture =
+ allBreakpointEventsReceivedPromise.get_future();
+ helper.Client->registerHandler([&](const dap::BreakpointEvent& event) {
+ breakpointEvents.emplace_back(event);
+ if (--remainingBreakpointEvents == 0) {
+ allBreakpointEventsReceivedPromise.set_value();
+ }
+ });
+ cmDebugger::cmDebuggerBreakpointManager breakpointManager(
+ helper.Debugger.get());
+ helper.bind();
+ dap::SetBreakpointsRequest setBreakpointRequest;
+ std::string sourcePath = "C:/CMakeLists.txt";
+ setBreakpointRequest.source.path = sourcePath;
+ dap::array<dap::SourceBreakpoint> sourceBreakpoints(5);
+ sourceBreakpoints[0].line = 1;
+ sourceBreakpoints[1].line = 2;
+ sourceBreakpoints[2].line = 3;
+ sourceBreakpoints[3].line = 4;
+ sourceBreakpoints[4].line = 5;
+ setBreakpointRequest.breakpoints = sourceBreakpoints;
+ std::vector<cmListFileFunction> functions = helper.CreateListFileFunctions(
+ "# Comment1\nset(var1 foo)\n# Comment2\nset(var2\nbar)\n",
+ sourcePath.c_str());
+ auto got = helper.Client->send(setBreakpointRequest).get();
+
+ // Act
+ breakpointManager.SourceFileLoaded(sourcePath, functions);
+ ASSERT_TRUE(allBreakpointEventsReceivedFuture.wait_for(
+ std::chrono::seconds(10)) == std::future_status::ready);
+
+ // Assert
+ ASSERT_TRUE(breakpointEvents.size() > 0);
+ // Line 1 is a comment. Move it to next valid function, which is line 2.
+ ASSERT_BREAKPOINT(breakpointEvents[0].breakpoint, 0, 2, sourcePath, true);
+ ASSERT_BREAKPOINT(breakpointEvents[1].breakpoint, 1,
+ sourceBreakpoints[1].line, sourcePath, true);
+ // Line 3 is a comment. Move it to next valid function, which is line 4.
+ ASSERT_BREAKPOINT(breakpointEvents[2].breakpoint, 2, 4, sourcePath, true);
+ ASSERT_BREAKPOINT(breakpointEvents[3].breakpoint, 3,
+ sourceBreakpoints[3].line, sourcePath, true);
+ // Line 5 is the 2nd part of line 4 function. No valid function after line 5,
+ // show the breakpoint at line 4.
+ ASSERT_BREAKPOINT(breakpointEvents[4].breakpoint, 4,
+ sourceBreakpoints[3].line, sourcePath, true);
+ return true;
+}
+
+int testDebuggerBreakpointManager(int, char*[])
+{
+ return runTests(std::vector<std::function<bool()>>{
+ testHandleBreakpointRequestBeforeFileIsLoaded,
+ testHandleBreakpointRequestAfterFileIsLoaded,
+ testSourceFileLoadedAfterHandleBreakpointRequest,
+ });
+}
diff --git a/Tests/CMakeLib/testDebuggerNamedPipe.cxx b/Tests/CMakeLib/testDebuggerNamedPipe.cxx
new file mode 100644
index 0000000..d2b0728
--- /dev/null
+++ b/Tests/CMakeLib/testDebuggerNamedPipe.cxx
@@ -0,0 +1,218 @@
+/* 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 <exception>
+#include <iostream>
+#include <memory>
+#include <sstream>
+#include <stdexcept>
+#include <string>
+#include <thread>
+#include <vector>
+
+#include <cm3p/cppdap/io.h>
+
+#include "cmsys/RegularExpression.hxx"
+
+#include "cmDebuggerPipeConnection.h"
+#include "cmSystemTools.h"
+
+#ifdef _WIN32
+# include "cmCryptoHash.h"
+#endif
+
+static void sendCommands(std::shared_ptr<dap::ReaderWriter> const& debugger,
+ int delayMs,
+ std::vector<std::string> const& initCommands)
+{
+ for (const auto& command : initCommands) {
+ std::string contentLength = "Content-Length:";
+ contentLength += std::to_string(command.size()) + "\r\n\r\n";
+ debugger->write(contentLength.c_str(), contentLength.size());
+ if (!debugger->write(command.c_str(), command.size())) {
+ std::cout << "debugger write error" << std::endl;
+ break;
+ }
+ std::this_thread::sleep_for(std::chrono::milliseconds(delayMs));
+ }
+}
+
+/** \brief Test CMake debugger named pipe.
+ *
+ * Test CMake debugger named pipe by
+ * 1. Create a named pipe for DAP traffic between the client and the debugger.
+ * 2. Create a client thread to wait for the debugger connection.
+ * - Once the debugger is connected, send the minimum required commands to
+ * get debugger going.
+ * - Wait for the CMake to complete the cache generation
+ * - Send the disconnect command.
+ * - Read and store the debugger's responses for validation.
+ * 3. Run the CMake command with debugger on and wait for it to complete.
+ * 4. Validate the response to ensure we are getting the expected responses.
+ *
+ */
+int runTest(int argc, char* argv[])
+{
+ if (argc < 3) {
+ std::cout << "Usage:\n";
+ std::cout << "\t(project mode) TestDebuggerNamedPipe <CMakePath> "
+ "<SourceFolder> <OutputFolder>\n";
+ std::cout << "\t(script mode) TestDebuggerNamedPipe <CMakePath> "
+ "<ScriptPath>\n";
+ return 1;
+ }
+
+ bool scriptMode = argc == 3;
+
+#ifdef _WIN32
+ std::string namedPipe = R"(\\.\pipe\LOCAL\CMakeDebuggerPipe_)" +
+ cmCryptoHash(cmCryptoHash::AlgoSHA256)
+ .HashString(scriptMode ? argv[2] : argv[3]);
+#else
+ std::string namedPipe =
+ std::string("CMakeDebuggerPipe") + (scriptMode ? "Script" : "Project");
+#endif
+
+ std::vector<std::string> cmakeCommand;
+ cmakeCommand.emplace_back(argv[1]);
+ cmakeCommand.emplace_back("--debugger");
+ cmakeCommand.emplace_back("--debugger-pipe");
+ cmakeCommand.emplace_back(namedPipe);
+
+ if (scriptMode) {
+ cmakeCommand.emplace_back("-P");
+ cmakeCommand.emplace_back(argv[2]);
+ } else {
+ cmakeCommand.emplace_back("-S");
+ cmakeCommand.emplace_back(argv[2]);
+ cmakeCommand.emplace_back("-B");
+ cmakeCommand.emplace_back(argv[3]);
+ }
+
+ // Capture debugger response stream.
+ std::stringstream debuggerResponseStream;
+
+ // Start the debugger client process.
+ std::thread clientThread([&]() {
+ // Poll until the pipe server is running. Clients can also look for a magic
+ // string in the CMake output, but this is easier for the test case.
+ std::shared_ptr<cmDebugger::cmDebuggerPipeClient> client;
+ int attempt = 0;
+ do {
+ attempt++;
+ try {
+ client = std::make_shared<cmDebugger::cmDebuggerPipeClient>(namedPipe);
+ client->Start();
+ client->WaitForConnection();
+ std::cout << "cmDebuggerPipeClient connected.\n";
+ break;
+ } catch (std::runtime_error&) {
+ std::cout << "Failed attempt " << attempt
+ << " to connect to pipe server. Retrying.\n";
+ client.reset();
+ std::this_thread::sleep_for(std::chrono::milliseconds(200));
+ }
+ } while (attempt < 50); // 10 seconds
+
+ if (attempt >= 50) {
+ return -1;
+ }
+
+ // Send init commands to get debugger going.
+ sendCommands(
+ client, 400,
+ { "{\"arguments\":{\"adapterID\":\"\"},\"command\":\"initialize\","
+ "\"seq\":"
+ "1,\"type\":\"request\"}",
+ "{\"arguments\":{},\"command\":\"launch\",\"seq\":2,\"type\":"
+ "\"request\"}",
+ "{\"arguments\":{},\"command\":\"configurationDone\",\"seq\":3,"
+ "\"type\":"
+ "\"request\"}" });
+
+ // Look for "exitCode" as a sign that configuration has completed and
+ // it's now safe to disconnect.
+ for (;;) {
+ char buffer[1];
+ size_t result = client->read(buffer, 1);
+ if (result != 1) {
+ std::cout << "debugger read error: " << result << std::endl;
+ break;
+ }
+ debuggerResponseStream << buffer[0];
+ if (debuggerResponseStream.str().find("exitCode") != std::string::npos) {
+ break;
+ }
+ }
+
+ // Send disconnect command.
+ sendCommands(
+ client, 200,
+ { "{\"arguments\":{},\"command\":\"disconnect\",\"seq\":4,\"type\":"
+ "\"request\"}" });
+
+ // Read any remaining debugger responses.
+ for (;;) {
+ char buffer[1];
+ size_t result = client->read(buffer, 1);
+ if (result != 1) {
+ std::cout << "debugger read error: " << result << std::endl;
+ break;
+ }
+ debuggerResponseStream << buffer[0];
+ }
+
+ client->close();
+
+ return 0;
+ });
+
+ if (!cmSystemTools::RunSingleCommand(cmakeCommand, nullptr, nullptr, nullptr,
+ nullptr, cmSystemTools::OUTPUT_MERGE)) {
+ std::cout << "Error running command" << std::endl;
+ return -1;
+ }
+
+ clientThread.join();
+
+ auto debuggerResponse = debuggerResponseStream.str();
+
+ std::vector<std::string> expectedResponses = {
+ R"("event" : "initialized".*"type" : "event")",
+ R"("command" : "launch".*"success" : true.*"type" : "response")",
+ R"("command" : "configurationDone".*"success" : true.*"type" : "response")",
+ R"("reason" : "started".*"threadId" : 1.*"event" : "thread".*"type" : "event")",
+ R"("reason" : "exited".*"threadId" : 1.*"event" : "thread".*"type" : "event")",
+ R"("exitCode" : 0.*"event" : "exited".*"type" : "event")",
+ R"("command" : "disconnect".*"success" : true.*"type" : "response")"
+ };
+
+ for (auto& regexString : expectedResponses) {
+ cmsys::RegularExpression regex(regexString);
+ if (!regex.find(debuggerResponse)) {
+ std::cout << "Expected response not found: " << regexString << std::endl;
+ std::cout << debuggerResponse << std::endl;
+ return -1;
+ }
+ }
+
+ return 0;
+}
+
+int main(int argc, char* argv[])
+{
+ try {
+ return runTest(argc, argv);
+ } catch (const std::exception& ex) {
+ std::cout << "An exception occurred: " << ex.what() << std::endl;
+ return -1;
+ } catch (const std::string& ex) {
+ std::cout << "An exception occurred: " << ex << std::endl;
+ return -1;
+ } catch (...) {
+ std::cout << "An unknown exception occurred" << std::endl;
+ return -1;
+ }
+}
diff --git a/Tests/CMakeLib/testDebuggerVariables.cxx b/Tests/CMakeLib/testDebuggerVariables.cxx
new file mode 100644
index 0000000..6c19baa
--- /dev/null
+++ b/Tests/CMakeLib/testDebuggerVariables.cxx
@@ -0,0 +1,185 @@
+/* Distributed under the OSI-approved BSD 3-Clause License. See accompanying
+ file Copyright.txt or https://cmake.org/licensing for details. */
+
+#include <cstdint>
+#include <functional>
+#include <memory>
+#include <string>
+#include <unordered_set>
+#include <vector>
+
+#include <cm3p/cppdap/protocol.h>
+#include <cm3p/cppdap/types.h>
+
+#include "cmDebuggerVariables.h"
+#include "cmDebuggerVariablesManager.h"
+
+#include "testCommon.h"
+#include "testDebugger.h"
+
+static dap::VariablesRequest CreateVariablesRequest(int64_t reference)
+{
+ dap::VariablesRequest variableRequest;
+ variableRequest.variablesReference = reference;
+ return variableRequest;
+}
+
+static bool testUniqueIds()
+{
+ auto variablesManager =
+ std::make_shared<cmDebugger::cmDebuggerVariablesManager>();
+
+ std::unordered_set<int64_t> variableIds;
+ bool noDuplicateIds = true;
+ for (int i = 0; i < 10000 && noDuplicateIds; ++i) {
+ auto variable =
+ cmDebugger::cmDebuggerVariables(variablesManager, "Locals", true, []() {
+ return std::vector<cmDebugger::cmDebuggerVariableEntry>();
+ });
+
+ if (variableIds.find(variable.GetId()) != variableIds.end()) {
+ noDuplicateIds = false;
+ }
+ variableIds.insert(variable.GetId());
+ }
+
+ ASSERT_TRUE(noDuplicateIds);
+
+ return true;
+}
+
+static bool testConstructors()
+{
+ auto variablesManager =
+ std::make_shared<cmDebugger::cmDebuggerVariablesManager>();
+
+ auto parent = std::make_shared<cmDebugger::cmDebuggerVariables>(
+ variablesManager, "Parent", true, [=]() {
+ return std::vector<cmDebugger::cmDebuggerVariableEntry>{
+ { "ParentKey", "ParentValue", "ParentType" }
+ };
+ });
+
+ auto children1 = std::make_shared<cmDebugger::cmDebuggerVariables>(
+ variablesManager, "Children1", true, [=]() {
+ return std::vector<cmDebugger::cmDebuggerVariableEntry>{
+ { "ChildKey1", "ChildValue1", "ChildType1" },
+ { "ChildKey2", "ChildValue2", "ChildType2" }
+ };
+ });
+
+ parent->AddSubVariables(children1);
+
+ auto children2 = std::make_shared<cmDebugger::cmDebuggerVariables>(
+ variablesManager, "Children2", true);
+
+ auto grandChildren21 = std::make_shared<cmDebugger::cmDebuggerVariables>(
+ variablesManager, "GrandChildren21", true);
+ grandChildren21->SetValue("GrandChildren21 Value");
+ children2->AddSubVariables(grandChildren21);
+ parent->AddSubVariables(children2);
+
+ dap::array<dap::Variable> variables =
+ variablesManager->HandleVariablesRequest(
+ CreateVariablesRequest(parent->GetId()));
+ ASSERT_TRUE(variables.size() == 3);
+ ASSERT_VARIABLE_REFERENCE(variables[0], "Children1", "", "collection",
+ children1->GetId());
+ ASSERT_VARIABLE_REFERENCE(variables[1], "Children2", "", "collection",
+ children2->GetId());
+ ASSERT_VARIABLE(variables[2], "ParentKey", "ParentValue", "ParentType");
+
+ variables = variablesManager->HandleVariablesRequest(
+ CreateVariablesRequest(children1->GetId()));
+ ASSERT_TRUE(variables.size() == 2);
+ ASSERT_VARIABLE(variables[0], "ChildKey1", "ChildValue1", "ChildType1");
+ ASSERT_VARIABLE(variables[1], "ChildKey2", "ChildValue2", "ChildType2");
+
+ variables = variablesManager->HandleVariablesRequest(
+ CreateVariablesRequest(children2->GetId()));
+ ASSERT_TRUE(variables.size() == 1);
+ ASSERT_VARIABLE_REFERENCE(variables[0], "GrandChildren21",
+ "GrandChildren21 Value", "collection",
+ grandChildren21->GetId());
+
+ return true;
+}
+
+static bool testIgnoreEmptyStringEntries()
+{
+ auto variablesManager =
+ std::make_shared<cmDebugger::cmDebuggerVariablesManager>();
+
+ auto vars = std::make_shared<cmDebugger::cmDebuggerVariables>(
+ variablesManager, "Variables", true, []() {
+ return std::vector<cmDebugger::cmDebuggerVariableEntry>{
+ { "IntValue1", 5 }, { "StringValue1", "" },
+ { "StringValue2", "foo" }, { "StringValue3", "" },
+ { "StringValue4", "bar" }, { "StringValue5", "" },
+ { "IntValue2", int64_t(99) }, { "BooleanTrue", true },
+ { "BooleanFalse", false },
+ };
+ });
+
+ vars->SetIgnoreEmptyStringEntries(true);
+ vars->SetEnableSorting(false);
+
+ dap::array<dap::Variable> variables =
+ variablesManager->HandleVariablesRequest(
+ CreateVariablesRequest(vars->GetId()));
+ ASSERT_TRUE(variables.size() == 6);
+ ASSERT_VARIABLE(variables[0], "IntValue1", "5", "int");
+ ASSERT_VARIABLE(variables[1], "StringValue2", "foo", "string");
+ ASSERT_VARIABLE(variables[2], "StringValue4", "bar", "string");
+ ASSERT_VARIABLE(variables[3], "IntValue2", "99", "int");
+ ASSERT_VARIABLE(variables[4], "BooleanTrue", "TRUE", "bool");
+ ASSERT_VARIABLE(variables[5], "BooleanFalse", "FALSE", "bool");
+
+ return true;
+}
+
+static bool testSortTheResult()
+{
+ auto variablesManager =
+ std::make_shared<cmDebugger::cmDebuggerVariablesManager>();
+
+ auto vars = std::make_shared<cmDebugger::cmDebuggerVariables>(
+ variablesManager, "Variables", true, []() {
+ return std::vector<cmDebugger::cmDebuggerVariableEntry>{
+ { "4", "4" }, { "2", "2" }, { "1", "1" }, { "3", "3" }, { "5", "5" },
+ };
+ });
+
+ dap::array<dap::Variable> variables =
+ variablesManager->HandleVariablesRequest(
+ CreateVariablesRequest(vars->GetId()));
+ ASSERT_TRUE(variables.size() == 5);
+ ASSERT_VARIABLE(variables[0], "1", "1", "string");
+ ASSERT_VARIABLE(variables[1], "2", "2", "string");
+ ASSERT_VARIABLE(variables[2], "3", "3", "string");
+ ASSERT_VARIABLE(variables[3], "4", "4", "string");
+ ASSERT_VARIABLE(variables[4], "5", "5", "string");
+
+ vars->SetEnableSorting(false);
+
+ variables = variablesManager->HandleVariablesRequest(
+ CreateVariablesRequest(vars->GetId()));
+ ASSERT_TRUE(variables.size() == 5);
+ ASSERT_VARIABLE(variables[0], "4", "4", "string");
+ ASSERT_VARIABLE(variables[1], "2", "2", "string");
+ ASSERT_VARIABLE(variables[2], "1", "1", "string");
+ ASSERT_VARIABLE(variables[3], "3", "3", "string");
+ ASSERT_VARIABLE(variables[4], "5", "5", "string");
+
+ return true;
+}
+
+int testDebuggerVariables(int, char*[])
+{
+ return runTests(std::vector<std::function<bool()>>{
+ testUniqueIds,
+ testConstructors,
+ testIgnoreEmptyStringEntries,
+ testSortTheResult,
+ });
+}
diff --git a/Tests/CMakeLib/testDebuggerVariablesHelper.cxx b/Tests/CMakeLib/testDebuggerVariablesHelper.cxx
new file mode 100644
index 0000000..e0bbdf0
--- /dev/null
+++ b/Tests/CMakeLib/testDebuggerVariablesHelper.cxx
@@ -0,0 +1,587 @@
+/* Distributed under the OSI-approved BSD 3-Clause License. See accompanying
+ file Copyright.txt or https://cmake.org/licensing for details. */
+
+#include <functional>
+#include <memory>
+#include <set>
+#include <string>
+#include <utility>
+#include <vector>
+
+#include <cm3p/cppdap/protocol.h>
+#include <cm3p/cppdap/types.h>
+#include <stddef.h>
+#include <stdint.h>
+
+#include "cmDebuggerStackFrame.h"
+#include "cmDebuggerVariables.h"
+#include "cmDebuggerVariablesHelper.h"
+#include "cmDebuggerVariablesManager.h"
+#include "cmFileSet.h"
+#include "cmGlobalGenerator.h"
+#include "cmListFileCache.h"
+#include "cmMakefile.h"
+#include "cmPolicies.h"
+#include "cmPropertyMap.h"
+#include "cmState.h"
+#include "cmStateDirectory.h"
+#include "cmStateSnapshot.h"
+#include "cmStateTypes.h"
+#include "cmTarget.h"
+#include "cmTest.h"
+#include "cmake.h"
+
+#include "testCommon.h"
+#include "testDebugger.h"
+
+static dap::VariablesRequest CreateVariablesRequest(int64_t reference)
+{
+ dap::VariablesRequest variableRequest;
+ variableRequest.variablesReference = reference;
+ return variableRequest;
+}
+
+struct Dummies
+{
+ std::shared_ptr<cmake> CMake;
+ std::shared_ptr<cmMakefile> Makefile;
+ std::shared_ptr<cmGlobalGenerator> GlobalGenerator;
+};
+
+static Dummies CreateDummies(
+ std::string targetName,
+ std::string currentSourceDirectory = "c:/CurrentSourceDirectory",
+ std::string currentBinaryDirectory = "c:/CurrentBinaryDirectory")
+{
+ Dummies dummies;
+ dummies.CMake =
+ std::make_shared<cmake>(cmake::RoleProject, cmState::Project);
+ cmState* state = dummies.CMake->GetState();
+ dummies.GlobalGenerator =
+ std::make_shared<cmGlobalGenerator>(dummies.CMake.get());
+ cmStateSnapshot snapshot = state->CreateBaseSnapshot();
+ snapshot.GetDirectory().SetCurrentSource(currentSourceDirectory);
+ snapshot.GetDirectory().SetCurrentBinary(currentBinaryDirectory);
+ dummies.Makefile =
+ std::make_shared<cmMakefile>(dummies.GlobalGenerator.get(), snapshot);
+ dummies.Makefile->CreateNewTarget(targetName, cmStateEnums::EXECUTABLE);
+ return dummies;
+}
+
+static bool testCreateFromPolicyMap()
+{
+ auto variablesManager =
+ std::make_shared<cmDebugger::cmDebuggerVariablesManager>();
+
+ cmPolicies::PolicyMap policyMap;
+ policyMap.Set(cmPolicies::CMP0000, cmPolicies::NEW);
+ policyMap.Set(cmPolicies::CMP0003, cmPolicies::WARN);
+ policyMap.Set(cmPolicies::CMP0005, cmPolicies::OLD);
+ auto vars = cmDebugger::cmDebuggerVariablesHelper::Create(
+ variablesManager, "Locals", true, policyMap);
+
+ dap::array<dap::Variable> variables =
+ variablesManager->HandleVariablesRequest(
+ CreateVariablesRequest(vars->GetId()));
+ ASSERT_TRUE(variables.size() == 3);
+ ASSERT_VARIABLE(variables[0], "CMP0000", "NEW", "string");
+ ASSERT_VARIABLE(variables[1], "CMP0003", "WARN", "string");
+ ASSERT_VARIABLE(variables[2], "CMP0005", "OLD", "string");
+
+ return true;
+}
+
+static bool testCreateFromPairVector()
+{
+ auto variablesManager =
+ std::make_shared<cmDebugger::cmDebuggerVariablesManager>();
+
+ std::vector<std::pair<std::string, std::string>> pairs = {
+ { "Foo1", "Bar1" }, { "Foo2", "Bar2" }
+ };
+
+ auto vars = cmDebugger::cmDebuggerVariablesHelper::CreateIfAny(
+ variablesManager, "Locals", true, pairs);
+
+ dap::array<dap::Variable> variables =
+ variablesManager->HandleVariablesRequest(
+ CreateVariablesRequest(vars->GetId()));
+
+ ASSERT_TRUE(vars->GetValue() == std::to_string(pairs.size()));
+ ASSERT_TRUE(variables.size() == 2);
+ ASSERT_VARIABLE(variables[0], "Foo1", "Bar1", "string");
+ ASSERT_VARIABLE(variables[1], "Foo2", "Bar2", "string");
+
+ auto none = cmDebugger::cmDebuggerVariablesHelper::CreateIfAny(
+ variablesManager, "Locals", true,
+ std::vector<std::pair<std::string, std::string>>());
+
+ ASSERT_TRUE(none == nullptr);
+
+ return true;
+}
+
+static bool testCreateFromSet()
+{
+ auto variablesManager =
+ std::make_shared<cmDebugger::cmDebuggerVariablesManager>();
+
+ std::set<std::string> set = { "Foo", "Bar" };
+
+ auto vars = cmDebugger::cmDebuggerVariablesHelper::CreateIfAny(
+ variablesManager, "Locals", true, set);
+
+ dap::array<dap::Variable> variables =
+ variablesManager->HandleVariablesRequest(
+ CreateVariablesRequest(vars->GetId()));
+
+ ASSERT_TRUE(vars->GetValue() == std::to_string(set.size()));
+ ASSERT_TRUE(variables.size() == 2);
+ ASSERT_VARIABLE(variables[0], "[0]", "Bar", "string");
+ ASSERT_VARIABLE(variables[1], "[1]", "Foo", "string");
+
+ auto none = cmDebugger::cmDebuggerVariablesHelper::CreateIfAny(
+ variablesManager, "Locals", true, std::set<std::string>());
+
+ ASSERT_TRUE(none == nullptr);
+
+ return true;
+}
+
+static bool testCreateFromStringVector()
+{
+ auto variablesManager =
+ std::make_shared<cmDebugger::cmDebuggerVariablesManager>();
+
+ std::vector<std::string> list = { "Foo", "Bar" };
+
+ auto vars = cmDebugger::cmDebuggerVariablesHelper::CreateIfAny(
+ variablesManager, "Locals", true, list);
+
+ dap::array<dap::Variable> variables =
+ variablesManager->HandleVariablesRequest(
+ CreateVariablesRequest(vars->GetId()));
+
+ ASSERT_TRUE(vars->GetValue() == std::to_string(list.size()));
+ ASSERT_TRUE(variables.size() == 2);
+ ASSERT_VARIABLE(variables[0], "[0]", "Foo", "string");
+ ASSERT_VARIABLE(variables[1], "[1]", "Bar", "string");
+
+ auto none = cmDebugger::cmDebuggerVariablesHelper::CreateIfAny(
+ variablesManager, "Locals", true, std::vector<std::string>());
+
+ ASSERT_TRUE(none == nullptr);
+
+ return true;
+}
+
+static bool testCreateFromTarget()
+{
+ auto variablesManager =
+ std::make_shared<cmDebugger::cmDebuggerVariablesManager>();
+
+ auto dummies = CreateDummies("Foo");
+
+ auto vars = cmDebugger::cmDebuggerVariablesHelper::CreateIfAny(
+ variablesManager, "Locals", true, dummies.Makefile->GetOrderedTargets());
+
+ dap::array<dap::Variable> variables =
+ variablesManager->HandleVariablesRequest(
+ CreateVariablesRequest(vars->GetId()));
+
+ ASSERT_TRUE(variables.size() == 1);
+ ASSERT_VARIABLE(variables[0], "Foo", "EXECUTABLE", "collection");
+
+ variables = variablesManager->HandleVariablesRequest(
+ CreateVariablesRequest(variables[0].variablesReference));
+
+ ASSERT_TRUE(variables.size() == 15);
+ ASSERT_VARIABLE(variables[0], "GlobalGenerator", "Generic", "collection");
+ ASSERT_VARIABLE(variables[1], "IsAIX", "FALSE", "bool");
+ ASSERT_VARIABLE(variables[2], "IsAndroidGuiExecutable", "FALSE", "bool");
+ ASSERT_VARIABLE(variables[3], "IsAppBundleOnApple", "FALSE", "bool");
+ ASSERT_VARIABLE(variables[4], "IsDLLPlatform", "FALSE", "bool");
+ ASSERT_VARIABLE(variables[5], "IsExecutableWithExports", "FALSE", "bool");
+ ASSERT_VARIABLE(variables[6], "IsFrameworkOnApple", "FALSE", "bool");
+ ASSERT_VARIABLE(variables[7], "IsImported", "FALSE", "bool");
+ ASSERT_VARIABLE(variables[8], "IsImportedGloballyVisible", "FALSE", "bool");
+ ASSERT_VARIABLE(variables[9], "IsPerConfig", "TRUE", "bool");
+ ASSERT_VARIABLE(variables[10], "Makefile",
+ dummies.Makefile->GetDirectoryId().String, "collection");
+ ASSERT_VARIABLE(variables[11], "Name", "Foo", "string");
+ ASSERT_VARIABLE(variables[12], "PolicyMap", "", "collection");
+ ASSERT_VARIABLE(variables[13], "Properties",
+ std::to_string(dummies.Makefile->GetOrderedTargets()[0]
+ ->GetProperties()
+ .GetList()
+ .size()),
+ "collection");
+ ASSERT_VARIABLE(variables[14], "Type", "EXECUTABLE", "string");
+
+ auto none = cmDebugger::cmDebuggerVariablesHelper::CreateIfAny(
+ variablesManager, "Locals", true, std::vector<cmTarget*>());
+
+ ASSERT_TRUE(none == nullptr);
+
+ return true;
+}
+
+static bool testCreateFromGlobalGenerator()
+{
+ auto variablesManager =
+ std::make_shared<cmDebugger::cmDebuggerVariablesManager>();
+
+ auto dummies = CreateDummies("Foo");
+
+ auto vars = cmDebugger::cmDebuggerVariablesHelper::CreateIfAny(
+ variablesManager, "Locals", true, dummies.GlobalGenerator.get());
+
+ dap::array<dap::Variable> variables =
+ variablesManager->HandleVariablesRequest(
+ CreateVariablesRequest(vars->GetId()));
+
+ ASSERT_TRUE(variables.size() == 10);
+ ASSERT_VARIABLE(variables[0], "AllTargetName", "ALL_BUILD", "string");
+ ASSERT_VARIABLE(variables[1], "ForceUnixPaths", "FALSE", "bool");
+ ASSERT_VARIABLE(variables[2], "InstallTargetName", "INSTALL", "string");
+ ASSERT_VARIABLE(variables[3], "IsMultiConfig", "FALSE", "bool");
+ ASSERT_VARIABLE(variables[4], "MakefileEncoding", "None", "string");
+ ASSERT_VARIABLE(variables[5], "Name", "Generic", "string");
+ ASSERT_VARIABLE(variables[6], "NeedSymbolicMark", "FALSE", "bool");
+ ASSERT_VARIABLE(variables[7], "PackageTargetName", "PACKAGE", "string");
+ ASSERT_VARIABLE(variables[8], "TestTargetName", "RUN_TESTS", "string");
+ ASSERT_VARIABLE(variables[9], "UseLinkScript", "FALSE", "bool");
+
+ auto none = cmDebugger::cmDebuggerVariablesHelper::CreateIfAny(
+ variablesManager, "Locals", true,
+ static_cast<cmGlobalGenerator*>(nullptr));
+
+ ASSERT_TRUE(none == nullptr);
+
+ return true;
+}
+
+static bool testCreateFromTests()
+{
+ auto variablesManager =
+ std::make_shared<cmDebugger::cmDebuggerVariablesManager>();
+
+ auto dummies = CreateDummies("Foo");
+ cmTest test1 = cmTest(dummies.Makefile.get());
+ test1.SetName("Test1");
+ test1.SetOldStyle(false);
+ test1.SetCommandExpandLists(true);
+ test1.SetCommand(std::vector<std::string>{ "Foo1", "arg1" });
+ test1.SetProperty("Prop1", "Prop1");
+ cmTest test2 = cmTest(dummies.Makefile.get());
+ test2.SetName("Test2");
+ test2.SetOldStyle(false);
+ test2.SetCommandExpandLists(false);
+ test2.SetCommand(std::vector<std::string>{ "Bar1", "arg1", "arg2" });
+ test2.SetProperty("Prop2", "Prop2");
+ test2.SetProperty("Prop3", "Prop3");
+
+ std::vector<cmTest*> tests = { &test1, &test2 };
+
+ auto vars = cmDebugger::cmDebuggerVariablesHelper::CreateIfAny(
+ variablesManager, "Locals", true, tests);
+
+ dap::array<dap::Variable> variables =
+ variablesManager->HandleVariablesRequest(
+ CreateVariablesRequest(vars->GetId()));
+
+ ASSERT_TRUE(vars->GetValue() == std::to_string(tests.size()));
+ ASSERT_TRUE(variables.size() == 2);
+ ASSERT_VARIABLE_REFERENCE_NOT_ZERO(variables[0], test1.GetName(), "",
+ "collection");
+ ASSERT_VARIABLE_REFERENCE_NOT_ZERO(variables[1], test2.GetName(), "",
+ "collection");
+
+ dap::array<dap::Variable> testVariables =
+ variablesManager->HandleVariablesRequest(
+ CreateVariablesRequest(variables[0].variablesReference));
+ ASSERT_TRUE(testVariables.size() == 5);
+ ASSERT_VARIABLE_REFERENCE_NOT_ZERO(testVariables[0], "Command",
+ std::to_string(test1.GetCommand().size()),
+ "collection");
+ ASSERT_VARIABLE(testVariables[1], "CommandExpandLists",
+ BOOL_STRING(test1.GetCommandExpandLists()), "bool");
+ ASSERT_VARIABLE(testVariables[2], "Name", test1.GetName(), "string");
+ ASSERT_VARIABLE(testVariables[3], "OldStyle",
+ BOOL_STRING(test1.GetOldStyle()), "bool");
+ ASSERT_VARIABLE_REFERENCE_NOT_ZERO(testVariables[4], "Properties", "1",
+ "collection");
+
+ dap::array<dap::Variable> commandVariables =
+ variablesManager->HandleVariablesRequest(
+ CreateVariablesRequest(testVariables[0].variablesReference));
+ ASSERT_TRUE(commandVariables.size() == test1.GetCommand().size());
+ for (size_t i = 0; i < commandVariables.size(); ++i) {
+ ASSERT_VARIABLE(commandVariables[i], "[" + std::to_string(i) + "]",
+ test1.GetCommand()[i], "string");
+ }
+
+ dap::array<dap::Variable> propertiesVariables =
+ variablesManager->HandleVariablesRequest(
+ CreateVariablesRequest(testVariables[4].variablesReference));
+ ASSERT_TRUE(propertiesVariables.size() == 1);
+ ASSERT_VARIABLE(propertiesVariables[0], "Prop1", "Prop1", "string");
+
+ testVariables = variablesManager->HandleVariablesRequest(
+ CreateVariablesRequest(variables[1].variablesReference));
+ ASSERT_TRUE(testVariables.size() == 5);
+ ASSERT_VARIABLE_REFERENCE_NOT_ZERO(testVariables[0], "Command",
+ std::to_string(test2.GetCommand().size()),
+ "collection");
+ ASSERT_VARIABLE(testVariables[1], "CommandExpandLists",
+ BOOL_STRING(test2.GetCommandExpandLists()), "bool");
+ ASSERT_VARIABLE(testVariables[2], "Name", test2.GetName(), "string");
+ ASSERT_VARIABLE(testVariables[3], "OldStyle",
+ BOOL_STRING(test2.GetOldStyle()), "bool");
+ ASSERT_VARIABLE_REFERENCE_NOT_ZERO(testVariables[4], "Properties", "2",
+ "collection");
+
+ commandVariables = variablesManager->HandleVariablesRequest(
+ CreateVariablesRequest(testVariables[0].variablesReference));
+ ASSERT_TRUE(commandVariables.size() == test2.GetCommand().size());
+ for (size_t i = 0; i < commandVariables.size(); ++i) {
+ ASSERT_VARIABLE(commandVariables[i], "[" + std::to_string(i) + "]",
+ test2.GetCommand()[i], "string");
+ }
+
+ propertiesVariables = variablesManager->HandleVariablesRequest(
+ CreateVariablesRequest(testVariables[4].variablesReference));
+ ASSERT_TRUE(propertiesVariables.size() == 2);
+ ASSERT_VARIABLE(propertiesVariables[0], "Prop2", "Prop2", "string");
+ ASSERT_VARIABLE(propertiesVariables[1], "Prop3", "Prop3", "string");
+
+ auto none = cmDebugger::cmDebuggerVariablesHelper::CreateIfAny(
+ variablesManager, "Locals", true, std::vector<cmTest*>());
+
+ ASSERT_TRUE(none == nullptr);
+
+ return true;
+}
+
+static bool testCreateFromMakefile()
+{
+ auto variablesManager =
+ std::make_shared<cmDebugger::cmDebuggerVariablesManager>();
+
+ auto dummies = CreateDummies("Foo");
+ auto snapshot = dummies.Makefile->GetStateSnapshot();
+ auto state = dummies.Makefile->GetState();
+ state->SetSourceDirectory("c:/HomeDirectory");
+ state->SetBinaryDirectory("c:/HomeOutputDirectory");
+ auto vars = cmDebugger::cmDebuggerVariablesHelper::CreateIfAny(
+ variablesManager, "Locals", true, dummies.Makefile.get());
+
+ dap::array<dap::Variable> variables =
+ variablesManager->HandleVariablesRequest(
+ CreateVariablesRequest(vars->GetId()));
+
+ ASSERT_TRUE(variables.size() == 12);
+ ASSERT_VARIABLE(variables[0], "AppleSDKType", "MacOS", "string");
+ ASSERT_VARIABLE(variables[1], "CurrentBinaryDirectory",
+ snapshot.GetDirectory().GetCurrentBinary(), "string");
+ ASSERT_VARIABLE(variables[2], "CurrentSourceDirectory",
+ snapshot.GetDirectory().GetCurrentSource(), "string");
+ ASSERT_VARIABLE(variables[3], "DefineFlags", " ", "string");
+ ASSERT_VARIABLE(variables[4], "DirectoryId",
+ dummies.Makefile->GetDirectoryId().String, "string");
+ ASSERT_VARIABLE(variables[5], "HomeDirectory", state->GetSourceDirectory(),
+ "string");
+ ASSERT_VARIABLE(variables[6], "HomeOutputDirectory",
+ state->GetBinaryDirectory(), "string");
+ ASSERT_VARIABLE(variables[7], "IsRootMakefile", "TRUE", "bool");
+ ASSERT_VARIABLE(variables[8], "PlatformIs32Bit", "FALSE", "bool");
+ ASSERT_VARIABLE(variables[9], "PlatformIs64Bit", "FALSE", "bool");
+ ASSERT_VARIABLE(variables[10], "PlatformIsAppleEmbedded", "FALSE", "bool");
+ ASSERT_VARIABLE(variables[11], "PlatformIsx32", "FALSE", "bool");
+
+ auto none = cmDebugger::cmDebuggerVariablesHelper::CreateIfAny(
+ variablesManager, "Locals", true, static_cast<cmMakefile*>(nullptr));
+
+ ASSERT_TRUE(none == nullptr);
+
+ return true;
+}
+
+static bool testCreateFromStackFrame()
+{
+ auto variablesManager =
+ std::make_shared<cmDebugger::cmDebuggerVariablesManager>();
+ auto dummies = CreateDummies("Foo");
+
+ cmListFileFunction lff = cmListFileFunction("set", 99, 99, {});
+ auto frame = std::make_shared<cmDebugger::cmDebuggerStackFrame>(
+ dummies.Makefile.get(), "c:/CMakeLists.txt", lff);
+
+ dummies.CMake->AddCacheEntry("CMAKE_BUILD_TYPE", "Debug", "Build Type",
+ cmStateEnums::CacheEntryType::STRING);
+
+ auto locals = cmDebugger::cmDebuggerVariablesHelper::Create(
+ variablesManager, "Locals", true, frame);
+
+ dap::array<dap::Variable> variables =
+ variablesManager->HandleVariablesRequest(
+ CreateVariablesRequest(locals->GetId()));
+
+ ASSERT_TRUE(variables.size() == 5);
+ ASSERT_VARIABLE(variables[0], "CacheVariables", "1", "collection");
+ ASSERT_VARIABLE(variables[1], "CurrentLine", std::to_string(lff.Line()),
+ "int");
+ ASSERT_VARIABLE(variables[2], "Directories", "2", "collection");
+ ASSERT_VARIABLE(variables[3], "Locals", "2", "collection");
+ ASSERT_VARIABLE(variables[4], "Targets", "1", "collection");
+
+ dap::array<dap::Variable> cacheVariables =
+ variablesManager->HandleVariablesRequest(
+ CreateVariablesRequest(variables[0].variablesReference));
+ ASSERT_TRUE(cacheVariables.size() == 1);
+ ASSERT_VARIABLE(cacheVariables[0], "CMAKE_BUILD_TYPE:STRING", "Debug",
+ "collection");
+
+ dap::array<dap::Variable> directoriesVariables =
+ variablesManager->HandleVariablesRequest(
+ CreateVariablesRequest(variables[2].variablesReference));
+ ASSERT_TRUE(directoriesVariables.size() == 2);
+ ASSERT_VARIABLE(
+ directoriesVariables[0], "CMAKE_CURRENT_BINARY_DIR",
+ dummies.Makefile->GetStateSnapshot().GetDirectory().GetCurrentBinary(),
+ "string");
+ ASSERT_VARIABLE(
+ directoriesVariables[1], "CMAKE_CURRENT_SOURCE_DIR",
+ dummies.Makefile->GetStateSnapshot().GetDirectory().GetCurrentSource(),
+ "string");
+
+ dap::array<dap::Variable> propertiesVariables =
+ variablesManager->HandleVariablesRequest(
+ CreateVariablesRequest(cacheVariables[0].variablesReference));
+ ASSERT_TRUE(propertiesVariables.size() == 3);
+ ASSERT_VARIABLE(propertiesVariables[0], "HELPSTRING", "Build Type",
+ "string");
+ ASSERT_VARIABLE(propertiesVariables[1], "TYPE", "STRING", "string");
+ ASSERT_VARIABLE(propertiesVariables[2], "VALUE", "Debug", "string");
+
+ return true;
+}
+
+static bool testCreateFromBTStringVector()
+{
+ auto variablesManager =
+ std::make_shared<cmDebugger::cmDebuggerVariablesManager>();
+
+ std::vector<BT<std::string>> list(2);
+ list[0].Value = "Foo";
+ list[1].Value = "Bar";
+
+ auto vars = cmDebugger::cmDebuggerVariablesHelper::CreateIfAny(
+ variablesManager, "Locals", true, list);
+
+ dap::array<dap::Variable> variables =
+ variablesManager->HandleVariablesRequest(
+ CreateVariablesRequest(vars->GetId()));
+
+ ASSERT_TRUE(vars->GetValue() == std::to_string(list.size()));
+ ASSERT_TRUE(variables.size() == 2);
+ ASSERT_VARIABLE(variables[0], "[0]", "Foo", "string");
+ ASSERT_VARIABLE(variables[1], "[1]", "Bar", "string");
+
+ auto none = cmDebugger::cmDebuggerVariablesHelper::CreateIfAny(
+ variablesManager, "Locals", true, std::vector<std::string>());
+
+ ASSERT_TRUE(none == nullptr);
+
+ return true;
+}
+
+static bool testCreateFromFileSet()
+{
+ auto variablesManager =
+ std::make_shared<cmDebugger::cmDebuggerVariablesManager>();
+
+ cmake cm(cmake::RoleScript, cmState::Unknown);
+ cmFileSet fileSet(cm, "Foo", "HEADERS", cmFileSetVisibility::Public);
+ BT<std::string> directory;
+ directory.Value = "c:/";
+ fileSet.AddDirectoryEntry(directory);
+ BT<std::string> file;
+ file.Value = "c:/foo.cxx";
+ fileSet.AddFileEntry(file);
+
+ auto vars = cmDebugger::cmDebuggerVariablesHelper::CreateIfAny(
+ variablesManager, "Locals", true, &fileSet);
+
+ dap::array<dap::Variable> variables =
+ variablesManager->HandleVariablesRequest(
+ CreateVariablesRequest(vars->GetId()));
+
+ ASSERT_TRUE(variables.size() == 5);
+ ASSERT_VARIABLE_REFERENCE_NOT_ZERO(variables[0], "Directories", "1",
+ "collection");
+ ASSERT_VARIABLE_REFERENCE_NOT_ZERO(variables[1], "Files", "1", "collection");
+ ASSERT_VARIABLE(variables[2], "Name", "Foo", "string");
+ ASSERT_VARIABLE(variables[3], "Type", "HEADERS", "string");
+ ASSERT_VARIABLE(variables[4], "Visibility", "Public", "string");
+
+ dap::array<dap::Variable> directoriesVariables =
+ variablesManager->HandleVariablesRequest(
+ CreateVariablesRequest(variables[0].variablesReference));
+ ASSERT_TRUE(directoriesVariables.size() == 1);
+ ASSERT_VARIABLE(directoriesVariables[0], "[0]", directory.Value, "string");
+
+ dap::array<dap::Variable> filesVariables =
+ variablesManager->HandleVariablesRequest(
+ CreateVariablesRequest(variables[1].variablesReference));
+ ASSERT_TRUE(filesVariables.size() == 1);
+ ASSERT_VARIABLE(filesVariables[0], "[0]", file.Value, "string");
+
+ return true;
+}
+
+static bool testCreateFromFileSets()
+{
+ auto variablesManager =
+ std::make_shared<cmDebugger::cmDebuggerVariablesManager>();
+
+ cmake cm(cmake::RoleScript, cmState::Unknown);
+ cmFileSet fileSet(cm, "Foo", "HEADERS", cmFileSetVisibility::Public);
+ BT<std::string> directory;
+ directory.Value = "c:/";
+ fileSet.AddDirectoryEntry(directory);
+ BT<std::string> file;
+ file.Value = "c:/foo.cxx";
+ fileSet.AddFileEntry(file);
+
+ auto fileSets = std::vector<cmFileSet*>{ &fileSet };
+ auto vars = cmDebugger::cmDebuggerVariablesHelper::CreateIfAny(
+ variablesManager, "Locals", true, fileSets);
+
+ dap::array<dap::Variable> variables =
+ variablesManager->HandleVariablesRequest(
+ CreateVariablesRequest(vars->GetId()));
+
+ ASSERT_TRUE(variables.size() == 1);
+ ASSERT_VARIABLE_REFERENCE_NOT_ZERO(variables[0], "Foo", "", "collection");
+
+ return true;
+}
+
+int testDebuggerVariablesHelper(int, char*[])
+{
+ return runTests(std::vector<std::function<bool()>>{
+ testCreateFromPolicyMap,
+ testCreateFromPairVector,
+ testCreateFromSet,
+ testCreateFromStringVector,
+ testCreateFromTarget,
+ testCreateFromGlobalGenerator,
+ testCreateFromMakefile,
+ testCreateFromStackFrame,
+ testCreateFromTests,
+ testCreateFromBTStringVector,
+ testCreateFromFileSet,
+ testCreateFromFileSets,
+ });
+}
diff --git a/Tests/CMakeLib/testDebuggerVariablesManager.cxx b/Tests/CMakeLib/testDebuggerVariablesManager.cxx
new file mode 100644
index 0000000..3013b9f
--- /dev/null
+++ b/Tests/CMakeLib/testDebuggerVariablesManager.cxx
@@ -0,0 +1,50 @@
+/* Distributed under the OSI-approved BSD 3-Clause License. See accompanying
+ file Copyright.txt or https://cmake.org/licensing for details. */
+
+#include <functional>
+#include <memory>
+#include <vector>
+
+#include <cm3p/cppdap/protocol.h>
+#include <cm3p/cppdap/types.h>
+#include <stdint.h>
+
+#include "cmDebuggerVariables.h"
+#include "cmDebuggerVariablesManager.h"
+
+#include "testCommon.h"
+
+static bool testVariablesRegistration()
+{
+ auto variablesManager =
+ std::make_shared<cmDebugger::cmDebuggerVariablesManager>();
+
+ int64_t line = 5;
+ auto local = std::make_shared<cmDebugger::cmDebuggerVariables>(
+ variablesManager, "Local", true, [=]() {
+ return std::vector<cmDebugger::cmDebuggerVariableEntry>{ { "CurrentLine",
+ line } };
+ });
+
+ dap::VariablesRequest variableRequest;
+ variableRequest.variablesReference = local->GetId();
+
+ dap::array<dap::Variable> variables =
+ variablesManager->HandleVariablesRequest(variableRequest);
+
+ ASSERT_TRUE(variables.size() == 1);
+
+ local.reset();
+
+ variables = variablesManager->HandleVariablesRequest(variableRequest);
+ ASSERT_TRUE(variables.size() == 0);
+
+ return true;
+}
+
+int testDebuggerVariablesManager(int, char*[])
+{
+ return runTests(std::vector<std::function<bool()>>{
+ testVariablesRegistration,
+ });
+}
diff --git a/Tests/RunCMake/CommandLine/DebuggerArgMissingDapLog-result.txt b/Tests/RunCMake/CommandLine/DebuggerArgMissingDapLog-result.txt
new file mode 100644
index 0000000..d00491f
--- /dev/null
+++ b/Tests/RunCMake/CommandLine/DebuggerArgMissingDapLog-result.txt
@@ -0,0 +1 @@
+1
diff --git a/Tests/RunCMake/CommandLine/DebuggerArgMissingDapLog-stderr.txt b/Tests/RunCMake/CommandLine/DebuggerArgMissingDapLog-stderr.txt
new file mode 100644
index 0000000..6269c19
--- /dev/null
+++ b/Tests/RunCMake/CommandLine/DebuggerArgMissingDapLog-stderr.txt
@@ -0,0 +1,2 @@
+^CMake Error: No file specified for --debugger-dap-log
+CMake Error: Run 'cmake --help' for all supported options\.$
diff --git a/Tests/RunCMake/CommandLine/DebuggerArgMissingDapLog.cmake b/Tests/RunCMake/CommandLine/DebuggerArgMissingDapLog.cmake
new file mode 100644
index 0000000..6ddce8b
--- /dev/null
+++ b/Tests/RunCMake/CommandLine/DebuggerArgMissingDapLog.cmake
@@ -0,0 +1 @@
+message(FATAL_ERROR "This should not be reached.")
diff --git a/Tests/RunCMake/CommandLine/DebuggerArgMissingPipe-result.txt b/Tests/RunCMake/CommandLine/DebuggerArgMissingPipe-result.txt
new file mode 100644
index 0000000..d00491f
--- /dev/null
+++ b/Tests/RunCMake/CommandLine/DebuggerArgMissingPipe-result.txt
@@ -0,0 +1 @@
+1
diff --git a/Tests/RunCMake/CommandLine/DebuggerArgMissingPipe-stderr.txt b/Tests/RunCMake/CommandLine/DebuggerArgMissingPipe-stderr.txt
new file mode 100644
index 0000000..947cb00
--- /dev/null
+++ b/Tests/RunCMake/CommandLine/DebuggerArgMissingPipe-stderr.txt
@@ -0,0 +1,2 @@
+^CMake Error: No path specified for --debugger-pipe
+CMake Error: Run 'cmake --help' for all supported options\.$
diff --git a/Tests/RunCMake/CommandLine/DebuggerArgMissingPipe.cmake b/Tests/RunCMake/CommandLine/DebuggerArgMissingPipe.cmake
new file mode 100644
index 0000000..6ddce8b
--- /dev/null
+++ b/Tests/RunCMake/CommandLine/DebuggerArgMissingPipe.cmake
@@ -0,0 +1 @@
+message(FATAL_ERROR "This should not be reached.")
diff --git a/Tests/RunCMake/CommandLine/DebuggerCapabilityInspect-check.cmake b/Tests/RunCMake/CommandLine/DebuggerCapabilityInspect-check.cmake
new file mode 100644
index 0000000..75769f2
--- /dev/null
+++ b/Tests/RunCMake/CommandLine/DebuggerCapabilityInspect-check.cmake
@@ -0,0 +1,5 @@
+if(actual_stdout MATCHES [["debugger" *: *true]])
+ set_property(DIRECTORY PROPERTY CMake_ENABLE_DEBUGGER 1)
+else()
+ set_property(DIRECTORY PROPERTY CMake_ENABLE_DEBUGGER 0)
+endif()
diff --git a/Tests/RunCMake/CommandLine/DebuggerNotSupported-result.txt b/Tests/RunCMake/CommandLine/DebuggerNotSupported-result.txt
new file mode 100644
index 0000000..d00491f
--- /dev/null
+++ b/Tests/RunCMake/CommandLine/DebuggerNotSupported-result.txt
@@ -0,0 +1 @@
+1
diff --git a/Tests/RunCMake/CommandLine/DebuggerNotSupported-stderr.txt b/Tests/RunCMake/CommandLine/DebuggerNotSupported-stderr.txt
new file mode 100644
index 0000000..5845bb3
--- /dev/null
+++ b/Tests/RunCMake/CommandLine/DebuggerNotSupported-stderr.txt
@@ -0,0 +1,2 @@
+^CMake Error: CMake was not built with support for --debugger
+CMake Error: Run 'cmake --help' for all supported options\.$
diff --git a/Tests/RunCMake/CommandLine/DebuggerNotSupported.cmake b/Tests/RunCMake/CommandLine/DebuggerNotSupported.cmake
new file mode 100644
index 0000000..6ddce8b
--- /dev/null
+++ b/Tests/RunCMake/CommandLine/DebuggerNotSupported.cmake
@@ -0,0 +1 @@
+message(FATAL_ERROR "This should not be reached.")
diff --git a/Tests/RunCMake/CommandLine/DebuggerNotSupportedDapLog-result.txt b/Tests/RunCMake/CommandLine/DebuggerNotSupportedDapLog-result.txt
new file mode 100644
index 0000000..d00491f
--- /dev/null
+++ b/Tests/RunCMake/CommandLine/DebuggerNotSupportedDapLog-result.txt
@@ -0,0 +1 @@
+1
diff --git a/Tests/RunCMake/CommandLine/DebuggerNotSupportedDapLog-stderr.txt b/Tests/RunCMake/CommandLine/DebuggerNotSupportedDapLog-stderr.txt
new file mode 100644
index 0000000..84c2200
--- /dev/null
+++ b/Tests/RunCMake/CommandLine/DebuggerNotSupportedDapLog-stderr.txt
@@ -0,0 +1,2 @@
+^CMake Error: CMake was not built with support for --debugger-dap-log
+CMake Error: Run 'cmake --help' for all supported options\.$
diff --git a/Tests/RunCMake/CommandLine/DebuggerNotSupportedDapLog.cmake b/Tests/RunCMake/CommandLine/DebuggerNotSupportedDapLog.cmake
new file mode 100644
index 0000000..6ddce8b
--- /dev/null
+++ b/Tests/RunCMake/CommandLine/DebuggerNotSupportedDapLog.cmake
@@ -0,0 +1 @@
+message(FATAL_ERROR "This should not be reached.")
diff --git a/Tests/RunCMake/CommandLine/DebuggerNotSupportedPipe-result.txt b/Tests/RunCMake/CommandLine/DebuggerNotSupportedPipe-result.txt
new file mode 100644
index 0000000..d00491f
--- /dev/null
+++ b/Tests/RunCMake/CommandLine/DebuggerNotSupportedPipe-result.txt
@@ -0,0 +1 @@
+1
diff --git a/Tests/RunCMake/CommandLine/DebuggerNotSupportedPipe-stderr.txt b/Tests/RunCMake/CommandLine/DebuggerNotSupportedPipe-stderr.txt
new file mode 100644
index 0000000..5684f4c
--- /dev/null
+++ b/Tests/RunCMake/CommandLine/DebuggerNotSupportedPipe-stderr.txt
@@ -0,0 +1,2 @@
+^CMake Error: CMake was not built with support for --debugger-pipe
+CMake Error: Run 'cmake --help' for all supported options\.$
diff --git a/Tests/RunCMake/CommandLine/DebuggerNotSupportedPipe.cmake b/Tests/RunCMake/CommandLine/DebuggerNotSupportedPipe.cmake
new file mode 100644
index 0000000..6ddce8b
--- /dev/null
+++ b/Tests/RunCMake/CommandLine/DebuggerNotSupportedPipe.cmake
@@ -0,0 +1 @@
+message(FATAL_ERROR "This should not be reached.")
diff --git a/Tests/RunCMake/CommandLine/E_capabilities-stdout.txt b/Tests/RunCMake/CommandLine/E_capabilities-stdout.txt
index e2f63cd..c01f414 100644
--- a/Tests/RunCMake/CommandLine/E_capabilities-stdout.txt
+++ b/Tests/RunCMake/CommandLine/E_capabilities-stdout.txt
@@ -1 +1 @@
-^{"fileApi":{"requests":\[{"kind":"codemodel","version":\[{"major":2,"minor":6}]},{"kind":"configureLog","version":\[{"major":1,"minor":0}]},{"kind":"cache","version":\[{"major":2,"minor":0}]},{"kind":"cmakeFiles","version":\[{"major":1,"minor":0}]},{"kind":"toolchains","version":\[{"major":1,"minor":0}]}]},"generators":\[.*\],"serverMode":false,"tls":(true|false),"version":{.*}}$
+^{"debugger":(true|false),"fileApi":{"requests":\[{"kind":"codemodel","version":\[{"major":2,"minor":6}]},{"kind":"configureLog","version":\[{"major":1,"minor":0}]},{"kind":"cache","version":\[{"major":2,"minor":0}]},{"kind":"cmakeFiles","version":\[{"major":1,"minor":0}]},{"kind":"toolchains","version":\[{"major":1,"minor":0}]}]},"generators":\[.*\],"serverMode":false,"tls":(true|false),"version":{.*}}$
diff --git a/Tests/RunCMake/CommandLine/RunCMakeTest.cmake b/Tests/RunCMake/CommandLine/RunCMakeTest.cmake
index 205949b..45b4c0e 100644
--- a/Tests/RunCMake/CommandLine/RunCMakeTest.cmake
+++ b/Tests/RunCMake/CommandLine/RunCMakeTest.cmake
@@ -125,6 +125,17 @@ run_cmake_command(cache-bad-entry
run_cmake_command(cache-empty-entry
${CMAKE_COMMAND} --build ${RunCMake_SOURCE_DIR}/cache-empty-entry/)
+run_cmake_command(DebuggerCapabilityInspect ${CMAKE_COMMAND} -E capabilities)
+get_property(CMake_ENABLE_DEBUGGER DIRECTORY PROPERTY CMake_ENABLE_DEBUGGER)
+if(CMake_ENABLE_DEBUGGER)
+ run_cmake_with_options(DebuggerArgMissingPipe --debugger-pipe)
+ run_cmake_with_options(DebuggerArgMissingDapLog --debugger-dap-log)
+else()
+ run_cmake_with_options(DebuggerNotSupported --debugger)
+ run_cmake_with_options(DebuggerNotSupportedPipe --debugger-pipe pipe)
+ run_cmake_with_options(DebuggerNotSupportedDapLog --debugger-dap-log dap-log)
+endif()
+
function(run_ExplicitDirs)
set(RunCMake_TEST_NO_CLEAN 1)
set(RunCMake_TEST_NO_SOURCE_DIR 1)