Repository: llvm/llvm-project
Pull request #139937: [lldb-dap] Setup DAP for unit testing.
Author: login: ashgti
Labels: name: lldb
name: lldb-dap
Created: 2025-05-14T17:33:50Z | Merged: 2025-05-15T17:45:17Z

This is a very simple case that currently only validates we can create a DAP instance and send a message over the transport layer. More in-depth tests will require additional helpers and possibly refactors of DAP to make it more testable, however this is some ground work to have basic support for unit tests.


Diff:
diff --git a/lldb/tools/lldb-dap/DAP.h b/lldb/tools/lldb-dap/DAP.h
index c2e4c2dea582e..2ff66d1cd0182 100644
--- a/lldb/tools/lldb-dap/DAP.h
+++ b/lldb/tools/lldb-dap/DAP.h
@@ -226,7 +226,8 @@ struct DAP {
   /// \param[in] default_repl_mode
   ///     Default repl mode behavior, as configured by the binary.
   /// \param[in] pre_init_commands
-  ///     LLDB commands to execute as soon as the debugger instance is allocaed.
+  ///     LLDB commands to execute as soon as the debugger instance is
+  ///     allocated.
   /// \param[in] transport
   ///     Transport for this debug session.
   DAP(Log *log, const ReplMode default_repl_mode,
diff --git a/lldb/unittests/DAP/CMakeLists.txt b/lldb/unittests/DAP/CMakeLists.txt
index 110733e93b192..af7d11e2e95e2 100644
--- a/lldb/unittests/DAP/CMakeLists.txt
+++ b/lldb/unittests/DAP/CMakeLists.txt
@@ -1,8 +1,11 @@
 add_lldb_unittest(DAPTests
+  DAPTest.cpp
+  Handler/DisconnectTest.cpp
   JSONUtilsTest.cpp
   LLDBUtilsTest.cpp
-  TransportTest.cpp
   ProtocolTypesTest.cpp
+  TestBase.cpp
+  TransportTest.cpp
 
   LINK_LIBS
     lldbDAP
diff --git a/lldb/unittests/DAP/DAPTest.cpp b/lldb/unittests/DAP/DAPTest.cpp
new file mode 100644
index 0000000000000..5fb6bf7e564ab
--- /dev/null
+++ b/lldb/unittests/DAP/DAPTest.cpp
@@ -0,0 +1,38 @@
+//===-- DAPTest.cpp -------------------------------------------------------===//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+
+#include "DAP.h"
+#include "Protocol/ProtocolBase.h"
+#include "TestBase.h"
+#include "Transport.h"
+#include "llvm/Testing/Support/Error.h"
+#include "gtest/gtest.h"
+#include <chrono>
+#include <memory>
+#include <optional>
+
+using namespace llvm;
+using namespace lldb;
+using namespace lldb_dap;
+using namespace lldb_dap_tests;
+using namespace lldb_dap::protocol;
+
+class DAPTest : public TransportBase {};
+
+TEST_F(DAPTest, SendProtocolMessages) {
+  DAP dap{
+      /*log=*/nullptr,
+      /*default_repl_mode=*/ReplMode::Auto,
+      /*pre_init_commands=*/{},
+      /*transport=*/*to_dap,
+  };
+  dap.Send(Event{/*event=*/"my-event", /*body=*/std::nullopt});
+  ASSERT_THAT_EXPECTED(from_dap->Read(std::chrono::milliseconds(1)),
+                       HasValue(testing::VariantWith<Event>(testing::FieldsAre(
+                           /*event=*/"my-event", /*body=*/std::nullopt))));
+}
diff --git a/lldb/unittests/DAP/Handler/DisconnectTest.cpp b/lldb/unittests/DAP/Handler/DisconnectTest.cpp
new file mode 100644
index 0000000000000..6f3470239e974
--- /dev/null
+++ b/lldb/unittests/DAP/Handler/DisconnectTest.cpp
@@ -0,0 +1,35 @@
+//===-- DisconnectTest.cpp ------------------------------------------------===//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+
+#include "DAP.h"
+#include "Handler/RequestHandler.h"
+#include "Protocol/ProtocolBase.h"
+#include "TestBase.h"
+#include "llvm/Testing/Support/Error.h"
+#include "gtest/gtest.h"
+#include <memory>
+#include <optional>
+
+using namespace llvm;
+using namespace lldb;
+using namespace lldb_dap;
+using namespace lldb_dap_tests;
+using namespace lldb_dap::protocol;
+
+class DisconnectRequestHandlerTest : public DAPTestBase {};
+
+TEST_F(DisconnectRequestHandlerTest, DisconnectingTriggersTerminated) {
+  DisconnectRequestHandler handler(*dap);
+  EXPECT_FALSE(dap->disconnecting);
+  ASSERT_THAT_ERROR(handler.Run(std::nullopt), Succeeded());
+  EXPECT_TRUE(dap->disconnecting);
+  std::vector<Message> messages = DrainOutput();
+  EXPECT_THAT(messages,
+              testing::Contains(testing::VariantWith<Event>(testing::FieldsAre(
+                  /*event=*/"terminated", /*body=*/std::nullopt))));
+}
diff --git a/lldb/unittests/DAP/TestBase.cpp b/lldb/unittests/DAP/TestBase.cpp
new file mode 100644
index 0000000000000..eb146cb2fa9f4
--- /dev/null
+++ b/lldb/unittests/DAP/TestBase.cpp
@@ -0,0 +1,70 @@
+//===-- TestBase.cpp ------------------------------------------------------===//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+
+#include "TestBase.h"
+#include "Protocol/ProtocolBase.h"
+#include "lldb/Host/File.h"
+#include "lldb/Host/Pipe.h"
+#include "llvm/Testing/Support/Error.h"
+
+using namespace llvm;
+using namespace lldb;
+using namespace lldb_dap;
+using namespace lldb_dap::protocol;
+using namespace lldb_dap_tests;
+using lldb_private::File;
+using lldb_private::NativeFile;
+using lldb_private::Pipe;
+
+void PipeBase::SetUp() {
+  ASSERT_THAT_ERROR(input.CreateNew(false).ToError(), Succeeded());
+  ASSERT_THAT_ERROR(output.CreateNew(false).ToError(), Succeeded());
+}
+
+void TransportBase::SetUp() {
+  PipeBase::SetUp();
+  to_dap = std::make_unique<Transport>(
+      "to_dap", nullptr,
+      std::make_shared<NativeFile>(input.GetReadFileDescriptor(),
+                                   File::eOpenOptionReadOnly,
+                                   NativeFile::Unowned),
+      std::make_shared<NativeFile>(output.GetWriteFileDescriptor(),
+                                   File::eOpenOptionWriteOnly,
+                                   NativeFile::Unowned));
+  from_dap = std::make_unique<Transport>(
+      "from_dap", nullptr,
+      std::make_shared<NativeFile>(output.GetReadFileDescriptor(),
+                                   File::eOpenOptionReadOnly,
+                                   NativeFile::Unowned),
+      std::make_shared<NativeFile>(input.GetWriteFileDescriptor(),
+                                   File::eOpenOptionWriteOnly,
+                                   NativeFile::Unowned));
+}
+
+void DAPTestBase::SetUp() {
+  TransportBase::SetUp();
+  dap = std::make_unique<DAP>(
+      /*log=*/nullptr,
+      /*default_repl_mode=*/ReplMode::Auto,
+      /*pre_init_commands=*/std::vector<std::string>(),
+      /*transport=*/*to_dap);
+}
+
+std::vector<Message> DAPTestBase::DrainOutput() {
+  std::vector<Message> msgs;
+  output.CloseWriteFileDescriptor();
+  while (true) {
+    Expected<Message> next = from_dap->Read(std::chrono::milliseconds(1));
+    if (!next) {
+      consumeError(next.takeError());
+      break;
+    }
+    msgs.push_back(*next);
+  }
+  return msgs;
+}
diff --git a/lldb/unittests/DAP/TestBase.h b/lldb/unittests/DAP/TestBase.h
new file mode 100644
index 0000000000000..c789adf53c225
--- /dev/null
+++ b/lldb/unittests/DAP/TestBase.h
@@ -0,0 +1,48 @@
+//===-- TestBase.cpp ------------------------------------------------------===//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+
+#include "DAP.h"
+#include "Protocol/ProtocolBase.h"
+#include "Transport.h"
+#include "lldb/Host/Pipe.h"
+#include "gtest/gtest.h"
+
+namespace lldb_dap_tests {
+
+/// A base class for tests that need a pair of pipes for communication.
+class PipeBase : public testing::Test {
+protected:
+  lldb_private::Pipe input;
+  lldb_private::Pipe output;
+
+  void SetUp() override;
+};
+
+/// A base class for tests that need transport configured for communicating DAP
+/// messages.
+class TransportBase : public PipeBase {
+protected:
+  std::unique_ptr<lldb_dap::Transport> to_dap;
+  std::unique_ptr<lldb_dap::Transport> from_dap;
+
+  void SetUp() override;
+};
+
+/// A base class for tests that interact with a `lldb_dap::DAP` instance.
+class DAPTestBase : public TransportBase {
+protected:
+  std::unique_ptr<lldb_dap::DAP> dap;
+
+  void SetUp() override;
+
+  /// Closes the DAP output pipe and returns the remaining protocol messages in
+  /// the buffer.
+  std::vector<lldb_dap::protocol::Message> DrainOutput();
+};
+
+} // namespace lldb_dap_tests
diff --git a/lldb/unittests/DAP/TransportTest.cpp b/lldb/unittests/DAP/TransportTest.cpp
index 5c77b4bb26343..e6dab42e30941 100644
--- a/lldb/unittests/DAP/TransportTest.cpp
+++ b/lldb/unittests/DAP/TransportTest.cpp
@@ -8,10 +8,10 @@
 
 #include "Transport.h"
 #include "Protocol/ProtocolBase.h"
+#include "TestBase.h"
 #include "lldb/Host/File.h"
 #include "lldb/Host/Pipe.h"
 #include "llvm/ADT/StringRef.h"
-#include "llvm/Support/FormatVariadic.h"
 #include "llvm/Testing/Support/Error.h"
 #include "gtest/gtest.h"
 #include <chrono>
@@ -21,20 +21,18 @@
 using namespace llvm;
 using namespace lldb;
 using namespace lldb_dap;
+using namespace lldb_dap_tests;
 using namespace lldb_dap::protocol;
 using lldb_private::File;
 using lldb_private::NativeFile;
 using lldb_private::Pipe;
 
-class TransportTest : public testing::Test {
+class TransportTest : public PipeBase {
 protected:
-  Pipe input;
-  Pipe output;
   std::unique_ptr<Transport> transport;
 
   void SetUp() override {
-    ASSERT_THAT_ERROR(input.CreateNew(false).ToError(), Succeeded());
-    ASSERT_THAT_ERROR(output.CreateNew(false).ToError(), Succeeded());
+    PipeBase::SetUp();
     transport = std::make_unique<Transport>(
         "stdio", nullptr,
         std::make_shared<NativeFile>(input.GetReadFileDescriptor(),
@@ -44,13 +42,6 @@ class TransportTest : public testing::Test {
                                      File::eOpenOptionWriteOnly,
                                      NativeFile::Unowned));
   }
-
-  void Write(StringRef json) {
-    std::string message =
-        formatv("Content-Length: {0}\r\n\r\n{1}", json.size(), json).str();
-    ASSERT_THAT_EXPECTED(input.Write(message.data(), message.size()),
-                         Succeeded());
-  }
 };
 
 TEST_F(TransportTest, MalformedRequests) {
@@ -65,7 +56,12 @@ TEST_F(TransportTest, MalformedRequests) {
 }
 
 TEST_F(TransportTest, Read) {
-  Write(R"json({"seq": 1, "type": "request", "command": "abc"})json");
+  std::string json =
+      R"json({"seq": 1, "type": "request", "command": "abc"})json";
+  std::string message =
+      formatv("Content-Length: {0}\r\n\r\n{1}", json.size(), json).str();
+  ASSERT_THAT_EXPECTED(input.Write(message.data(), message.size()),
+                       Succeeded());
   ASSERT_THAT_EXPECTED(
       transport->Read(std::chrono::milliseconds(1)),
       HasValue(testing::VariantWith<Request>(testing::FieldsAre(


Reviews:
user: login: JDevlieghere
body: Very excited to see more unit testing!
state: APPROVED
author_association: MEMBER
submitted_at: 2025-05-14T17:35:19Z
user: login: JDevlieghere
body: I like the test base classes.
state: APPROVED
author_association: MEMBER
submitted_at: 2025-05-14T20:54:48Z
user: login: ashgti
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-05-14T21:05:07Z
user: login: ashgti
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-05-14T21:05:43Z
user: login: JDevlieghere
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-05-14T22:00:30Z
user: login: JDevlieghere
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-05-15T01:28:00Z

Review comments:
diff_hunk: @@ -0,0 +1,70 @@
+//===-- TestBase.cpp ------------------------------------------------------===//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+
+#include "TestBase.h"
+#include "Protocol/ProtocolBase.h"
+#include "lldb/Host/File.h"
+#include "lldb/Host/Pipe.h"
+#include "llvm/Testing/Support/Error.h"
+
+using namespace llvm;
+using namespace lldb;
+using namespace lldb_dap;
+using namespace lldb_dap::protocol;
+using namespace lldb_dap_tests;
+using lldb_private::File;
+using lldb_private::NativeFile;
+using lldb_private::Pipe;
+
+void PipeBase::SetUp() {
+  ASSERT_THAT_ERROR(input.CreateNew(false).ToError(), Succeeded());
+  ASSERT_THAT_ERROR(output.CreateNew(false).ToError(), Succeeded());
+}
+
+void TransportBase::SetUp() {
+  PipeBase::SetUp();
+  to_dap = std::make_unique<Transport>(
+      "to_dap", nullptr,
+      std::make_shared<NativeFile>(input.GetReadFileDescriptor(),
+                                   File::eOpenOptionReadOnly,
+                                   NativeFile::Unowned),
+      std::make_shared<NativeFile>(output.GetWriteFileDescriptor(),
+                                   File::eOpenOptionWriteOnly,
+                                   NativeFile::Unowned));
+  from_dap = std::make_unique<Transport>(
+      "from_dap", nullptr,
+      std::make_shared<NativeFile>(output.GetReadFileDescriptor(),
+                                   File::eOpenOptionReadOnly,
+                                   NativeFile::Unowned),
+      std::make_shared<NativeFile>(input.GetWriteFileDescriptor(),
+                                   File::eOpenOptionWriteOnly,
+                                   NativeFile::Unowned));
+}
+
+void DAPTestBase::SetUp() {
+  TransportBase::SetUp();
+  dap = std::make_unique<DAP>(
+      /*log=*/nullptr,
+      /*default_repl_mode=*/ReplMode::Auto,
+      /*pre_init_commands=*/std::vector<std::string>(),
+      /*transport=*/*to_dap);
+}
+
+std::vector<Message> DAPTestBase::DrainOutput() {
+  std::vector<Message> msgs;
+  output.CloseWriteFileDescriptor();
+  while (true) {
+    Expected<Message> next = from_dap->Read(std::chrono::milliseconds(1));
+    if (!next) {
+      consumeError(next.takeError());
+      break;
+    }
+    msgs.push_back(*next);
+  }
+  return msgs;
+}
path: lldb/unittests/DAP/TestBase.cpp
user: login: JDevlieghere
body: newline
created_at: 2025-05-14T20:50:26Z
author_association: MEMBER
diff_hunk: @@ -1,8 +1,11 @@
 add_lldb_unittest(DAPTests
+  DAPTest.cpp
+  Handler/DisconnectRequestHandlerTest.cpp
path: lldb/unittests/DAP/CMakeLists.txt
user: login: JDevlieghere
body: I somewhat regret the naming of the request handlers (*). Learning from our mistakes, maybe here we should  just call it `Handler/DisconnectTest.cpp` to reduce the verbosity?

(*) In hindsight, I wish I called them `RequestHandlerDisconnect` instead  of `DisconnectRequestHandler`, though that doesn't apply here as all the unit test files end with `Test` and I  think consistency is more important. 

created_at: 2025-05-14T20:54:33Z
author_association: MEMBER
diff_hunk: @@ -0,0 +1,70 @@
+//===-- TestBase.cpp ------------------------------------------------------===//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+
+#include "TestBase.h"
+#include "Protocol/ProtocolBase.h"
+#include "lldb/Host/File.h"
+#include "lldb/Host/Pipe.h"
+#include "llvm/Testing/Support/Error.h"
+
+using namespace llvm;
+using namespace lldb;
+using namespace lldb_dap;
+using namespace lldb_dap::protocol;
+using namespace lldb_dap_tests;
+using lldb_private::File;
+using lldb_private::NativeFile;
+using lldb_private::Pipe;
+
+void PipeBase::SetUp() {
+  ASSERT_THAT_ERROR(input.CreateNew(false).ToError(), Succeeded());
+  ASSERT_THAT_ERROR(output.CreateNew(false).ToError(), Succeeded());
+}
+
+void TransportBase::SetUp() {
+  PipeBase::SetUp();
+  to_dap = std::make_unique<Transport>(
+      "to_dap", nullptr,
+      std::make_shared<NativeFile>(input.GetReadFileDescriptor(),
+                                   File::eOpenOptionReadOnly,
+                                   NativeFile::Unowned),
+      std::make_shared<NativeFile>(output.GetWriteFileDescriptor(),
+                                   File::eOpenOptionWriteOnly,
+                                   NativeFile::Unowned));
+  from_dap = std::make_unique<Transport>(
+      "from_dap", nullptr,
+      std::make_shared<NativeFile>(output.GetReadFileDescriptor(),
+                                   File::eOpenOptionReadOnly,
+                                   NativeFile::Unowned),
+      std::make_shared<NativeFile>(input.GetWriteFileDescriptor(),
+                                   File::eOpenOptionWriteOnly,
+                                   NativeFile::Unowned));
+}
+
+void DAPTestBase::SetUp() {
+  TransportBase::SetUp();
+  dap = std::make_unique<DAP>(
+      /*log=*/nullptr,
+      /*default_repl_mode=*/ReplMode::Auto,
+      /*pre_init_commands=*/std::vector<std::string>(),
+      /*transport=*/*to_dap);
+}
+
+std::vector<Message> DAPTestBase::DrainOutput() {
+  std::vector<Message> msgs;
+  output.CloseWriteFileDescriptor();
+  while (true) {
+    Expected<Message> next = from_dap->Read(std::chrono::milliseconds(1));
+    if (!next) {
+      consumeError(next.takeError());
+      break;
+    }
+    msgs.push_back(*next);
+  }
+  return msgs;
+}
path: lldb/unittests/DAP/TestBase.cpp
user: login: ashgti
body: Done.
created_at: 2025-05-14T21:05:07Z
author_association: CONTRIBUTOR
diff_hunk: @@ -1,8 +1,11 @@
 add_lldb_unittest(DAPTests
+  DAPTest.cpp
+  Handler/DisconnectRequestHandlerTest.cpp
path: lldb/unittests/DAP/CMakeLists.txt
user: login: ashgti
body: I renamed the file to `Handler/DisconnectTest.cpp` but should the test be `DisconnectTest` or should it be `DisconnectRequestHandlerTest` since thats the actual class I'm testing?
created_at: 2025-05-14T21:05:43Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,35 @@
+//===-- DisconnectRequestHandlerTest.cpp ----------------------------------===//
path: lldb/unittests/DAP/Handler/DisconnectTest.cpp
user: login: JDevlieghere
body: ```suggestion
//===-- DisconnectTest.cpp ------------------------------------------------===//
```
created_at: 2025-05-14T22:00:29Z
author_association: MEMBER
diff_hunk: @@ -1,8 +1,11 @@
 add_lldb_unittest(DAPTests
+  DAPTest.cpp
+  Handler/DisconnectRequestHandlerTest.cpp
path: lldb/unittests/DAP/CMakeLists.txt
user: login: JDevlieghere
body: I think `Handler/DisconnectTest` is sufficiently clear. 
created_at: 2025-05-15T01:27:59Z
author_association: MEMBER

PR comments:
user: login: llvmbot
created_at: 2025-05-14T17:34:27Z
body: 
<!--LLVM PR SUMMARY COMMENT-->

@llvm/pr-subscribers-lldb

Author: John Harrison (ashgti)

<details>
<summary>Changes</summary>

This is a very simple case that currently only validates we can create a DAP instance and send a message over the transport layer. More in-depth tests will require additional helpers and possibly refactors of DAP to make it more testable, however this is some ground work to have basic support for unit tests.

---
Full diff: https://github.com/llvm/llvm-project/pull/139937.diff


3 Files Affected:

- (modified) lldb/tools/lldb-dap/DAP.h (+2-1) 
- (modified) lldb/unittests/DAP/CMakeLists.txt (+1) 
- (added) lldb/unittests/DAP/DAPTest.cpp (+63) 


``````````diff
diff --git a/lldb/tools/lldb-dap/DAP.h b/lldb/tools/lldb-dap/DAP.h
index c2e4c2dea582e..2ff66d1cd0182 100644
--- a/lldb/tools/lldb-dap/DAP.h
+++ b/lldb/tools/lldb-dap/DAP.h
@@ -226,7 +226,8 @@ struct DAP {
   /// \param[in] default_repl_mode
   ///     Default repl mode behavior, as configured by the binary.
   /// \param[in] pre_init_commands
-  ///     LLDB commands to execute as soon as the debugger instance is allocaed.
+  ///     LLDB commands to execute as soon as the debugger instance is
+  ///     allocated.
   /// \param[in] transport
   ///     Transport for this debug session.
   DAP(Log *log, const ReplMode default_repl_mode,
diff --git a/lldb/unittests/DAP/CMakeLists.txt b/lldb/unittests/DAP/CMakeLists.txt
index 110733e93b192..6074e9b872c49 100644
--- a/lldb/unittests/DAP/CMakeLists.txt
+++ b/lldb/unittests/DAP/CMakeLists.txt
@@ -3,6 +3,7 @@ add_lldb_unittest(DAPTests
   LLDBUtilsTest.cpp
   TransportTest.cpp
   ProtocolTypesTest.cpp
+  DAPTest.cpp
 
   LINK_LIBS
     lldbDAP
diff --git a/lldb/unittests/DAP/DAPTest.cpp b/lldb/unittests/DAP/DAPTest.cpp
new file mode 100644
index 0000000000000..9d2a9b944678e
--- /dev/null
+++ b/lldb/unittests/DAP/DAPTest.cpp
@@ -0,0 +1,63 @@
+//===-- DAPTest.cpp -------------------------------------------------===//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+
+#include "DAP.h"
+#include "Protocol/ProtocolBase.h"
+#include "Transport.h"
+#include "lldb/Host/File.h"
+#include "lldb/Host/Pipe.h"
+#include "llvm/Testing/Support/Error.h"
+#include "gtest/gtest.h"
+#include <chrono>
+#include <memory>
+#include <optional>
+
+using namespace llvm;
+using namespace lldb;
+using namespace lldb_dap;
+using namespace lldb_dap::protocol;
+using lldb_private::File;
+using lldb_private::NativeFile;
+using lldb_private::Pipe;
+
+class DAPTest : public testing::Test {
+protected:
+  Pipe input;
+  Pipe output;
+  std::unique_ptr<Transport> toDAP;
+  std::unique_ptr<Transport> fromDAP;
+
+  void SetUp() override {
+    ASSERT_THAT_ERROR(input.CreateNew(false).ToError(), Succeeded());
+    ASSERT_THAT_ERROR(output.CreateNew(false).ToError(), Succeeded());
+    toDAP = std::make_unique<Transport>(
+        "toDAP", nullptr,
+        std::make_shared<NativeFile>(input.GetReadFileDescriptor(),
+                                     File::eOpenOptionReadOnly,
+                                     NativeFile::Unowned),
+        std::make_shared<NativeFile>(output.GetWriteFileDescriptor(),
+                                     File::eOpenOptionWriteOnly,
+                                     NativeFile::Unowned));
+    fromDAP = std::make_unique<Transport>(
+        "fromDAP", nullptr,
+        std::make_shared<NativeFile>(output.GetReadFileDescriptor(),
+                                     File::eOpenOptionReadOnly,
+                                     NativeFile::Unowned),
+        std::make_shared<NativeFile>(input.GetWriteFileDescriptor(),
+                                     File::eOpenOptionWriteOnly,
+                                     NativeFile::Unowned));
+  }
+};
+
+TEST_F(DAPTest, SendProtocolMessages) {
+  DAP dap{nullptr, ReplMode::Auto, {}, *toDAP};
+  dap.Send(Event{"my-event", std::nullopt});
+  ASSERT_THAT_EXPECTED(fromDAP->Read(std::chrono::milliseconds(1)),
+                       HasValue(testing::VariantWith<Event>(testing::FieldsAre(
+                           /*event=*/"my-event", /*body=*/std::nullopt))));
+}

``````````

</details>

author_association: MEMBER
user: login: ashgti
created_at: 2025-05-14T17:38:11Z
body: Should I rename `toDAP`/`fromDAP` to `to_dap`/`from_dap`? I think thats more inline with how lldb names variables, right?
author_association: CONTRIBUTOR
user: login: JDevlieghere
created_at: 2025-05-14T17:45:39Z
body: > Should I rename `toDAP`/`fromDAP` to `to_dap`/`from_dap`? I think thats more inline with how lldb names variables, right?

Yes, good point. Currently we have a weird mix of styles. Once everything has been migrated to use the protocol classes, we should go through the code and fix the remaining inconsistencies. 
author_association: MEMBER
user: login: ashgti
created_at: 2025-05-14T17:48:07Z
body: Yea, thats my mistake. I made the protocol classes to match the names of the spec, but we don't really have to do that as long as the `toJSON`/`fromJSON` adjusts the names, they can be different.
author_association: CONTRIBUTOR
user: login: JDevlieghere
created_at: 2025-05-14T17:56:23Z
body: > Yea, thats my mistake. I made the protocol classes to match the names of the spec, but we don't really have to do that as long as the `toJSON`/`fromJSON` adjusts the names, they can be different.

I think we discussed this at some point in the past and I'm personally okay with saying that everything outside the protocol dir should follow the LLDB style and the Protocol dir matches the spec (although we're already diverging from that for the enums). 
author_association: MEMBER
user: login: ashgti
created_at: 2025-05-14T20:08:45Z
body: I added a few extra tests here and some extra base classes to help setup tests.

LMKWYT
author_association: CONTRIBUTOR