diff --git a/lldb/tools/lldb-dap/DAPError.cpp b/lldb/tools/lldb-dap/DAPError.cpp index 60347d577f821..c60e8bcc11fa4 100644 --- a/lldb/tools/lldb-dap/DAPError.cpp +++ b/lldb/tools/lldb-dap/DAPError.cpp @@ -16,8 +16,7 @@ namespace lldb_dap { char DAPError::ID; DAPError::DAPError(std::string message, std::error_code EC, bool show_user, - std::optional url, - std::optional url_label) + std::string url, std::string url_label) : m_message(message), m_ec(EC), m_show_user(show_user), m_url(url), m_url_label(url_label) {} diff --git a/lldb/tools/lldb-dap/DAPError.h b/lldb/tools/lldb-dap/DAPError.h index 4c94bdd6ac3d6..f44361f7be641 100644 --- a/lldb/tools/lldb-dap/DAPError.h +++ b/lldb/tools/lldb-dap/DAPError.h @@ -21,23 +21,23 @@ class DAPError : public llvm::ErrorInfo { DAPError(std::string message, std::error_code EC = llvm::inconvertibleErrorCode(), - bool show_user = true, std::optional url = std::nullopt, - std::optional url_label = std::nullopt); + bool show_user = true, std::string url = "", + std::string url_label = ""); void log(llvm::raw_ostream &OS) const override; std::error_code convertToErrorCode() const override; const std::string &getMessage() const { return m_message; } bool getShowUser() const { return m_show_user; } - const std::optional &getURL() const { return m_url; } - const std::optional &getURLLabel() const { return m_url; } + const std::string &getURL() const { return m_url; } + const std::string &getURLLabel() const { return m_url; } private: std::string m_message; std::error_code m_ec; bool m_show_user; - std::optional m_url; - std::optional m_url_label; + std::string m_url; + std::string m_url_label; }; /// An error that indicates the current request handler cannot execute because diff --git a/lldb/tools/lldb-dap/Protocol/ProtocolBase.cpp b/lldb/tools/lldb-dap/Protocol/ProtocolBase.cpp index bc4fee4aa8b8d..2b23d9c43aadc 100644 --- a/lldb/tools/lldb-dap/Protocol/ProtocolBase.cpp +++ b/lldb/tools/lldb-dap/Protocol/ProtocolBase.cpp @@ -180,9 +180,9 @@ bool fromJSON(json::Value const &Params, Response &R, json::Path P) { json::Value toJSON(const ErrorMessage &EM) { json::Object Result{{"id", EM.id}, {"format", EM.format}}; - if (EM.variables) { + if (!EM.variables.empty()) { json::Object variables; - for (auto &var : *EM.variables) + for (auto &var : EM.variables) variables[var.first] = var.second; Result.insert({"variables", std::move(variables)}); } @@ -190,9 +190,9 @@ json::Value toJSON(const ErrorMessage &EM) { Result.insert({"sendTelemetry", EM.sendTelemetry}); if (EM.showUser) Result.insert({"showUser", EM.showUser}); - if (EM.url) + if (!EM.url.empty()) Result.insert({"url", EM.url}); - if (EM.urlLabel) + if (!EM.urlLabel.empty()) Result.insert({"urlLabel", EM.urlLabel}); return std::move(Result); @@ -201,10 +201,10 @@ json::Value toJSON(const ErrorMessage &EM) { bool fromJSON(json::Value const &Params, ErrorMessage &EM, json::Path P) { json::ObjectMapper O(Params, P); return O && O.map("id", EM.id) && O.map("format", EM.format) && - O.map("variables", EM.variables) && - O.map("sendTelemetry", EM.sendTelemetry) && - O.map("showUser", EM.showUser) && O.map("url", EM.url) && - O.map("urlLabel", EM.urlLabel); + O.mapOptional("variables", EM.variables) && + O.mapOptional("sendTelemetry", EM.sendTelemetry) && + O.mapOptional("showUser", EM.showUser) && + O.mapOptional("url", EM.url) && O.mapOptional("urlLabel", EM.urlLabel); } json::Value toJSON(const Event &E) { diff --git a/lldb/tools/lldb-dap/Protocol/ProtocolBase.h b/lldb/tools/lldb-dap/Protocol/ProtocolBase.h index 81496380d412f..2017258b72618 100644 --- a/lldb/tools/lldb-dap/Protocol/ProtocolBase.h +++ b/lldb/tools/lldb-dap/Protocol/ProtocolBase.h @@ -119,7 +119,7 @@ struct ErrorMessage { /// An object used as a dictionary for looking up the variables in the format /// string. - std::optional> variables; + std::map variables; /// If true send to telemetry. bool sendTelemetry = false; @@ -128,10 +128,10 @@ struct ErrorMessage { bool showUser = false; /// A url where additional information about this message can be found. - std::optional url; + std::string url; /// A label that is presented to the user as the UI for opening the url. - std::optional urlLabel; + std::string urlLabel; }; bool fromJSON(const llvm::json::Value &, ErrorMessage &, llvm::json::Path); llvm::json::Value toJSON(const ErrorMessage &); diff --git a/lldb/unittests/DAP/CMakeLists.txt b/lldb/unittests/DAP/CMakeLists.txt index ee623d341ec69..0806e1a28eb85 100644 --- a/lldb/unittests/DAP/CMakeLists.txt +++ b/lldb/unittests/DAP/CMakeLists.txt @@ -5,6 +5,9 @@ add_lldb_unittest(DAPTests Handler/ContinueTest.cpp JSONUtilsTest.cpp LLDBUtilsTest.cpp + ProtocolBaseTest.cpp + ProtocolEventsTest.cpp + ProtocolRequestsTest.cpp ProtocolTypesTest.cpp TestBase.cpp VariablesTest.cpp diff --git a/lldb/unittests/DAP/ProtocolBaseTest.cpp b/lldb/unittests/DAP/ProtocolBaseTest.cpp new file mode 100644 index 0000000000000..176eaba5a7874 --- /dev/null +++ b/lldb/unittests/DAP/ProtocolBaseTest.cpp @@ -0,0 +1,130 @@ +//===-- ProtocolBaseTest.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 "Protocol/ProtocolBase.h" +#include "TestingSupport/TestUtilities.h" +#include "llvm/Testing/Support/Error.h" +#include "gtest/gtest.h" + +using namespace llvm; +using namespace lldb_dap; +using namespace lldb_dap::protocol; +using lldb_private::pp; +using llvm::json::parse; +using llvm::json::Value; + +TEST(ProtocolBaseTest, Request) { + // Validate toJSON + EXPECT_EQ(pp(Request{/*seq=*/42, /*command=*/"hello_world", + /*arguments=*/std::nullopt}), + R"({ + "command": "hello_world", + "seq": 42, + "type": "request" +})"); + + // Validate fromJSON + EXPECT_THAT_EXPECTED( + parse(R"({"type":"request","seq":42,"command":"hello_world"})"), + HasValue(Value(Request{/*seq=*/42, /*command=*/"hello_world", + /*arguments*/ std::nullopt}))); + + // Validate parsing errors + EXPECT_THAT_EXPECTED(parse(R"({})"), + FailedWithMessage("missing value at (root).type")); + EXPECT_THAT_EXPECTED(parse(R"({"type":"request"})"), + FailedWithMessage("missing value at (root).command")); +} + +TEST(ProtocolBaseTest, Response) { + // Validate toJSON + EXPECT_EQ(pp(Response{/*request_seq=*/42, /*command=*/"hello_world", + /*success=*/true, + /*message=*/std::nullopt, /*body=*/std::nullopt}), + R"({ + "command": "hello_world", + "request_seq": 42, + "seq": 0, + "success": true, + "type": "response" +})"); + + // Validate fromJSON + EXPECT_THAT_EXPECTED( + parse( + R"({"type":"response","seq":0,"request_seq":42,"command":"hello_world","success":true})"), + HasValue( + Value(Response{/*seq=*/42, /*command=*/"hello_world", + /*success=*/true, + /*message*/ std::nullopt, /*body=*/std::nullopt}))); + + // Validate parsing errors + EXPECT_THAT_EXPECTED(parse(R"({})"), + FailedWithMessage("missing value at (root).type")); + EXPECT_THAT_EXPECTED(parse(R"({"type":"response"})"), + FailedWithMessage("missing value at (root).seq")); +} + +TEST(ProtocolBaseTest, Event) { + // Validate toJSON + EXPECT_EQ(pp(Event{/*event=*/"hello_world", /*body=*/std::nullopt}), + R"({ + "event": "hello_world", + "seq": 0, + "type": "event" +})"); + + // Validate fromJSON + EXPECT_THAT_EXPECTED( + parse(R"({"type":"event","seq":0,"event":"hello_world"})"), + HasValue(Value(Event{/*command=*/"hello_world", /*body=*/std::nullopt}))); + + // Validate parsing errors + EXPECT_THAT_EXPECTED(parse(R"({})"), + FailedWithMessage("missing value at (root).type")); +} + +TEST(ProtocolBaseTest, ErrorMessage) { + // Validate toJSON + EXPECT_EQ(pp(ErrorMessage{/*id=*/42, + /*format=*/"Hello world!", + /*variables=*/{{"name", "value"}}, + /*sendTelemetry=*/true, + /*showUser=*/true, + /*url=*/"http://example.com/error/42", + /*urlLabel*/ "ErrorLabel"}), + R"({ + "format": "Hello world!", + "id": 42, + "sendTelemetry": true, + "showUser": true, + "url": "http://example.com/error/42", + "urlLabel": "ErrorLabel", + "variables": { + "name": "value" + } +})"); + + // Validate fromJSON + EXPECT_THAT_EXPECTED( + parse( + R"({"format":"Hello world!","id":42,"sendTelemetry":true,"showUser":true,"url":"http://example.com/error/42","urlLabel":"ErrorLabel","variables":{"name": "value"}})"), + HasValue(Value(ErrorMessage{/*id=*/42, + /*format=*/"Hello world!", + /*variables=*/{{"name", "value"}}, + /*sendTelemetry=*/true, + /*showUser=*/true, + /*url=*/"http://example.com/error/42", + /*urlLabel*/ "ErrorLabel"}))); + + // Validate parsing errors + EXPECT_THAT_EXPECTED(parse(R"({})"), + FailedWithMessage("missing value at (root).id")); + EXPECT_THAT_EXPECTED(parse(R"({"id":42})"), + FailedWithMessage("missing value at (root).format")); +} diff --git a/lldb/unittests/DAP/ProtocolEventsTest.cpp b/lldb/unittests/DAP/ProtocolEventsTest.cpp new file mode 100644 index 0000000000000..7898033b3b99a --- /dev/null +++ b/lldb/unittests/DAP/ProtocolEventsTest.cpp @@ -0,0 +1,35 @@ +//===-- ProtocolEventsTest.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 "Protocol/ProtocolEvents.h" +#include "Protocol/ProtocolTypes.h" +#include "TestingSupport/TestUtilities.h" +#include "llvm/ADT/StringRef.h" +#include "gtest/gtest.h" + +using namespace llvm; +using namespace lldb_dap::protocol; +using lldb_private::pp; + +TEST(ProtocolEventsTest, CapabilitiesEventBody) { + Capabilities capabilities; + capabilities.supportedFeatures = { + eAdapterFeatureANSIStyling, + eAdapterFeatureBreakpointLocationsRequest, + }; + CapabilitiesEventBody body; + body.capabilities = capabilities; + StringRef json = R"({ + "capabilities": { + "supportsANSIStyling": true, + "supportsBreakpointLocationsRequest": true + } +})"; + // Validate toJSON + EXPECT_EQ(json, pp(body)); +} diff --git a/lldb/unittests/DAP/ProtocolRequestsTest.cpp b/lldb/unittests/DAP/ProtocolRequestsTest.cpp new file mode 100644 index 0000000000000..6e114a459adba --- /dev/null +++ b/lldb/unittests/DAP/ProtocolRequestsTest.cpp @@ -0,0 +1,80 @@ +//===-- ProtocolRequestsTest.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 "Protocol/ProtocolRequests.h" +#include "Protocol/ProtocolTypes.h" +#include "TestingSupport/TestUtilities.h" +#include "llvm/ADT/StringRef.h" +#include "llvm/Testing/Support/Error.h" +#include "gmock/gmock.h" +#include "gtest/gtest.h" +#include + +using namespace llvm; +using namespace lldb_dap::protocol; +using lldb_private::pp; +using llvm::json::parse; + +TEST(ProtocolRequestsTest, ThreadResponseBody) { + const ThreadsResponseBody body{{{1, "thr1"}, {2, "thr2"}}}; + const StringRef json = R"({ + "threads": [ + { + "id": 1, + "name": "thr1" + }, + { + "id": 2, + "name": "thr2" + } + ] +})"; + // Validate toJSON + EXPECT_EQ(json, pp(body)); +} + +TEST(ProtocolRequestsTest, SetExceptionBreakpointsArguments) { + EXPECT_THAT_EXPECTED( + parse(R"({"filters":[]})"), + HasValue(testing::FieldsAre(/*filters=*/testing::IsEmpty(), + /*filterOptions=*/testing::IsEmpty()))); + EXPECT_THAT_EXPECTED( + parse(R"({"filters":["abc"]})"), + HasValue(testing::FieldsAre(/*filters=*/std::vector{"abc"}, + /*filterOptions=*/testing::IsEmpty()))); + EXPECT_THAT_EXPECTED( + parse( + R"({"filters":[],"filterOptions":[{"filterId":"abc"}]})"), + HasValue(testing::FieldsAre( + /*filters=*/testing::IsEmpty(), + /*filterOptions=*/testing::Contains(testing::FieldsAre( + /*filterId=*/"abc", /*condition=*/"", /*mode=*/""))))); + + // Validate parse errors + EXPECT_THAT_EXPECTED(parse(R"({})"), + FailedWithMessage("missing value at (root).filters")); + EXPECT_THAT_EXPECTED( + parse(R"({"filters":false})"), + FailedWithMessage("expected array at (root).filters")); +} + +TEST(ProtocolRequestsTest, SetExceptionBreakpointsResponseBody) { + SetExceptionBreakpointsResponseBody body; + Breakpoint bp; + bp.id = 12, bp.verified = true; + body.breakpoints = {bp}; + EXPECT_EQ(R"({ + "breakpoints": [ + { + "id": 12, + "verified": true + } + ] +})", + pp(body)); +} diff --git a/lldb/unittests/DAP/ProtocolTypesTest.cpp b/lldb/unittests/DAP/ProtocolTypesTest.cpp index 46a09f090fea2..4d51ffbc21ff1 100644 --- a/lldb/unittests/DAP/ProtocolTypesTest.cpp +++ b/lldb/unittests/DAP/ProtocolTypesTest.cpp @@ -1,4 +1,4 @@ -//===-- ProtocolTypesTest.cpp -----------------------------------*- C++ -*-===// +//===-- ProtocolTypesTest.cpp ---------------------------------------------===// // // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. // See https://llvm.org/LICENSE.txt for license information. @@ -7,8 +7,7 @@ //===----------------------------------------------------------------------===// #include "Protocol/ProtocolTypes.h" -#include "Protocol/ProtocolEvents.h" -#include "Protocol/ProtocolRequests.h" +#include "TestingSupport/TestUtilities.h" #include "llvm/ADT/StringRef.h" #include "llvm/Support/JSON.h" #include "llvm/Testing/Support/Error.h" @@ -17,26 +16,12 @@ #include using namespace llvm; -using namespace lldb; -using namespace lldb_dap; using namespace lldb_dap::protocol; +using lldb_private::pp; +using lldb_private::roundtrip; using llvm::json::parse; using llvm::json::Value; -/// Returns a pretty printed json string of a `llvm::json::Value`. -static std::string pp(const json::Value &E) { - return formatv("{0:2}", E).str(); -} - -template static llvm::Expected roundtrip(const T &input) { - llvm::json::Value value = toJSON(input); - llvm::json::Path::Root root; - T output; - if (!fromJSON(value, output, root)) - return root.getError(); - return output; -} - TEST(ProtocolTypesTest, ExceptionBreakpointsFilter) { ExceptionBreakpointsFilter filter; filter.filter = "testFilter"; @@ -641,42 +626,6 @@ TEST(ProtocolTypesTest, Thread) { FailedWithMessage("expected string at thread.name")); } -TEST(ProtocolTypesTest, ThreadResponseBody) { - const ThreadsResponseBody body{{{1, "thr1"}, {2, "thr2"}}}; - const StringRef json = R"({ - "threads": [ - { - "id": 1, - "name": "thr1" - }, - { - "id": 2, - "name": "thr2" - } - ] -})"; - // Validate toJSON - EXPECT_EQ(json, pp(body)); -} - -TEST(ProtocolTypesTest, CapabilitiesEventBody) { - Capabilities capabilities; - capabilities.supportedFeatures = { - eAdapterFeatureANSIStyling, - eAdapterFeatureBreakpointLocationsRequest, - }; - CapabilitiesEventBody body; - body.capabilities = capabilities; - StringRef json = R"({ - "capabilities": { - "supportsANSIStyling": true, - "supportsBreakpointLocationsRequest": true - } -})"; - // Validate toJSON - EXPECT_EQ(json, pp(body)); -} - TEST(ProtocolTypesTest, ExceptionFilterOptions) { EXPECT_THAT_EXPECTED(parse(R"({"filterId":"id"})"), HasValue(Value(ExceptionFilterOptions{ @@ -705,47 +654,6 @@ TEST(ProtocolTypesTest, ExceptionFilterOptions) { FailedWithMessage("expected string at exceptionFilterOptions.mode")); } -TEST(ProtocolTypesTest, SetExceptionBreakpointsArguments) { - EXPECT_THAT_EXPECTED( - parse(R"({"filters":[]})"), - HasValue(testing::FieldsAre(/*filters=*/testing::IsEmpty(), - /*filterOptions=*/testing::IsEmpty()))); - EXPECT_THAT_EXPECTED( - parse(R"({"filters":["abc"]})"), - HasValue(testing::FieldsAre(/*filters=*/std::vector{"abc"}, - /*filterOptions=*/testing::IsEmpty()))); - EXPECT_THAT_EXPECTED( - parse( - R"({"filters":[],"filterOptions":[{"filterId":"abc"}]})"), - HasValue(testing::FieldsAre( - /*filters=*/testing::IsEmpty(), - /*filterOptions=*/testing::Contains(testing::FieldsAre( - /*filterId=*/"abc", /*condition=*/"", /*mode=*/""))))); - - // Validate parse errors - EXPECT_THAT_EXPECTED(parse(R"({})"), - FailedWithMessage("missing value at (root).filters")); - EXPECT_THAT_EXPECTED( - parse(R"({"filters":false})"), - FailedWithMessage("expected array at (root).filters")); -} - -TEST(ProtocolTypesTest, SetExceptionBreakpointsResponseBody) { - SetExceptionBreakpointsResponseBody body; - Breakpoint bp; - bp.id = 12, bp.verified = true; - body.breakpoints = {bp}; - EXPECT_EQ(R"({ - "breakpoints": [ - { - "id": 12, - "verified": true - } - ] -})", - pp(body)); -} - TEST(ProtocolTypesTest, StepInTarget) { StepInTarget target; target.id = 230; diff --git a/lldb/unittests/TestingSupport/TestUtilities.cpp b/lldb/unittests/TestingSupport/TestUtilities.cpp index b53822e38324b..0c9860f479449 100644 --- a/lldb/unittests/TestingSupport/TestUtilities.cpp +++ b/lldb/unittests/TestingSupport/TestUtilities.cpp @@ -20,6 +20,11 @@ using namespace lldb_private; extern const char *TestMainArgv0; std::once_flag TestUtilities::g_debugger_initialize_flag; + +std::string lldb_private::pp(const llvm::json::Value &E) { + return llvm::formatv("{0:2}", E).str(); +} + std::string lldb_private::GetInputFilePath(const llvm::Twine &name) { llvm::SmallString<128> result = llvm::sys::path::parent_path(TestMainArgv0); llvm::sys::fs::make_absolute(result); diff --git a/lldb/unittests/TestingSupport/TestUtilities.h b/lldb/unittests/TestingSupport/TestUtilities.h index 65994384059fb..a77a16b0812ec 100644 --- a/lldb/unittests/TestingSupport/TestUtilities.h +++ b/lldb/unittests/TestingSupport/TestUtilities.h @@ -30,6 +30,20 @@ } namespace lldb_private { + +/// Returns a pretty printed json string of a `llvm::json::Value`. +std::string pp(const llvm::json::Value &E); + +/// Converts the given value into JSON and back into the same type. +template static llvm::Expected roundtrip(const T &input) { + llvm::json::Value value = toJSON(input); + llvm::json::Path::Root root; + T output; + if (!fromJSON(value, output, root)) + return root.getError(); + return output; +} + std::string GetInputFilePath(const llvm::Twine &name); class TestUtilities { @@ -59,6 +73,7 @@ class TestFile { std::string Buffer; }; + } // namespace lldb_private #endif