• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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/suffix.h"
19 #include "pw_multibuf_private/test_utils.h"
20 #include "pw_span/span.h"
21 #include "pw_unit_test/framework.h"
22 
23 namespace pw::multibuf {
24 namespace {
25 
26 using namespace pw::multibuf::test_utils;
27 
28 #if __cplusplus >= 202002L
29 static_assert(std::forward_iterator<MultiBuf::iterator>);
30 static_assert(std::forward_iterator<MultiBuf::const_iterator>);
31 static_assert(std::forward_iterator<MultiBuf::ChunkIterator>);
32 static_assert(std::forward_iterator<MultiBuf::ConstChunkIterator>);
33 #endif  // __cplusplus >= 202002L
34 
TEST(MultiBuf,IsDefaultConstructible)35 TEST(MultiBuf, IsDefaultConstructible) { [[maybe_unused]] MultiBuf buf; }
36 
TEST(MultiBuf,WithOneChunkReleases)37 TEST(MultiBuf, WithOneChunkReleases) {
38   AllocatorForTest<kArbitraryAllocatorSize> allocator;
39   const auto& metrics = allocator.metrics();
40   MultiBuf buf;
41   buf.PushFrontChunk(MakeChunk(allocator, kArbitraryChunkSize));
42   EXPECT_EQ(metrics.num_allocations.value(), 2U);
43   buf.Release();
44   EXPECT_EQ(metrics.num_deallocations.value(), 2U);
45 }
46 
TEST(MultiBuf,WithOneChunkReleasesOnDestruction)47 TEST(MultiBuf, WithOneChunkReleasesOnDestruction) {
48   AllocatorForTest<kArbitraryAllocatorSize> allocator;
49   const auto& metrics = allocator.metrics();
50   {
51     MultiBuf buf;
52     buf.PushFrontChunk(MakeChunk(allocator, kArbitraryChunkSize));
53     EXPECT_EQ(metrics.num_allocations.value(), 2U);
54   }
55   EXPECT_EQ(metrics.num_deallocations.value(), 2U);
56 }
57 
TEST(MultiBuf,WithMultipleChunksReleasesAllOnDestruction)58 TEST(MultiBuf, WithMultipleChunksReleasesAllOnDestruction) {
59   AllocatorForTest<kArbitraryAllocatorSize> allocator;
60   const auto& metrics = allocator.metrics();
61   {
62     MultiBuf buf;
63     buf.PushFrontChunk(MakeChunk(allocator, kArbitraryChunkSize));
64     buf.PushFrontChunk(MakeChunk(allocator, kArbitraryChunkSize));
65     EXPECT_EQ(metrics.num_allocations.value(), 4U);
66   }
67   EXPECT_EQ(metrics.num_deallocations.value(), 4U);
68 }
69 
TEST(MultiBuf,SizeReturnsNumberOfBytes)70 TEST(MultiBuf, SizeReturnsNumberOfBytes) {
71   AllocatorForTest<kArbitraryAllocatorSize> allocator;
72   MultiBuf buf;
73   EXPECT_EQ(buf.size(), 0U);
74   buf.PushFrontChunk(MakeChunk(allocator, kArbitraryChunkSize));
75   EXPECT_EQ(buf.size(), kArbitraryChunkSize);
76   buf.PushFrontChunk(MakeChunk(allocator, kArbitraryChunkSize));
77   EXPECT_EQ(buf.size(), kArbitraryChunkSize * 2);
78 }
79 
TEST(MultiBuf,EmptyIfNoChunks)80 TEST(MultiBuf, EmptyIfNoChunks) {
81   AllocatorForTest<kArbitraryAllocatorSize> allocator;
82   MultiBuf buf;
83   EXPECT_EQ(buf.size(), 0U);
84   EXPECT_TRUE(buf.empty());
85   buf.PushFrontChunk(MakeChunk(allocator, kArbitraryChunkSize));
86   EXPECT_NE(buf.size(), 0U);
87   EXPECT_FALSE(buf.empty());
88 }
89 
TEST(MultiBuf,EmptyIfOnlyEmptyChunks)90 TEST(MultiBuf, EmptyIfOnlyEmptyChunks) {
91   AllocatorForTest<kArbitraryAllocatorSize> allocator;
92   MultiBuf buf;
93   EXPECT_TRUE(buf.empty());
94   buf.PushFrontChunk(MakeChunk(allocator, 0));
95   EXPECT_TRUE(buf.empty());
96   buf.PushFrontChunk(MakeChunk(allocator, 0));
97   EXPECT_TRUE(buf.empty());
98   EXPECT_EQ(buf.size(), 0U);
99 }
100 
TEST(MultiBuf,EmptyIsFalseIfAnyNonEmptyChunks)101 TEST(MultiBuf, EmptyIsFalseIfAnyNonEmptyChunks) {
102   AllocatorForTest<kArbitraryAllocatorSize> allocator;
103   MultiBuf buf;
104   buf.PushFrontChunk(MakeChunk(allocator, 0));
105   EXPECT_TRUE(buf.empty());
106   buf.PushFrontChunk(MakeChunk(allocator, kArbitraryChunkSize));
107   EXPECT_FALSE(buf.empty());
108   EXPECT_EQ(buf.size(), kArbitraryChunkSize);
109 }
110 
TEST(MultiBuf,ClaimPrefixReclaimsFirstChunkPrefix)111 TEST(MultiBuf, ClaimPrefixReclaimsFirstChunkPrefix) {
112   AllocatorForTest<kArbitraryAllocatorSize> allocator;
113   MultiBuf buf;
114   OwnedChunk chunk = MakeChunk(allocator, 16);
115   chunk->DiscardPrefix(7);
116   buf.PushFrontChunk(std::move(chunk));
117   EXPECT_EQ(buf.size(), 9U);
118   EXPECT_EQ(buf.ClaimPrefix(7), true);
119   EXPECT_EQ(buf.size(), 16U);
120 }
121 
TEST(MultiBuf,ClaimPrefixOnFirstChunkWithoutPrefixReturnsFalse)122 TEST(MultiBuf, ClaimPrefixOnFirstChunkWithoutPrefixReturnsFalse) {
123   AllocatorForTest<kArbitraryAllocatorSize> allocator;
124   MultiBuf buf;
125   buf.PushFrontChunk(MakeChunk(allocator, 16));
126   EXPECT_EQ(buf.size(), 16U);
127   EXPECT_EQ(buf.ClaimPrefix(7), false);
128   EXPECT_EQ(buf.size(), 16U);
129 }
130 
TEST(MultiBuf,ClaimPrefixWithoutChunksReturnsFalse)131 TEST(MultiBuf, ClaimPrefixWithoutChunksReturnsFalse) {
132   AllocatorForTest<kArbitraryAllocatorSize> allocator;
133   MultiBuf buf;
134   EXPECT_EQ(buf.size(), 0U);
135   EXPECT_EQ(buf.ClaimPrefix(7), false);
136   EXPECT_EQ(buf.size(), 0U);
137 }
138 
TEST(MultiBuf,ClaimSuffixReclaimsLastChunkSuffix)139 TEST(MultiBuf, ClaimSuffixReclaimsLastChunkSuffix) {
140   AllocatorForTest<kArbitraryAllocatorSize> allocator;
141   MultiBuf buf;
142   OwnedChunk chunk = MakeChunk(allocator, 16U);
143   chunk->Truncate(9U);
144   buf.PushFrontChunk(std::move(chunk));
145   buf.PushFrontChunk(MakeChunk(allocator, 4U));
146   EXPECT_EQ(buf.size(), 13U);
147   EXPECT_EQ(buf.ClaimSuffix(7U), true);
148   EXPECT_EQ(buf.size(), 20U);
149 }
150 
TEST(MultiBuf,ClaimSuffixOnLastChunkWithoutSuffixReturnsFalse)151 TEST(MultiBuf, ClaimSuffixOnLastChunkWithoutSuffixReturnsFalse) {
152   AllocatorForTest<kArbitraryAllocatorSize> allocator;
153   MultiBuf buf;
154   buf.PushFrontChunk(MakeChunk(allocator, 16U));
155   EXPECT_EQ(buf.size(), 16U);
156   EXPECT_EQ(buf.ClaimPrefix(7U), false);
157   EXPECT_EQ(buf.size(), 16U);
158 }
159 
TEST(MultiBuf,ClaimSuffixWithoutChunksReturnsFalse)160 TEST(MultiBuf, ClaimSuffixWithoutChunksReturnsFalse) {
161   AllocatorForTest<kArbitraryAllocatorSize> allocator;
162   MultiBuf buf;
163   EXPECT_EQ(buf.size(), 0U);
164   EXPECT_EQ(buf.ClaimSuffix(7U), false);
165   EXPECT_EQ(buf.size(), 0U);
166 }
167 
TEST(MultiBuf,DiscardPrefixWithZeroDoesNothing)168 TEST(MultiBuf, DiscardPrefixWithZeroDoesNothing) {
169   AllocatorForTest<kArbitraryAllocatorSize> allocator;
170   MultiBuf buf;
171   buf.DiscardPrefix(0);
172   EXPECT_EQ(buf.size(), 0U);
173 }
174 
TEST(MultiBuf,DiscardPrefixDiscardsPartialChunk)175 TEST(MultiBuf, DiscardPrefixDiscardsPartialChunk) {
176   AllocatorForTest<kArbitraryAllocatorSize> allocator;
177   MultiBuf buf;
178   buf.PushFrontChunk(MakeChunk(allocator, 16U));
179   buf.DiscardPrefix(5U);
180   EXPECT_EQ(buf.size(), 11U);
181 }
182 
TEST(MultiBuf,DiscardPrefixDiscardsWholeChunk)183 TEST(MultiBuf, DiscardPrefixDiscardsWholeChunk) {
184   AllocatorForTest<kArbitraryAllocatorSize> allocator;
185   MultiBuf buf;
186   buf.PushFrontChunk(MakeChunk(allocator, 16U));
187   buf.PushFrontChunk(MakeChunk(allocator, 3U));
188   buf.DiscardPrefix(16U);
189   EXPECT_EQ(buf.size(), 3U);
190 }
191 
TEST(MultiBuf,DiscardPrefixDiscardsMultipleChunks)192 TEST(MultiBuf, DiscardPrefixDiscardsMultipleChunks) {
193   AllocatorForTest<kArbitraryAllocatorSize> allocator;
194   MultiBuf buf;
195   buf.PushFrontChunk(MakeChunk(allocator, 16U));
196   buf.PushFrontChunk(MakeChunk(allocator, 4U));
197   buf.PushFrontChunk(MakeChunk(allocator, 3U));
198   buf.DiscardPrefix(21U);
199   EXPECT_EQ(buf.size(), 2U);
200 }
201 
TEST(MultiBuf,SliceDiscardsPrefixAndSuffixWholeAndPartialChunks)202 TEST(MultiBuf, SliceDiscardsPrefixAndSuffixWholeAndPartialChunks) {
203   AllocatorForTest<kArbitraryAllocatorSize> allocator;
204   MultiBuf buf;
205   buf.PushBackChunk(MakeChunk(allocator, {1_b, 1_b, 1_b}));
206   buf.PushBackChunk(MakeChunk(allocator, {2_b, 2_b, 2_b}));
207   buf.PushBackChunk(MakeChunk(allocator, {3_b, 3_b, 3_b}));
208   buf.PushBackChunk(MakeChunk(allocator, {4_b, 4_b, 4_b}));
209   buf.Slice(4, 7);
210   ExpectElementsEqual(buf, {2_b, 2_b, 3_b});
211 }
212 
TEST(MultiBuf,SliceDoesNotModifyChunkMemory)213 TEST(MultiBuf, SliceDoesNotModifyChunkMemory) {
214   AllocatorForTest<kArbitraryAllocatorSize> allocator;
215   MultiBuf buf;
216   std::array<std::byte, 4> kBytes = {1_b, 2_b, 3_b, 4_b};
217   OwnedChunk chunk = MakeChunk(allocator, kBytes);
218   ConstByteSpan span(chunk);
219   buf.PushFrontChunk(std::move(chunk));
220   buf.Slice(2, 3);
221   ExpectElementsEqual(span, kBytes);
222 }
223 
TEST(MultiBuf,TruncateRemovesWholeAndPartialChunks)224 TEST(MultiBuf, TruncateRemovesWholeAndPartialChunks) {
225   AllocatorForTest<kArbitraryAllocatorSize> allocator;
226   MultiBuf buf;
227   buf.PushFrontChunk(MakeChunk(allocator, 3U));
228   buf.PushFrontChunk(MakeChunk(allocator, 3U));
229   buf.Truncate(2U);
230   EXPECT_EQ(buf.size(), 2U);
231 }
232 
TEST(MultiBuf,TruncateEmptyBuffer)233 TEST(MultiBuf, TruncateEmptyBuffer) {
234   MultiBuf buf;
235   buf.Truncate(0);
236   EXPECT_TRUE(buf.empty());
237 }
238 
TEST(MultiBuf,TakePrefixWithNoBytesDoesNothing)239 TEST(MultiBuf, TakePrefixWithNoBytesDoesNothing) {
240   AllocatorForTest<kArbitraryAllocatorSize> allocator;
241   MultiBuf buf;
242   std::optional<MultiBuf> empty_front = buf.TakePrefix(0);
243   ASSERT_TRUE(empty_front.has_value());
244   EXPECT_EQ(buf.size(), 0U);
245   EXPECT_EQ(empty_front->size(), 0U);
246 }
247 
TEST(MultiBuf,TakePrefixReturnsPartialChunk)248 TEST(MultiBuf, TakePrefixReturnsPartialChunk) {
249   AllocatorForTest<kArbitraryAllocatorSize> allocator;
250   MultiBuf buf;
251   buf.PushBackChunk(MakeChunk(allocator, {1_b, 2_b, 3_b}));
252   std::optional<MultiBuf> old_front = buf.TakePrefix(2);
253   ASSERT_TRUE(old_front.has_value());
254   ExpectElementsEqual(*old_front, {1_b, 2_b});
255   ExpectElementsEqual(buf, {3_b});
256 }
257 
TEST(MultiBuf,TakePrefixReturnsWholeAndPartialChunks)258 TEST(MultiBuf, TakePrefixReturnsWholeAndPartialChunks) {
259   AllocatorForTest<kArbitraryAllocatorSize> allocator;
260   MultiBuf buf;
261   buf.PushBackChunk(MakeChunk(allocator, {1_b, 2_b, 3_b}));
262   buf.PushBackChunk(MakeChunk(allocator, {4_b, 5_b, 6_b}));
263   std::optional<MultiBuf> old_front = buf.TakePrefix(4);
264   ASSERT_TRUE(old_front.has_value());
265   ExpectElementsEqual(*old_front, {1_b, 2_b, 3_b, 4_b});
266   ExpectElementsEqual(buf, {5_b, 6_b});
267 }
268 
TEST(MultiBuf,TakeSuffixReturnsWholeAndPartialChunks)269 TEST(MultiBuf, TakeSuffixReturnsWholeAndPartialChunks) {
270   AllocatorForTest<kArbitraryAllocatorSize> allocator;
271   MultiBuf buf;
272   buf.PushBackChunk(MakeChunk(allocator, {1_b, 2_b, 3_b}));
273   buf.PushBackChunk(MakeChunk(allocator, {4_b, 5_b, 6_b}));
274   std::optional<MultiBuf> old_tail = buf.TakeSuffix(4);
275   ASSERT_TRUE(old_tail.has_value());
276   ExpectElementsEqual(buf, {1_b, 2_b});
277   ExpectElementsEqual(*old_tail, {3_b, 4_b, 5_b, 6_b});
278 }
279 
TEST(MultiBuf,PushPrefixPrependsData)280 TEST(MultiBuf, PushPrefixPrependsData) {
281   AllocatorForTest<kArbitraryAllocatorSize> allocator;
282   MultiBuf buf;
283   buf.PushBackChunk(MakeChunk(allocator, {1_b, 2_b, 3_b}));
284   buf.PushBackChunk(MakeChunk(allocator, {4_b, 5_b, 6_b}));
285   MultiBuf buf2;
286   buf2.PushBackChunk(MakeChunk(allocator, {7_b, 8_b}));
287   buf2.PushPrefix(std::move(buf));
288   ExpectElementsEqual(buf2, {1_b, 2_b, 3_b, 4_b, 5_b, 6_b, 7_b, 8_b});
289 }
290 
TEST(MultiBuf,PushSuffixAppendsData)291 TEST(MultiBuf, PushSuffixAppendsData) {
292   AllocatorForTest<kArbitraryAllocatorSize> allocator;
293   MultiBuf buf;
294   buf.PushBackChunk(MakeChunk(allocator, {1_b, 2_b, 3_b}));
295   buf.PushBackChunk(MakeChunk(allocator, {4_b, 5_b, 6_b}));
296   MultiBuf buf2;
297   buf2.PushBackChunk(MakeChunk(allocator, {7_b, 8_b}));
298   buf2.PushSuffix(std::move(buf));
299   ExpectElementsEqual(buf2, {7_b, 8_b, 1_b, 2_b, 3_b, 4_b, 5_b, 6_b});
300 }
301 
TEST(MultiBuf,PushFrontChunkAddsBytesToFront)302 TEST(MultiBuf, PushFrontChunkAddsBytesToFront) {
303   AllocatorForTest<kArbitraryAllocatorSize> allocator;
304   MultiBuf buf;
305 
306   const std::array<std::byte, 3> kBytesOne = {0_b, 1_b, 2_b};
307   auto chunk_one = MakeChunk(allocator, kBytesOne);
308   buf.PushFrontChunk(std::move(chunk_one));
309   ExpectElementsEqual(buf, kBytesOne);
310 
311   const std::array<std::byte, 4> kBytesTwo = {9_b, 10_b, 11_b, 12_b};
312   auto chunk_two = MakeChunk(allocator, kBytesTwo);
313   buf.PushFrontChunk(std::move(chunk_two));
314 
315   // clang-format off
316   ExpectElementsEqual(buf, {
317       9_b, 10_b, 11_b, 12_b,
318       0_b, 1_b, 2_b,
319   });
320   // clang-format on
321 }
322 
TEST(MultiBuf,InsertChunkOnEmptyBufAddsFirstChunk)323 TEST(MultiBuf, InsertChunkOnEmptyBufAddsFirstChunk) {
324   AllocatorForTest<kArbitraryAllocatorSize> allocator;
325   MultiBuf buf;
326 
327   const std::array<std::byte, 3> kBytes = {0_b, 1_b, 2_b};
328   auto chunk = MakeChunk(allocator, kBytes);
329   auto inserted_iter = buf.InsertChunk(buf.Chunks().begin(), std::move(chunk));
330   EXPECT_EQ(inserted_iter, buf.Chunks().begin());
331   ExpectElementsEqual(buf, kBytes);
332   EXPECT_EQ(++inserted_iter, buf.Chunks().end());
333 }
334 
TEST(MultiBuf,InsertChunkAtEndOfBufAddsLastChunk)335 TEST(MultiBuf, InsertChunkAtEndOfBufAddsLastChunk) {
336   AllocatorForTest<kArbitraryAllocatorSize> allocator;
337   MultiBuf buf;
338 
339   // Add a chunk to the beginning
340   buf.PushFrontChunk(MakeChunk(allocator, kArbitraryChunkSize));
341 
342   const std::array<std::byte, 3> kBytes = {0_b, 1_b, 2_b};
343   auto chunk = MakeChunk(allocator, kBytes);
344   auto inserted_iter = buf.InsertChunk(buf.Chunks().end(), std::move(chunk));
345   EXPECT_EQ(inserted_iter, ++buf.Chunks().begin());
346   EXPECT_EQ(++inserted_iter, buf.Chunks().end());
347   const Chunk& second_chunk = *(++buf.Chunks().begin());
348   ExpectElementsEqual(second_chunk, kBytes);
349 }
350 
TEST(MultiBuf,TakeChunkAtBeginRemovesAndReturnsFirstChunk)351 TEST(MultiBuf, TakeChunkAtBeginRemovesAndReturnsFirstChunk) {
352   AllocatorForTest<kArbitraryAllocatorSize> allocator;
353   MultiBuf buf;
354   auto insert_iter = buf.Chunks().begin();
355   insert_iter = buf.InsertChunk(insert_iter, MakeChunk(allocator, 2));
356   insert_iter = buf.InsertChunk(++insert_iter, MakeChunk(allocator, 4));
357 
358   auto [chunk_iter, chunk] = buf.TakeChunk(buf.Chunks().begin());
359   EXPECT_EQ(chunk.size(), 2U);
360   EXPECT_EQ(chunk_iter->size(), 4U);
361   ++chunk_iter;
362   EXPECT_EQ(chunk_iter, buf.Chunks().end());
363 }
364 
TEST(MultiBuf,TakeChunkOnLastInsertedIterReturnsLastInserted)365 TEST(MultiBuf, TakeChunkOnLastInsertedIterReturnsLastInserted) {
366   AllocatorForTest<kArbitraryAllocatorSize> allocator;
367   MultiBuf buf;
368   auto iter = buf.Chunks().begin();
369   iter = buf.InsertChunk(iter, MakeChunk(allocator, 42));
370   iter = buf.InsertChunk(++iter, MakeChunk(allocator, 11));
371   iter = buf.InsertChunk(++iter, MakeChunk(allocator, 65));
372   OwnedChunk chunk;
373   std::tie(iter, chunk) = buf.TakeChunk(iter);
374   EXPECT_EQ(iter, buf.Chunks().end());
375   EXPECT_EQ(chunk.size(), 65U);
376 }
377 
TEST(MultiBuf,RangeBasedForLoopsCompile)378 TEST(MultiBuf, RangeBasedForLoopsCompile) {
379   MultiBuf buf;
380   for ([[maybe_unused]] std::byte& byte : buf) {
381   }
382   for ([[maybe_unused]] const std::byte& byte : buf) {
383   }
384   for ([[maybe_unused]] Chunk& chunk : buf.Chunks()) {
385   }
386   for ([[maybe_unused]] const Chunk& chunk : buf.Chunks()) {
387   }
388 
389   const MultiBuf const_buf;
390   for ([[maybe_unused]] const std::byte& byte : const_buf) {
391   }
392   for ([[maybe_unused]] const Chunk& chunk : const_buf.Chunks()) {
393   }
394 }
395 
TEST(MultiBuf,IteratorAdvancesNAcrossChunks)396 TEST(MultiBuf, IteratorAdvancesNAcrossChunks) {
397   AllocatorForTest<kArbitraryAllocatorSize> allocator;
398   MultiBuf buf;
399   buf.PushBackChunk(MakeChunk(allocator, {1_b, 2_b, 3_b}));
400   buf.PushBackChunk(MakeChunk(allocator, {4_b, 5_b, 6_b}));
401 
402   MultiBuf::iterator iter = buf.begin();
403   iter += 4;
404   EXPECT_EQ(*iter, 5_b);
405 }
406 
TEST(MultiBuf,IteratorAdvancesNAcrossZeroLengthChunk)407 TEST(MultiBuf, IteratorAdvancesNAcrossZeroLengthChunk) {
408   AllocatorForTest<kArbitraryAllocatorSize> allocator;
409   MultiBuf buf;
410   buf.PushBackChunk(MakeChunk(allocator, 0));
411   buf.PushBackChunk(MakeChunk(allocator, {1_b, 2_b, 3_b}));
412   buf.PushBackChunk(MakeChunk(allocator, 0));
413   buf.PushBackChunk(MakeChunk(allocator, {4_b, 5_b, 6_b}));
414 
415   MultiBuf::iterator iter = buf.begin();
416   iter += 4;
417   EXPECT_EQ(*iter, 5_b);
418 }
419 
TEST(MultiBuf,ConstIteratorAdvancesNAcrossChunks)420 TEST(MultiBuf, ConstIteratorAdvancesNAcrossChunks) {
421   AllocatorForTest<kArbitraryAllocatorSize> allocator;
422   MultiBuf buf;
423   buf.PushBackChunk(MakeChunk(allocator, {1_b, 2_b, 3_b}));
424   buf.PushBackChunk(MakeChunk(allocator, {4_b, 5_b, 6_b}));
425 
426   MultiBuf::const_iterator iter = buf.cbegin();
427   iter += 4;
428   EXPECT_EQ(*iter, 5_b);
429 }
430 
TEST(MultiBuf,IteratorSkipsEmptyChunks)431 TEST(MultiBuf, IteratorSkipsEmptyChunks) {
432   AllocatorForTest<kArbitraryAllocatorSize> allocator;
433   MultiBuf buf;
434   buf.PushBackChunk(MakeChunk(allocator, 0));
435   buf.PushBackChunk(MakeChunk(allocator, 0));
436   buf.PushBackChunk(MakeChunk(allocator, {1_b}));
437   buf.PushBackChunk(MakeChunk(allocator, 0));
438   buf.PushBackChunk(MakeChunk(allocator, {2_b, 3_b}));
439   buf.PushBackChunk(MakeChunk(allocator, 0));
440 
441   MultiBuf::iterator it = buf.begin();
442   ASSERT_EQ(*it++, 1_b);
443   ASSERT_EQ(*it++, 2_b);
444   ASSERT_EQ(*it++, 3_b);
445   ASSERT_EQ(it, buf.end());
446 }
447 
448 }  // namespace
449 }  // namespace pw::multibuf
450