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