| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201 | /* 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 runTest(std::function<bool(dap::Session&)> onThreadExitedEvent){  std::promise<bool> debuggerAdapterInitializedPromise;  std::future<bool> debuggerAdapterInitializedFuture =    debuggerAdapterInitializedPromise.get_future();  std::promise<bool> initializedEventReceivedPromise;  std::future<bool> initializedEventReceivedFuture =    initializedEventReceivedPromise.get_future();  std::promise<bool> exitedEventReceivedPromise;  std::future<bool> exitedEventReceivedFuture =    exitedEventReceivedPromise.get_future();  std::promise<bool> terminatedEventReceivedPromise;  std::future<bool> terminatedEventReceivedFuture =    terminatedEventReceivedPromise.get_future();  std::promise<bool> threadStartedPromise;  std::future<bool> threadStartedFuture = threadStartedPromise.get_future();  std::promise<bool> threadExitedPromise;  std::future<bool> threadExitedFuture = threadExitedPromise.get_future();  std::promise<bool> disconnectResponseReceivedPromise;  std::future<bool> disconnectResponseReceivedFuture =    disconnectResponseReceivedPromise.get_future();  auto futureTimeout = std::chrono::seconds(60);  auto connection = std::make_shared<DebuggerLocalConnection>();  std::unique_ptr<dap::Session> client = dap::Session::create();  client->registerHandler([&](const dap::InitializedEvent& e) {    (void)e;    initializedEventReceivedPromise.set_value(true);  });  client->registerHandler([&](const dap::ExitedEvent& e) {    (void)e;    exitedEventReceivedPromise.set_value(true);  });  client->registerHandler([&](const dap::TerminatedEvent& e) {    (void)e;    terminatedEventReceivedPromise.set_value(true);  });  client->registerHandler([&](const dap::ThreadEvent& e) {    if (e.reason == "started") {      threadStartedPromise.set_value(true);    } else if (e.reason == "exited") {      threadExitedPromise.set_value(true);    }  });  client->bind(connection->DebuggerToClient, connection->ClientToDebugger);  ScopedThread debuggerThread([&]() -> int {    std::shared_ptr<cmDebugger::cmDebuggerAdapter> debuggerAdapter =      std::make_shared<cmDebugger::cmDebuggerAdapter>(        connection, dap::file(stdout, false));    debuggerAdapterInitializedPromise.set_value(true);    debuggerAdapter->ReportExitCode(0);    // Ensure the disconnectResponse has been received before    // destructing debuggerAdapter.    ASSERT_TRUE(disconnectResponseReceivedFuture.wait_for(futureTimeout) ==                std::future_status::ready);    return 0;  });  dap::CMakeInitializeRequest initializeRequest;  auto initializeResponse = client->send(initializeRequest).get();  ASSERT_TRUE(initializeResponse.response.cmakeVersion.full == CMake_VERSION);  ASSERT_TRUE(initializeResponse.response.cmakeVersion.major ==              CMake_VERSION_MAJOR);  ASSERT_TRUE(initializeResponse.response.cmakeVersion.minor ==              CMake_VERSION_MINOR);  ASSERT_TRUE(initializeResponse.response.cmakeVersion.patch ==              CMake_VERSION_PATCH);  ASSERT_TRUE(initializeResponse.response.supportsExceptionInfoRequest);  ASSERT_TRUE(    initializeResponse.response.exceptionBreakpointFilters.has_value());  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(std::vector<std::function<bool()>>{    testBasicProtocol,    testThreadsRequestAfterThreadExitedEvent,  });}
 |