Skip to content

Commit 9ebb398

Browse files
committed
[lldb] Implement JSON RPC (newline delimited) Transport
This PR implements JSON RPC-style (i.e. newline delimited) JSON transport. I moved the existing transport tests from DAP to Host and moved the PipeTest base class into TestingSupport so it can be shared by both.
1 parent 2e5fb77 commit 9ebb398

File tree

9 files changed

+223
-117
lines changed

9 files changed

+223
-117
lines changed

lldb/include/lldb/Host/JSONTransport.h

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -121,6 +121,21 @@ class HTTPDelimitedJSONTransport : public JSONTransport {
121121
static constexpr llvm::StringLiteral kHeaderSeparator = "\r\n\r\n";
122122
};
123123

124+
/// A transport class for JSON RPC.
125+
class JSONRPCTransport : public JSONTransport {
126+
public:
127+
JSONRPCTransport(lldb::IOObjectSP input, lldb::IOObjectSP output)
128+
: JSONTransport(input, output) {}
129+
virtual ~JSONRPCTransport() = default;
130+
131+
protected:
132+
virtual llvm::Error WriteImpl(const std::string &message) override;
133+
virtual llvm::Expected<std::string>
134+
ReadImpl(const std::chrono::microseconds &timeout) override;
135+
136+
static constexpr llvm::StringLiteral kMessageSeparator = "\n";
137+
};
138+
124139
} // namespace lldb_private
125140

126141
#endif

lldb/source/Host/common/JSONTransport.cpp

Lines changed: 30 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -92,7 +92,7 @@ void JSONTransport::Log(llvm::StringRef message) {
9292
Expected<std::string>
9393
HTTPDelimitedJSONTransport::ReadImpl(const std::chrono::microseconds &timeout) {
9494
if (!m_input || !m_input->IsValid())
95-
return createStringError("transport output is closed");
95+
return llvm::make_error<TransportClosedError>();
9696

9797
IOObject *input = m_input.get();
9898
Expected<std::string> message_header =
@@ -142,6 +142,35 @@ Error HTTPDelimitedJSONTransport::WriteImpl(const std::string &message) {
142142
return m_output->Write(Output.data(), num_bytes).takeError();
143143
}
144144

145+
Expected<std::string>
146+
JSONRPCTransport::ReadImpl(const std::chrono::microseconds &timeout) {
147+
if (!m_input || !m_input->IsValid())
148+
return make_error<TransportClosedError>();
149+
150+
IOObject *input = m_input.get();
151+
Expected<std::string> raw_json =
152+
ReadUntil(*input, kMessageSeparator, timeout);
153+
if (!raw_json)
154+
return raw_json.takeError();
155+
156+
Log(llvm::formatv("--> {0}", *raw_json).str());
157+
158+
return *raw_json;
159+
}
160+
161+
Error JSONRPCTransport::WriteImpl(const std::string &message) {
162+
if (!m_output || !m_output->IsValid())
163+
return llvm::make_error<TransportClosedError>();
164+
165+
Log(llvm::formatv("<-- {0}", message).str());
166+
167+
std::string Output;
168+
llvm::raw_string_ostream OS(Output);
169+
OS << message << kMessageSeparator;
170+
size_t num_bytes = Output.size();
171+
return m_output->Write(Output.data(), num_bytes).takeError();
172+
}
173+
145174
char TransportEOFError::ID;
146175
char TransportTimeoutError::ID;
147176
char TransportClosedError::ID;

lldb/unittests/DAP/CMakeLists.txt

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,6 @@ add_lldb_unittest(DAPTests
77
LLDBUtilsTest.cpp
88
ProtocolTypesTest.cpp
99
TestBase.cpp
10-
TransportTest.cpp
1110
VariablesTest.cpp
1211

1312
LINK_COMPONENTS

lldb/unittests/DAP/TestBase.cpp

Lines changed: 1 addition & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -28,13 +28,8 @@ using lldb_private::File;
2828
using lldb_private::NativeFile;
2929
using lldb_private::Pipe;
3030

31-
void PipeBase::SetUp() {
32-
ASSERT_THAT_ERROR(input.CreateNew(false).ToError(), Succeeded());
33-
ASSERT_THAT_ERROR(output.CreateNew(false).ToError(), Succeeded());
34-
}
35-
3631
void TransportBase::SetUp() {
37-
PipeBase::SetUp();
32+
PipeTest::SetUp();
3833
to_dap = std::make_unique<Transport>(
3934
"to_dap", nullptr,
4035
std::make_shared<NativeFile>(input.GetReadFileDescriptor(),

lldb/unittests/DAP/TestBase.h

Lines changed: 2 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -8,26 +8,17 @@
88

99
#include "DAP.h"
1010
#include "Protocol/ProtocolBase.h"
11+
#include "TestingSupport/Host/PipeTestUtilities.h"
1112
#include "Transport.h"
12-
#include "lldb/Host/Pipe.h"
1313
#include "llvm/ADT/StringRef.h"
1414
#include "gmock/gmock.h"
1515
#include "gtest/gtest.h"
1616

1717
namespace lldb_dap_tests {
1818

19-
/// A base class for tests that need a pair of pipes for communication.
20-
class PipeBase : public testing::Test {
21-
protected:
22-
lldb_private::Pipe input;
23-
lldb_private::Pipe output;
24-
25-
void SetUp() override;
26-
};
27-
2819
/// A base class for tests that need transport configured for communicating DAP
2920
/// messages.
30-
class TransportBase : public PipeBase {
21+
class TransportBase : public PipeTest {
3122
protected:
3223
std::unique_ptr<lldb_dap::Transport> to_dap;
3324
std::unique_ptr<lldb_dap::Transport> from_dap;

lldb/unittests/DAP/TransportTest.cpp

Lines changed: 0 additions & 98 deletions
This file was deleted.

lldb/unittests/Host/CMakeLists.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ set (FILES
66
HostInfoTest.cpp
77
HostTest.cpp
88
MainLoopTest.cpp
9+
JSONTransportTest.cpp
910
NativeProcessProtocolTest.cpp
1011
PipeTest.cpp
1112
ProcessLaunchInfoTest.cpp
Lines changed: 146 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,146 @@
1+
//===-- JSONTransportTest.cpp ---------------------------------------------===//
2+
//
3+
// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
4+
// See https://llvm.org/LICENSE.txt for license information.
5+
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
6+
//
7+
//===----------------------------------------------------------------------===//
8+
9+
#include "lldb/Host/JSONTransport.h"
10+
#include "TestingSupport/Host/PipeTestUtilities.h"
11+
#include "lldb/Host/File.h"
12+
13+
using namespace llvm;
14+
using namespace lldb_private;
15+
16+
namespace {
17+
template <typename T> class JSONTransportTest : public PipeTest {
18+
protected:
19+
std::unique_ptr<JSONTransport> transport;
20+
21+
void SetUp() override {
22+
PipeTest::SetUp();
23+
transport = std::make_unique<T>(
24+
std::make_shared<NativeFile>(input.GetReadFileDescriptor(),
25+
File::eOpenOptionReadOnly,
26+
NativeFile::Unowned),
27+
std::make_shared<NativeFile>(output.GetWriteFileDescriptor(),
28+
File::eOpenOptionWriteOnly,
29+
NativeFile::Unowned));
30+
}
31+
};
32+
33+
class HTTPDelimitedJSONTransportTest
34+
: public JSONTransportTest<HTTPDelimitedJSONTransport> {
35+
public:
36+
using JSONTransportTest::JSONTransportTest;
37+
};
38+
39+
class JSONRPCTransportTest : public JSONTransportTest<JSONRPCTransport> {
40+
public:
41+
using JSONTransportTest::JSONTransportTest;
42+
};
43+
44+
struct JSONTestType {
45+
std::string str;
46+
};
47+
48+
llvm::json::Value toJSON(const JSONTestType &T) {
49+
return llvm::json::Object{{"str", T.str}};
50+
}
51+
52+
bool fromJSON(const llvm::json::Value &V, JSONTestType &T, llvm::json::Path P) {
53+
llvm::json::ObjectMapper O(V, P);
54+
return O && O.map("str", T.str);
55+
}
56+
} // namespace
57+
58+
TEST_F(HTTPDelimitedJSONTransportTest, MalformedRequests) {
59+
std::string malformed_header = "COnTent-LenGth: -1{}\r\n\r\nnotjosn";
60+
ASSERT_THAT_EXPECTED(
61+
input.Write(malformed_header.data(), malformed_header.size()),
62+
Succeeded());
63+
ASSERT_THAT_EXPECTED(
64+
transport->Read<JSONTestType>(std::chrono::milliseconds(1)),
65+
FailedWithMessage(
66+
"expected 'Content-Length: ' and got 'COnTent-LenGth: '"));
67+
}
68+
69+
TEST_F(HTTPDelimitedJSONTransportTest, Read) {
70+
std::string json = R"json({"str": "foo"})json";
71+
std::string message =
72+
formatv("Content-Length: {0}\r\n\r\n{1}", json.size(), json).str();
73+
ASSERT_THAT_EXPECTED(input.Write(message.data(), message.size()),
74+
Succeeded());
75+
ASSERT_THAT_EXPECTED(
76+
transport->Read<JSONTestType>(std::chrono::milliseconds(1)),
77+
HasValue(testing::FieldsAre(/*str=*/"foo")));
78+
}
79+
80+
TEST_F(HTTPDelimitedJSONTransportTest, ReadWithTimeout) {
81+
ASSERT_THAT_EXPECTED(
82+
transport->Read<JSONTestType>(std::chrono::milliseconds(1)),
83+
Failed<TransportTimeoutError>());
84+
}
85+
86+
TEST_F(HTTPDelimitedJSONTransportTest, ReadWithEOF) {
87+
input.CloseWriteFileDescriptor();
88+
ASSERT_THAT_EXPECTED(
89+
transport->Read<JSONTestType>(std::chrono::milliseconds(1)),
90+
Failed<TransportEOFError>());
91+
}
92+
93+
TEST_F(HTTPDelimitedJSONTransportTest, Write) {
94+
ASSERT_THAT_ERROR(transport->Write(JSONTestType{"foo"}), Succeeded());
95+
output.CloseWriteFileDescriptor();
96+
char buf[1024];
97+
Expected<size_t> bytes_read =
98+
output.Read(buf, sizeof(buf), std::chrono::milliseconds(1));
99+
ASSERT_THAT_EXPECTED(bytes_read, Succeeded());
100+
ASSERT_EQ(StringRef(buf, *bytes_read), StringRef("Content-Length: 13\r\n\r\n"
101+
R"json({"str":"foo"})json"));
102+
}
103+
104+
TEST_F(JSONRPCTransportTest, MalformedRequests) {
105+
std::string malformed_header = "notjson\n";
106+
ASSERT_THAT_EXPECTED(
107+
input.Write(malformed_header.data(), malformed_header.size()),
108+
Succeeded());
109+
ASSERT_THAT_EXPECTED(
110+
transport->Read<JSONTestType>(std::chrono::milliseconds(1)),
111+
llvm::Failed());
112+
}
113+
114+
TEST_F(JSONRPCTransportTest, Read) {
115+
std::string json = R"json({"str": "foo"})json";
116+
std::string message = formatv("{0}\n", json).str();
117+
ASSERT_THAT_EXPECTED(input.Write(message.data(), message.size()),
118+
Succeeded());
119+
ASSERT_THAT_EXPECTED(
120+
transport->Read<JSONTestType>(std::chrono::milliseconds(1)),
121+
HasValue(testing::FieldsAre(/*str=*/"foo")));
122+
}
123+
124+
TEST_F(JSONRPCTransportTest, ReadWithTimeout) {
125+
ASSERT_THAT_EXPECTED(
126+
transport->Read<JSONTestType>(std::chrono::milliseconds(1)),
127+
Failed<TransportTimeoutError>());
128+
}
129+
130+
TEST_F(JSONRPCTransportTest, ReadWithEOF) {
131+
input.CloseWriteFileDescriptor();
132+
ASSERT_THAT_EXPECTED(
133+
transport->Read<JSONTestType>(std::chrono::milliseconds(1)),
134+
Failed<TransportEOFError>());
135+
}
136+
137+
TEST_F(JSONRPCTransportTest, Write) {
138+
ASSERT_THAT_ERROR(transport->Write(JSONTestType{"foo"}), Succeeded());
139+
output.CloseWriteFileDescriptor();
140+
char buf[1024];
141+
Expected<size_t> bytes_read =
142+
output.Read(buf, sizeof(buf), std::chrono::milliseconds(1));
143+
ASSERT_THAT_EXPECTED(bytes_read, Succeeded());
144+
ASSERT_EQ(StringRef(buf, *bytes_read), StringRef(R"json({"str":"foo"})json"
145+
"\n"));
146+
}

0 commit comments

Comments
 (0)