Skip to content

[lldb-dap] Add unit tests for protocol types #139502

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 1 commit into from
May 12, 2025

Conversation

JDevlieghere
Copy link
Member

Add unit tests for serializing and deserializing protocol types.

@llvmbot
Copy link
Member

llvmbot commented May 12, 2025

@llvm/pr-subscribers-lldb

Author: Jonas Devlieghere (JDevlieghere)

Changes

Add unit tests for serializing and deserializing protocol types.


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

2 Files Affected:

  • (modified) lldb/unittests/DAP/CMakeLists.txt (+1)
  • (added) lldb/unittests/DAP/ProtocolTypesTest.cpp (+268)
diff --git a/lldb/unittests/DAP/CMakeLists.txt b/lldb/unittests/DAP/CMakeLists.txt
index 4bbb552be9f34..62318d2ecbad3 100644
--- a/lldb/unittests/DAP/CMakeLists.txt
+++ b/lldb/unittests/DAP/CMakeLists.txt
@@ -1,6 +1,7 @@
 add_lldb_unittest(DAPTests
   JSONUtilsTest.cpp
   LLDBUtilsTest.cpp
+  ProtocolTypesTest.cpp
 
   LINK_LIBS
     lldbDAP
diff --git a/lldb/unittests/DAP/ProtocolTypesTest.cpp b/lldb/unittests/DAP/ProtocolTypesTest.cpp
new file mode 100644
index 0000000000000..b64810dc713af
--- /dev/null
+++ b/lldb/unittests/DAP/ProtocolTypesTest.cpp
@@ -0,0 +1,268 @@
+//===-- ProtocolTypesTest.cpp -----------------------------------*- C++ -*-===//
+//
+// 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 "Protocol/ProtocolTypes.h"
+#include "gtest/gtest.h"
+
+using namespace llvm;
+using namespace lldb;
+using namespace lldb_dap;
+using namespace lldb_dap::protocol;
+
+TEST(ProtocolTypesTest, ExceptionBreakpointsFilter) {
+  ExceptionBreakpointsFilter filter;
+  filter.filter = "testFilter";
+  filter.label = "Test Filter";
+  filter.description = "This is a test filter";
+  filter.defaultState = true;
+  filter.supportsCondition = true;
+  filter.conditionDescription = "Condition for test filter";
+
+  llvm::json::Value value = toJSON(filter);
+  const json::Object *obj = value.getAsObject();
+  ASSERT_NE(obj, nullptr);
+
+  EXPECT_EQ(obj->getString("filter"), "testFilter");
+  EXPECT_EQ(obj->getString("label"), "Test Filter");
+  EXPECT_EQ(obj->getString("description"), "This is a test filter");
+  EXPECT_EQ(obj->getBoolean("default"), true);
+  EXPECT_EQ(obj->getBoolean("supportsCondition"), true);
+  EXPECT_EQ(obj->getString("conditionDescription"),
+            "Condition for test filter");
+}
+
+TEST(ProtocolTypesTest, ColumnDescriptor) {
+  ColumnDescriptor column;
+  column.attributeName = "testAttribute";
+  column.label = "Test Label";
+  column.format = "testFormat";
+  column.type = eColumnTypeString;
+  column.width = 20;
+
+  llvm::json::Value value = toJSON(column);
+  const json::Object *obj = value.getAsObject();
+  ASSERT_NE(obj, nullptr);
+
+  EXPECT_EQ(obj->getString("attributeName"), "testAttribute");
+  EXPECT_EQ(obj->getString("label"), "Test Label");
+  EXPECT_EQ(obj->getString("format"), "testFormat");
+  EXPECT_EQ(obj->getString("type"), "string");
+  EXPECT_EQ(obj->getInteger("width"), 20);
+}
+
+TEST(ProtocolTypesTest, BreakpointMode) {
+  BreakpointMode mode;
+  mode.mode = "testMode";
+  mode.label = "Test Label";
+  mode.description = "This is a test description";
+  mode.appliesTo = {eBreakpointModeApplicabilitySource,
+                    eBreakpointModeApplicabilityException};
+
+  llvm::json::Value value = toJSON(mode);
+  const json::Object *obj = value.getAsObject();
+  ASSERT_NE(obj, nullptr);
+
+  EXPECT_EQ(obj->getString("mode"), "testMode");
+  EXPECT_EQ(obj->getString("label"), "Test Label");
+  EXPECT_EQ(obj->getString("description"), "This is a test description");
+
+  const auto *appliesToArray = obj->getArray("appliesTo");
+  ASSERT_NE(appliesToArray, nullptr);
+  ASSERT_EQ(appliesToArray->size(), 2UL);
+  EXPECT_EQ(appliesToArray->front().getAsString(), "source");
+  EXPECT_EQ(appliesToArray->back().getAsString(), "exception");
+}
+
+TEST(ProtocolTypesTest, Capabilities) {
+  Capabilities capabilities;
+  capabilities.supportedFeatures = {eAdapterFeatureANSIStyling,
+                                    eAdapterFeatureTerminateRequest};
+  capabilities.exceptionBreakpointFilters = {
+      {"filter1", "Filter 1", "Description 1", true, true, "Condition 1"},
+      {"filter2", "Filter 2", "Description 2", false, false, "Condition 2"}};
+  capabilities.completionTriggerCharacters = {"."};
+  capabilities.additionalModuleColumns = {
+      {"attribute1", "Label 1", "Format 1", eColumnTypeString, 10},
+      {"attribute2", "Label 2", "Format 2", eColumnTypeNumber, 20}};
+  capabilities.supportedChecksumAlgorithms = {eChecksumAlgorithmMD5,
+                                              eChecksumAlgorithmSHA256};
+  capabilities.breakpointModes = {{"mode1",
+                                   "Mode 1",
+                                   "Description 1",
+                                   {eBreakpointModeApplicabilitySource}},
+                                  {"mode2",
+                                   "Mode 2",
+                                   "Description 2",
+                                   {eBreakpointModeApplicabilityException}}};
+  capabilities.lldbExtVersion = "1.0.0";
+
+  llvm::json::Value value = toJSON(capabilities);
+  const json::Object *obj = value.getAsObject();
+  ASSERT_NE(obj, nullptr);
+
+  // Verify supported features.
+  EXPECT_EQ(obj->getBoolean("supportsANSIStyling"), true);
+  EXPECT_EQ(obj->getBoolean("supportsTerminateRequest"), true);
+
+  // Verify exception breakpoint filters.
+  const auto *filtersArray = obj->getArray("exceptionBreakpointFilters");
+  ASSERT_NE(filtersArray, nullptr);
+  ASSERT_EQ(filtersArray->size(), 2UL);
+  const auto *filter1 = filtersArray->front().getAsObject();
+  ASSERT_NE(filter1, nullptr);
+  EXPECT_EQ(filter1->getString("filter"), "filter1");
+  EXPECT_EQ(filter1->getString("label"), "Filter 1");
+  EXPECT_EQ(filter1->getString("description"), "Description 1");
+  EXPECT_EQ(filter1->getBoolean("default"), true);
+  EXPECT_EQ(filter1->getBoolean("supportsCondition"), true);
+  EXPECT_EQ(filter1->getString("conditionDescription"), "Condition 1");
+
+  const auto *filter2 = filtersArray->back().getAsObject();
+  ASSERT_NE(filter2, nullptr);
+  EXPECT_EQ(filter2->getString("filter"), "filter2");
+  EXPECT_EQ(filter2->getString("label"), "Filter 2");
+  EXPECT_EQ(filter2->getString("description"), "Description 2");
+  EXPECT_EQ(filter2->getBoolean("default"), false);
+  EXPECT_EQ(filter2->getBoolean("supportsCondition"), false);
+  EXPECT_EQ(filter2->getString("conditionDescription"), "Condition 2");
+
+  // Verify completion trigger characters.
+  const auto *completionArray = obj->getArray("completionTriggerCharacters");
+  ASSERT_NE(completionArray, nullptr);
+  ASSERT_EQ(completionArray->size(), 1UL);
+  EXPECT_EQ(completionArray->front().getAsString(), ".");
+
+  // Verify additional module columns.
+  const auto *columnsArray = obj->getArray("additionalModuleColumns");
+  ASSERT_NE(columnsArray, nullptr);
+  ASSERT_EQ(columnsArray->size(), 2UL);
+  const auto *column1 = columnsArray->front().getAsObject();
+  ASSERT_NE(column1, nullptr);
+  EXPECT_EQ(column1->getString("attributeName"), "attribute1");
+  EXPECT_EQ(column1->getString("label"), "Label 1");
+  EXPECT_EQ(column1->getString("format"), "Format 1");
+  EXPECT_EQ(column1->getString("type"), "string");
+  EXPECT_EQ(column1->getInteger("width"), 10);
+
+  const auto *column2 = columnsArray->back().getAsObject();
+  ASSERT_NE(column2, nullptr);
+  EXPECT_EQ(column2->getString("attributeName"), "attribute2");
+  EXPECT_EQ(column2->getString("label"), "Label 2");
+  EXPECT_EQ(column2->getString("format"), "Format 2");
+  EXPECT_EQ(column2->getString("type"), "number");
+  EXPECT_EQ(column2->getInteger("width"), 20);
+
+  // Verify supported checksum algorithms.
+  const auto *checksumArray = obj->getArray("supportedChecksumAlgorithms");
+  ASSERT_NE(checksumArray, nullptr);
+  ASSERT_EQ(checksumArray->size(), 2UL);
+  EXPECT_EQ(checksumArray->front().getAsString(), "MD5");
+  EXPECT_EQ(checksumArray->back().getAsString(), "SHA256");
+
+  // Verify breakpoint modes.
+  const auto *modesArray = obj->getArray("breakpointModes");
+  ASSERT_NE(modesArray, nullptr);
+  ASSERT_EQ(modesArray->size(), 2UL);
+  const auto *mode1 = modesArray->front().getAsObject();
+  ASSERT_NE(mode1, nullptr);
+  EXPECT_EQ(mode1->getString("mode"), "mode1");
+  EXPECT_EQ(mode1->getString("label"), "Mode 1");
+  EXPECT_EQ(mode1->getString("description"), "Description 1");
+  const auto *appliesTo1 = mode1->getArray("appliesTo");
+  ASSERT_NE(appliesTo1, nullptr);
+  ASSERT_EQ(appliesTo1->size(), 1UL);
+  EXPECT_EQ(appliesTo1->front().getAsString(), "source");
+
+  const auto *mode2 = modesArray->back().getAsObject();
+  ASSERT_NE(mode2, nullptr);
+  EXPECT_EQ(mode2->getString("mode"), "mode2");
+  EXPECT_EQ(mode2->getString("label"), "Mode 2");
+  EXPECT_EQ(mode2->getString("description"), "Description 2");
+  const auto *appliesTo2 = mode2->getArray("appliesTo");
+  ASSERT_NE(appliesTo2, nullptr);
+  ASSERT_EQ(appliesTo2->size(), 1UL);
+  EXPECT_EQ(appliesTo2->front().getAsString(), "exception");
+
+  // Verify lldb extension version.
+  EXPECT_EQ(obj->getString("$__lldb_version"), "1.0.0");
+}
+
+TEST(ProtocolTypesTest, Source) {
+  Source source;
+  source.name = "testName";
+  source.path = "/path/to/source";
+  source.sourceReference = 12345;
+  source.presentationHint = ePresentationHintEmphasize;
+
+  llvm::json::Value value = toJSON(source);
+
+  Source deserialized_source;
+  llvm::json::Path::Root root;
+  ASSERT_TRUE(fromJSON(value, deserialized_source, root));
+
+  ASSERT_EQ(source.name, deserialized_source.name);
+  ASSERT_EQ(source.path, deserialized_source.path);
+  ASSERT_EQ(source.sourceReference, deserialized_source.sourceReference);
+  ASSERT_EQ(source.presentationHint, deserialized_source.presentationHint);
+}
+
+TEST(ProtocolTypesTest, BreakpointLocation) {
+  BreakpointLocation location;
+  location.line = 42;
+  location.column = 5;
+  location.endLine = 50;
+  location.endColumn = 10;
+
+  llvm::json::Value value = toJSON(location);
+  const json::Object *obj = value.getAsObject();
+  ASSERT_NE(obj, nullptr);
+
+  EXPECT_EQ(obj->getInteger("line"), 42);
+  EXPECT_EQ(obj->getInteger("column"), 5);
+  EXPECT_EQ(obj->getInteger("endLine"), 50);
+  EXPECT_EQ(obj->getInteger("endColumn"), 10);
+}
+
+TEST(ProtocolTypesTest, Breakpoint) {
+  Breakpoint breakpoint;
+  breakpoint.id = 1;
+  breakpoint.verified = true;
+  breakpoint.message = "Breakpoint set successfully";
+  breakpoint.source = {"testSource", "/path/to/source", 12345,
+                       ePresentationHintNormal};
+  breakpoint.line = 42;
+  breakpoint.column = 5;
+  breakpoint.endLine = 50;
+  breakpoint.endColumn = 10;
+  breakpoint.instructionReference = "0x1234";
+  breakpoint.offset = -4;
+  breakpoint.reason = BreakpointReason::eBreakpointReasonPending;
+
+  llvm::json::Value value = toJSON(breakpoint);
+  const json::Object *obj = value.getAsObject();
+  ASSERT_NE(obj, nullptr);
+
+  EXPECT_EQ(obj->getInteger("id"), 1);
+  EXPECT_EQ(obj->getBoolean("verified"), true);
+  EXPECT_EQ(obj->getString("message"), "Breakpoint set successfully");
+
+  const auto *source_obj = obj->getObject("source");
+  ASSERT_NE(source_obj, nullptr);
+  EXPECT_EQ(source_obj->getString("name"), "testSource");
+  EXPECT_EQ(source_obj->getString("path"), "/path/to/source");
+  EXPECT_EQ(source_obj->getInteger("sourceReference"), 12345);
+  EXPECT_EQ(source_obj->getString("presentationHint"), "normal");
+
+  EXPECT_EQ(obj->getInteger("line"), 42);
+  EXPECT_EQ(obj->getInteger("column"), 5);
+  EXPECT_EQ(obj->getInteger("endLine"), 50);
+  EXPECT_EQ(obj->getInteger("endColumn"), 10);
+  EXPECT_EQ(obj->getString("instructionReference"), "0x1234");
+  EXPECT_EQ(obj->getInteger("offset"), -4);
+  EXPECT_EQ(obj->getString("reason"), "pending");
+}
\ No newline at end of file

@da-viper
Copy link
Contributor

LGTM. tiny nit for concrete types

@JDevlieghere
Copy link
Member Author

tiny nit for concrete types

@da-viper I don't see your comment, is it possible it wasn't submitted?

Comment on lines 27 to 36
const json::Object *obj = value.getAsObject();
ASSERT_NE(obj, nullptr);

EXPECT_EQ(obj->getString("filter"), "testFilter");
EXPECT_EQ(obj->getString("label"), "Test Filter");
EXPECT_EQ(obj->getString("description"), "This is a test filter");
EXPECT_EQ(obj->getBoolean("default"), true);
EXPECT_EQ(obj->getBoolean("supportsCondition"), true);
EXPECT_EQ(obj->getString("conditionDescription"),
"Condition for test filter");
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should we make a helper for matching these with a string literal? Something like:

MATCHER_P(equalToDAP, msg,
          "Protocol Message " + llvm::to_string(msg)) {
  if (toJSON(arg) != json::parse(msg)) {
    *result_listener << llvm::formatv("expected:\n{0:2}\ngot\n{1:2}",
                                      toJSON(msg), toJSON(arg))
                            .str();
    return false;
  }
  return true;
}

And then use:

EXPECT_THAT(filter, equalToDAP(R"json({"filter": "testFilter",...})json"));

I think that might help improve the brevity of these tests and help account for some additional fields or defaults that may appear in some structs.

We might be able to make the test case into a vector<pair<protocol::Message, string>> to make it easier to add cases as well.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I was thinking about ways to make the test easier to write. I'm not super thrilled about putting JSON into the tests but maybe it's not worse than how verbose the current approach is. I think my preference would be to have what I did for the Source struct: if every type has both a fromJSON and toJSON, then we can just roundtrip every object.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We could make it more of a convention to have both going forward for testing purposes.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Alright, if that's the direction, let me start by landing the Source test and we can support the other types in a similar fashion as we add support for serialization.

Add unit tests for serializing and deserializing protocol types.
@JDevlieghere JDevlieghere force-pushed the lldb-dap-protocol-types branch from 5fdd0d6 to aaae07d Compare May 12, 2025 17:50
@JDevlieghere
Copy link
Member Author

Updated the PR and settled on the roundtrip approach for Source and ExceptionBreakpointsFilter.

@JDevlieghere JDevlieghere merged commit 8bec5e5 into llvm:main May 12, 2025
9 of 10 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants