xref: /aosp_15_r20/external/pigweed/pw_multibuf/multibuf_test.cc (revision 61c4878ac05f98d0ceed94b57d316916de578985)
1 // Copyright 2023 The Pigweed Authors
2 //
3 // Licensed under the Apache License, Version 2.0 (the "License"); you may not
4 // use this file except in compliance with the License. You may obtain a copy of
5 // the License at
6 //
7 //     https://www.apache.org/licenses/LICENSE-2.0
8 //
9 // Unless required by applicable law or agreed to in writing, software
10 // distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
11 // WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
12 // License for the specific language governing permissions and limitations under
13 // the License.
14 
15 #include "pw_multibuf/multibuf.h"
16 
17 #include "pw_assert/check.h"
18 #include "pw_bytes/array.h"
19 #include "pw_bytes/suffix.h"
20 #include "pw_multibuf_private/test_utils.h"
21 #include "pw_span/span.h"
22 #include "pw_unit_test/framework.h"
23 
24 namespace pw::multibuf {
25 namespace {
26 
27 using namespace pw::multibuf::test_utils;
28 
29 #if __cplusplus >= 202002L
30 static_assert(std::forward_iterator<MultiBuf::iterator>);
31 static_assert(std::forward_iterator<MultiBuf::const_iterator>);
32 static_assert(std::forward_iterator<MultiBufChunks::iterator>);
33 static_assert(std::forward_iterator<MultiBufChunks::const_iterator>);
34 #endif  // __cplusplus >= 202002L
35 
36 static_assert(
37     sizeof(MultiBufChunks) == sizeof(MultiBuf),
38     "MultiBuf is a byte view of MultiBufChunks and does not add members");
39 
TEST(MultiBuf,IsDefaultConstructible)40 TEST(MultiBuf, IsDefaultConstructible) { [[maybe_unused]] MultiBuf buf; }
41 
TEST(MultiBuf,WithOneChunkReleases)42 TEST(MultiBuf, WithOneChunkReleases) {
43   AllocatorForTest<kArbitraryAllocatorSize> allocator;
44   const auto& metrics = allocator.metrics();
45   MultiBuf buf;
46   buf.PushFrontChunk(MakeChunk(allocator, kArbitraryChunkSize));
47   EXPECT_EQ(metrics.num_allocations.value(), 2U);
48   buf.Release();
49   EXPECT_EQ(metrics.num_deallocations.value(), 2U);
50 }
51 
TEST(MultiBuf,WithOneChunkReleasesOnDestruction)52 TEST(MultiBuf, WithOneChunkReleasesOnDestruction) {
53   AllocatorForTest<kArbitraryAllocatorSize> allocator;
54   const auto& metrics = allocator.metrics();
55   {
56     MultiBuf buf;
57     buf.PushFrontChunk(MakeChunk(allocator, kArbitraryChunkSize));
58     EXPECT_EQ(metrics.num_allocations.value(), 2U);
59   }
60   EXPECT_EQ(metrics.num_deallocations.value(), 2U);
61 }
62 
TEST(MultiBuf,WithMultipleChunksReleasesAllOnDestruction)63 TEST(MultiBuf, WithMultipleChunksReleasesAllOnDestruction) {
64   AllocatorForTest<kArbitraryAllocatorSize> allocator;
65   const auto& metrics = allocator.metrics();
66   {
67     MultiBuf buf;
68     buf.PushFrontChunk(MakeChunk(allocator, kArbitraryChunkSize));
69     buf.PushFrontChunk(MakeChunk(allocator, kArbitraryChunkSize));
70     EXPECT_EQ(metrics.num_allocations.value(), 4U);
71   }
72   EXPECT_EQ(metrics.num_deallocations.value(), 4U);
73 }
74 
TEST(MultiBuf,SizeReturnsNumberOfBytes)75 TEST(MultiBuf, SizeReturnsNumberOfBytes) {
76   AllocatorForTest<kArbitraryAllocatorSize> allocator;
77   MultiBuf buf;
78   EXPECT_EQ(buf.size(), 0U);
79   buf.PushFrontChunk(MakeChunk(allocator, kArbitraryChunkSize));
80   EXPECT_EQ(buf.size(), kArbitraryChunkSize);
81   buf.PushFrontChunk(MakeChunk(allocator, kArbitraryChunkSize));
82   EXPECT_EQ(buf.size(), kArbitraryChunkSize * 2);
83 }
84 
TEST(MultiBuf,EmptyIfNoChunks)85 TEST(MultiBuf, EmptyIfNoChunks) {
86   AllocatorForTest<kArbitraryAllocatorSize> allocator;
87   MultiBuf buf;
88   EXPECT_EQ(buf.size(), 0U);
89   EXPECT_TRUE(buf.empty());
90   buf.PushFrontChunk(MakeChunk(allocator, kArbitraryChunkSize));
91   EXPECT_NE(buf.size(), 0U);
92   EXPECT_FALSE(buf.empty());
93 }
94 
TEST(MultiBuf,EmptyIfOnlyEmptyChunks)95 TEST(MultiBuf, EmptyIfOnlyEmptyChunks) {
96   AllocatorForTest<kArbitraryAllocatorSize> allocator;
97   MultiBuf buf;
98   EXPECT_TRUE(buf.empty());
99   buf.PushFrontChunk(MakeChunk(allocator, 0));
100   EXPECT_TRUE(buf.empty());
101   buf.PushFrontChunk(MakeChunk(allocator, 0));
102   EXPECT_TRUE(buf.empty());
103   EXPECT_EQ(buf.size(), 0U);
104 }
105 
TEST(MultiBuf,EmptyIsFalseIfAnyNonEmptyChunks)106 TEST(MultiBuf, EmptyIsFalseIfAnyNonEmptyChunks) {
107   AllocatorForTest<kArbitraryAllocatorSize> allocator;
108   MultiBuf buf;
109   buf.PushFrontChunk(MakeChunk(allocator, 0));
110   EXPECT_TRUE(buf.empty());
111   buf.PushFrontChunk(MakeChunk(allocator, kArbitraryChunkSize));
112   EXPECT_FALSE(buf.empty());
113   EXPECT_EQ(buf.size(), kArbitraryChunkSize);
114 }
115 
TEST(MultiBuf,ClaimPrefixReclaimsFirstChunkPrefix)116 TEST(MultiBuf, ClaimPrefixReclaimsFirstChunkPrefix) {
117   AllocatorForTest<kArbitraryAllocatorSize> allocator;
118   MultiBuf buf;
119   OwnedChunk chunk = MakeChunk(allocator, 16);
120   chunk->DiscardPrefix(7);
121   buf.PushFrontChunk(std::move(chunk));
122   EXPECT_EQ(buf.size(), 9U);
123   EXPECT_EQ(buf.ClaimPrefix(7), true);
124   EXPECT_EQ(buf.size(), 16U);
125 }
126 
TEST(MultiBuf,ClaimPrefixOnFirstChunkWithoutPrefixReturnsFalse)127 TEST(MultiBuf, ClaimPrefixOnFirstChunkWithoutPrefixReturnsFalse) {
128   AllocatorForTest<kArbitraryAllocatorSize> allocator;
129   MultiBuf buf;
130   buf.PushFrontChunk(MakeChunk(allocator, 16));
131   EXPECT_EQ(buf.size(), 16U);
132   EXPECT_EQ(buf.ClaimPrefix(7), false);
133   EXPECT_EQ(buf.size(), 16U);
134 }
135 
TEST(MultiBuf,ClaimPrefixWithoutChunksReturnsFalse)136 TEST(MultiBuf, ClaimPrefixWithoutChunksReturnsFalse) {
137   AllocatorForTest<kArbitraryAllocatorSize> allocator;
138   MultiBuf buf;
139   EXPECT_EQ(buf.size(), 0U);
140   EXPECT_EQ(buf.ClaimPrefix(7), false);
141   EXPECT_EQ(buf.size(), 0U);
142 }
143 
TEST(MultiBuf,ClaimSuffixReclaimsLastChunkSuffix)144 TEST(MultiBuf, ClaimSuffixReclaimsLastChunkSuffix) {
145   AllocatorForTest<kArbitraryAllocatorSize> allocator;
146   MultiBuf buf;
147   OwnedChunk chunk = MakeChunk(allocator, 16U);
148   chunk->Truncate(9U);
149   buf.PushFrontChunk(std::move(chunk));
150   buf.PushFrontChunk(MakeChunk(allocator, 4U));
151   EXPECT_EQ(buf.size(), 13U);
152   EXPECT_EQ(buf.ClaimSuffix(7U), true);
153   EXPECT_EQ(buf.size(), 20U);
154 }
155 
TEST(MultiBuf,ClaimSuffixOnLastChunkWithoutSuffixReturnsFalse)156 TEST(MultiBuf, ClaimSuffixOnLastChunkWithoutSuffixReturnsFalse) {
157   AllocatorForTest<kArbitraryAllocatorSize> allocator;
158   MultiBuf buf;
159   buf.PushFrontChunk(MakeChunk(allocator, 16U));
160   EXPECT_EQ(buf.size(), 16U);
161   EXPECT_EQ(buf.ClaimPrefix(7U), false);
162   EXPECT_EQ(buf.size(), 16U);
163 }
164 
TEST(MultiBuf,ClaimSuffixWithoutChunksReturnsFalse)165 TEST(MultiBuf, ClaimSuffixWithoutChunksReturnsFalse) {
166   AllocatorForTest<kArbitraryAllocatorSize> allocator;
167   MultiBuf buf;
168   EXPECT_EQ(buf.size(), 0U);
169   EXPECT_EQ(buf.ClaimSuffix(7U), false);
170   EXPECT_EQ(buf.size(), 0U);
171 }
172 
TEST(MultiBuf,DiscardPrefixWithZeroDoesNothing)173 TEST(MultiBuf, DiscardPrefixWithZeroDoesNothing) {
174   AllocatorForTest<kArbitraryAllocatorSize> allocator;
175   MultiBuf buf;
176   buf.DiscardPrefix(0);
177   EXPECT_EQ(buf.size(), 0U);
178 }
179 
TEST(MultiBuf,DiscardPrefixDiscardsPartialChunk)180 TEST(MultiBuf, DiscardPrefixDiscardsPartialChunk) {
181   AllocatorForTest<kArbitraryAllocatorSize> allocator;
182   MultiBuf buf;
183   buf.PushFrontChunk(MakeChunk(allocator, 16U));
184   buf.DiscardPrefix(5U);
185   EXPECT_EQ(buf.size(), 11U);
186 }
187 
TEST(MultiBuf,DiscardPrefixDiscardsWholeChunk)188 TEST(MultiBuf, DiscardPrefixDiscardsWholeChunk) {
189   AllocatorForTest<kArbitraryAllocatorSize> allocator;
190   MultiBuf buf;
191   buf.PushFrontChunk(MakeChunk(allocator, 16U));
192   buf.PushFrontChunk(MakeChunk(allocator, 3U));
193   buf.DiscardPrefix(16U);
194   EXPECT_EQ(buf.size(), 3U);
195 }
196 
TEST(MultiBuf,DiscardPrefixDiscardsMultipleChunks)197 TEST(MultiBuf, DiscardPrefixDiscardsMultipleChunks) {
198   AllocatorForTest<kArbitraryAllocatorSize> allocator;
199   MultiBuf buf;
200   buf.PushFrontChunk(MakeChunk(allocator, 16U));
201   buf.PushFrontChunk(MakeChunk(allocator, 4U));
202   buf.PushFrontChunk(MakeChunk(allocator, 3U));
203   buf.DiscardPrefix(21U);
204   EXPECT_EQ(buf.size(), 2U);
205 }
206 
TEST(MultiBuf,SliceDiscardsPrefixAndSuffixWholeAndPartialChunks)207 TEST(MultiBuf, SliceDiscardsPrefixAndSuffixWholeAndPartialChunks) {
208   AllocatorForTest<kArbitraryAllocatorSize> allocator;
209   MultiBuf buf;
210   buf.PushBackChunk(MakeChunk(allocator, {1_b, 1_b, 1_b}));
211   buf.PushBackChunk(MakeChunk(allocator, {2_b, 2_b, 2_b}));
212   buf.PushBackChunk(MakeChunk(allocator, {3_b, 3_b, 3_b}));
213   buf.PushBackChunk(MakeChunk(allocator, {4_b, 4_b, 4_b}));
214   buf.Slice(4, 7);
215   ExpectElementsEqual(buf, {2_b, 2_b, 3_b});
216 }
217 
TEST(MultiBuf,SliceDoesNotModifyChunkMemory)218 TEST(MultiBuf, SliceDoesNotModifyChunkMemory) {
219   AllocatorForTest<kArbitraryAllocatorSize> allocator;
220   MultiBuf buf;
221   std::array<std::byte, 4> kBytes = {1_b, 2_b, 3_b, 4_b};
222   OwnedChunk chunk = MakeChunk(allocator, kBytes);
223   ConstByteSpan span(chunk);
224   buf.PushFrontChunk(std::move(chunk));
225   buf.Slice(2, 3);
226   ExpectElementsEqual(span, kBytes);
227 }
228 
TEST(MultiBuf,TruncateRemovesFinalEmptyChunk)229 TEST(MultiBuf, TruncateRemovesFinalEmptyChunk) {
230   AllocatorForTest<kArbitraryAllocatorSize> allocator;
231   MultiBuf buf;
232   buf.PushFrontChunk(MakeChunk(allocator, 3U));
233   buf.PushFrontChunk(MakeChunk(allocator, 3U));
234   buf.Truncate(3U);
235   EXPECT_EQ(buf.size(), 3U);
236   EXPECT_EQ(buf.Chunks().size(), 1U);
237 }
238 
TEST(MultiBuf,TruncateRemovesWholeAndPartialChunks)239 TEST(MultiBuf, TruncateRemovesWholeAndPartialChunks) {
240   AllocatorForTest<kArbitraryAllocatorSize> allocator;
241   MultiBuf buf;
242   buf.PushFrontChunk(MakeChunk(allocator, 3U));
243   buf.PushFrontChunk(MakeChunk(allocator, 3U));
244   buf.Truncate(2U);
245   EXPECT_EQ(buf.size(), 2U);
246 }
247 
TEST(MultiBuf,TruncateAfterRemovesWholeAndPartialChunks)248 TEST(MultiBuf, TruncateAfterRemovesWholeAndPartialChunks) {
249   AllocatorForTest<kArbitraryAllocatorSize> allocator;
250   MultiBuf buf;
251   buf.PushFrontChunk(MakeChunk(allocator, 3U));
252   buf.PushFrontChunk(MakeChunk(allocator, 0U));
253   buf.PushFrontChunk(MakeChunk(allocator, 1U));
254   auto it = buf.begin();
255   ++it;
256   buf.TruncateAfter(it);
257   EXPECT_EQ(buf.size(), 2U);
258 }
259 
TEST(MultiBuf,TruncateEmptyBuffer)260 TEST(MultiBuf, TruncateEmptyBuffer) {
261   MultiBuf buf;
262   buf.Truncate(0);
263   EXPECT_TRUE(buf.empty());
264 }
265 
TEST(MultiBuf,TakePrefixWithNoBytesDoesNothing)266 TEST(MultiBuf, TakePrefixWithNoBytesDoesNothing) {
267   AllocatorForTest<kArbitraryAllocatorSize> allocator;
268   MultiBuf buf;
269   std::optional<MultiBuf> empty_front = buf.TakePrefix(0);
270   ASSERT_TRUE(empty_front.has_value());
271   EXPECT_EQ(buf.size(), 0U);
272   EXPECT_EQ(empty_front->size(), 0U);
273 }
274 
TEST(MultiBuf,TakePrefixReturnsPartialChunk)275 TEST(MultiBuf, TakePrefixReturnsPartialChunk) {
276   AllocatorForTest<kArbitraryAllocatorSize> allocator;
277   MultiBuf buf;
278   buf.PushBackChunk(MakeChunk(allocator, {1_b, 2_b, 3_b}));
279   std::optional<MultiBuf> old_front = buf.TakePrefix(2);
280   ASSERT_TRUE(old_front.has_value());
281   ExpectElementsEqual(*old_front, {1_b, 2_b});
282   ExpectElementsEqual(buf, {3_b});
283 }
284 
TEST(MultiBuf,TakePrefixReturnsWholeAndPartialChunks)285 TEST(MultiBuf, TakePrefixReturnsWholeAndPartialChunks) {
286   AllocatorForTest<kArbitraryAllocatorSize> allocator;
287   MultiBuf buf;
288   buf.PushBackChunk(MakeChunk(allocator, {1_b, 2_b, 3_b}));
289   buf.PushBackChunk(MakeChunk(allocator, {4_b, 5_b, 6_b}));
290   std::optional<MultiBuf> old_front = buf.TakePrefix(4);
291   ASSERT_TRUE(old_front.has_value());
292   ExpectElementsEqual(*old_front, {1_b, 2_b, 3_b, 4_b});
293   ExpectElementsEqual(buf, {5_b, 6_b});
294 }
295 
TEST(MultiBuf,TakeSuffixReturnsWholeAndPartialChunks)296 TEST(MultiBuf, TakeSuffixReturnsWholeAndPartialChunks) {
297   AllocatorForTest<kArbitraryAllocatorSize> allocator;
298   MultiBuf buf;
299   buf.PushBackChunk(MakeChunk(allocator, {1_b, 2_b, 3_b}));
300   buf.PushBackChunk(MakeChunk(allocator, {4_b, 5_b, 6_b}));
301   std::optional<MultiBuf> old_tail = buf.TakeSuffix(4);
302   ASSERT_TRUE(old_tail.has_value());
303   ExpectElementsEqual(buf, {1_b, 2_b});
304   ExpectElementsEqual(*old_tail, {3_b, 4_b, 5_b, 6_b});
305 }
306 
TEST(MultiBuf,PushPrefixPrependsData)307 TEST(MultiBuf, PushPrefixPrependsData) {
308   AllocatorForTest<kArbitraryAllocatorSize> allocator;
309   MultiBuf buf;
310   buf.PushBackChunk(MakeChunk(allocator, {1_b, 2_b, 3_b}));
311   buf.PushBackChunk(MakeChunk(allocator, {4_b, 5_b, 6_b}));
312   MultiBuf buf2;
313   buf2.PushBackChunk(MakeChunk(allocator, {7_b, 8_b}));
314   buf2.PushPrefix(std::move(buf));
315   ExpectElementsEqual(buf2, {1_b, 2_b, 3_b, 4_b, 5_b, 6_b, 7_b, 8_b});
316 }
317 
TEST(MultiBuf,PushSuffixAppendsData)318 TEST(MultiBuf, PushSuffixAppendsData) {
319   AllocatorForTest<kArbitraryAllocatorSize> allocator;
320   MultiBuf buf;
321   buf.PushBackChunk(MakeChunk(allocator, {1_b, 2_b, 3_b}));
322   buf.PushBackChunk(MakeChunk(allocator, {4_b, 5_b, 6_b}));
323   MultiBuf buf2;
324   buf2.PushBackChunk(MakeChunk(allocator, {7_b, 8_b}));
325   buf2.PushSuffix(std::move(buf));
326   ExpectElementsEqual(buf2, {7_b, 8_b, 1_b, 2_b, 3_b, 4_b, 5_b, 6_b});
327 }
328 
TEST(MultiBuf,PushFrontChunkAddsBytesToFront)329 TEST(MultiBuf, PushFrontChunkAddsBytesToFront) {
330   AllocatorForTest<kArbitraryAllocatorSize> allocator;
331   MultiBuf buf;
332 
333   const std::array<std::byte, 3> kBytesOne = {0_b, 1_b, 2_b};
334   auto chunk_one = MakeChunk(allocator, kBytesOne);
335   buf.PushFrontChunk(std::move(chunk_one));
336   ExpectElementsEqual(buf, kBytesOne);
337 
338   const std::array<std::byte, 4> kBytesTwo = {9_b, 10_b, 11_b, 12_b};
339   auto chunk_two = MakeChunk(allocator, kBytesTwo);
340   buf.PushFrontChunk(std::move(chunk_two));
341 
342   // clang-format off
343   ExpectElementsEqual(buf, {
344       9_b, 10_b, 11_b, 12_b,
345       0_b, 1_b, 2_b,
346   });
347   // clang-format on
348 }
349 
TEST(MultiBuf,InsertChunkOnEmptyBufAddsFirstChunk)350 TEST(MultiBuf, InsertChunkOnEmptyBufAddsFirstChunk) {
351   AllocatorForTest<kArbitraryAllocatorSize> allocator;
352   MultiBuf buf;
353 
354   const std::array<std::byte, 3> kBytes = {0_b, 1_b, 2_b};
355   auto chunk = MakeChunk(allocator, kBytes);
356   auto inserted_iter = buf.InsertChunk(buf.Chunks().begin(), std::move(chunk));
357   EXPECT_EQ(inserted_iter, buf.Chunks().begin());
358   ExpectElementsEqual(buf, kBytes);
359   EXPECT_EQ(++inserted_iter, buf.Chunks().end());
360 }
361 
TEST(MultiBuf,InsertChunkAtEndOfBufAddsLastChunk)362 TEST(MultiBuf, InsertChunkAtEndOfBufAddsLastChunk) {
363   AllocatorForTest<kArbitraryAllocatorSize> allocator;
364   MultiBuf buf;
365 
366   // Add a chunk to the beginning
367   buf.PushFrontChunk(MakeChunk(allocator, kArbitraryChunkSize));
368 
369   const std::array<std::byte, 3> kBytes = {0_b, 1_b, 2_b};
370   auto chunk = MakeChunk(allocator, kBytes);
371   auto inserted_iter = buf.InsertChunk(buf.Chunks().end(), std::move(chunk));
372   EXPECT_EQ(inserted_iter, ++buf.Chunks().begin());
373   EXPECT_EQ(++inserted_iter, buf.Chunks().end());
374   const Chunk& second_chunk = *(++buf.Chunks().begin());
375   ExpectElementsEqual(second_chunk, kBytes);
376 }
377 
TEST(MultiBuf,TakeChunkAtBeginRemovesAndReturnsFirstChunk)378 TEST(MultiBuf, TakeChunkAtBeginRemovesAndReturnsFirstChunk) {
379   AllocatorForTest<kArbitraryAllocatorSize> allocator;
380   MultiBuf buf;
381   auto insert_iter = buf.Chunks().begin();
382   insert_iter = buf.InsertChunk(insert_iter, MakeChunk(allocator, 2));
383   insert_iter = buf.InsertChunk(++insert_iter, MakeChunk(allocator, 4));
384 
385   auto [chunk_iter, chunk] = buf.TakeChunk(buf.Chunks().begin());
386   EXPECT_EQ(chunk.size(), 2U);
387   EXPECT_EQ(chunk_iter->size(), 4U);
388   ++chunk_iter;
389   EXPECT_EQ(chunk_iter, buf.Chunks().end());
390 }
391 
TEST(MultiBuf,TakeChunkOnLastInsertedIterReturnsLastInserted)392 TEST(MultiBuf, TakeChunkOnLastInsertedIterReturnsLastInserted) {
393   AllocatorForTest<kArbitraryAllocatorSize> allocator;
394   MultiBuf buf;
395   auto iter = buf.Chunks().begin();
396   iter = buf.InsertChunk(iter, MakeChunk(allocator, 42));
397   iter = buf.InsertChunk(++iter, MakeChunk(allocator, 11));
398   iter = buf.InsertChunk(++iter, MakeChunk(allocator, 65));
399   OwnedChunk chunk;
400   std::tie(iter, chunk) = buf.TakeChunk(iter);
401   EXPECT_EQ(iter, buf.Chunks().end());
402   EXPECT_EQ(chunk.size(), 65U);
403 }
404 
TEST(MultiBuf,RangeBasedForLoopsCompile)405 TEST(MultiBuf, RangeBasedForLoopsCompile) {
406   MultiBuf buf;
407   for ([[maybe_unused]] std::byte& byte : buf) {
408   }
409   for ([[maybe_unused]] const std::byte& byte : buf) {
410   }
411   for ([[maybe_unused]] Chunk& chunk : buf.Chunks()) {
412   }
413   for ([[maybe_unused]] const Chunk& chunk : buf.Chunks()) {
414   }
415 
416   const MultiBuf const_buf;
417   for ([[maybe_unused]] const std::byte& byte : const_buf) {
418   }
419   for ([[maybe_unused]] const Chunk& chunk : const_buf.Chunks()) {
420   }
421 }
422 
TEST(MultiBuf,IteratorAdvancesNAcrossChunks)423 TEST(MultiBuf, IteratorAdvancesNAcrossChunks) {
424   AllocatorForTest<kArbitraryAllocatorSize> allocator;
425   MultiBuf buf;
426   buf.PushBackChunk(MakeChunk(allocator, {1_b, 2_b, 3_b}));
427   buf.PushBackChunk(MakeChunk(allocator, {4_b, 5_b, 6_b}));
428 
429   MultiBuf::iterator iter = buf.begin();
430   iter += 4;
431   EXPECT_EQ(*iter, 5_b);
432 }
433 
TEST(MultiBuf,IteratorAdvancesNAcrossZeroLengthChunk)434 TEST(MultiBuf, IteratorAdvancesNAcrossZeroLengthChunk) {
435   AllocatorForTest<kArbitraryAllocatorSize> allocator;
436   MultiBuf buf;
437   buf.PushBackChunk(MakeChunk(allocator, 0));
438   buf.PushBackChunk(MakeChunk(allocator, {1_b, 2_b, 3_b}));
439   buf.PushBackChunk(MakeChunk(allocator, 0));
440   buf.PushBackChunk(MakeChunk(allocator, {4_b, 5_b, 6_b}));
441 
442   MultiBuf::iterator iter = buf.begin();
443   iter += 4;
444   EXPECT_EQ(*iter, 5_b);
445 }
446 
TEST(MultiBuf,ConstIteratorAdvancesNAcrossChunks)447 TEST(MultiBuf, ConstIteratorAdvancesNAcrossChunks) {
448   AllocatorForTest<kArbitraryAllocatorSize> allocator;
449   MultiBuf buf;
450   buf.PushBackChunk(MakeChunk(allocator, {1_b, 2_b, 3_b}));
451   buf.PushBackChunk(MakeChunk(allocator, {4_b, 5_b, 6_b}));
452 
453   MultiBuf::const_iterator iter = buf.cbegin();
454   iter += 4;
455   EXPECT_EQ(*iter, 5_b);
456 }
457 
TEST(MultiBuf,IteratorSkipsEmptyChunks)458 TEST(MultiBuf, IteratorSkipsEmptyChunks) {
459   AllocatorForTest<kArbitraryAllocatorSize> allocator;
460   MultiBuf buf;
461   buf.PushBackChunk(MakeChunk(allocator, 0));
462   buf.PushBackChunk(MakeChunk(allocator, 0));
463   buf.PushBackChunk(MakeChunk(allocator, {1_b}));
464   buf.PushBackChunk(MakeChunk(allocator, 0));
465   buf.PushBackChunk(MakeChunk(allocator, {2_b, 3_b}));
466   buf.PushBackChunk(MakeChunk(allocator, 0));
467 
468   MultiBuf::iterator it = buf.begin();
469   ASSERT_EQ(*it++, 1_b);
470   ASSERT_EQ(*it++, 2_b);
471   ASSERT_EQ(*it++, 3_b);
472   ASSERT_EQ(it, buf.end());
473 }
474 
475 constexpr auto kSequentialBytes =
__anon141f71f30202(size_t i) 476     bytes::Initialized<6>([](size_t i) { return i + 1; });
477 
TEST(MultiBuf,CopyToFromEmptyMultiBuf)478 TEST(MultiBuf, CopyToFromEmptyMultiBuf) {
479   AllocatorForTest<kArbitraryAllocatorSize> allocator;
480   MultiBuf buf;
481   std::array<std::byte, 6> buffer = {};
482   StatusWithSize result = buf.CopyTo(buffer);
483   ASSERT_EQ(result.status(), OkStatus());
484   EXPECT_EQ(result.size(), 0u);
485 
486   result = buf.CopyTo({});
487   ASSERT_EQ(result.status(), OkStatus());
488   EXPECT_EQ(result.size(), 0u);
489 }
490 
TEST(MultiBuf,CopyToEmptyDestination)491 TEST(MultiBuf, CopyToEmptyDestination) {
492   AllocatorForTest<kArbitraryAllocatorSize> allocator;
493   MultiBuf buf;
494   buf.PushBackChunk(MakeChunk(allocator, {1_b, 2_b, 3_b, 4_b}));
495   StatusWithSize result = buf.CopyTo({});
496   ASSERT_EQ(result.status(), Status::ResourceExhausted());
497   EXPECT_EQ(result.size(), 0u);
498 }
499 
TEST(MultiBuf,CopyToOneChunk)500 TEST(MultiBuf, CopyToOneChunk) {
501   AllocatorForTest<kArbitraryAllocatorSize> allocator;
502   MultiBuf buf;
503   buf.PushBackChunk(MakeChunk(allocator, {1_b, 2_b, 3_b, 4_b}));
504 
505   std::array<std::byte, 4> buffer = {};
506   StatusWithSize result = buf.CopyTo(buffer);
507   ASSERT_EQ(result.status(), OkStatus());
508   EXPECT_EQ(result.size(), 4u);
509   EXPECT_TRUE(
510       std::equal(buffer.begin(), buffer.end(), kSequentialBytes.begin()));
511 }
512 
TEST(MultiBuf,CopyToVariousChunks)513 TEST(MultiBuf, CopyToVariousChunks) {
514   AllocatorForTest<kArbitraryAllocatorSize> allocator;
515   MultiBuf buf;
516   buf.PushBackChunk(MakeChunk(allocator, {1_b}));
517   buf.PushBackChunk(MakeChunk(allocator, {2_b, 3_b}));
518   buf.PushBackChunk(MakeChunk(allocator, {}));
519   buf.PushBackChunk(MakeChunk(allocator, {4_b, 5_b, 6_b}));
520 
521   std::array<std::byte, 6> buffer = {};
522   StatusWithSize result = buf.CopyTo(buffer);
523   ASSERT_EQ(result.status(), OkStatus());
524   EXPECT_EQ(result.size(), 6u);
525   EXPECT_TRUE(
526       std::equal(buffer.begin(), buffer.end(), kSequentialBytes.begin()));
527 }
528 
TEST(MultiBuf,CopyToInTwoParts)529 TEST(MultiBuf, CopyToInTwoParts) {
530   AllocatorForTest<kArbitraryAllocatorSize> allocator;
531 
532   constexpr size_t kMultiBufSize = 6;
533   MultiBuf buf;
534   buf.PushBackChunk(MakeChunk(allocator, {1_b}));
535   buf.PushBackChunk(MakeChunk(allocator, {2_b, 3_b}));
536   buf.PushBackChunk(MakeChunk(allocator, {}));
537   buf.PushBackChunk(MakeChunk(allocator, {4_b, 5_b, 6_b}));
538   ASSERT_EQ(buf.size(), kMultiBufSize);
539 
540   for (size_t first = 0; first < kMultiBufSize; ++first) {
541     std::array<std::byte, kMultiBufSize> buffer = {};
542     StatusWithSize result = buf.CopyTo(span(buffer).first(first));
543     ASSERT_EQ(result.status(), Status::ResourceExhausted());
544     ASSERT_EQ(result.size(), first);
545 
546     result = buf.CopyTo(span(buffer).last(kMultiBufSize - first),
547                         result.size());  // start from last offset
548     ASSERT_EQ(result.status(), OkStatus());
549     ASSERT_EQ(result.size(), kMultiBufSize - first);
550 
551     ASSERT_TRUE(
552         std::equal(buffer.begin(), buffer.end(), kSequentialBytes.begin()))
553         << "The whole buffer should have copied";
554   }
555 }
556 
TEST(MultiBuf,CopyToPositionIsEnd)557 TEST(MultiBuf, CopyToPositionIsEnd) {
558   AllocatorForTest<kArbitraryAllocatorSize> allocator;
559   MultiBuf buf;
560   buf.PushBackChunk(MakeChunk(allocator, {1_b}));
561   buf.PushBackChunk(MakeChunk(allocator, {2_b, 3_b}));
562   buf.PushBackChunk(MakeChunk(allocator, {}));
563 
564   StatusWithSize result = buf.CopyTo({}, 3u);
565   ASSERT_EQ(result.status(), OkStatus());
566   EXPECT_EQ(result.size(), 0u);
567 }
568 
TEST(MultiBuf,CopyFromIntoOneChunk)569 TEST(MultiBuf, CopyFromIntoOneChunk) {
570   AllocatorForTest<kArbitraryAllocatorSize> allocator;
571   MultiBuf mb;
572   mb.PushBackChunk(MakeChunk(allocator, 6));
573 
574   StatusWithSize result = mb.CopyFrom(kSequentialBytes);
575   EXPECT_EQ(result.status(), OkStatus());
576   ASSERT_EQ(result.size(), 6u);
577   EXPECT_TRUE(std::equal(mb.begin(), mb.end(), kSequentialBytes.begin()));
578 }
579 
TEST(MultiBuf,CopyFromIntoMultipleChunks)580 TEST(MultiBuf, CopyFromIntoMultipleChunks) {
581   AllocatorForTest<kArbitraryAllocatorSize> allocator;
582   MultiBuf mb;
583   mb.PushBackChunk(MakeChunk(allocator, 2));
584   mb.PushBackChunk(MakeChunk(allocator, 0));
585   mb.PushBackChunk(MakeChunk(allocator, 3));
586   mb.PushBackChunk(MakeChunk(allocator, 1));
587   mb.PushBackChunk(MakeChunk(allocator, 0));
588 
589   StatusWithSize result = mb.CopyFrom(kSequentialBytes);
590   EXPECT_EQ(result.status(), OkStatus());
591   ASSERT_EQ(result.size(), 6u);
592   EXPECT_TRUE(std::equal(mb.begin(), mb.end(), kSequentialBytes.begin()));
593 }
594 
TEST(MultiBuf,CopyFromInTwoParts)595 TEST(MultiBuf, CopyFromInTwoParts) {
596   AllocatorForTest<kArbitraryAllocatorSize> allocator;
597 
598   for (size_t first = 0; first < kSequentialBytes.size(); ++first) {
599     MultiBuf mb;
600     mb.PushBackChunk(MakeChunk(allocator, 1));
601     mb.PushBackChunk(MakeChunk(allocator, 0));
602     mb.PushBackChunk(MakeChunk(allocator, 0));
603     mb.PushBackChunk(MakeChunk(allocator, 2));
604     mb.PushBackChunk(MakeChunk(allocator, 3));
605     ASSERT_EQ(mb.size(), kSequentialBytes.size());
606 
607     StatusWithSize result = mb.CopyFrom(span(kSequentialBytes).first(first));
608     ASSERT_EQ(result.status(), OkStatus());
609     ASSERT_EQ(result.size(), first);
610 
611     result = mb.CopyFrom(
612         span(kSequentialBytes).last(kSequentialBytes.size() - first),
613         result.size());  // start from last offset
614     ASSERT_EQ(result.status(), OkStatus());
615     ASSERT_EQ(result.size(), kSequentialBytes.size() - first);
616 
617     ASSERT_TRUE(std::equal(mb.begin(), mb.end(), kSequentialBytes.begin()))
618         << "The whole buffer should have copied";
619   }
620 }
621 
TEST(MultiBuf,CopyFromAndTruncate)622 TEST(MultiBuf, CopyFromAndTruncate) {
623   AllocatorForTest<kArbitraryAllocatorSize> allocator;
624 
625   for (size_t to_copy = 0; to_copy < kSequentialBytes.size(); ++to_copy) {
626     MultiBuf mb;
627     mb.PushBackChunk(MakeChunk(allocator, 1));
628     mb.PushBackChunk(MakeChunk(allocator, 0));
629     mb.PushBackChunk(MakeChunk(allocator, 0));
630     mb.PushBackChunk(MakeChunk(allocator, 2));
631     mb.PushBackChunk(MakeChunk(allocator, 3));
632     mb.PushBackChunk(MakeChunk(allocator, 0));
633     ASSERT_EQ(mb.size(), kSequentialBytes.size());
634 
635     StatusWithSize result =
636         mb.CopyFromAndTruncate(span(kSequentialBytes).first(to_copy));
637     ASSERT_EQ(result.status(), OkStatus());
638     ASSERT_EQ(result.size(), to_copy);
639     ASSERT_EQ(mb.size(), result.size());
640     ASSERT_TRUE(std::equal(mb.begin(), mb.end(), kSequentialBytes.begin()));
641   }
642 }
643 
TEST(MultiBuf,CopyFromAndTruncateFromOffset)644 TEST(MultiBuf, CopyFromAndTruncateFromOffset) {
645   AllocatorForTest<kArbitraryAllocatorSize> allocator;
646 
647   static constexpr std::array<std::byte, 6> kZeroes = {};
648 
649   // Sweep offsets 0–6 (inclusive), and copy 0–all bytes for each offset.
650   for (size_t offset = 0; offset <= kSequentialBytes.size(); ++offset) {
651     for (size_t to_copy = 0; to_copy <= kSequentialBytes.size() - offset;
652          ++to_copy) {
653       MultiBuf mb;
654       mb.PushBackChunk(MakeChunk(allocator, 2));
655       mb.PushBackChunk(MakeChunk(allocator, 0));
656       mb.PushBackChunk(MakeChunk(allocator, 3));
657       mb.PushBackChunk(MakeChunk(allocator, 0));
658       mb.PushBackChunk(MakeChunk(allocator, 0));
659       mb.PushBackChunk(MakeChunk(allocator, 1));
660       ASSERT_EQ(mb.size(), kSequentialBytes.size());
661 
662       StatusWithSize result =
663           mb.CopyFromAndTruncate(span(kSequentialBytes).first(to_copy), offset);
664       ASSERT_EQ(result.status(), OkStatus());
665       ASSERT_EQ(result.size(), to_copy);
666       ASSERT_EQ(mb.size(), offset + to_copy);
667 
668       // MultiBuf contains to_copy 0s followed by to_copy sequential bytes.
669       ASSERT_TRUE(std::equal(mb.begin(), mb.begin() + offset, kZeroes.begin()));
670       ASSERT_TRUE(
671           std::equal(mb.begin() + offset, mb.end(), kSequentialBytes.begin()));
672     }
673   }
674 }
675 
TEST(MultiBuf,CopyFromIntoEmptyMultibuf)676 TEST(MultiBuf, CopyFromIntoEmptyMultibuf) {
677   AllocatorForTest<kArbitraryAllocatorSize> allocator;
678   MultiBuf mb;
679 
680   StatusWithSize result = mb.CopyFrom({});
681   EXPECT_EQ(result.status(), OkStatus());  // empty source, so copy succeeded
682   EXPECT_EQ(result.size(), 0u);
683 
684   result = mb.CopyFrom(kSequentialBytes);
685   EXPECT_EQ(result.status(), Status::ResourceExhausted());
686   EXPECT_EQ(result.size(), 0u);
687 
688   mb.PushBackChunk(MakeChunk(allocator, 0));  // add an empty chunk
689 
690   result = mb.CopyFrom({});
691   EXPECT_EQ(result.status(), OkStatus());  // empty source, so copy succeeded
692   EXPECT_EQ(result.size(), 0u);
693 
694   result = mb.CopyFrom(kSequentialBytes);
695   EXPECT_EQ(result.status(), Status::ResourceExhausted());
696   EXPECT_EQ(result.size(), 0u);
697 }
698 
TEST(MultiBuf,IsContiguousTrueForEmptyBuffer)699 TEST(MultiBuf, IsContiguousTrueForEmptyBuffer) {
700   AllocatorForTest<kArbitraryAllocatorSize> allocator;
701 
702   MultiBuf buf;
703   EXPECT_TRUE(buf.IsContiguous());
704 
705   buf.PushBackChunk(MakeChunk(allocator, {}));
706   EXPECT_TRUE(buf.IsContiguous());
707   buf.PushBackChunk(MakeChunk(allocator, {}));
708   EXPECT_TRUE(buf.IsContiguous());
709   buf.PushBackChunk(MakeChunk(allocator, {}));
710   EXPECT_TRUE(buf.IsContiguous());
711 }
712 
TEST(MultiBuf,IsContiguousTrueForSingleNonEmptyChunk)713 TEST(MultiBuf, IsContiguousTrueForSingleNonEmptyChunk) {
714   AllocatorForTest<kArbitraryAllocatorSize> allocator;
715 
716   MultiBuf buf;
717   buf.PushBackChunk(MakeChunk(allocator, {1_b}));
718   EXPECT_TRUE(buf.IsContiguous());
719   buf.PushBackChunk(MakeChunk(allocator, {}));
720   EXPECT_TRUE(buf.IsContiguous());
721   buf.PushFrontChunk(MakeChunk(allocator, {}));
722   EXPECT_TRUE(buf.IsContiguous());
723 }
724 
TEST(MultiBuf,IsContiguousFalseIfMultipleNonEmptyChunks)725 TEST(MultiBuf, IsContiguousFalseIfMultipleNonEmptyChunks) {
726   AllocatorForTest<kArbitraryAllocatorSize> allocator;
727 
728   MultiBuf buf;
729   buf.PushBackChunk(MakeChunk(allocator, {1_b}));
730   buf.PushBackChunk(MakeChunk(allocator, {2_b}));
731   EXPECT_FALSE(buf.IsContiguous());
732 }
733 
TEST(MultiBuf,ContiguousSpanAcrossMultipleChunks)734 TEST(MultiBuf, ContiguousSpanAcrossMultipleChunks) {
735   AllocatorForTest<kArbitraryAllocatorSize> allocator;
736   OwnedChunk chunk_1 = MakeChunk(allocator, 10);
737   const ConstByteSpan contiguous_span = chunk_1;
738   OwnedChunk chunk_2 = chunk_1->TakeSuffix(5).value();
739   OwnedChunk chunk_3 = chunk_2->TakeSuffix(5).value();
740   OwnedChunk chunk_4 = chunk_3->TakeSuffix(1).value();
741 
742   MultiBuf buf;
743   buf.PushBackChunk(std::move(chunk_1));       // 5 bytes
744   buf.PushBackChunk(std::move(chunk_2));       // 0 bytes
745   buf.PushBackChunk(std::move(chunk_3));       // 4 bytes
746   buf.PushBackChunk(std::move(chunk_4));       // 1 byte
747   buf.PushBackChunk(MakeChunk(allocator, 0));  // empty
748 
749   auto it = buf.Chunks().begin();
750   ASSERT_EQ((it++)->size(), 5u);
751   ASSERT_EQ((it++)->size(), 0u);
752   ASSERT_EQ((it++)->size(), 4u);
753   ASSERT_EQ((it++)->size(), 1u);
754   ASSERT_EQ((it++)->size(), 0u);
755   ASSERT_EQ(it, buf.Chunks().end());
756 
757   EXPECT_TRUE(buf.IsContiguous());
758   ByteSpan span = buf.ContiguousSpan().value();
759   EXPECT_EQ(span.data(), contiguous_span.data());
760   EXPECT_EQ(span.size(), contiguous_span.size());
761 
762   it = buf.Chunks().begin();
763   buf.InsertChunk(++it, MakeChunk(allocator, 1));
764   EXPECT_FALSE(buf.IsContiguous());
765 }
766 
767 }  // namespace
768 }  // namespace pw::multibuf
769