// Copyright 2023 The Pigweed 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 // // https://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 "pw_multibuf/multibuf.h" #include "pw_assert/check.h" #include "pw_bytes/array.h" #include "pw_bytes/suffix.h" #include "pw_multibuf_private/test_utils.h" #include "pw_span/span.h" #include "pw_unit_test/framework.h" namespace pw::multibuf { namespace { using namespace pw::multibuf::test_utils; #if __cplusplus >= 202002L static_assert(std::forward_iterator); static_assert(std::forward_iterator); static_assert(std::forward_iterator); static_assert(std::forward_iterator); #endif // __cplusplus >= 202002L static_assert( sizeof(MultiBufChunks) == sizeof(MultiBuf), "MultiBuf is a byte view of MultiBufChunks and does not add members"); TEST(MultiBuf, IsDefaultConstructible) { [[maybe_unused]] MultiBuf buf; } TEST(MultiBuf, WithOneChunkReleases) { AllocatorForTest allocator; const auto& metrics = allocator.metrics(); MultiBuf buf; buf.PushFrontChunk(MakeChunk(allocator, kArbitraryChunkSize)); EXPECT_EQ(metrics.num_allocations.value(), 2U); buf.Release(); EXPECT_EQ(metrics.num_deallocations.value(), 2U); } TEST(MultiBuf, WithOneChunkReleasesOnDestruction) { AllocatorForTest allocator; const auto& metrics = allocator.metrics(); { MultiBuf buf; buf.PushFrontChunk(MakeChunk(allocator, kArbitraryChunkSize)); EXPECT_EQ(metrics.num_allocations.value(), 2U); } EXPECT_EQ(metrics.num_deallocations.value(), 2U); } TEST(MultiBuf, WithMultipleChunksReleasesAllOnDestruction) { AllocatorForTest allocator; const auto& metrics = allocator.metrics(); { MultiBuf buf; buf.PushFrontChunk(MakeChunk(allocator, kArbitraryChunkSize)); buf.PushFrontChunk(MakeChunk(allocator, kArbitraryChunkSize)); EXPECT_EQ(metrics.num_allocations.value(), 4U); } EXPECT_EQ(metrics.num_deallocations.value(), 4U); } TEST(MultiBuf, SizeReturnsNumberOfBytes) { AllocatorForTest allocator; MultiBuf buf; EXPECT_EQ(buf.size(), 0U); buf.PushFrontChunk(MakeChunk(allocator, kArbitraryChunkSize)); EXPECT_EQ(buf.size(), kArbitraryChunkSize); buf.PushFrontChunk(MakeChunk(allocator, kArbitraryChunkSize)); EXPECT_EQ(buf.size(), kArbitraryChunkSize * 2); } TEST(MultiBuf, EmptyIfNoChunks) { AllocatorForTest allocator; MultiBuf buf; EXPECT_EQ(buf.size(), 0U); EXPECT_TRUE(buf.empty()); buf.PushFrontChunk(MakeChunk(allocator, kArbitraryChunkSize)); EXPECT_NE(buf.size(), 0U); EXPECT_FALSE(buf.empty()); } TEST(MultiBuf, EmptyIfOnlyEmptyChunks) { AllocatorForTest allocator; MultiBuf buf; EXPECT_TRUE(buf.empty()); buf.PushFrontChunk(MakeChunk(allocator, 0)); EXPECT_TRUE(buf.empty()); buf.PushFrontChunk(MakeChunk(allocator, 0)); EXPECT_TRUE(buf.empty()); EXPECT_EQ(buf.size(), 0U); } TEST(MultiBuf, EmptyIsFalseIfAnyNonEmptyChunks) { AllocatorForTest allocator; MultiBuf buf; buf.PushFrontChunk(MakeChunk(allocator, 0)); EXPECT_TRUE(buf.empty()); buf.PushFrontChunk(MakeChunk(allocator, kArbitraryChunkSize)); EXPECT_FALSE(buf.empty()); EXPECT_EQ(buf.size(), kArbitraryChunkSize); } TEST(MultiBuf, ClaimPrefixReclaimsFirstChunkPrefix) { AllocatorForTest allocator; MultiBuf buf; OwnedChunk chunk = MakeChunk(allocator, 16); chunk->DiscardPrefix(7); buf.PushFrontChunk(std::move(chunk)); EXPECT_EQ(buf.size(), 9U); EXPECT_EQ(buf.ClaimPrefix(7), true); EXPECT_EQ(buf.size(), 16U); } TEST(MultiBuf, ClaimPrefixOnFirstChunkWithoutPrefixReturnsFalse) { AllocatorForTest allocator; MultiBuf buf; buf.PushFrontChunk(MakeChunk(allocator, 16)); EXPECT_EQ(buf.size(), 16U); EXPECT_EQ(buf.ClaimPrefix(7), false); EXPECT_EQ(buf.size(), 16U); } TEST(MultiBuf, ClaimPrefixWithoutChunksReturnsFalse) { AllocatorForTest allocator; MultiBuf buf; EXPECT_EQ(buf.size(), 0U); EXPECT_EQ(buf.ClaimPrefix(7), false); EXPECT_EQ(buf.size(), 0U); } TEST(MultiBuf, ClaimSuffixReclaimsLastChunkSuffix) { AllocatorForTest allocator; MultiBuf buf; OwnedChunk chunk = MakeChunk(allocator, 16U); chunk->Truncate(9U); buf.PushFrontChunk(std::move(chunk)); buf.PushFrontChunk(MakeChunk(allocator, 4U)); EXPECT_EQ(buf.size(), 13U); EXPECT_EQ(buf.ClaimSuffix(7U), true); EXPECT_EQ(buf.size(), 20U); } TEST(MultiBuf, ClaimSuffixOnLastChunkWithoutSuffixReturnsFalse) { AllocatorForTest allocator; MultiBuf buf; buf.PushFrontChunk(MakeChunk(allocator, 16U)); EXPECT_EQ(buf.size(), 16U); EXPECT_EQ(buf.ClaimPrefix(7U), false); EXPECT_EQ(buf.size(), 16U); } TEST(MultiBuf, ClaimSuffixWithoutChunksReturnsFalse) { AllocatorForTest allocator; MultiBuf buf; EXPECT_EQ(buf.size(), 0U); EXPECT_EQ(buf.ClaimSuffix(7U), false); EXPECT_EQ(buf.size(), 0U); } TEST(MultiBuf, DiscardPrefixWithZeroDoesNothing) { AllocatorForTest allocator; MultiBuf buf; buf.DiscardPrefix(0); EXPECT_EQ(buf.size(), 0U); } TEST(MultiBuf, DiscardPrefixDiscardsPartialChunk) { AllocatorForTest allocator; MultiBuf buf; buf.PushFrontChunk(MakeChunk(allocator, 16U)); buf.DiscardPrefix(5U); EXPECT_EQ(buf.size(), 11U); } TEST(MultiBuf, DiscardPrefixDiscardsWholeChunk) { AllocatorForTest allocator; MultiBuf buf; buf.PushFrontChunk(MakeChunk(allocator, 16U)); buf.PushFrontChunk(MakeChunk(allocator, 3U)); buf.DiscardPrefix(16U); EXPECT_EQ(buf.size(), 3U); } TEST(MultiBuf, DiscardPrefixDiscardsMultipleChunks) { AllocatorForTest allocator; MultiBuf buf; buf.PushFrontChunk(MakeChunk(allocator, 16U)); buf.PushFrontChunk(MakeChunk(allocator, 4U)); buf.PushFrontChunk(MakeChunk(allocator, 3U)); buf.DiscardPrefix(21U); EXPECT_EQ(buf.size(), 2U); } TEST(MultiBuf, SliceDiscardsPrefixAndSuffixWholeAndPartialChunks) { AllocatorForTest allocator; MultiBuf buf; buf.PushBackChunk(MakeChunk(allocator, {1_b, 1_b, 1_b})); buf.PushBackChunk(MakeChunk(allocator, {2_b, 2_b, 2_b})); buf.PushBackChunk(MakeChunk(allocator, {3_b, 3_b, 3_b})); buf.PushBackChunk(MakeChunk(allocator, {4_b, 4_b, 4_b})); buf.Slice(4, 7); ExpectElementsEqual(buf, {2_b, 2_b, 3_b}); } TEST(MultiBuf, SliceDoesNotModifyChunkMemory) { AllocatorForTest allocator; MultiBuf buf; std::array kBytes = {1_b, 2_b, 3_b, 4_b}; OwnedChunk chunk = MakeChunk(allocator, kBytes); ConstByteSpan span(chunk); buf.PushFrontChunk(std::move(chunk)); buf.Slice(2, 3); ExpectElementsEqual(span, kBytes); } TEST(MultiBuf, TruncateRemovesFinalEmptyChunk) { AllocatorForTest allocator; MultiBuf buf; buf.PushFrontChunk(MakeChunk(allocator, 3U)); buf.PushFrontChunk(MakeChunk(allocator, 3U)); buf.Truncate(3U); EXPECT_EQ(buf.size(), 3U); EXPECT_EQ(buf.Chunks().size(), 1U); } TEST(MultiBuf, TruncateRemovesWholeAndPartialChunks) { AllocatorForTest allocator; MultiBuf buf; buf.PushFrontChunk(MakeChunk(allocator, 3U)); buf.PushFrontChunk(MakeChunk(allocator, 3U)); buf.Truncate(2U); EXPECT_EQ(buf.size(), 2U); } TEST(MultiBuf, TruncateAfterRemovesWholeAndPartialChunks) { AllocatorForTest allocator; MultiBuf buf; buf.PushFrontChunk(MakeChunk(allocator, 3U)); buf.PushFrontChunk(MakeChunk(allocator, 0U)); buf.PushFrontChunk(MakeChunk(allocator, 1U)); auto it = buf.begin(); ++it; buf.TruncateAfter(it); EXPECT_EQ(buf.size(), 2U); } TEST(MultiBuf, TruncateEmptyBuffer) { MultiBuf buf; buf.Truncate(0); EXPECT_TRUE(buf.empty()); } TEST(MultiBuf, TakePrefixWithNoBytesDoesNothing) { AllocatorForTest allocator; MultiBuf buf; std::optional empty_front = buf.TakePrefix(0); ASSERT_TRUE(empty_front.has_value()); EXPECT_EQ(buf.size(), 0U); EXPECT_EQ(empty_front->size(), 0U); } TEST(MultiBuf, TakePrefixReturnsPartialChunk) { AllocatorForTest allocator; MultiBuf buf; buf.PushBackChunk(MakeChunk(allocator, {1_b, 2_b, 3_b})); std::optional old_front = buf.TakePrefix(2); ASSERT_TRUE(old_front.has_value()); ExpectElementsEqual(*old_front, {1_b, 2_b}); ExpectElementsEqual(buf, {3_b}); } TEST(MultiBuf, TakePrefixReturnsWholeAndPartialChunks) { AllocatorForTest allocator; MultiBuf buf; buf.PushBackChunk(MakeChunk(allocator, {1_b, 2_b, 3_b})); buf.PushBackChunk(MakeChunk(allocator, {4_b, 5_b, 6_b})); std::optional old_front = buf.TakePrefix(4); ASSERT_TRUE(old_front.has_value()); ExpectElementsEqual(*old_front, {1_b, 2_b, 3_b, 4_b}); ExpectElementsEqual(buf, {5_b, 6_b}); } TEST(MultiBuf, TakeSuffixReturnsWholeAndPartialChunks) { AllocatorForTest allocator; MultiBuf buf; buf.PushBackChunk(MakeChunk(allocator, {1_b, 2_b, 3_b})); buf.PushBackChunk(MakeChunk(allocator, {4_b, 5_b, 6_b})); std::optional old_tail = buf.TakeSuffix(4); ASSERT_TRUE(old_tail.has_value()); ExpectElementsEqual(buf, {1_b, 2_b}); ExpectElementsEqual(*old_tail, {3_b, 4_b, 5_b, 6_b}); } TEST(MultiBuf, PushPrefixPrependsData) { AllocatorForTest allocator; MultiBuf buf; buf.PushBackChunk(MakeChunk(allocator, {1_b, 2_b, 3_b})); buf.PushBackChunk(MakeChunk(allocator, {4_b, 5_b, 6_b})); MultiBuf buf2; buf2.PushBackChunk(MakeChunk(allocator, {7_b, 8_b})); buf2.PushPrefix(std::move(buf)); ExpectElementsEqual(buf2, {1_b, 2_b, 3_b, 4_b, 5_b, 6_b, 7_b, 8_b}); } TEST(MultiBuf, PushSuffixAppendsData) { AllocatorForTest allocator; MultiBuf buf; buf.PushBackChunk(MakeChunk(allocator, {1_b, 2_b, 3_b})); buf.PushBackChunk(MakeChunk(allocator, {4_b, 5_b, 6_b})); MultiBuf buf2; buf2.PushBackChunk(MakeChunk(allocator, {7_b, 8_b})); buf2.PushSuffix(std::move(buf)); ExpectElementsEqual(buf2, {7_b, 8_b, 1_b, 2_b, 3_b, 4_b, 5_b, 6_b}); } TEST(MultiBuf, PushFrontChunkAddsBytesToFront) { AllocatorForTest allocator; MultiBuf buf; const std::array kBytesOne = {0_b, 1_b, 2_b}; auto chunk_one = MakeChunk(allocator, kBytesOne); buf.PushFrontChunk(std::move(chunk_one)); ExpectElementsEqual(buf, kBytesOne); const std::array kBytesTwo = {9_b, 10_b, 11_b, 12_b}; auto chunk_two = MakeChunk(allocator, kBytesTwo); buf.PushFrontChunk(std::move(chunk_two)); // clang-format off ExpectElementsEqual(buf, { 9_b, 10_b, 11_b, 12_b, 0_b, 1_b, 2_b, }); // clang-format on } TEST(MultiBuf, InsertChunkOnEmptyBufAddsFirstChunk) { AllocatorForTest allocator; MultiBuf buf; const std::array kBytes = {0_b, 1_b, 2_b}; auto chunk = MakeChunk(allocator, kBytes); auto inserted_iter = buf.InsertChunk(buf.Chunks().begin(), std::move(chunk)); EXPECT_EQ(inserted_iter, buf.Chunks().begin()); ExpectElementsEqual(buf, kBytes); EXPECT_EQ(++inserted_iter, buf.Chunks().end()); } TEST(MultiBuf, InsertChunkAtEndOfBufAddsLastChunk) { AllocatorForTest allocator; MultiBuf buf; // Add a chunk to the beginning buf.PushFrontChunk(MakeChunk(allocator, kArbitraryChunkSize)); const std::array kBytes = {0_b, 1_b, 2_b}; auto chunk = MakeChunk(allocator, kBytes); auto inserted_iter = buf.InsertChunk(buf.Chunks().end(), std::move(chunk)); EXPECT_EQ(inserted_iter, ++buf.Chunks().begin()); EXPECT_EQ(++inserted_iter, buf.Chunks().end()); const Chunk& second_chunk = *(++buf.Chunks().begin()); ExpectElementsEqual(second_chunk, kBytes); } TEST(MultiBuf, TakeChunkAtBeginRemovesAndReturnsFirstChunk) { AllocatorForTest allocator; MultiBuf buf; auto insert_iter = buf.Chunks().begin(); insert_iter = buf.InsertChunk(insert_iter, MakeChunk(allocator, 2)); insert_iter = buf.InsertChunk(++insert_iter, MakeChunk(allocator, 4)); auto [chunk_iter, chunk] = buf.TakeChunk(buf.Chunks().begin()); EXPECT_EQ(chunk.size(), 2U); EXPECT_EQ(chunk_iter->size(), 4U); ++chunk_iter; EXPECT_EQ(chunk_iter, buf.Chunks().end()); } TEST(MultiBuf, TakeChunkOnLastInsertedIterReturnsLastInserted) { AllocatorForTest allocator; MultiBuf buf; auto iter = buf.Chunks().begin(); iter = buf.InsertChunk(iter, MakeChunk(allocator, 42)); iter = buf.InsertChunk(++iter, MakeChunk(allocator, 11)); iter = buf.InsertChunk(++iter, MakeChunk(allocator, 65)); OwnedChunk chunk; std::tie(iter, chunk) = buf.TakeChunk(iter); EXPECT_EQ(iter, buf.Chunks().end()); EXPECT_EQ(chunk.size(), 65U); } TEST(MultiBuf, RangeBasedForLoopsCompile) { MultiBuf buf; for ([[maybe_unused]] std::byte& byte : buf) { } for ([[maybe_unused]] const std::byte& byte : buf) { } for ([[maybe_unused]] Chunk& chunk : buf.Chunks()) { } for ([[maybe_unused]] const Chunk& chunk : buf.Chunks()) { } const MultiBuf const_buf; for ([[maybe_unused]] const std::byte& byte : const_buf) { } for ([[maybe_unused]] const Chunk& chunk : const_buf.Chunks()) { } } TEST(MultiBuf, IteratorAdvancesNAcrossChunks) { AllocatorForTest allocator; MultiBuf buf; buf.PushBackChunk(MakeChunk(allocator, {1_b, 2_b, 3_b})); buf.PushBackChunk(MakeChunk(allocator, {4_b, 5_b, 6_b})); MultiBuf::iterator iter = buf.begin(); iter += 4; EXPECT_EQ(*iter, 5_b); } TEST(MultiBuf, IteratorAdvancesNAcrossZeroLengthChunk) { AllocatorForTest allocator; MultiBuf buf; buf.PushBackChunk(MakeChunk(allocator, 0)); buf.PushBackChunk(MakeChunk(allocator, {1_b, 2_b, 3_b})); buf.PushBackChunk(MakeChunk(allocator, 0)); buf.PushBackChunk(MakeChunk(allocator, {4_b, 5_b, 6_b})); MultiBuf::iterator iter = buf.begin(); iter += 4; EXPECT_EQ(*iter, 5_b); } TEST(MultiBuf, ConstIteratorAdvancesNAcrossChunks) { AllocatorForTest allocator; MultiBuf buf; buf.PushBackChunk(MakeChunk(allocator, {1_b, 2_b, 3_b})); buf.PushBackChunk(MakeChunk(allocator, {4_b, 5_b, 6_b})); MultiBuf::const_iterator iter = buf.cbegin(); iter += 4; EXPECT_EQ(*iter, 5_b); } TEST(MultiBuf, IteratorSkipsEmptyChunks) { AllocatorForTest allocator; MultiBuf buf; buf.PushBackChunk(MakeChunk(allocator, 0)); buf.PushBackChunk(MakeChunk(allocator, 0)); buf.PushBackChunk(MakeChunk(allocator, {1_b})); buf.PushBackChunk(MakeChunk(allocator, 0)); buf.PushBackChunk(MakeChunk(allocator, {2_b, 3_b})); buf.PushBackChunk(MakeChunk(allocator, 0)); MultiBuf::iterator it = buf.begin(); ASSERT_EQ(*it++, 1_b); ASSERT_EQ(*it++, 2_b); ASSERT_EQ(*it++, 3_b); ASSERT_EQ(it, buf.end()); } constexpr auto kSequentialBytes = bytes::Initialized<6>([](size_t i) { return i + 1; }); TEST(MultiBuf, CopyToFromEmptyMultiBuf) { AllocatorForTest allocator; MultiBuf buf; std::array buffer = {}; StatusWithSize result = buf.CopyTo(buffer); ASSERT_EQ(result.status(), OkStatus()); EXPECT_EQ(result.size(), 0u); result = buf.CopyTo({}); ASSERT_EQ(result.status(), OkStatus()); EXPECT_EQ(result.size(), 0u); } TEST(MultiBuf, CopyToEmptyDestination) { AllocatorForTest allocator; MultiBuf buf; buf.PushBackChunk(MakeChunk(allocator, {1_b, 2_b, 3_b, 4_b})); StatusWithSize result = buf.CopyTo({}); ASSERT_EQ(result.status(), Status::ResourceExhausted()); EXPECT_EQ(result.size(), 0u); } TEST(MultiBuf, CopyToOneChunk) { AllocatorForTest allocator; MultiBuf buf; buf.PushBackChunk(MakeChunk(allocator, {1_b, 2_b, 3_b, 4_b})); std::array buffer = {}; StatusWithSize result = buf.CopyTo(buffer); ASSERT_EQ(result.status(), OkStatus()); EXPECT_EQ(result.size(), 4u); EXPECT_TRUE( std::equal(buffer.begin(), buffer.end(), kSequentialBytes.begin())); } TEST(MultiBuf, CopyToVariousChunks) { AllocatorForTest allocator; MultiBuf buf; buf.PushBackChunk(MakeChunk(allocator, {1_b})); buf.PushBackChunk(MakeChunk(allocator, {2_b, 3_b})); buf.PushBackChunk(MakeChunk(allocator, {})); buf.PushBackChunk(MakeChunk(allocator, {4_b, 5_b, 6_b})); std::array buffer = {}; StatusWithSize result = buf.CopyTo(buffer); ASSERT_EQ(result.status(), OkStatus()); EXPECT_EQ(result.size(), 6u); EXPECT_TRUE( std::equal(buffer.begin(), buffer.end(), kSequentialBytes.begin())); } TEST(MultiBuf, CopyToInTwoParts) { AllocatorForTest allocator; constexpr size_t kMultiBufSize = 6; MultiBuf buf; buf.PushBackChunk(MakeChunk(allocator, {1_b})); buf.PushBackChunk(MakeChunk(allocator, {2_b, 3_b})); buf.PushBackChunk(MakeChunk(allocator, {})); buf.PushBackChunk(MakeChunk(allocator, {4_b, 5_b, 6_b})); ASSERT_EQ(buf.size(), kMultiBufSize); for (size_t first = 0; first < kMultiBufSize; ++first) { std::array buffer = {}; StatusWithSize result = buf.CopyTo(span(buffer).first(first)); ASSERT_EQ(result.status(), Status::ResourceExhausted()); ASSERT_EQ(result.size(), first); result = buf.CopyTo(span(buffer).last(kMultiBufSize - first), result.size()); // start from last offset ASSERT_EQ(result.status(), OkStatus()); ASSERT_EQ(result.size(), kMultiBufSize - first); ASSERT_TRUE( std::equal(buffer.begin(), buffer.end(), kSequentialBytes.begin())) << "The whole buffer should have copied"; } } TEST(MultiBuf, CopyToPositionIsEnd) { AllocatorForTest allocator; MultiBuf buf; buf.PushBackChunk(MakeChunk(allocator, {1_b})); buf.PushBackChunk(MakeChunk(allocator, {2_b, 3_b})); buf.PushBackChunk(MakeChunk(allocator, {})); StatusWithSize result = buf.CopyTo({}, 3u); ASSERT_EQ(result.status(), OkStatus()); EXPECT_EQ(result.size(), 0u); } TEST(MultiBuf, CopyFromIntoOneChunk) { AllocatorForTest allocator; MultiBuf mb; mb.PushBackChunk(MakeChunk(allocator, 6)); StatusWithSize result = mb.CopyFrom(kSequentialBytes); EXPECT_EQ(result.status(), OkStatus()); ASSERT_EQ(result.size(), 6u); EXPECT_TRUE(std::equal(mb.begin(), mb.end(), kSequentialBytes.begin())); } TEST(MultiBuf, CopyFromIntoMultipleChunks) { AllocatorForTest allocator; MultiBuf mb; mb.PushBackChunk(MakeChunk(allocator, 2)); mb.PushBackChunk(MakeChunk(allocator, 0)); mb.PushBackChunk(MakeChunk(allocator, 3)); mb.PushBackChunk(MakeChunk(allocator, 1)); mb.PushBackChunk(MakeChunk(allocator, 0)); StatusWithSize result = mb.CopyFrom(kSequentialBytes); EXPECT_EQ(result.status(), OkStatus()); ASSERT_EQ(result.size(), 6u); EXPECT_TRUE(std::equal(mb.begin(), mb.end(), kSequentialBytes.begin())); } TEST(MultiBuf, CopyFromInTwoParts) { AllocatorForTest allocator; for (size_t first = 0; first < kSequentialBytes.size(); ++first) { MultiBuf mb; mb.PushBackChunk(MakeChunk(allocator, 1)); mb.PushBackChunk(MakeChunk(allocator, 0)); mb.PushBackChunk(MakeChunk(allocator, 0)); mb.PushBackChunk(MakeChunk(allocator, 2)); mb.PushBackChunk(MakeChunk(allocator, 3)); ASSERT_EQ(mb.size(), kSequentialBytes.size()); StatusWithSize result = mb.CopyFrom(span(kSequentialBytes).first(first)); ASSERT_EQ(result.status(), OkStatus()); ASSERT_EQ(result.size(), first); result = mb.CopyFrom( span(kSequentialBytes).last(kSequentialBytes.size() - first), result.size()); // start from last offset ASSERT_EQ(result.status(), OkStatus()); ASSERT_EQ(result.size(), kSequentialBytes.size() - first); ASSERT_TRUE(std::equal(mb.begin(), mb.end(), kSequentialBytes.begin())) << "The whole buffer should have copied"; } } TEST(MultiBuf, CopyFromAndTruncate) { AllocatorForTest allocator; for (size_t to_copy = 0; to_copy < kSequentialBytes.size(); ++to_copy) { MultiBuf mb; mb.PushBackChunk(MakeChunk(allocator, 1)); mb.PushBackChunk(MakeChunk(allocator, 0)); mb.PushBackChunk(MakeChunk(allocator, 0)); mb.PushBackChunk(MakeChunk(allocator, 2)); mb.PushBackChunk(MakeChunk(allocator, 3)); mb.PushBackChunk(MakeChunk(allocator, 0)); ASSERT_EQ(mb.size(), kSequentialBytes.size()); StatusWithSize result = mb.CopyFromAndTruncate(span(kSequentialBytes).first(to_copy)); ASSERT_EQ(result.status(), OkStatus()); ASSERT_EQ(result.size(), to_copy); ASSERT_EQ(mb.size(), result.size()); ASSERT_TRUE(std::equal(mb.begin(), mb.end(), kSequentialBytes.begin())); } } TEST(MultiBuf, CopyFromAndTruncateFromOffset) { AllocatorForTest allocator; static constexpr std::array kZeroes = {}; // Sweep offsets 0–6 (inclusive), and copy 0–all bytes for each offset. for (size_t offset = 0; offset <= kSequentialBytes.size(); ++offset) { for (size_t to_copy = 0; to_copy <= kSequentialBytes.size() - offset; ++to_copy) { MultiBuf mb; mb.PushBackChunk(MakeChunk(allocator, 2)); mb.PushBackChunk(MakeChunk(allocator, 0)); mb.PushBackChunk(MakeChunk(allocator, 3)); mb.PushBackChunk(MakeChunk(allocator, 0)); mb.PushBackChunk(MakeChunk(allocator, 0)); mb.PushBackChunk(MakeChunk(allocator, 1)); ASSERT_EQ(mb.size(), kSequentialBytes.size()); StatusWithSize result = mb.CopyFromAndTruncate(span(kSequentialBytes).first(to_copy), offset); ASSERT_EQ(result.status(), OkStatus()); ASSERT_EQ(result.size(), to_copy); ASSERT_EQ(mb.size(), offset + to_copy); // MultiBuf contains to_copy 0s followed by to_copy sequential bytes. ASSERT_TRUE(std::equal(mb.begin(), mb.begin() + offset, kZeroes.begin())); ASSERT_TRUE( std::equal(mb.begin() + offset, mb.end(), kSequentialBytes.begin())); } } } TEST(MultiBuf, CopyFromIntoEmptyMultibuf) { AllocatorForTest allocator; MultiBuf mb; StatusWithSize result = mb.CopyFrom({}); EXPECT_EQ(result.status(), OkStatus()); // empty source, so copy succeeded EXPECT_EQ(result.size(), 0u); result = mb.CopyFrom(kSequentialBytes); EXPECT_EQ(result.status(), Status::ResourceExhausted()); EXPECT_EQ(result.size(), 0u); mb.PushBackChunk(MakeChunk(allocator, 0)); // add an empty chunk result = mb.CopyFrom({}); EXPECT_EQ(result.status(), OkStatus()); // empty source, so copy succeeded EXPECT_EQ(result.size(), 0u); result = mb.CopyFrom(kSequentialBytes); EXPECT_EQ(result.status(), Status::ResourceExhausted()); EXPECT_EQ(result.size(), 0u); } TEST(MultiBuf, IsContiguousTrueForEmptyBuffer) { AllocatorForTest allocator; MultiBuf buf; EXPECT_TRUE(buf.IsContiguous()); buf.PushBackChunk(MakeChunk(allocator, {})); EXPECT_TRUE(buf.IsContiguous()); buf.PushBackChunk(MakeChunk(allocator, {})); EXPECT_TRUE(buf.IsContiguous()); buf.PushBackChunk(MakeChunk(allocator, {})); EXPECT_TRUE(buf.IsContiguous()); } TEST(MultiBuf, IsContiguousTrueForSingleNonEmptyChunk) { AllocatorForTest allocator; MultiBuf buf; buf.PushBackChunk(MakeChunk(allocator, {1_b})); EXPECT_TRUE(buf.IsContiguous()); buf.PushBackChunk(MakeChunk(allocator, {})); EXPECT_TRUE(buf.IsContiguous()); buf.PushFrontChunk(MakeChunk(allocator, {})); EXPECT_TRUE(buf.IsContiguous()); } TEST(MultiBuf, IsContiguousFalseIfMultipleNonEmptyChunks) { AllocatorForTest allocator; MultiBuf buf; buf.PushBackChunk(MakeChunk(allocator, {1_b})); buf.PushBackChunk(MakeChunk(allocator, {2_b})); EXPECT_FALSE(buf.IsContiguous()); } TEST(MultiBuf, ContiguousSpanAcrossMultipleChunks) { AllocatorForTest allocator; OwnedChunk chunk_1 = MakeChunk(allocator, 10); const ConstByteSpan contiguous_span = chunk_1; OwnedChunk chunk_2 = chunk_1->TakeSuffix(5).value(); OwnedChunk chunk_3 = chunk_2->TakeSuffix(5).value(); OwnedChunk chunk_4 = chunk_3->TakeSuffix(1).value(); MultiBuf buf; buf.PushBackChunk(std::move(chunk_1)); // 5 bytes buf.PushBackChunk(std::move(chunk_2)); // 0 bytes buf.PushBackChunk(std::move(chunk_3)); // 4 bytes buf.PushBackChunk(std::move(chunk_4)); // 1 byte buf.PushBackChunk(MakeChunk(allocator, 0)); // empty auto it = buf.Chunks().begin(); ASSERT_EQ((it++)->size(), 5u); ASSERT_EQ((it++)->size(), 0u); ASSERT_EQ((it++)->size(), 4u); ASSERT_EQ((it++)->size(), 1u); ASSERT_EQ((it++)->size(), 0u); ASSERT_EQ(it, buf.Chunks().end()); EXPECT_TRUE(buf.IsContiguous()); ByteSpan span = buf.ContiguousSpan().value(); EXPECT_EQ(span.data(), contiguous_span.data()); EXPECT_EQ(span.size(), contiguous_span.size()); it = buf.Chunks().begin(); buf.InsertChunk(++it, MakeChunk(allocator, 1)); EXPECT_FALSE(buf.IsContiguous()); } } // namespace } // namespace pw::multibuf