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