1 // Copyright 2020 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_blob_store/blob_store.h"
16
17 #include <array>
18 #include <cstddef>
19 #include <cstring>
20
21 #include "pw_kvs/crc16_checksum.h"
22 #include "pw_kvs/fake_flash_memory.h"
23 #include "pw_kvs/flash_memory.h"
24 #include "pw_kvs/test_key_value_store.h"
25 #include "pw_log/log.h"
26 #include "pw_random/xor_shift.h"
27 #include "pw_span/span.h"
28 #include "pw_unit_test/framework.h"
29
30 #ifndef PW_FLASH_TEST_ALIGNMENT
31 #define PW_FLASH_TEST_ALIGNMENT 1
32 #endif
33
34 namespace pw::blob_store {
35 namespace {
36
37 class BlobStoreTest : public ::testing::Test {
38 protected:
39 static constexpr char kBlobTitle[] = "TestBlobBlock";
40
BlobStoreTest()41 BlobStoreTest() : flash_(kFlashAlignment), partition_(&flash_) {}
42
InitFlashTo(span<const std::byte> contents)43 void InitFlashTo(span<const std::byte> contents) {
44 ASSERT_EQ(OkStatus(), partition_.Erase());
45 std::memcpy(flash_.buffer().data(), contents.data(), contents.size());
46 }
47
InitSourceBufferToRandom(uint64_t seed,size_t init_size_bytes=kBlobDataSize)48 void InitSourceBufferToRandom(uint64_t seed,
49 size_t init_size_bytes = kBlobDataSize) {
50 ASSERT_LE(init_size_bytes, source_buffer_.size());
51 random::XorShiftStarRng64 rng(seed);
52
53 std::memset(source_buffer_.data(),
54 static_cast<int>(flash_.erased_memory_content()),
55 source_buffer_.size());
56 rng.Get(span(source_buffer_).first(init_size_bytes));
57 }
58
InitSourceBufferToFill(char fill,size_t fill_size_bytes=kBlobDataSize)59 void InitSourceBufferToFill(char fill,
60 size_t fill_size_bytes = kBlobDataSize) {
61 ASSERT_LE(fill_size_bytes, source_buffer_.size());
62 std::memset(source_buffer_.data(),
63 static_cast<int>(flash_.erased_memory_content()),
64 source_buffer_.size());
65 std::memset(source_buffer_.data(), fill, fill_size_bytes);
66 }
67
68 // Fill the source buffer with random pattern based on given seed, written to
69 // BlobStore in specified chunk size.
WriteTestBlock(size_t write_size_bytes=kBlobDataSize)70 void WriteTestBlock(size_t write_size_bytes = kBlobDataSize) {
71 ASSERT_LE(write_size_bytes, source_buffer_.size());
72 constexpr size_t kBufferSize = 256;
73 kvs::ChecksumCrc16 checksum;
74
75 ConstByteSpan write_data = span(source_buffer_).first(write_size_bytes);
76
77 BlobStoreBuffer<kBufferSize> blob(
78 kBlobTitle, partition_, &checksum, kvs::TestKvs(), kBufferSize);
79 EXPECT_EQ(OkStatus(), blob.Init());
80
81 BlobStore::BlobWriterWithBuffer writer(blob);
82 EXPECT_EQ(OkStatus(), writer.Open());
83 ASSERT_EQ(OkStatus(), writer.Write(write_data));
84 EXPECT_EQ(OkStatus(), writer.Close());
85
86 VerifyBlob(blob, write_size_bytes);
87 }
88
VerifyBlob(BlobStore & blob,size_t expected_data_size)89 void VerifyBlob(BlobStore& blob, size_t expected_data_size) {
90 EXPECT_TRUE(blob.HasData());
91 BlobStore::BlobReader reader(blob);
92 ASSERT_EQ(OkStatus(), reader.Open());
93 EXPECT_EQ(expected_data_size, reader.ConservativeReadLimit());
94 Result<ConstByteSpan> result = reader.GetMemoryMappedBlob();
95 ASSERT_TRUE(result.ok());
96 EXPECT_EQ(expected_data_size, result.value().size_bytes());
97 VerifyFlash(result.value().first(result.value().size_bytes()));
98 VerifyFlash(flash_.buffer().first(result.value().size_bytes()));
99 EXPECT_EQ(OkStatus(), reader.Close());
100 }
101
102 // Open a new blob instance and read the blob using the given read chunk size.
ChunkReadTest(size_t read_chunk_size)103 void ChunkReadTest(size_t read_chunk_size) {
104 kvs::ChecksumCrc16 checksum;
105
106 VerifyFlash(flash_.buffer());
107
108 constexpr size_t kBufferSize = 16;
109 BlobStoreBuffer<kBufferSize> blob(
110 kBlobTitle, partition_, &checksum, kvs::TestKvs(), kBufferSize);
111 EXPECT_EQ(OkStatus(), blob.Init());
112
113 // Use reader to check for valid data.
114 BlobStore::BlobReader reader1(blob);
115 ASSERT_EQ(OkStatus(), reader1.Open());
116 Result<ConstByteSpan> possible_blob = reader1.GetMemoryMappedBlob();
117 ASSERT_TRUE(possible_blob.ok());
118 VerifyFlash(possible_blob.value());
119 EXPECT_EQ(OkStatus(), reader1.Close());
120
121 BlobStore::BlobReader reader(blob);
122 ASSERT_EQ(OkStatus(), reader.Open());
123
124 std::array<std::byte, kBlobDataSize> read_buffer;
125
126 ByteSpan read_span = read_buffer;
127 while (read_span.size_bytes() > 0) {
128 size_t read_size = std::min(read_span.size_bytes(), read_chunk_size);
129
130 PW_LOG_DEBUG("Do write of %u bytes, %u bytes remain",
131 static_cast<unsigned>(read_size),
132 static_cast<unsigned>(read_span.size_bytes()));
133
134 ASSERT_EQ(read_span.size_bytes(), reader.ConservativeReadLimit());
135 auto result = reader.Read(read_span.first(read_size));
136 ASSERT_EQ(result.status(), OkStatus());
137 read_span = read_span.subspan(read_size);
138 }
139 EXPECT_EQ(OkStatus(), reader.Close());
140
141 VerifyFlash(read_buffer);
142 }
143
VerifyFlash(ConstByteSpan verify_bytes,size_t offset=0)144 void VerifyFlash(ConstByteSpan verify_bytes, size_t offset = 0) {
145 // Should be defined as same size.
146 EXPECT_EQ(source_buffer_.size(), flash_.buffer().size_bytes());
147
148 // Can't allow it to march off the end of source_buffer_.
149 ASSERT_LE((verify_bytes.size_bytes() + offset), source_buffer_.size());
150
151 for (size_t i = 0; i < verify_bytes.size_bytes(); i++) {
152 ASSERT_EQ(source_buffer_[i + offset], verify_bytes[i]);
153 }
154 }
155
156 static constexpr size_t kFlashAlignment = PW_FLASH_TEST_ALIGNMENT;
157 static constexpr size_t kSectorSize = 1024;
158 static constexpr size_t kSectorCount = 6;
159 static constexpr size_t kBlobDataSize = (kSectorCount * kSectorSize);
160
161 kvs::FakeFlashMemoryBuffer<kSectorSize, kSectorCount> flash_;
162 kvs::FlashPartition partition_;
163 std::array<std::byte, kBlobDataSize> source_buffer_;
164 };
165
TEST_F(BlobStoreTest,Init_Ok)166 TEST_F(BlobStoreTest, Init_Ok) {
167 // TODO(davidrogers): Do init test with flash/kvs explicitly in the different
168 // possible entry states.
169 constexpr size_t kBufferSize = 256;
170 BlobStoreBuffer<kBufferSize> blob(
171 "Blob_OK", partition_, nullptr, kvs::TestKvs(), kBufferSize);
172 EXPECT_EQ(OkStatus(), blob.Init());
173 }
174
TEST_F(BlobStoreTest,Writer_ConservativeLimits)175 TEST_F(BlobStoreTest, Writer_ConservativeLimits) {
176 constexpr size_t kBufferSize = 256;
177 BlobStoreBuffer<kBufferSize> blob(
178 "Blob_OK", partition_, nullptr, kvs::TestKvs(), kBufferSize);
179 ASSERT_EQ(OkStatus(), blob.Init());
180
181 BlobStore::BlobWriterWithBuffer writer(blob);
182 ASSERT_EQ(OkStatus(), writer.Open());
183 EXPECT_EQ(writer.ConservativeReadLimit(), 0u);
184 EXPECT_EQ(writer.ConservativeWriteLimit(), kSectorSize * kSectorCount);
185 ASSERT_EQ(OkStatus(), writer.Close());
186
187 BlobStore::DeferredWriterWithBuffer deferred_writer(blob);
188 ASSERT_EQ(OkStatus(), deferred_writer.Open());
189 EXPECT_EQ(deferred_writer.ConservativeReadLimit(), 0u);
190 EXPECT_EQ(deferred_writer.ConservativeWriteLimit(), kBufferSize);
191 }
192
193 // Write to the blob using a flash_write_size_bytes smaller than the
194 // buffer size. Use Write operations smaller than flash_write_size_bytes
195 // to ensure it checks the internal buffering path.
TEST_F(BlobStoreTest,OversizedWriteBuffer)196 TEST_F(BlobStoreTest, OversizedWriteBuffer) {
197 size_t write_size_bytes = 8;
198 ASSERT_LE(write_size_bytes, source_buffer_.size());
199 constexpr size_t kBufferSize = 256;
200 kvs::ChecksumCrc16 checksum;
201
202 InitSourceBufferToRandom(0x123d123);
203
204 ConstByteSpan write_data = span(source_buffer_);
205 ConstByteSpan original_source = span(source_buffer_);
206
207 EXPECT_EQ(OkStatus(), partition_.Erase());
208
209 BlobStoreBuffer<kBufferSize> blob(
210 kBlobTitle, partition_, &checksum, kvs::TestKvs(), 64);
211 EXPECT_EQ(OkStatus(), blob.Init());
212
213 BlobStore::BlobWriterWithBuffer writer(blob);
214 EXPECT_EQ(OkStatus(), writer.Open());
215 while (write_data.size_bytes() > 0) {
216 ASSERT_EQ(OkStatus(), writer.Write(write_data.first(write_size_bytes)));
217 write_data = write_data.subspan(write_size_bytes);
218 }
219 EXPECT_EQ(OkStatus(), writer.Close());
220
221 VerifyBlob(blob, original_source.size_bytes());
222 }
223
TEST_F(BlobStoreTest,Reader_ConservativeLimits)224 TEST_F(BlobStoreTest, Reader_ConservativeLimits) {
225 InitSourceBufferToRandom(0x11309);
226 WriteTestBlock();
227
228 kvs::ChecksumCrc16 checksum;
229 constexpr size_t kBufferSize = 16;
230 BlobStoreBuffer<kBufferSize> blob(
231 "TestBlobBlock", partition_, &checksum, kvs::TestKvs(), kBufferSize);
232 EXPECT_EQ(OkStatus(), blob.Init());
233 EXPECT_TRUE(blob.HasData());
234 BlobStore::BlobReader reader(blob);
235 ASSERT_EQ(OkStatus(), reader.Open());
236
237 EXPECT_EQ(kBlobDataSize, reader.ConservativeReadLimit());
238 EXPECT_EQ(0u, reader.ConservativeWriteLimit());
239 }
240
TEST_F(BlobStoreTest,IsOpen)241 TEST_F(BlobStoreTest, IsOpen) {
242 constexpr size_t kBufferSize = 256;
243 BlobStoreBuffer<kBufferSize> blob(
244 "Blob_open", partition_, nullptr, kvs::TestKvs(), kBufferSize);
245
246 BlobStore::DeferredWriterWithBuffer deferred_writer(blob);
247 BlobStore::BlobWriterWithBuffer regular_writer(blob);
248 BlobStore::BlobReader reader(blob);
249 EXPECT_EQ(Status::FailedPrecondition(), regular_writer.Open());
250 EXPECT_EQ(Status::FailedPrecondition(), regular_writer.Resume().status());
251 EXPECT_EQ(Status::FailedPrecondition(), reader.Open());
252
253 EXPECT_EQ(OkStatus(), blob.Init());
254
255 EXPECT_EQ(false, deferred_writer.IsOpen());
256 EXPECT_EQ(OkStatus(), deferred_writer.Open());
257 EXPECT_EQ(true, deferred_writer.IsOpen());
258
259 EXPECT_EQ(Status::Unavailable(), regular_writer.Open());
260 EXPECT_EQ(Status::Unavailable(), reader.Open());
261
262 EXPECT_EQ(OkStatus(), deferred_writer.Close());
263 EXPECT_EQ(false, deferred_writer.IsOpen());
264
265 EXPECT_EQ(false, regular_writer.IsOpen());
266 EXPECT_EQ(OkStatus(), regular_writer.Open());
267 EXPECT_EQ(true, regular_writer.IsOpen());
268
269 EXPECT_FALSE(blob.HasData());
270
271 // Need to write something, so the blob reader is able to open.
272 std::array<std::byte, 64> tmp_buffer = {};
273 EXPECT_EQ(OkStatus(), regular_writer.Write(tmp_buffer));
274 EXPECT_EQ(OkStatus(), regular_writer.Close());
275 EXPECT_EQ(false, regular_writer.IsOpen());
276
277 EXPECT_TRUE(blob.HasData());
278 EXPECT_EQ(false, reader.IsOpen());
279 ASSERT_EQ(OkStatus(), reader.Open());
280 EXPECT_EQ(true, reader.IsOpen());
281 EXPECT_EQ(OkStatus(), reader.Close());
282 EXPECT_EQ(false, reader.IsOpen());
283 }
284
285 // Write to the blob using no write buffer size. Write operations must be
286 // multiples of flash_write_size_bytes.
TEST_F(BlobStoreTest,NoWriteBuffer_1Alignment)287 TEST_F(BlobStoreTest, NoWriteBuffer_1Alignment) {
288 if (kFlashAlignment > 1) {
289 // Test not valid for flash alignments greater than 1.
290 return;
291 }
292
293 const size_t kWriteSizeBytes = 1;
294 kvs::ChecksumCrc16 checksum;
295
296 InitSourceBufferToRandom(0xaabd123);
297
298 ConstByteSpan write_data = span(source_buffer_);
299 ConstByteSpan original_source = span(source_buffer_);
300
301 EXPECT_EQ(OkStatus(), partition_.Erase());
302
303 BlobStore blob(kBlobTitle,
304 partition_,
305 &checksum,
306 kvs::TestKvs(),
307 span<std::byte>(),
308 kWriteSizeBytes);
309 EXPECT_EQ(OkStatus(), blob.Init());
310
311 BlobStore::BlobWriterWithBuffer writer(blob);
312 EXPECT_EQ(OkStatus(), writer.Open());
313
314 size_t test_write_size[] = {1, 1, 2, 4, 32, 128};
315
316 for (size_t size : test_write_size) {
317 ASSERT_EQ(OkStatus(), writer.Write(write_data.first(size)));
318 write_data = write_data.subspan(size);
319 }
320
321 while (write_data.size_bytes() > 0) {
322 const size_t finish_write_size = 8;
323 ASSERT_EQ(OkStatus(), writer.Write(write_data.first(finish_write_size)));
324 write_data = write_data.subspan(finish_write_size);
325 }
326 EXPECT_EQ(write_data.size_bytes(), 0U);
327 EXPECT_EQ(OkStatus(), writer.Close());
328
329 VerifyBlob(blob, original_source.size_bytes());
330 }
331
332 // Write to the blob using no write buffer size. Write operations must be
333 // multiples of flash_write_size_bytes.
TEST_F(BlobStoreTest,NoWriteBuffer_16Alignment)334 TEST_F(BlobStoreTest, NoWriteBuffer_16Alignment) {
335 if (kFlashAlignment > 16) {
336 // Test not valid for flash alignments greater than 16.
337 return;
338 }
339
340 const size_t kWriteSizeBytes = 16;
341 kvs::ChecksumCrc16 checksum;
342
343 InitSourceBufferToRandom(0x6745d123);
344
345 ConstByteSpan write_data = span(source_buffer_);
346 ConstByteSpan original_source = span(source_buffer_);
347
348 EXPECT_EQ(OkStatus(), partition_.Erase());
349
350 BlobStore blob(kBlobTitle,
351 partition_,
352 &checksum,
353 kvs::TestKvs(),
354 span<std::byte>(),
355 kWriteSizeBytes);
356 EXPECT_EQ(OkStatus(), blob.Init());
357
358 BlobStore::BlobWriterWithBuffer writer(blob);
359 EXPECT_EQ(OkStatus(), writer.Open());
360 ASSERT_EQ(Status::InvalidArgument(), writer.Write(write_data.first(1)));
361 ASSERT_EQ(Status::InvalidArgument(),
362 writer.Write(write_data.first(kWriteSizeBytes / 2)));
363
364 ASSERT_EQ(OkStatus(), writer.Write(write_data.first(4 * kWriteSizeBytes)));
365 write_data = write_data.subspan(4 * kWriteSizeBytes);
366
367 ASSERT_EQ(Status::InvalidArgument(), writer.Write(write_data.first(1)));
368 ASSERT_EQ(Status::InvalidArgument(),
369 writer.Write(write_data.first(kWriteSizeBytes / 2)));
370
371 while (write_data.size_bytes() > 0) {
372 ASSERT_EQ(OkStatus(), writer.Write(write_data.first(kWriteSizeBytes)));
373 write_data = write_data.subspan(kWriteSizeBytes);
374 }
375 EXPECT_EQ(OkStatus(), writer.Close());
376
377 VerifyBlob(blob, original_source.size_bytes());
378 }
379
TEST_F(BlobStoreTest,FileName)380 TEST_F(BlobStoreTest, FileName) {
381 InitSourceBufferToRandom(0x8675309);
382 WriteTestBlock();
383 constexpr std::string_view kFileName("my_file_1.bin");
384 std::array<std::byte, 64> tmp_buffer = {};
385 static_assert(kFileName.size() <= tmp_buffer.size());
386 kvs::ChecksumCrc16 checksum;
387 constexpr size_t kBufferSize = 256;
388 {
389 // Create/init a blob store in a nested scope so it can be re-initialized
390 // later when checking the read.
391 BlobStoreBuffer<kBufferSize> blob(
392 kBlobTitle, partition_, &checksum, kvs::TestKvs(), kBufferSize);
393 EXPECT_EQ(OkStatus(), blob.Init());
394
395 BlobStore::BlobWriterWithBuffer<kFileName.size()> writer(blob);
396
397 EXPECT_EQ(OkStatus(), writer.Open());
398 EXPECT_EQ(OkStatus(), writer.SetFileName(kFileName));
399 EXPECT_EQ(OkStatus(), writer.Write(tmp_buffer));
400
401 // Read back filename from the open writer.
402 memset(tmp_buffer.data(), 0, tmp_buffer.size());
403 StatusWithSize sws = writer.GetFileName(
404 {reinterpret_cast<char*>(tmp_buffer.data()), tmp_buffer.size()});
405 EXPECT_EQ(OkStatus(), sws.status());
406 ASSERT_EQ(kFileName.size(), sws.size());
407 EXPECT_EQ(memcmp(kFileName.data(), tmp_buffer.data(), kFileName.size()), 0);
408 // END of read back filename from the open writer.
409
410 EXPECT_EQ(OkStatus(), writer.Close());
411 EXPECT_EQ(OkStatus(),
412 kvs::TestKvs().acquire()->Get(kBlobTitle, tmp_buffer).status());
413 }
414
415 BlobStoreBuffer<kBufferSize> blob(
416 kBlobTitle, partition_, &checksum, kvs::TestKvs(), kBufferSize);
417 EXPECT_EQ(OkStatus(), blob.Init());
418
419 // Ensure the file name can be read from a reader.
420 BlobStore::BlobReader reader(blob);
421 ASSERT_EQ(OkStatus(), reader.Open());
422
423 memset(tmp_buffer.data(), 0, tmp_buffer.size());
424 StatusWithSize sws = reader.GetFileName(
425 {reinterpret_cast<char*>(tmp_buffer.data()), tmp_buffer.size()});
426
427 EXPECT_EQ(OkStatus(), sws.status());
428 ASSERT_EQ(kFileName.size(), sws.size());
429 EXPECT_EQ(memcmp(kFileName.data(), tmp_buffer.data(), kFileName.size()), 0);
430 }
431
TEST_F(BlobStoreTest,FileNameUndersizedRead)432 TEST_F(BlobStoreTest, FileNameUndersizedRead) {
433 InitSourceBufferToRandom(0x8675309);
434 WriteTestBlock();
435 constexpr std::string_view kFileName("my_file_1.bin");
436 std::array<std::byte, 4> tmp_buffer = {};
437 static_assert(kFileName.size() > tmp_buffer.size());
438
439 kvs::ChecksumCrc16 checksum;
440 constexpr size_t kBufferSize = 256;
441 BlobStoreBuffer<kBufferSize> blob(
442 kBlobTitle, partition_, &checksum, kvs::TestKvs(), kBufferSize);
443 EXPECT_EQ(OkStatus(), blob.Init());
444
445 BlobStore::BlobWriterWithBuffer<kFileName.size()> writer(blob);
446
447 EXPECT_EQ(OkStatus(), writer.Open());
448 EXPECT_EQ(OkStatus(), writer.SetFileName(kFileName));
449 EXPECT_EQ(OkStatus(), writer.Write(as_bytes(span("some interesting data"))));
450 EXPECT_EQ(OkStatus(), writer.Close());
451
452 // Ensure the file name can be read from a reader.
453 BlobStore::BlobReader reader(blob);
454 ASSERT_EQ(OkStatus(), reader.Open());
455
456 StatusWithSize sws = reader.GetFileName(
457 {reinterpret_cast<char*>(tmp_buffer.data()), tmp_buffer.size()});
458 EXPECT_EQ(Status::ResourceExhausted(), sws.status());
459 ASSERT_EQ(tmp_buffer.size(), sws.size());
460 EXPECT_EQ(memcmp(kFileName.data(), tmp_buffer.data(), sws.size()), 0);
461 }
462
TEST_F(BlobStoreTest,FileNameUndersizedSet)463 TEST_F(BlobStoreTest, FileNameUndersizedSet) {
464 InitSourceBufferToRandom(0x8675309);
465 WriteTestBlock();
466 constexpr std::string_view kFileName("my_file_1.bin");
467 std::array<std::byte, 64> tmp_buffer = {};
468 std::array<std::byte, 64> zero_buffer = {};
469 static_assert(kFileName.size() <= tmp_buffer.size());
470
471 kvs::ChecksumCrc16 checksum;
472 constexpr size_t kBufferSize = 256;
473 BlobStoreBuffer<kBufferSize> blob(
474 kBlobTitle, partition_, &checksum, kvs::TestKvs(), kBufferSize);
475 EXPECT_EQ(OkStatus(), blob.Init());
476
477 BlobStore::BlobWriterWithBuffer<2> writer(blob);
478
479 EXPECT_EQ(OkStatus(), writer.Open());
480 EXPECT_EQ(Status::ResourceExhausted(), writer.SetFileName(kFileName));
481
482 // Read back filename from the open writer.
483 memset(tmp_buffer.data(), 0, tmp_buffer.size());
484 memset(zero_buffer.data(), 0, tmp_buffer.size());
485 StatusWithSize sws = writer.GetFileName(
486 {reinterpret_cast<char*>(tmp_buffer.data()), tmp_buffer.size()});
487 EXPECT_EQ(Status::NotFound(), sws.status());
488 ASSERT_EQ(0U, sws.size());
489 EXPECT_EQ(memcmp(zero_buffer.data(), tmp_buffer.data(), tmp_buffer.size()),
490 0);
491 // END of read back filename from the open writer.
492
493 EXPECT_EQ(OkStatus(), writer.Close());
494 }
495
TEST_F(BlobStoreTest,FileNameInvalidation)496 TEST_F(BlobStoreTest, FileNameInvalidation) {
497 InitSourceBufferToRandom(0x8675309);
498 WriteTestBlock();
499
500 constexpr std::string_view kFileName("sliced_cheese.png");
501 std::array<std::byte, 64> tmp_buffer = {};
502 static_assert(kFileName.size() <= tmp_buffer.size());
503
504 kvs::ChecksumCrc16 checksum;
505 constexpr size_t kBufferSize = 256;
506 BlobStoreBuffer<kBufferSize> blob(
507 kBlobTitle, partition_, &checksum, kvs::TestKvs(), kBufferSize);
508 EXPECT_EQ(OkStatus(), blob.Init());
509
510 BlobStore::BlobWriterWithBuffer<kFileName.size()> writer(blob);
511
512 EXPECT_EQ(OkStatus(), writer.Open());
513 EXPECT_EQ(OkStatus(), writer.SetFileName(kFileName));
514 EXPECT_EQ(OkStatus(), writer.Write(tmp_buffer));
515 EXPECT_EQ(OkStatus(), writer.Discard());
516 EXPECT_EQ(OkStatus(), writer.Write(tmp_buffer));
517
518 // Read back filename from the open writer.
519 {
520 StatusWithSize sws = writer.GetFileName(
521 {reinterpret_cast<char*>(tmp_buffer.data()), tmp_buffer.size()});
522 EXPECT_EQ(Status::NotFound(), sws.status());
523 ASSERT_EQ(0u, sws.size());
524 }
525
526 EXPECT_EQ(OkStatus(), writer.Close());
527
528 // Check that the file name was discarded by Discard().
529 memset(tmp_buffer.data(), 0, tmp_buffer.size());
530 BlobStore::BlobReader reader(blob);
531 ASSERT_EQ(OkStatus(), reader.Open());
532 StatusWithSize sws = reader.GetFileName(
533 {reinterpret_cast<char*>(tmp_buffer.data()), tmp_buffer.size()});
534 EXPECT_EQ(Status::NotFound(), sws.status());
535 ASSERT_EQ(0u, sws.size());
536 }
537
TEST_F(BlobStoreTest,NoFileName)538 TEST_F(BlobStoreTest, NoFileName) {
539 InitSourceBufferToRandom(0x8675309);
540 WriteTestBlock();
541
542 std::array<std::byte, 64> tmp_buffer = {};
543 kvs::ChecksumCrc16 checksum;
544 constexpr size_t kBufferSize = 256;
545 BlobStoreBuffer<kBufferSize> blob(
546 kBlobTitle, partition_, &checksum, kvs::TestKvs(), kBufferSize);
547 EXPECT_EQ(OkStatus(), blob.Init());
548
549 // Ensure blobs with no file names work as expected.
550 BlobStore::BlobReader reader(blob);
551 ASSERT_EQ(OkStatus(), reader.Open());
552
553 StatusWithSize sws = reader.GetFileName(
554 {reinterpret_cast<char*>(tmp_buffer.data()), tmp_buffer.size()});
555 EXPECT_EQ(Status::NotFound(), sws.status());
556 ASSERT_EQ(0u, sws.size());
557 }
558
TEST_F(BlobStoreTest,V1MetadataBackwardsCompatible)559 TEST_F(BlobStoreTest, V1MetadataBackwardsCompatible) {
560 constexpr size_t kWriteSize = 25;
561 WriteTestBlock(kWriteSize);
562
563 kvs::ChecksumCrc16 checksum;
564 constexpr size_t kBufferSize = 16;
565 BlobStoreBuffer<kBufferSize> blob(
566 kBlobTitle, partition_, &checksum, kvs::TestKvs(), kBufferSize);
567 EXPECT_EQ(OkStatus(), blob.Init());
568
569 // Read the written data in the current format.
570 internal::BlobMetadataHeader current_metadata;
571 ASSERT_EQ(OkStatus(),
572 kvs::TestKvs().acquire()->Get(kBlobTitle, ¤t_metadata));
573
574 // Re-save only the V1 metadata contents.
575 ASSERT_EQ(
576 OkStatus(),
577 kvs::TestKvs().acquire()->Put(kBlobTitle, current_metadata.v1_metadata));
578
579 // Ensure the BlobStore's contents aren't invalid.
580 BlobStore::BlobReader reader(blob);
581 ASSERT_EQ(OkStatus(), reader.Open());
582 ASSERT_EQ(kWriteSize, reader.ConservativeReadLimit());
583 ASSERT_EQ(current_metadata.v1_metadata.data_size_bytes,
584 reader.ConservativeReadLimit());
585 }
586
TEST_F(BlobStoreTest,Discard)587 TEST_F(BlobStoreTest, Discard) {
588 InitSourceBufferToRandom(0x8675309);
589 WriteTestBlock();
590 constexpr char blob_title[] = "TestBlobBlock";
591 std::array<std::byte, 64> tmp_buffer = {};
592
593 kvs::ChecksumCrc16 checksum;
594
595 // TODO(davidrogers): Do this test with flash/kvs in the different entry state
596 // combinations.
597
598 constexpr size_t kBufferSize = 256;
599 BlobStoreBuffer<kBufferSize> blob(
600 blob_title, partition_, &checksum, kvs::TestKvs(), kBufferSize);
601 EXPECT_EQ(OkStatus(), blob.Init());
602
603 EXPECT_TRUE(blob.HasData());
604
605 BlobStore::BlobWriterWithBuffer writer(blob);
606
607 EXPECT_EQ(OkStatus(), writer.Open());
608 EXPECT_EQ(OkStatus(), writer.Write(tmp_buffer));
609
610 // Blob should NOT be valid to read, because the write data was only buffered,
611 // and has not been written to flash yet.
612 EXPECT_FALSE(blob.HasData());
613
614 // The write does an implicit erase so there should be no key for this blob.
615 EXPECT_EQ(Status::NotFound(),
616 kvs::TestKvs().acquire()->Get(blob_title, tmp_buffer).status());
617 EXPECT_EQ(OkStatus(), writer.Close());
618
619 EXPECT_TRUE(blob.HasData());
620
621 EXPECT_EQ(OkStatus(),
622 kvs::TestKvs().acquire()->Get(blob_title, tmp_buffer).status());
623
624 EXPECT_EQ(OkStatus(), writer.Open());
625 EXPECT_EQ(OkStatus(), writer.Discard());
626 EXPECT_EQ(OkStatus(), writer.Close());
627
628 EXPECT_FALSE(blob.HasData());
629
630 EXPECT_EQ(Status::NotFound(),
631 kvs::TestKvs().acquire()->Get(blob_title, tmp_buffer).status());
632 }
633
TEST_F(BlobStoreTest,MultipleErase)634 TEST_F(BlobStoreTest, MultipleErase) {
635 constexpr size_t kBufferSize = 256;
636 BlobStoreBuffer<kBufferSize> blob(
637 "Blob_OK", partition_, nullptr, kvs::TestKvs(), kBufferSize);
638 EXPECT_EQ(OkStatus(), blob.Init());
639
640 BlobStore::BlobWriterWithBuffer writer(blob);
641 EXPECT_EQ(OkStatus(), writer.Open());
642
643 EXPECT_EQ(OkStatus(), writer.Erase());
644 EXPECT_EQ(OkStatus(), writer.Erase());
645 EXPECT_EQ(OkStatus(), writer.Erase());
646 }
647
TEST_F(BlobStoreTest,OffsetRead)648 TEST_F(BlobStoreTest, OffsetRead) {
649 InitSourceBufferToRandom(0x11309);
650 WriteTestBlock();
651
652 constexpr size_t kOffset = 10;
653 ASSERT_LT(kOffset, kBlobDataSize);
654
655 kvs::ChecksumCrc16 checksum;
656
657 char name[16] = "TestBlobBlock";
658 constexpr size_t kBufferSize = 16;
659 BlobStoreBuffer<kBufferSize> blob(
660 name, partition_, &checksum, kvs::TestKvs(), kBufferSize);
661 EXPECT_EQ(OkStatus(), blob.Init());
662 BlobStore::BlobReader reader(blob);
663 ASSERT_EQ(OkStatus(), reader.Open(kOffset));
664
665 std::array<std::byte, kBlobDataSize - kOffset> read_buffer;
666 ByteSpan read_span = read_buffer;
667 ASSERT_EQ(read_span.size_bytes(), reader.ConservativeReadLimit());
668
669 auto result = reader.Read(read_span);
670 ASSERT_EQ(result.status(), OkStatus());
671 EXPECT_EQ(OkStatus(), reader.Close());
672 VerifyFlash(read_buffer, kOffset);
673 }
674
TEST_F(BlobStoreTest,SeekOffsetRead)675 TEST_F(BlobStoreTest, SeekOffsetRead) {
676 InitSourceBufferToRandom(0x11309);
677 WriteTestBlock();
678
679 constexpr size_t kOffset = 10;
680 ASSERT_LT(kOffset, kBlobDataSize);
681
682 kvs::ChecksumCrc16 checksum;
683
684 char name[16] = "TestBlobBlock";
685 constexpr size_t kBufferSize = 16;
686 BlobStoreBuffer<kBufferSize> blob(
687 name, partition_, &checksum, kvs::TestKvs(), kBufferSize);
688 EXPECT_EQ(OkStatus(), blob.Init());
689 BlobStore::BlobReader reader(blob);
690 ASSERT_EQ(OkStatus(), reader.Open());
691 ASSERT_EQ(OkStatus(), reader.Seek(kOffset));
692
693 std::array<std::byte, kBlobDataSize - kOffset> read_buffer;
694 ByteSpan read_span = read_buffer;
695 ASSERT_EQ(read_span.size_bytes(), reader.ConservativeReadLimit());
696
697 auto result = reader.Read(read_span);
698 ASSERT_EQ(result.status(), OkStatus());
699 EXPECT_EQ(OkStatus(), reader.Close());
700 VerifyFlash(read_buffer, kOffset);
701 }
702
TEST_F(BlobStoreTest,InvalidReadOffset)703 TEST_F(BlobStoreTest, InvalidReadOffset) {
704 InitSourceBufferToRandom(0x11309);
705 WriteTestBlock();
706
707 constexpr size_t kOffset = kBlobDataSize;
708
709 kvs::ChecksumCrc16 checksum;
710
711 char name[16] = "TestBlobBlock";
712 constexpr size_t kBufferSize = 16;
713 BlobStoreBuffer<kBufferSize> blob(
714 name, partition_, &checksum, kvs::TestKvs(), kBufferSize);
715 EXPECT_EQ(OkStatus(), blob.Init());
716 BlobStore::BlobReader reader(blob);
717 ASSERT_EQ(Status::InvalidArgument(), reader.Open(kOffset));
718 }
719
TEST_F(BlobStoreTest,ReadSeekClosedReader)720 TEST_F(BlobStoreTest, ReadSeekClosedReader) {
721 InitSourceBufferToRandom(0x11309);
722 WriteTestBlock();
723
724 kvs::ChecksumCrc16 checksum;
725
726 char name[16] = "TestBlobBlock";
727 constexpr size_t kBufferSize = 16;
728 BlobStoreBuffer<kBufferSize> blob(
729 name, partition_, &checksum, kvs::TestKvs(), kBufferSize);
730 EXPECT_EQ(OkStatus(), blob.Init());
731 BlobStore::BlobReader reader(blob);
732 ASSERT_EQ(OkStatus(), reader.Open());
733 ASSERT_EQ(OkStatus(), reader.Close());
734
735 EXPECT_EQ(Status::FailedPrecondition(), reader.Seek(0));
736
737 std::byte read_buffer[32];
738 EXPECT_EQ(Status::FailedPrecondition(), reader.Read(read_buffer).status());
739 }
740
TEST_F(BlobStoreTest,InvalidSeekOffset)741 TEST_F(BlobStoreTest, InvalidSeekOffset) {
742 InitSourceBufferToRandom(0x11309);
743 WriteTestBlock();
744
745 constexpr size_t kOffset = kBlobDataSize;
746
747 kvs::ChecksumCrc16 checksum;
748
749 char name[16] = "TestBlobBlock";
750 constexpr size_t kBufferSize = 16;
751 BlobStoreBuffer<kBufferSize> blob(
752 name, partition_, &checksum, kvs::TestKvs(), kBufferSize);
753 EXPECT_EQ(OkStatus(), blob.Init());
754 BlobStore::BlobReader reader(blob);
755 ASSERT_EQ(OkStatus(), reader.Open());
756 ASSERT_EQ(Status::OutOfRange(), reader.Seek(kOffset));
757 }
758
759 // Write a block to blob and close with part of a write buffer with unflushed
760 // data.
TEST_F(BlobStoreTest,WriteBufferWithRemainderInBuffer)761 TEST_F(BlobStoreTest, WriteBufferWithRemainderInBuffer) {
762 InitSourceBufferToRandom(0x11309);
763
764 kvs::ChecksumCrc16 checksum;
765 constexpr size_t kBufferSize = 256;
766 BlobStoreBuffer<kBufferSize> blob(
767 "TestBlobBlock", partition_, &checksum, kvs::TestKvs(), kBufferSize);
768 EXPECT_EQ(OkStatus(), blob.Init());
769
770 const size_t write_size_bytes = kBlobDataSize - 10;
771 ConstByteSpan write_data = span(source_buffer_).first(write_size_bytes);
772
773 BlobStore::BlobWriterWithBuffer writer(blob);
774 EXPECT_EQ(OkStatus(), writer.Open());
775 ASSERT_EQ(OkStatus(), writer.Write(write_data));
776 EXPECT_EQ(OkStatus(), writer.Close());
777
778 BlobStore::BlobReader reader(blob);
779 ASSERT_EQ(OkStatus(), reader.Open());
780 EXPECT_EQ(write_size_bytes, reader.ConservativeReadLimit());
781 }
782
783 // Test reading with a read buffer larger than the available data in the blob.
TEST_F(BlobStoreTest,ReadBufferIsLargerThanData)784 TEST_F(BlobStoreTest, ReadBufferIsLargerThanData) {
785 InitSourceBufferToRandom(0x57326);
786
787 constexpr size_t kWriteBytes = 64;
788 WriteTestBlock(kWriteBytes);
789
790 kvs::ChecksumCrc16 checksum;
791
792 char name[16] = "TestBlobBlock";
793 constexpr size_t kBufferSize = 16;
794 BlobStoreBuffer<kBufferSize> blob(
795 name, partition_, &checksum, kvs::TestKvs(), kBufferSize);
796 EXPECT_EQ(OkStatus(), blob.Init());
797 BlobStore::BlobReader reader(blob);
798 ASSERT_EQ(OkStatus(), reader.Open());
799 EXPECT_EQ(kWriteBytes, reader.ConservativeReadLimit());
800
801 std::array<std::byte, kWriteBytes + 10> read_buffer;
802 ByteSpan read_span = read_buffer;
803
804 auto result = reader.Read(read_span);
805 ASSERT_EQ(result.status(), OkStatus());
806 EXPECT_EQ(OkStatus(), reader.Close());
807 }
808
TEST_F(BlobStoreTest,ChunkRead1)809 TEST_F(BlobStoreTest, ChunkRead1) {
810 InitSourceBufferToRandom(0x8675309);
811 WriteTestBlock();
812 ChunkReadTest(1);
813 }
814
TEST_F(BlobStoreTest,ChunkRead3)815 TEST_F(BlobStoreTest, ChunkRead3) {
816 InitSourceBufferToFill(0);
817 WriteTestBlock();
818 ChunkReadTest(3);
819 }
820
TEST_F(BlobStoreTest,ChunkRead4)821 TEST_F(BlobStoreTest, ChunkRead4) {
822 InitSourceBufferToFill(1);
823 WriteTestBlock();
824 ChunkReadTest(4);
825 }
826
TEST_F(BlobStoreTest,ChunkRead5)827 TEST_F(BlobStoreTest, ChunkRead5) {
828 InitSourceBufferToFill(0xff);
829 WriteTestBlock();
830 ChunkReadTest(5);
831 }
832
TEST_F(BlobStoreTest,ChunkRead16)833 TEST_F(BlobStoreTest, ChunkRead16) {
834 InitSourceBufferToRandom(0x86);
835 WriteTestBlock();
836 ChunkReadTest(16);
837 }
838
TEST_F(BlobStoreTest,ChunkRead64)839 TEST_F(BlobStoreTest, ChunkRead64) {
840 InitSourceBufferToRandom(0x9);
841 WriteTestBlock();
842 ChunkReadTest(64);
843 }
844
TEST_F(BlobStoreTest,ChunkReadFull)845 TEST_F(BlobStoreTest, ChunkReadFull) {
846 InitSourceBufferToRandom(0x9);
847 WriteTestBlock();
848 ChunkReadTest(kBlobDataSize);
849 }
850
TEST_F(BlobStoreTest,PartialBufferThenClose)851 TEST_F(BlobStoreTest, PartialBufferThenClose) {
852 // Do write of only a partial chunk, which will only have bytes in buffer
853 // (none written to flash) at close.
854 size_t data_bytes = 12;
855 InitSourceBufferToRandom(0x111, data_bytes);
856 WriteTestBlock(data_bytes);
857
858 // Do write with several full chunks and then some partial.
859 data_bytes = 158;
860 InitSourceBufferToRandom(0x3222, data_bytes);
861 }
862
863 // Test to do write/close, write/close multiple times.
TEST_F(BlobStoreTest,MultipleWrites)864 TEST_F(BlobStoreTest, MultipleWrites) {
865 InitSourceBufferToRandom(0x1121);
866 WriteTestBlock();
867 InitSourceBufferToRandom(0x515);
868 WriteTestBlock();
869 InitSourceBufferToRandom(0x4321);
870 WriteTestBlock();
871 }
872
TEST_F(BlobStoreTest,ResumeEmptyAbandonBlob)873 TEST_F(BlobStoreTest, ResumeEmptyAbandonBlob) {
874 InitSourceBufferToRandom(0x11309);
875
876 kvs::ChecksumCrc16 checksum;
877 constexpr size_t kBufferSize = 256;
878 BlobStoreBuffer<kBufferSize> blob(
879 "TestBlobBlock", partition_, &checksum, kvs::TestKvs(), kBufferSize);
880 EXPECT_EQ(OkStatus(), blob.Init());
881
882 BlobStore::BlobWriterWithBuffer writer(blob);
883 BlobStore::BlobWriterWithBuffer second_writer(blob);
884 BlobStore::BlobReader reader(blob);
885
886 EXPECT_EQ(OkStatus(), writer.Open());
887
888 EXPECT_EQ(OkStatus(), writer.Erase());
889 EXPECT_EQ(OkStatus(), writer.Abandon());
890 EXPECT_FALSE(blob.HasData());
891
892 StatusWithSize resume_sws = writer.Resume();
893 EXPECT_EQ(OkStatus(), resume_sws.status());
894 EXPECT_EQ(0U, resume_sws.size());
895
896 EXPECT_EQ(Status::Unavailable(), reader.Open());
897 EXPECT_EQ(Status::Unavailable(), second_writer.Open());
898 EXPECT_EQ(Status::Unavailable(), second_writer.Resume().status());
899
900 EXPECT_EQ(OkStatus(), writer.Abandon());
901 }
902
TEST_F(BlobStoreTest,ResumePartialAbandonBlob)903 TEST_F(BlobStoreTest, ResumePartialAbandonBlob) {
904 InitSourceBufferToRandom(0x11309);
905
906 kvs::ChecksumCrc16 checksum;
907 constexpr size_t kBufferSize = 16;
908 BlobStoreBuffer<kBufferSize> blob(
909 "TestBlobBlock", partition_, &checksum, kvs::TestKvs(), kBufferSize);
910 EXPECT_EQ(OkStatus(), blob.Init());
911
912 // The test fills 1/2 sector size less than the full blob size. Resume will
913 // backup 1.5 sectors for the resume point. Make sure the blob has enough
914 // sectors to do all that.
915 ASSERT_GE(kSectorCount, 3U);
916
917 const size_t write_size_bytes = kBlobDataSize - (kSectorSize / 2);
918 ConstByteSpan write_data = span(source_buffer_).first(write_size_bytes);
919
920 BlobStore::BlobWriterWithBuffer writer(blob);
921 EXPECT_EQ(OkStatus(), writer.Open());
922 EXPECT_EQ(OkStatus(), writer.Write(write_data));
923 EXPECT_EQ(OkStatus(), writer.Abandon());
924 EXPECT_FALSE(blob.HasData());
925
926 StatusWithSize resume_sws = writer.Resume();
927 EXPECT_EQ(OkStatus(), resume_sws.status());
928 const size_t expected_resume_size = (kSectorCount - 2) * kSectorSize;
929 EXPECT_EQ(expected_resume_size, resume_sws.size());
930 StatusWithSize eod_sws = partition_.EndOfWrittenData();
931 EXPECT_EQ(OkStatus(), eod_sws.status());
932 EXPECT_EQ(eod_sws.size(), resume_sws.size());
933 VerifyFlash(flash_.buffer().first(resume_sws.size()));
934
935 EXPECT_EQ(OkStatus(),
936 writer.Write(span(source_buffer_).subspan(resume_sws.size())));
937 EXPECT_EQ(kBlobDataSize, writer.CurrentSizeBytes());
938 EXPECT_EQ(OkStatus(), writer.Close());
939
940 VerifyBlob(blob, kBlobDataSize);
941 }
942
TEST_F(BlobStoreTest,ResumePartialAfterRebootBlob)943 TEST_F(BlobStoreTest, ResumePartialAfterRebootBlob) {
944 InitSourceBufferToRandom(0x11309);
945
946 kvs::ChecksumCrc16 first_checksum;
947 constexpr size_t kBufferSize = 16;
948 BlobStoreBuffer<kBufferSize> first_blob("TestBlobBlock",
949 partition_,
950 &first_checksum,
951 kvs::TestKvs(),
952 kBufferSize);
953 EXPECT_EQ(OkStatus(), first_blob.Init());
954
955 // The test fills 1/2 sector size less than the full blob size. Resume will
956 // backup 1.5 sectors for the resume point. Make sure the blob has enough
957 // sectors to do all that.
958 ASSERT_GE(kSectorCount, 3U);
959
960 const size_t write_size_bytes = kBlobDataSize - (kSectorSize / 2);
961 ConstByteSpan write_data = span(source_buffer_).first(write_size_bytes);
962
963 // Write the data using
964 BlobStore::BlobWriterWithBuffer writer(first_blob);
965 EXPECT_EQ(OkStatus(), writer.Open());
966 EXPECT_EQ(OkStatus(), writer.Write(write_data));
967
968 // Use a new blob and writer for the resume to simulate having
969 // crashed/rebooted and starting fresh.
970
971 kvs::ChecksumCrc16 second_checksum;
972 BlobStoreBuffer<kBufferSize> second_blob("TestBlobBlock",
973 partition_,
974 &second_checksum,
975 kvs::TestKvs(),
976 kBufferSize);
977 EXPECT_EQ(OkStatus(), second_blob.Init());
978 BlobStore::BlobWriterWithBuffer second_writer(second_blob);
979
980 StatusWithSize resume_sws = second_writer.Resume();
981 EXPECT_EQ(OkStatus(), resume_sws.status());
982 const size_t expected_resume_size = (kSectorCount - 2) * kSectorSize;
983 EXPECT_EQ(expected_resume_size, resume_sws.size());
984 StatusWithSize eod_sws = partition_.EndOfWrittenData();
985 EXPECT_EQ(OkStatus(), eod_sws.status());
986 EXPECT_EQ(eod_sws.size(), resume_sws.size());
987 VerifyFlash(flash_.buffer().first(resume_sws.size()));
988
989 EXPECT_EQ(
990 OkStatus(),
991 second_writer.Write(span(source_buffer_).subspan(resume_sws.size())));
992 EXPECT_EQ(kBlobDataSize, second_writer.CurrentSizeBytes());
993 EXPECT_EQ(OkStatus(), second_writer.Close());
994
995 VerifyBlob(second_blob, kBlobDataSize);
996 }
997
TEST_F(BlobStoreTest,ResumeAbandonBlobBackedUpToZero)998 TEST_F(BlobStoreTest, ResumeAbandonBlobBackedUpToZero) {
999 InitSourceBufferToRandom(0x11309);
1000
1001 kvs::ChecksumCrc16 checksum;
1002 constexpr size_t kBufferSize = 256;
1003 BlobStoreBuffer<kBufferSize> blob(
1004 "TestBlobBlock", partition_, &checksum, kvs::TestKvs(), kBufferSize);
1005 EXPECT_EQ(OkStatus(), blob.Init());
1006
1007 // The test fills 1/2 sector size less than the full blob size. Resume will
1008 // backup 1.5 sectors for the resume point. Make sure the blob has enough
1009 // sectors to do all that.
1010 ASSERT_GE(kSectorCount, 3U);
1011
1012 const size_t write_size_bytes = kSectorSize + (kSectorSize / 2);
1013 ConstByteSpan write_data = span(source_buffer_).first(write_size_bytes);
1014
1015 // Write 1.5 sectors, should resume with zero bytes.
1016 BlobStore::BlobWriterWithBuffer writer(blob);
1017 EXPECT_EQ(OkStatus(), writer.Open());
1018 EXPECT_EQ(OkStatus(), writer.Write(write_data));
1019 EXPECT_EQ(OkStatus(), writer.Abandon());
1020 StatusWithSize resume_sws = writer.Resume();
1021 EXPECT_EQ(OkStatus(), resume_sws.status());
1022 EXPECT_EQ(0U, resume_sws.size());
1023
1024 // Write 0.5 sectors, should resume with zero bytes.
1025 EXPECT_EQ(OkStatus(), writer.Erase());
1026 EXPECT_EQ(OkStatus(), writer.Write(write_data.first(kSectorSize / 2)));
1027 EXPECT_EQ(OkStatus(), writer.Abandon());
1028 resume_sws = writer.Resume();
1029 EXPECT_EQ(OkStatus(), resume_sws.status());
1030 EXPECT_EQ(0U, resume_sws.size());
1031 }
1032 } // namespace
1033 } // namespace pw::blob_store
1034