| // 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/common/quiche_cord_utils.h" |
| |
| #include <array> |
| #include <cstddef> |
| #include <cstdint> |
| #include <cstring> |
| #include <memory> |
| #include <string> |
| #include <utility> |
| #include <vector> |
| |
| #include "absl/strings/cord.h" |
| #include "absl/strings/str_cat.h" |
| #include "absl/strings/string_view.h" |
| #include "absl/types/span.h" |
| #include "quiche/common/platform/api/quiche_test.h" |
| #include "quiche/common/quiche_mem_slice.h" |
| |
| namespace quiche::test { |
| namespace { |
| |
| TEST(QuicheCordUtilsTest, MemSliceToCord) { |
| QuicheMemSlice slice1 = QuicheMemSlice::Copy("foo"); |
| QuicheMemSlice slice2 = QuicheMemSlice::Copy("bar"); |
| absl::Cord cord = MemSliceToCord(std::move(slice1)); |
| cord.Append(MemSliceToCord(std::move(slice2))); |
| EXPECT_EQ(cord, "foobar"); |
| } |
| |
| TEST(QuicheCordUtilsTest, MemSliceToCordEmpty) { |
| absl::Cord cord = MemSliceToCord(QuicheMemSlice()); |
| EXPECT_TRUE(cord.empty()); |
| } |
| |
| TEST(QuicheCordUtilsTest, MemSliceToCordNullDeleter) { |
| absl::string_view kText = "test"; |
| absl::Cord cord = |
| MemSliceToCord(QuicheMemSlice(kText.data(), kText.size(), nullptr)); |
| EXPECT_EQ(cord, kText); |
| } |
| |
| TEST(QuicheCordUtilsTest, MemSliceSpanToCord) { |
| std::array<QuicheMemSlice, 2> slices = {QuicheMemSlice::Copy("foo"), |
| QuicheMemSlice::Copy("bar")}; |
| absl::Cord cord = MemSliceSpanToCord(absl::MakeSpan(slices)); |
| EXPECT_EQ(cord, "foobar"); |
| } |
| |
| TEST(QuicheCordUtilsTest, CordToMemSlicesInlined) { |
| absl::Cord cord("test"); |
| std::vector<QuicheMemSlice> slices; |
| CordToMemSlicesTo(cord, slices); |
| ASSERT_EQ(slices.size(), 1); |
| EXPECT_EQ(slices[0].AsStringView(), "test"); |
| } |
| |
| TEST(QuicheCordUtilsTest, CordToMemSlicesNotInlined) { |
| constexpr size_t kSize = 8192; |
| auto buffer = std::make_unique<char[]>(kSize); |
| uintptr_t original_address = reinterpret_cast<uintptr_t>(buffer.get()); |
| memset(buffer.get(), 'a', kSize); |
| absl::Cord cord = absl::MakeCordFromExternal( |
| absl::string_view(buffer.release(), kSize), |
| [](absl::string_view data) { delete[] data.data(); }); |
| std::vector<QuicheMemSlice> slices; |
| CordToMemSlicesTo(cord, slices); |
| cord.Clear(); |
| |
| ASSERT_EQ(slices.size(), 1); |
| EXPECT_EQ(slices[0].length(), kSize); |
| EXPECT_EQ(reinterpret_cast<uintptr_t>(slices[0].data()), original_address); |
| EXPECT_EQ(slices[0].AsStringView(), std::string(kSize, 'a')); |
| } |
| |
| TEST(QuicheCordUtilsTest, CordToMemSlicesLarge) { |
| const std::string kBlock(1024, 'a'); |
| constexpr size_t kBlockCount = 128; |
| absl::Cord cord; |
| for (size_t i = 0; i < kBlockCount; ++i) { |
| cord.Append(kBlock); |
| } |
| |
| std::vector<QuicheMemSlice> slices; |
| CordToMemSlicesTo(cord, slices); |
| cord.Clear(); |
| |
| ASSERT_GT(slices.size(), 1); |
| size_t total_size = 0; |
| for (const QuicheMemSlice& slice : slices) { |
| total_size += slice.length(); |
| } |
| EXPECT_EQ(total_size, kBlock.size() * kBlockCount); |
| for (const QuicheMemSlice& slice : slices) { |
| ASSERT_THAT(slice.AsStringView(), testing::Each('a')); |
| } |
| } |
| |
| TEST(QuicheCordUtilsTest, CordWithMixedTypes) { |
| absl::Cord cord("bar"); |
| cord.Prepend("foo"); |
| |
| auto block_before = std::make_unique<std::string>(8192, 'a'); |
| absl::string_view block_before_view(*block_before); |
| cord.Prepend(absl::MakeCordFromExternal( |
| block_before_view, |
| [ptr = block_before.release()](absl::string_view) { delete ptr; })); |
| |
| auto block_after = std::make_unique<std::string>(8192, 'b'); |
| absl::string_view block_after_view(*block_after); |
| cord.Append(absl::MakeCordFromExternal( |
| block_after_view, |
| [ptr = block_after.release()](absl::string_view) { delete ptr; })); |
| |
| std::vector<QuicheMemSlice> slices; |
| CordToMemSlicesTo(cord, slices); |
| cord.Clear(); |
| |
| ASSERT_GT(slices.size(), 1); |
| std::string concatenated; |
| for (const QuicheMemSlice& slice : slices) { |
| absl::StrAppend(&concatenated, slice.AsStringView()); |
| } |
| EXPECT_EQ(concatenated, absl::StrCat(std::string(8192, 'a'), "foobar", |
| std::string(8192, 'b'))); |
| } |
| |
| TEST(QuicheCordUtilsTest, Subcord) { |
| constexpr size_t kCount = 100; |
| absl::Cord cord; |
| for (size_t i = 0; i < kCount; ++i) { |
| cord.Append("foobar"); |
| } |
| absl::Cord subcord = cord.Subcord(99, 6); |
| std::vector<QuicheMemSlice> slices; |
| CordToMemSlicesTo(subcord, slices); |
| ASSERT_EQ(slices.size(), 1); |
| EXPECT_EQ(slices[0].AsStringView(), "barfoo"); // 99 = 16 * 6 + 3 |
| cord.Clear(); |
| subcord.Clear(); |
| EXPECT_EQ(slices[0].AsStringView(), "barfoo"); |
| } |
| |
| } // namespace |
| } // namespace quiche::test |