-
Couldn't load subscription status.
- Fork 15k
[lldb] Implement JSON RPC (newline delimited) Transport #143946
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
Changes from 1 commit
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
This file was deleted.
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,146 @@ | ||
| //===-- JSONTransportTest.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 "lldb/Host/JSONTransport.h" | ||
| #include "TestingSupport/Host/PipeTestUtilities.h" | ||
| #include "lldb/Host/File.h" | ||
|
|
||
| using namespace llvm; | ||
| using namespace lldb_private; | ||
|
|
||
| namespace { | ||
| template <typename T> class JSONTransportTest : public PipeTest { | ||
| protected: | ||
| std::unique_ptr<JSONTransport> transport; | ||
|
|
||
| void SetUp() override { | ||
| PipeTest::SetUp(); | ||
| transport = std::make_unique<T>( | ||
| std::make_shared<NativeFile>(input.GetReadFileDescriptor(), | ||
| File::eOpenOptionReadOnly, | ||
| NativeFile::Unowned), | ||
| std::make_shared<NativeFile>(output.GetWriteFileDescriptor(), | ||
| File::eOpenOptionWriteOnly, | ||
| NativeFile::Unowned)); | ||
| } | ||
| }; | ||
|
|
||
| class HTTPDelimitedJSONTransportTest | ||
| : public JSONTransportTest<HTTPDelimitedJSONTransport> { | ||
| public: | ||
| using JSONTransportTest::JSONTransportTest; | ||
| }; | ||
|
|
||
| class JSONRPCTransportTest : public JSONTransportTest<JSONRPCTransport> { | ||
| public: | ||
| using JSONTransportTest::JSONTransportTest; | ||
| }; | ||
|
|
||
| struct JSONTestType { | ||
| std::string str; | ||
| }; | ||
|
|
||
| llvm::json::Value toJSON(const JSONTestType &T) { | ||
| return llvm::json::Object{{"str", T.str}}; | ||
| } | ||
|
|
||
| bool fromJSON(const llvm::json::Value &V, JSONTestType &T, llvm::json::Path P) { | ||
| llvm::json::ObjectMapper O(V, P); | ||
| return O && O.map("str", T.str); | ||
| } | ||
| } // namespace | ||
|
|
||
| TEST_F(HTTPDelimitedJSONTransportTest, MalformedRequests) { | ||
| std::string malformed_header = "COnTent-LenGth: -1{}\r\n\r\nnotjosn"; | ||
| ASSERT_THAT_EXPECTED( | ||
| input.Write(malformed_header.data(), malformed_header.size()), | ||
| Succeeded()); | ||
| ASSERT_THAT_EXPECTED( | ||
| transport->Read<JSONTestType>(std::chrono::milliseconds(1)), | ||
| FailedWithMessage( | ||
| "expected 'Content-Length: ' and got 'COnTent-LenGth: '")); | ||
| } | ||
|
|
||
| TEST_F(HTTPDelimitedJSONTransportTest, Read) { | ||
| std::string json = R"json({"str": "foo"})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<JSONTestType>(std::chrono::milliseconds(1)), | ||
| HasValue(testing::FieldsAre(/*str=*/"foo"))); | ||
| } | ||
|
|
||
| TEST_F(HTTPDelimitedJSONTransportTest, ReadWithTimeout) { | ||
| ASSERT_THAT_EXPECTED( | ||
| transport->Read<JSONTestType>(std::chrono::milliseconds(1)), | ||
| Failed<TransportTimeoutError>()); | ||
| } | ||
|
|
||
| TEST_F(HTTPDelimitedJSONTransportTest, ReadWithEOF) { | ||
| input.CloseWriteFileDescriptor(); | ||
| ASSERT_THAT_EXPECTED( | ||
| transport->Read<JSONTestType>(std::chrono::milliseconds(1)), | ||
| Failed<TransportEOFError>()); | ||
| } | ||
|
|
||
| TEST_F(HTTPDelimitedJSONTransportTest, Write) { | ||
| ASSERT_THAT_ERROR(transport->Write(JSONTestType{"foo"}), Succeeded()); | ||
| output.CloseWriteFileDescriptor(); | ||
| char buf[1024]; | ||
| Expected<size_t> bytes_read = | ||
| output.Read(buf, sizeof(buf), std::chrono::milliseconds(1)); | ||
| ASSERT_THAT_EXPECTED(bytes_read, Succeeded()); | ||
| ASSERT_EQ(StringRef(buf, *bytes_read), StringRef("Content-Length: 13\r\n\r\n" | ||
| R"json({"str":"foo"})json")); | ||
| } | ||
|
|
||
| TEST_F(JSONRPCTransportTest, MalformedRequests) { | ||
| std::string malformed_header = "notjson\n"; | ||
| ASSERT_THAT_EXPECTED( | ||
| input.Write(malformed_header.data(), malformed_header.size()), | ||
| Succeeded()); | ||
| ASSERT_THAT_EXPECTED( | ||
| transport->Read<JSONTestType>(std::chrono::milliseconds(1)), | ||
| llvm::Failed()); | ||
| } | ||
|
|
||
| TEST_F(JSONRPCTransportTest, Read) { | ||
| std::string json = R"json({"str": "foo"})json"; | ||
| std::string message = formatv("{0}\n", json).str(); | ||
| ASSERT_THAT_EXPECTED(input.Write(message.data(), message.size()), | ||
| Succeeded()); | ||
| ASSERT_THAT_EXPECTED( | ||
| transport->Read<JSONTestType>(std::chrono::milliseconds(1)), | ||
| HasValue(testing::FieldsAre(/*str=*/"foo"))); | ||
| } | ||
|
|
||
| TEST_F(JSONRPCTransportTest, ReadWithTimeout) { | ||
| ASSERT_THAT_EXPECTED( | ||
| transport->Read<JSONTestType>(std::chrono::milliseconds(1)), | ||
| Failed<TransportTimeoutError>()); | ||
| } | ||
|
|
||
| TEST_F(JSONRPCTransportTest, ReadWithEOF) { | ||
| input.CloseWriteFileDescriptor(); | ||
| ASSERT_THAT_EXPECTED( | ||
| transport->Read<JSONTestType>(std::chrono::milliseconds(1)), | ||
| Failed<TransportEOFError>()); | ||
| } | ||
|
|
||
| TEST_F(JSONRPCTransportTest, Write) { | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Should we also have a test where we close the input or output handle before the read/write to trigger the There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Added a test and renamed the error as the current name isn't really accurate: this error occurs when the underlying IO object is invalid. If you close the write side, you get an EOF error, which we have a test for. If you close the read side, then you get a bad file descriptor error, which I've added a new test for too. |
||
| ASSERT_THAT_ERROR(transport->Write(JSONTestType{"foo"}), Succeeded()); | ||
| output.CloseWriteFileDescriptor(); | ||
| char buf[1024]; | ||
| Expected<size_t> bytes_read = | ||
| output.Read(buf, sizeof(buf), std::chrono::milliseconds(1)); | ||
| ASSERT_THAT_EXPECTED(bytes_read, Succeeded()); | ||
| ASSERT_EQ(StringRef(buf, *bytes_read), StringRef(R"json({"str":"foo"})json" | ||
| "\n")); | ||
| } | ||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I've just realized, should this test be skipped on Windows?
The lldb
SelectHelperonly works with sockets on Windows and under the hood thePipeis making a named socket, but I'm not sure if we're using the right API to realize that or not. When we create the handle we're usingNativeFilethat may be handling this correctly.This all might be working correctly on Windows, but I'll be honest that I cannot tell for sure.
The frustrating (for me) part is that on Windows, stdin/stdout are anonymous pipes and you cannot use overlapping IO operations on an anonymous pipe on Windows. Libraries like libdispatch (used by swift), libuv (used by nodejs and others), pythons asyncio, etc. on Windows emulate this behavior by spawning a thread to do the reading, but at the moment we don't have that sort of abstraction in lldb yet.
If we're planning on making more improvements in this area for a json-rpc server, we may need to add this to lldb so we can properly handle stopping these read threads.
I'll be honest though that I haven't done much programing on Windows and I tried seeing if I could figure this out when I was working on lldb-dap server mode and kept running into issues. I could be wrong on some of this, since I don't know the Windows APIs all that well.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ack, yeah I'm far from an expert myself, but it's good context, thanks.