| // Copyright 2026 The Chromium Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| #include "quiche/quic/moqt/moqt_control_message_queue.h" |
| |
| #include "absl/status/status.h" |
| #include "absl/strings/string_view.h" |
| #include "absl/types/span.h" |
| #include "quiche/common/platform/api/quiche_test.h" |
| #include "quiche/common/quiche_buffer_allocator.h" |
| #include "quiche/common/quiche_mem_slice.h" |
| #include "quiche/common/simple_buffer_allocator.h" |
| #include "quiche/common/test_tools/quiche_test_utils.h" |
| #include "quiche/web_transport/test_tools/mock_web_transport.h" |
| #include "quiche/web_transport/web_transport.h" |
| |
| using ::testing::Return; |
| |
| namespace moqt::test { |
| namespace { |
| |
| class MoqtControlMessageQueueTest : public quiche::test::QuicheTest { |
| public: |
| MoqtControlMessageQueueTest() : queue_(&mock_stream_) {} |
| |
| quiche::QuicheBuffer MakeMessage(absl::string_view payload) { |
| return quiche::QuicheBuffer::Copy(quiche::SimpleBufferAllocator::Get(), |
| payload); |
| } |
| |
| webtransport::test::MockStream mock_stream_; |
| MoqtControlMessageQueue queue_; |
| }; |
| |
| TEST_F(MoqtControlMessageQueueTest, MessageBufferedThenSent) { |
| EXPECT_CALL(mock_stream_, CanWrite).WillRepeatedly(Return(false)); |
| EXPECT_CALL(mock_stream_, Writev).Times(0); |
| QUICHE_EXPECT_OK(queue_.SendOrBufferMessage(MakeMessage("message1"))); |
| QUICHE_EXPECT_OK(queue_.SendOrBufferMessage(MakeMessage("message2"))); |
| QUICHE_EXPECT_OK(queue_.Fin()); |
| { |
| testing::InSequence seq; |
| EXPECT_CALL(mock_stream_, CanWrite).WillRepeatedly(Return(true)); |
| EXPECT_CALL(mock_stream_, Writev) |
| .WillOnce([](absl::Span<quiche::QuicheMemSlice> slices, |
| const webtransport::StreamWriteOptions& options) { |
| EXPECT_EQ(slices.size(), 1u); |
| EXPECT_EQ(slices[0].AsStringView(), "message1"); |
| EXPECT_FALSE(options.send_fin()); |
| return absl::OkStatus(); |
| }); |
| EXPECT_CALL(mock_stream_, CanWrite).WillRepeatedly(Return(true)); |
| EXPECT_CALL(mock_stream_, Writev) |
| .WillOnce([](absl::Span<quiche::QuicheMemSlice> slices, |
| const webtransport::StreamWriteOptions& options) { |
| EXPECT_EQ(slices.size(), 1u); |
| EXPECT_EQ(slices[0].AsStringView(), "message2"); |
| EXPECT_TRUE(options.send_fin()); |
| return absl::OkStatus(); |
| }); |
| } |
| QUICHE_EXPECT_OK(queue_.OnCanWrite()); |
| } |
| |
| TEST_F(MoqtControlMessageQueueTest, FinSentWhenEmpty) { |
| EXPECT_CALL(mock_stream_, Writev) |
| .WillOnce([](absl::Span<quiche::QuicheMemSlice> slices, |
| const webtransport::StreamWriteOptions& options) { |
| EXPECT_TRUE(slices.empty()); |
| EXPECT_TRUE(options.send_fin()); |
| return absl::OkStatus(); |
| }); |
| QUICHE_EXPECT_OK(queue_.Fin()); |
| } |
| |
| TEST_F(MoqtControlMessageQueueTest, PendingQueueFull) { |
| EXPECT_CALL(mock_stream_, CanWrite).WillRepeatedly(Return(false)); |
| for (int i = 0; i < MoqtControlMessageQueue::kMaxPendingMessages; ++i) { |
| EXPECT_FALSE(queue_.QueueIsFull()); |
| QUICHE_EXPECT_OK(queue_.SendOrBufferMessage(MakeMessage("msg"))); |
| } |
| EXPECT_TRUE(queue_.QueueIsFull()); |
| EXPECT_EQ(queue_.SendOrBufferMessage(MakeMessage("msg")).code(), |
| absl::StatusCode::kResourceExhausted); |
| } |
| |
| TEST_F(MoqtControlMessageQueueTest, SendWhenCanWrite) { |
| EXPECT_CALL(mock_stream_, CanWrite).WillRepeatedly(Return(true)); |
| EXPECT_CALL(mock_stream_, Writev) |
| .WillOnce([](absl::Span<quiche::QuicheMemSlice> slices, |
| const webtransport::StreamWriteOptions& options) { |
| EXPECT_EQ(slices.size(), 1u); |
| EXPECT_EQ(slices[0].AsStringView(), "immediate"); |
| EXPECT_FALSE(options.send_fin()); |
| return absl::OkStatus(); |
| }); |
| QUICHE_EXPECT_OK( |
| queue_.SendOrBufferMessage(MakeMessage("immediate"), /*fin=*/false)); |
| } |
| |
| TEST_F(MoqtControlMessageQueueTest, ErrorWhenFinAlreadyQueued) { |
| EXPECT_CALL(mock_stream_, Writev).WillOnce(Return(absl::OkStatus())); |
| QUICHE_EXPECT_OK(queue_.Fin()); |
| EXPECT_EQ(queue_.SendOrBufferMessage(MakeMessage("msg")).code(), |
| absl::StatusCode::kInternal); |
| } |
| |
| TEST_F(MoqtControlMessageQueueTest, OnCanWriteWithoutStream) { |
| MoqtControlMessageQueue queue(nullptr); |
| EXPECT_EQ(queue.OnCanWrite().code(), absl::StatusCode::kInternal); |
| } |
| |
| TEST_F(MoqtControlMessageQueueTest, BufferFinWithoutStream) { |
| MoqtControlMessageQueue queue(nullptr); |
| QUICHE_EXPECT_OK(queue.Fin()); |
| queue.SetStream(&mock_stream_); |
| |
| EXPECT_CALL(mock_stream_, Writev) |
| .WillOnce([](absl::Span<quiche::QuicheMemSlice> slices, |
| const webtransport::StreamWriteOptions& options) { |
| EXPECT_TRUE(slices.empty()); |
| EXPECT_TRUE(options.send_fin()); |
| return absl::OkStatus(); |
| }); |
| QUICHE_EXPECT_OK(queue.OnCanWrite()); |
| } |
| |
| } // namespace |
| } // namespace moqt::test |