diff options
author | Glen Chung <kuchung@microsoft.com> | 2023-03-16 00:50:08 (GMT) |
---|---|---|
committer | Brad King <brad.king@kitware.com> | 2023-05-30 13:46:12 (GMT) |
commit | a9a592f96e6498da302f8e968be1db0ad3c32123 (patch) | |
tree | 0d75f16ee2eae99b1a3f063e575b3f5f8f2ee931 /Tests | |
parent | b0d1ddb7234950374977b83f8dbded806c15b356 (diff) | |
download | CMake-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')
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) |