/* Distributed under the OSI-approved BSD 3-Clause License. See accompanying file Copyright.txt or https://cmake.org/licensing for details. */ #include #include #include #include #include #include #include #include #include #include #include #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 GetReader() override { return ClientToDebugger; }; std::shared_ptr GetWriter() override { return DebuggerToClient; } std::shared_ptr ClientToDebugger; std::shared_ptr DebuggerToClient; }; bool runTest(std::function onThreadExitedEvent) { std::promise debuggerAdapterInitializedPromise; std::future debuggerAdapterInitializedFuture = debuggerAdapterInitializedPromise.get_future(); std::promise initializedEventReceivedPromise; std::future initializedEventReceivedFuture = initializedEventReceivedPromise.get_future(); std::promise exitedEventReceivedPromise; std::future exitedEventReceivedFuture = exitedEventReceivedPromise.get_future(); std::promise terminatedEventReceivedPromise; std::future terminatedEventReceivedFuture = terminatedEventReceivedPromise.get_future(); std::promise threadStartedPromise; std::future threadStartedFuture = threadStartedPromise.get_future(); std::promise threadExitedPromise; std::future threadExitedFuture = threadExitedPromise.get_future(); std::promise disconnectResponseReceivedPromise; std::future disconnectResponseReceivedFuture = disconnectResponseReceivedPromise.get_future(); auto futureTimeout = std::chrono::seconds(60); auto connection = std::make_shared(); std::unique_ptr 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 debuggerAdapter = std::make_shared( 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( { testBasicProtocol, testThreadsRequestAfterThreadExitedEvent }); }