| // Copyright 2021 The Verible Authors. | |
| // | |
| // Licensed under the Apache License, Version 2.0 (the "License"); | |
| // you may not use this file except in compliance with the License. | |
| // You may obtain a copy of the License at | |
| // | |
| // http://www.apache.org/licenses/LICENSE-2.0 | |
| // | |
| // Unless required by applicable law or agreed to in writing, software | |
| // distributed under the License is distributed on an "AS IS" BASIS, | |
| // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |
| // See the License for the specific language governing permissions and | |
| // limitations under the License. | |
| #include "common/lsp/message-stream-splitter.h" | |
| #include <algorithm> | |
| #include "absl/status/status.h" | |
| #include "absl/strings/string_view.h" | |
| #include "gmock/gmock.h" | |
| #include "gtest/gtest.h" | |
| using ::testing::HasSubstr; | |
| namespace verible { | |
| namespace lsp { | |
| TEST(MessageStreamSplitterTest, NotRegisteredMessageProcessor) { | |
| MessageStreamSplitter s(4096); | |
| // We need to have had a message processor registered before, otherwise | |
| // the read would not know where to send results. | |
| auto status = s.PullFrom([](char *, int) { return 0; }); | |
| EXPECT_FALSE(status.ok()); | |
| EXPECT_EQ(status.code(), absl::StatusCode::kFailedPrecondition); | |
| } | |
| // A stream simulator that is pre-filled with data and allows to | |
| // simulate partial reads. | |
| class DataStreamSimulator { | |
| public: | |
| DataStreamSimulator(absl::string_view content, int max_chunk = -1) | |
| : content_(content), max_chunk_(max_chunk) {} | |
| int read(char *buf, int size) { | |
| if (max_chunk_ > 0 && size > max_chunk_) size = max_chunk_; | |
| size = std::min(size, (int)content_.length() - read_pos_); | |
| memcpy(buf, content_.data() + read_pos_, size); | |
| read_pos_ += size; | |
| return size; | |
| } | |
| private: | |
| const std::string content_; | |
| const int max_chunk_; | |
| int read_pos_ = 0; | |
| }; | |
| TEST(MessageStreamSplitterTest, LenientHeaderNewline) { | |
| for (const bool strict_header_crlf : {false, true}) { | |
| MessageStreamSplitter header_test(256, strict_header_crlf); | |
| int count_call = 0; | |
| header_test.SetMessageProcessor( | |
| [&count_call](absl::string_view, absl::string_view body) { | |
| EXPECT_EQ(std::string(body), "x"); | |
| ++count_call; | |
| }); | |
| DataStreamSimulator two_messages( | |
| "Content-Length: 1\r\n\r\nx" // this first one will always parse | |
| "Content-Length: 1\n\nx"); // this will only parse if !strict | |
| absl::Status status = absl::OkStatus(); | |
| while (status.ok()) { | |
| status = header_test.PullFrom( | |
| [&](char *buf, int size) { return two_messages.read(buf, size); }); | |
| } | |
| if (strict_header_crlf) { | |
| // The second message never contained \r\n\r\n, still pending at end. | |
| EXPECT_EQ(status.code(), absl::StatusCode::kDataLoss) << status; | |
| } else { | |
| // An expected 'read everything reached EOF' status. | |
| EXPECT_EQ(status.code(), absl::StatusCode::kUnavailable) << status; | |
| } | |
| EXPECT_EQ(count_call, strict_header_crlf ? 1 : 2) << status; | |
| } | |
| } | |
| TEST(MessageStreamSplitterTest, CompleteReadValidMessage) { | |
| static constexpr absl::string_view kHeader = "Content-Length: 3\r\n\r\n"; | |
| static constexpr absl::string_view kBody = "foo"; | |
| DataStreamSimulator stream(absl::StrCat(kHeader, kBody)); | |
| MessageStreamSplitter s(4096); | |
| int processor_call_count = 0; | |
| s.SetMessageProcessor([&](absl::string_view header, absl::string_view body) { | |
| ++processor_call_count; | |
| EXPECT_EQ(std::string(header), kHeader); | |
| EXPECT_EQ(std::string(body), kBody); | |
| }); | |
| auto status = | |
| s.PullFrom([&](char *buf, int size) { return stream.read(buf, size); }); | |
| EXPECT_TRUE(status.ok()); | |
| EXPECT_EQ(processor_call_count, 1); | |
| // Calling more read will report EOF as we have finished our data. | |
| // This is reported as kUnavailable, the expected status code in this case. | |
| status = s.PullFrom([&](char *buf, int size) { // | |
| return stream.read(buf, size); | |
| }); | |
| EXPECT_FALSE(status.ok()); | |
| EXPECT_EQ(status.code(), absl::StatusCode::kUnavailable); | |
| EXPECT_EQ(processor_call_count, 1); // No additional calls recorded here. | |
| } | |
| TEST(MessageStreamSplitterTest, BufferSizeReallocated) { | |
| static constexpr absl::string_view kHeader = "Content-Length: 3\r\n\r\n"; | |
| static constexpr absl::string_view kBody = "foo"; | |
| DataStreamSimulator stream(absl::StrCat(kHeader, kBody)); | |
| MessageStreamSplitter s(2); // Start with way too small buffer | |
| int processor_call_count = 0; | |
| s.SetMessageProcessor([&](absl::string_view header, absl::string_view body) { | |
| EXPECT_EQ(std::string(body), "foo"); | |
| ++processor_call_count; | |
| }); | |
| absl::Status status = absl::OkStatus(); | |
| while (status.ok()) { | |
| status = | |
| s.PullFrom([&](char *buf, int size) { return stream.read(buf, size); }); | |
| } | |
| EXPECT_FALSE(status.ok()); | |
| EXPECT_EQ(status.code(), absl::StatusCode::kUnavailable); // regular EOF | |
| EXPECT_EQ(processor_call_count, 1); | |
| } | |
| TEST(MessageStreamSplitterTest, StreamDoesNotContainCompleteData) { | |
| static constexpr absl::string_view kHeader = "Content-Length: 3\r\n\r\n"; | |
| static constexpr absl::string_view kBody = "fo"; // <- too short | |
| DataStreamSimulator stream(absl::StrCat(kHeader, kBody)); | |
| MessageStreamSplitter s(4096); | |
| int processor_call_count = 0; | |
| s.SetMessageProcessor( | |
| [&](absl::string_view, absl::string_view) { ++processor_call_count; }); | |
| absl::Status status = absl::OkStatus(); | |
| while (status.ok()) { | |
| status = | |
| s.PullFrom([&](char *buf, int size) { return stream.read(buf, size); }); | |
| } | |
| // We reached EOF, but we still have data pending. Reported as data loss. | |
| EXPECT_FALSE(status.ok()); | |
| EXPECT_EQ(status.code(), absl::StatusCode::kDataLoss); | |
| EXPECT_EQ(processor_call_count, 0); | |
| } | |
| TEST(MessageStreamSplitterTest, CompleteReadMultipleMessages) { | |
| static constexpr absl::string_view kHeader = "Content-Length: 3\r\n\r\n"; | |
| static constexpr absl::string_view kBody[2] = {"foo", "bar"}; | |
| DataStreamSimulator stream( | |
| absl::StrCat(kHeader, kBody[0], kHeader, kBody[1])); | |
| MessageStreamSplitter s(4096); | |
| int processor_call_count = 0; | |
| // We expect one call per complete header/body pair. | |
| s.SetMessageProcessor([&](absl::string_view header, absl::string_view body) { | |
| EXPECT_EQ(std::string(header), kHeader); | |
| EXPECT_EQ(std::string(body), kBody[processor_call_count]); | |
| ++processor_call_count; | |
| }); | |
| auto status = s.PullFrom([&](char *buf, int size) { | |
| return stream.read(buf, size); // The complete chunk is read in one go. | |
| }); | |
| EXPECT_EQ(processor_call_count, 2); | |
| } | |
| // Simulate short reads. Each read call only trickles out a few bytes. | |
| TEST(MessageStreamSplitterTest, CompleteReadMultipleMessagesShortRead) { | |
| static constexpr absl::string_view kHeader = "Content-Length: 3\r\n\r\n"; | |
| static constexpr absl::string_view kBody[2] = {"foo", "bar"}; | |
| static constexpr int kTrickleReadSize = 2; | |
| DataStreamSimulator stream(absl::StrCat(kHeader, kBody[0], kHeader, kBody[1]), | |
| kTrickleReadSize); | |
| MessageStreamSplitter s(4096); | |
| int processor_call_count = 0; | |
| s.SetMessageProcessor([&](absl::string_view header, absl::string_view body) { | |
| EXPECT_EQ(std::string(header), kHeader); | |
| EXPECT_EQ(std::string(body), kBody[processor_call_count]); | |
| ++processor_call_count; | |
| }); | |
| int read_call_count = 0; | |
| absl::Status status = absl::OkStatus(); | |
| while (status.ok()) { | |
| ++read_call_count; | |
| status = | |
| s.PullFrom([&](char *buf, int size) { return stream.read(buf, size); }); | |
| } | |
| // Read until we reached EOF, indicated as kUnavailable | |
| EXPECT_EQ(status.code(), absl::StatusCode::kUnavailable); // EOF | |
| EXPECT_GT(read_call_count, 10); // Just checking that it is significantly > 1 | |
| EXPECT_EQ(processor_call_count, 2); | |
| } | |
| TEST(MessageStreamSplitterTest, NotAvailableContentHeaderReadError) { | |
| static constexpr absl::string_view kHeader = "not-content-length: 3\r\n\r\n"; | |
| static constexpr absl::string_view kBody = "foo"; | |
| DataStreamSimulator stream(absl::StrCat(kHeader, kBody)); | |
| MessageStreamSplitter s(4096); | |
| int processor_call_count = 0; | |
| s.SetMessageProcessor([&](absl::string_view header, absl::string_view body) { | |
| ++processor_call_count; | |
| }); | |
| auto status = | |
| s.PullFrom([&](char *buf, int size) { return stream.read(buf, size); }); | |
| EXPECT_FALSE(status.ok()); | |
| EXPECT_EQ(status.code(), absl::StatusCode::kInvalidArgument); | |
| EXPECT_THAT(status.message(), HasSubstr("header")); | |
| EXPECT_EQ(processor_call_count, 0); | |
| } | |
| TEST(MessageStreamSplitterTest, GarbledSizeInContentHeader) { | |
| static constexpr absl::string_view kHeader = "Content-Length: xyz\r\n\r\n"; | |
| static constexpr absl::string_view kBody = "foo"; | |
| DataStreamSimulator stream(absl::StrCat(kHeader, kBody)); | |
| MessageStreamSplitter s(4096); | |
| int processor_call_count = 0; | |
| s.SetMessageProcessor([&](absl::string_view header, absl::string_view body) { | |
| ++processor_call_count; | |
| }); | |
| auto status = | |
| s.PullFrom([&](char *buf, int size) { return stream.read(buf, size); }); | |
| EXPECT_FALSE(status.ok()); | |
| EXPECT_EQ(status.code(), absl::StatusCode::kInvalidArgument); | |
| EXPECT_THAT(status.message(), HasSubstr("header")); | |
| EXPECT_EQ(processor_call_count, 0); | |
| } | |
| } // namespace lsp | |
| } // namespace verible |