1 // Copyright 2020 The Pigweed Authors
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
7 // https://www.apache.org/licenses/LICENSE-2.0
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
15 // Tests that directly work with the KVS's binary format and flash layer.
17 #include <string_view>
19 #include "gtest/gtest.h"
20 #include "pw_bytes/array.h"
21 #include "pw_kvs/crc16_checksum.h"
22 #include "pw_kvs/fake_flash_memory.h"
23 #include "pw_kvs/format.h"
24 #include "pw_kvs/internal/hash.h"
25 #include "pw_kvs/key_value_store.h"
31 using std::string_view;
33 constexpr size_t kMaxEntries = 256;
34 constexpr size_t kMaxUsableSectors = 256;
36 constexpr uint32_t SimpleChecksum(std::span<const byte> data, uint32_t state) {
43 template <typename State>
44 class ChecksumFunction final : public ChecksumAlgorithm {
46 ChecksumFunction(State (&algorithm)(std::span<const byte>, State))
47 : ChecksumAlgorithm(std::as_bytes(std::span(&state_, 1))),
48 algorithm_(algorithm) {}
50 void Reset() override { state_ = {}; }
52 void Update(std::span<const byte> data) override {
53 state_ = algorithm_(data, state_);
58 State (&algorithm_)(std::span<const byte>, State);
61 ChecksumFunction<uint32_t> default_checksum(SimpleChecksum);
63 // Returns a buffer containing the necessary padding for an entry.
64 template <size_t kAlignmentBytes, size_t kKeyLength, size_t kValueSize = 0>
65 constexpr auto EntryPadding() {
66 constexpr size_t content =
67 sizeof(internal::EntryHeader) + kKeyLength + kValueSize;
68 return std::array<byte, Padding(content, kAlignmentBytes)>{};
71 // Creates a buffer containing a valid entry at compile time.
72 template <uint32_t (*kChecksum)(std::span<const byte>,
73 uint32_t) = &SimpleChecksum,
74 size_t kAlignmentBytes = sizeof(internal::EntryHeader),
75 size_t kKeyLengthWithNull,
77 constexpr auto MakeValidEntry(uint32_t magic,
79 const char (&key)[kKeyLengthWithNull],
80 const std::array<byte, kValueSize>& value) {
81 constexpr size_t kKeyLength = kKeyLengthWithNull - 1;
86 uint8_t(kAlignmentBytes / 16 - 1),
92 EntryPadding<kAlignmentBytes, kKeyLength, kValueSize>());
94 // Calculate the checksum
95 uint32_t checksum = kChecksum(data, 0);
96 for (size_t i = 0; i < sizeof(checksum); ++i) {
97 data[4 + i] = byte(checksum & 0xff);
104 // Creates a buffer containing a deleted entry at compile time.
105 template <uint32_t (*kChecksum)(std::span<const byte>,
106 uint32_t) = &SimpleChecksum,
107 size_t kAlignmentBytes = sizeof(internal::EntryHeader),
108 size_t kKeyLengthWithNull>
109 constexpr auto MakeDeletedEntry(uint32_t magic,
111 const char (&key)[kKeyLengthWithNull]) {
112 constexpr size_t kKeyLength = kKeyLengthWithNull - 1;
114 auto data = bytes::Concat(magic,
116 uint8_t(kAlignmentBytes / 16 - 1),
121 EntryPadding<kAlignmentBytes, kKeyLength>());
123 // Calculate the checksum
124 uint32_t checksum = kChecksum(data, 0);
125 for (size_t i = 0; i < sizeof(checksum); ++i) {
126 data[4 + i] = byte(checksum & 0xff);
133 // For KVS magic value always use a random 32 bit integer rather than a
134 // human readable 4 bytes. See pw_kvs/format.h for more information.
135 constexpr uint32_t kMagic = 0x5ab2f0b5;
137 constexpr Options kNoGcOptions{
138 .gc_on_write = GargbageCollectOnWrite::kDisabled,
139 .recovery = ErrorRecovery::kManual,
140 .verify_on_read = true,
141 .verify_on_write = true,
144 constexpr Options kRecoveryNoGcOptions{
145 .gc_on_write = GargbageCollectOnWrite::kDisabled,
146 .recovery = ErrorRecovery::kLazy,
147 .verify_on_read = true,
148 .verify_on_write = true,
151 constexpr Options kRecoveryLazyGcOptions{
152 .gc_on_write = GargbageCollectOnWrite::kOneSector,
153 .recovery = ErrorRecovery::kLazy,
154 .verify_on_read = true,
155 .verify_on_write = true,
158 constexpr auto kEntry1 =
159 MakeValidEntry(kMagic, 1, "key1", bytes::String("value1"));
160 constexpr auto kEntry2 =
161 MakeValidEntry(kMagic, 3, "k2", bytes::String("value2"));
162 constexpr auto kEntry3 =
163 MakeValidEntry(kMagic, 4, "k3y", bytes::String("value3"));
164 constexpr auto kEntry4 =
165 MakeValidEntry(kMagic, 5, "4k", bytes::String("value4"));
167 constexpr auto kEmpty32Bytes = bytes::Initialized<32>(0xff);
168 static_assert(sizeof(kEmpty32Bytes) == 32);
170 EntryFormat default_format = {.magic = kMagic, .checksum = &default_checksum};
172 class KvsErrorHandling : public ::testing::Test {
175 : flash_(internal::Entry::kMinAlignmentBytes),
177 kvs_(&partition_, default_format, kNoGcOptions) {}
179 void InitFlashTo(std::span<const byte> contents) {
181 std::memcpy(flash_.buffer().data(), contents.data(), contents.size());
184 FakeFlashMemoryBuffer<512, 4> flash_;
185 FlashPartition partition_;
186 KeyValueStoreBuffer<kMaxEntries, kMaxUsableSectors> kvs_;
189 TEST_F(KvsErrorHandling, Init_Ok) {
190 InitFlashTo(bytes::Concat(kEntry1, kEntry2));
192 EXPECT_EQ(OkStatus(), kvs_.Init());
194 EXPECT_EQ(OkStatus(), kvs_.Get("key1", buffer).status());
195 EXPECT_EQ(OkStatus(), kvs_.Get("k2", buffer).status());
198 TEST_F(KvsErrorHandling, Init_DuplicateEntries_ReturnsDataLossButReadsEntry) {
199 InitFlashTo(bytes::Concat(kEntry1, kEntry1));
201 EXPECT_EQ(Status::DataLoss(), kvs_.Init());
203 EXPECT_EQ(OkStatus(), kvs_.Get("key1", buffer).status());
204 EXPECT_EQ(Status::NotFound(), kvs_.Get("k2", buffer).status());
207 TEST_F(KvsErrorHandling, Init_CorruptEntry_FindsSubsequentValidEntry) {
208 // Corrupt each byte in the first entry once.
209 for (size_t i = 0; i < kEntry1.size(); ++i) {
210 InitFlashTo(bytes::Concat(kEntry1, kEntry2));
211 flash_.buffer()[i] = byte(int(flash_.buffer()[i]) + 1);
213 ASSERT_EQ(Status::DataLoss(), kvs_.Init());
215 ASSERT_EQ(Status::NotFound(), kvs_.Get("key1", buffer).status());
216 ASSERT_EQ(OkStatus(), kvs_.Get("k2", buffer).status());
218 auto stats = kvs_.GetStorageStats();
220 ASSERT_EQ(32u, stats.in_use_bytes);
221 // Rest of space is reclaimable as the sector is corrupt.
222 ASSERT_EQ(480u, stats.reclaimable_bytes);
226 TEST_F(KvsErrorHandling, Init_CorruptEntry_CorrectlyAccountsForSectorSize) {
227 InitFlashTo(bytes::Concat(kEntry1, kEntry2, kEntry3, kEntry4));
229 // Corrupt the first and third entries.
230 flash_.buffer()[9] = byte(0xef);
231 flash_.buffer()[67] = byte(0xef);
233 ASSERT_EQ(Status::DataLoss(), kvs_.Init());
235 EXPECT_EQ(2u, kvs_.size());
238 EXPECT_EQ(Status::NotFound(), kvs_.Get("key1", buffer).status());
239 EXPECT_EQ(OkStatus(), kvs_.Get("k2", buffer).status());
240 EXPECT_EQ(Status::NotFound(), kvs_.Get("k3y", buffer).status());
241 EXPECT_EQ(OkStatus(), kvs_.Get("4k", buffer).status());
243 auto stats = kvs_.GetStorageStats();
244 ASSERT_EQ(64u, stats.in_use_bytes);
245 ASSERT_EQ(448u, stats.reclaimable_bytes);
246 ASSERT_EQ(1024u, stats.writable_bytes);
249 TEST_F(KvsErrorHandling, Init_ReadError_InitializedWithSingleEntryError) {
250 InitFlashTo(bytes::Concat(kEntry1, kEntry2));
252 flash_.InjectReadError(
253 FlashError::InRange(Status::Unauthenticated(), kEntry1.size()));
255 EXPECT_EQ(Status::DataLoss(), kvs_.Init());
256 EXPECT_FALSE(kvs_.initialized());
259 TEST_F(KvsErrorHandling, Init_CorruptSectors_ShouldBeUnwritable) {
260 InitFlashTo(bytes::Concat(kEntry1, kEntry2));
262 // Corrupt 3 of the 4 512-byte flash sectors. Corrupt sectors should be
263 // unwritable, and the KVS must maintain one empty sector at all times.
264 // As GC on write is disabled through KVS options, writes should no longer be
265 // possible due to lack of space.
266 flash_.buffer()[1] = byte(0xef);
267 flash_.buffer()[513] = byte(0xef);
268 flash_.buffer()[1025] = byte(0xef);
270 ASSERT_EQ(Status::DataLoss(), kvs_.Init());
271 EXPECT_EQ(Status::FailedPrecondition(),
272 kvs_.Put("hello", bytes::String("world")));
273 EXPECT_EQ(Status::FailedPrecondition(), kvs_.Put("a", bytes::String("b")));
275 // Existing valid entries should still be readable.
276 EXPECT_EQ(1u, kvs_.size());
278 EXPECT_EQ(Status::NotFound(), kvs_.Get("key1", buffer).status());
279 EXPECT_EQ(OkStatus(), kvs_.Get("k2", buffer).status());
281 auto stats = kvs_.GetStorageStats();
282 EXPECT_EQ(32u, stats.in_use_bytes);
283 EXPECT_EQ(480u + 2 * 512u, stats.reclaimable_bytes);
284 EXPECT_EQ(0u, stats.writable_bytes);
287 TEST_F(KvsErrorHandling, Init_CorruptSectors_ShouldRecoverOne) {
288 InitFlashTo(bytes::Concat(kEntry1, kEntry2));
290 // Corrupt all of the 4 512-byte flash sectors. Leave the pre-init entries
291 // intact. The KVS should be unavailable because recovery is set to full
292 // manual, and it does not have the required one empty sector at all times.
293 flash_.buffer()[64] = byte(0xef);
294 flash_.buffer()[513] = byte(0xef);
295 flash_.buffer()[1025] = byte(0xef);
296 flash_.buffer()[1537] = byte(0xef);
298 ASSERT_EQ(Status::DataLoss(), kvs_.Init());
300 auto stats = kvs_.GetStorageStats();
301 EXPECT_EQ(64u, stats.in_use_bytes);
302 EXPECT_EQ(4 * 512u - 64u, stats.reclaimable_bytes);
303 EXPECT_EQ(0u, stats.writable_bytes);
306 // Currently disabled due to KVS failing the test. KVS fails due to Init bailing
307 // out when it sees a small patch of "erased" looking flash space, which could
308 // result in missing keys that are actually written after a write error in
310 TEST_F(KvsErrorHandling, DISABLED_Init_OkWithWriteErrorOnFlash) {
311 InitFlashTo(bytes::Concat(kEntry1, kEmpty32Bytes, kEntry2));
313 EXPECT_EQ(Status::DataLoss(), kvs_.Init());
315 EXPECT_EQ(2u, kvs_.size());
316 EXPECT_EQ(true, kvs_.error_detected());
317 EXPECT_EQ(OkStatus(), kvs_.Get("key1", buffer).status());
318 EXPECT_EQ(OkStatus(), kvs_.Get("k2", buffer).status());
320 auto stats = kvs_.GetStorageStats();
321 EXPECT_EQ(64u, stats.in_use_bytes);
322 EXPECT_EQ(512u - 64u, stats.reclaimable_bytes);
323 EXPECT_EQ(2 * 512u, stats.writable_bytes);
326 TEST_F(KvsErrorHandling, Init_CorruptKey_RevertsToPreviousVersion) {
327 constexpr auto kVersion7 =
328 MakeValidEntry(kMagic, 7, "my_key", bytes::String("version 7"));
329 constexpr auto kVersion8 =
330 MakeValidEntry(kMagic, 8, "my_key", bytes::String("version 8"));
332 InitFlashTo(bytes::Concat(kVersion7, kVersion8));
334 // Corrupt a byte of entry version 8 (addresses 32-63).
335 flash_.buffer()[34] = byte(0xef);
337 ASSERT_EQ(Status::DataLoss(), kvs_.Init());
339 char buffer[64] = {};
341 EXPECT_EQ(1u, kvs_.size());
343 auto result = kvs_.Get("my_key", std::as_writable_bytes(std::span(buffer)));
344 EXPECT_EQ(OkStatus(), result.status());
345 EXPECT_EQ(sizeof("version 7") - 1, result.size());
346 EXPECT_STREQ("version 7", buffer);
348 EXPECT_EQ(32u, kvs_.GetStorageStats().in_use_bytes);
351 // The Put_WriteFailure_EntryNotAddedButBytesMarkedWritten test is run with both
352 // the KvsErrorRecovery and KvsErrorHandling test fixtures (different KVS
354 TEST_F(KvsErrorHandling, Put_WriteFailure_EntryNotAddedButBytesMarkedWritten) {
355 ASSERT_EQ(OkStatus(), kvs_.Init());
356 flash_.InjectWriteError(FlashError::Unconditional(Status::Unavailable(), 1));
358 EXPECT_EQ(Status::Unavailable(), kvs_.Put("key1", bytes::String("value1")));
360 EXPECT_EQ(Status::NotFound(), kvs_.Get("key1", std::span<byte>()).status());
361 ASSERT_TRUE(kvs_.empty());
363 auto stats = kvs_.GetStorageStats();
364 EXPECT_EQ(stats.in_use_bytes, 0u);
365 EXPECT_EQ(stats.reclaimable_bytes, 512u);
366 EXPECT_EQ(stats.writable_bytes, 512u * 2);
368 // The bytes were marked used, so a new key should not overlap with the bytes
369 // from the failed Put.
370 EXPECT_EQ(OkStatus(), kvs_.Put("key1", bytes::String("value1")));
372 stats = kvs_.GetStorageStats();
373 EXPECT_EQ(stats.in_use_bytes, (32u * kvs_.redundancy()));
374 EXPECT_EQ(stats.reclaimable_bytes, 512u);
375 EXPECT_EQ(stats.writable_bytes, 512u * 2 - (32 * kvs_.redundancy()));
378 class KvsErrorRecovery : public ::testing::Test {
381 : flash_(internal::Entry::kMinAlignmentBytes),
384 {.magic = kMagic, .checksum = &default_checksum},
385 kRecoveryNoGcOptions) {}
387 void InitFlashTo(std::span<const byte> contents) {
389 std::memcpy(flash_.buffer().data(), contents.data(), contents.size());
392 FakeFlashMemoryBuffer<512, 4> flash_;
393 FlashPartition partition_;
394 KeyValueStoreBuffer<kMaxEntries, kMaxUsableSectors> kvs_;
397 TEST_F(KvsErrorRecovery, Init_Ok) {
398 InitFlashTo(bytes::Concat(kEntry1, kEntry2));
400 EXPECT_EQ(OkStatus(), kvs_.Init());
402 EXPECT_EQ(OkStatus(), kvs_.Get("key1", buffer).status());
403 EXPECT_EQ(OkStatus(), kvs_.Get("k2", buffer).status());
406 TEST_F(KvsErrorRecovery, Init_DuplicateEntries_RecoversDuringInit) {
407 InitFlashTo(bytes::Concat(kEntry1, kEntry1));
409 EXPECT_EQ(OkStatus(), kvs_.Init());
410 auto stats = kvs_.GetStorageStats();
411 EXPECT_EQ(stats.corrupt_sectors_recovered, 1u);
414 EXPECT_EQ(OkStatus(), kvs_.Get("key1", buffer).status());
415 EXPECT_EQ(Status::NotFound(), kvs_.Get("k2", buffer).status());
418 TEST_F(KvsErrorRecovery, Init_CorruptEntry_FindsSubsequentValidEntry) {
419 // Corrupt each byte in the first entry once.
420 for (size_t i = 0; i < kEntry1.size(); ++i) {
421 InitFlashTo(bytes::Concat(kEntry1, kEntry2));
422 flash_.buffer()[i] = byte(int(flash_.buffer()[i]) + 1);
424 ASSERT_EQ(OkStatus(), kvs_.Init());
426 ASSERT_EQ(Status::NotFound(), kvs_.Get("key1", buffer).status());
427 ASSERT_EQ(OkStatus(), kvs_.Get("k2", buffer).status());
429 auto stats = kvs_.GetStorageStats();
431 ASSERT_EQ(32u, stats.in_use_bytes);
432 // The sector with corruption should have been recovered.
433 ASSERT_EQ(0u, stats.reclaimable_bytes);
434 ASSERT_EQ(i + 1u, stats.corrupt_sectors_recovered);
438 TEST_F(KvsErrorRecovery, Init_CorruptEntry_CorrectlyAccountsForSectorSize) {
439 InitFlashTo(bytes::Concat(kEntry1, kEntry2, kEntry3, kEntry4));
441 // Corrupt the first and third entries.
442 flash_.buffer()[9] = byte(0xef);
443 flash_.buffer()[67] = byte(0xef);
445 ASSERT_EQ(OkStatus(), kvs_.Init());
447 EXPECT_EQ(2u, kvs_.size());
450 EXPECT_EQ(Status::NotFound(), kvs_.Get("key1", buffer).status());
451 EXPECT_EQ(OkStatus(), kvs_.Get("k2", buffer).status());
452 EXPECT_EQ(Status::NotFound(), kvs_.Get("k3y", buffer).status());
453 EXPECT_EQ(OkStatus(), kvs_.Get("4k", buffer).status());
455 auto stats = kvs_.GetStorageStats();
456 ASSERT_EQ(64u, stats.in_use_bytes);
457 ASSERT_EQ(0u, stats.reclaimable_bytes);
458 ASSERT_EQ(1472u, stats.writable_bytes);
459 ASSERT_EQ(1u, stats.corrupt_sectors_recovered);
462 TEST_F(KvsErrorRecovery, Init_ReadError_InitializedWithSingleEntryError) {
463 InitFlashTo(bytes::Concat(kEntry1, kEntry2));
465 flash_.InjectReadError(
466 FlashError::InRange(Status::Unauthenticated(), kEntry1.size()));
468 EXPECT_EQ(OkStatus(), kvs_.Init());
469 EXPECT_TRUE(kvs_.initialized());
470 auto stats = kvs_.GetStorageStats();
471 ASSERT_EQ(32u, stats.in_use_bytes);
472 ASSERT_EQ(0u, stats.reclaimable_bytes);
473 ASSERT_EQ(3 * 512u - 32u, stats.writable_bytes);
474 ASSERT_EQ(1u, stats.corrupt_sectors_recovered);
475 ASSERT_EQ(0u, stats.missing_redundant_entries_recovered);
478 TEST_F(KvsErrorRecovery, Init_CorruptSectors_ShouldBeUnwritable) {
479 InitFlashTo(bytes::Concat(kEntry1, kEntry2));
481 // Corrupt 3 of the 4 512-byte flash sectors. Corrupt sectors should be
482 // recovered via garbage collection.
483 flash_.buffer()[1] = byte(0xef);
484 flash_.buffer()[513] = byte(0xef);
485 flash_.buffer()[1025] = byte(0xef);
487 ASSERT_EQ(OkStatus(), kvs_.Init());
488 EXPECT_EQ(OkStatus(), kvs_.Put("hello", bytes::String("world")));
489 EXPECT_EQ(OkStatus(), kvs_.Put("a", bytes::String("b")));
491 // Existing valid entries should still be readable.
492 EXPECT_EQ(3u, kvs_.size());
494 EXPECT_EQ(Status::NotFound(), kvs_.Get("key1", buffer).status());
495 EXPECT_EQ(OkStatus(), kvs_.Get("k2", buffer).status());
497 auto stats = kvs_.GetStorageStats();
498 EXPECT_EQ(96u, stats.in_use_bytes);
499 EXPECT_EQ(0u, stats.reclaimable_bytes);
500 EXPECT_EQ(1440u, stats.writable_bytes);
501 EXPECT_EQ(3u, stats.corrupt_sectors_recovered);
504 TEST_F(KvsErrorRecovery, Init_CorruptSectors_ShouldRecoverOne) {
505 InitFlashTo(bytes::Concat(kEntry1, kEntry2));
507 // Corrupt all of the 4 512-byte flash sectors. Leave the pre-init entries
508 // intact. As part of recovery all corrupt sectors should get garbage
510 flash_.buffer()[64] = byte(0xef);
511 flash_.buffer()[513] = byte(0xef);
512 flash_.buffer()[1025] = byte(0xef);
513 flash_.buffer()[1537] = byte(0xef);
515 ASSERT_EQ(OkStatus(), kvs_.Init());
517 auto stats = kvs_.GetStorageStats();
518 EXPECT_EQ(64u, stats.in_use_bytes);
519 EXPECT_EQ(0u, stats.reclaimable_bytes);
520 EXPECT_EQ(3 * 512u - 64u, stats.writable_bytes);
521 EXPECT_EQ(4u, stats.corrupt_sectors_recovered);
524 // Currently disabled due to KVS failing the test. KVS fails due to Init bailing
525 // out when it sees a small patch of "erased" looking flash space, which could
526 // result in missing keys that are actually written after a write error in
528 TEST_F(KvsErrorRecovery, DISABLED_Init_OkWithWriteErrorOnFlash) {
529 InitFlashTo(bytes::Concat(kEntry1, kEmpty32Bytes, kEntry2));
531 EXPECT_EQ(OkStatus(), kvs_.Init());
533 EXPECT_EQ(2u, kvs_.size());
534 EXPECT_EQ(false, kvs_.error_detected());
535 EXPECT_EQ(OkStatus(), kvs_.Get("key1", buffer).status());
536 EXPECT_EQ(OkStatus(), kvs_.Get("k2", buffer).status());
538 auto stats = kvs_.GetStorageStats();
539 EXPECT_EQ(64u, stats.in_use_bytes);
540 EXPECT_EQ(0u, stats.reclaimable_bytes);
541 EXPECT_EQ(3 * 512u - 64u, stats.writable_bytes);
542 EXPECT_EQ(1u, stats.corrupt_sectors_recovered);
543 EXPECT_EQ(0u, stats.missing_redundant_entries_recovered);
546 TEST_F(KvsErrorRecovery, Init_CorruptKey_RevertsToPreviousVersion) {
547 constexpr auto kVersion7 =
548 MakeValidEntry(kMagic, 7, "my_key", bytes::String("version 7"));
549 constexpr auto kVersion8 =
550 MakeValidEntry(kMagic, 8, "my_key", bytes::String("version 8"));
552 InitFlashTo(bytes::Concat(kVersion7, kVersion8));
554 // Corrupt a byte of entry version 8 (addresses 32-63).
555 flash_.buffer()[34] = byte(0xef);
557 ASSERT_EQ(OkStatus(), kvs_.Init());
559 char buffer[64] = {};
561 EXPECT_EQ(1u, kvs_.size());
563 auto result = kvs_.Get("my_key", std::as_writable_bytes(std::span(buffer)));
564 EXPECT_EQ(OkStatus(), result.status());
565 EXPECT_EQ(sizeof("version 7") - 1, result.size());
566 EXPECT_STREQ("version 7", buffer);
568 EXPECT_EQ(32u, kvs_.GetStorageStats().in_use_bytes);
571 // The Put_WriteFailure_EntryNotAddedButBytesMarkedWritten test is run with both
572 // the KvsErrorRecovery and KvsErrorHandling test fixtures (different KVS
574 TEST_F(KvsErrorRecovery, Put_WriteFailure_EntryNotAddedButBytesMarkedWritten) {
575 ASSERT_EQ(OkStatus(), kvs_.Init());
576 flash_.InjectWriteError(FlashError::Unconditional(Status::Unavailable(), 1));
578 EXPECT_EQ(Status::Unavailable(), kvs_.Put("key1", bytes::String("value1")));
579 EXPECT_EQ(true, kvs_.error_detected());
581 EXPECT_EQ(Status::NotFound(), kvs_.Get("key1", std::span<byte>()).status());
582 ASSERT_TRUE(kvs_.empty());
584 auto stats = kvs_.GetStorageStats();
585 EXPECT_EQ(stats.in_use_bytes, 0u);
586 EXPECT_EQ(stats.reclaimable_bytes, 512u);
587 EXPECT_EQ(stats.writable_bytes, 512u * 2);
588 EXPECT_EQ(stats.corrupt_sectors_recovered, 0u);
589 EXPECT_EQ(stats.missing_redundant_entries_recovered, 0u);
591 // The bytes were marked used, so a new key should not overlap with the bytes
592 // from the failed Put.
593 EXPECT_EQ(OkStatus(), kvs_.Put("key1", bytes::String("value1")));
595 stats = kvs_.GetStorageStats();
596 EXPECT_EQ(stats.in_use_bytes, (32u * kvs_.redundancy()));
597 EXPECT_EQ(stats.reclaimable_bytes, 512u);
598 EXPECT_EQ(stats.writable_bytes, 512u * 2 - (32 * kvs_.redundancy()));
599 EXPECT_EQ(stats.corrupt_sectors_recovered, 0u);
600 EXPECT_EQ(stats.missing_redundant_entries_recovered, 0u);
603 // For KVS magic value always use a random 32 bit integer rather than a
604 // human readable 4 bytes. See pw_kvs/format.h for more information.
605 constexpr uint32_t kAltMagic = 0x41a2db83;
607 constexpr uint32_t AltChecksum(std::span<const byte> data, uint32_t state) {
608 for (byte b : data) {
609 state = (state << 8) | uint32_t(byte(state >> 24) ^ b);
614 ChecksumFunction<uint32_t> alt_checksum(AltChecksum);
616 constexpr auto kAltEntry =
617 MakeValidEntry<AltChecksum>(kAltMagic, 32, "A Key", bytes::String("XD"));
619 constexpr uint32_t NoChecksum(std::span<const byte>, uint32_t) { return 0; }
620 // For KVS magic value always use a random 32 bit integer rather than a
621 // human readable 4 bytes. See pw_kvs/format.h for more information.
622 constexpr uint32_t kNoChecksumMagic = 0xd49ba138;
624 constexpr auto kNoChecksumEntry = MakeValidEntry<NoChecksum>(
625 kNoChecksumMagic, 64, "kee", bytes::String("O_o"));
627 constexpr auto kDeletedEntry =
628 MakeDeletedEntry<AltChecksum>(kAltMagic, 128, "gone");
630 class InitializedRedundantMultiMagicKvs : public ::testing::Test {
632 static constexpr auto kInitialContents = bytes::Concat(
633 kNoChecksumEntry, kEntry1, kAltEntry, kEntry2, kEntry3, kDeletedEntry);
635 InitializedRedundantMultiMagicKvs()
636 : flash_(internal::Entry::kMinAlignmentBytes),
640 {.magic = kMagic, .checksum = &default_checksum},
641 {.magic = kAltMagic, .checksum = &alt_checksum},
642 {.magic = kNoChecksumMagic, .checksum = nullptr},
644 kRecoveryNoGcOptions) {
646 std::memcpy(flash_.buffer().data(),
647 kInitialContents.data(),
648 kInitialContents.size());
650 EXPECT_EQ(OkStatus(), kvs_.Init());
653 FakeFlashMemoryBuffer<512, 4, 3> flash_;
654 FlashPartition partition_;
655 KeyValueStoreBuffer<kMaxEntries, kMaxUsableSectors, 2, 3> kvs_;
658 #define ASSERT_CONTAINS_ENTRY(key, str_value) \
660 char val[sizeof(str_value)] = {}; \
661 StatusWithSize stat = \
662 kvs_.Get(key, std::as_writable_bytes(std::span(val))); \
663 ASSERT_EQ(OkStatus(), stat.status()); \
664 ASSERT_EQ(sizeof(str_value) - 1, stat.size()); \
665 ASSERT_STREQ(str_value, val); \
668 TEST_F(InitializedRedundantMultiMagicKvs, AllEntriesArePresent) {
669 ASSERT_CONTAINS_ENTRY("key1", "value1");
670 ASSERT_CONTAINS_ENTRY("k2", "value2");
671 ASSERT_CONTAINS_ENTRY("k3y", "value3");
672 ASSERT_CONTAINS_ENTRY("A Key", "XD");
673 ASSERT_CONTAINS_ENTRY("kee", "O_o");
676 TEST_F(InitializedRedundantMultiMagicKvs, RecoversLossOfFirstSector) {
677 auto stats = kvs_.GetStorageStats();
678 EXPECT_EQ(stats.in_use_bytes, (192u * kvs_.redundancy()));
679 EXPECT_EQ(stats.reclaimable_bytes, 0u);
680 EXPECT_EQ(stats.writable_bytes, 512u * 3 - (192 * kvs_.redundancy()));
681 EXPECT_EQ(stats.corrupt_sectors_recovered, 0u);
682 EXPECT_EQ(stats.missing_redundant_entries_recovered, 0u);
684 EXPECT_EQ(OkStatus(), partition_.Erase(0, 1));
686 ASSERT_CONTAINS_ENTRY("key1", "value1");
687 ASSERT_CONTAINS_ENTRY("k2", "value2");
688 ASSERT_CONTAINS_ENTRY("k3y", "value3");
689 ASSERT_CONTAINS_ENTRY("A Key", "XD");
690 ASSERT_CONTAINS_ENTRY("kee", "O_o");
692 EXPECT_EQ(true, kvs_.error_detected());
694 stats = kvs_.GetStorageStats();
695 EXPECT_EQ(stats.in_use_bytes, (192u * kvs_.redundancy()));
696 EXPECT_EQ(stats.reclaimable_bytes, 320u);
697 EXPECT_EQ(stats.writable_bytes, 512u * 2 - (192 * (kvs_.redundancy() - 1)));
698 EXPECT_EQ(stats.corrupt_sectors_recovered, 0u);
699 EXPECT_EQ(stats.missing_redundant_entries_recovered, 0u);
701 EXPECT_EQ(OkStatus(), kvs_.FullMaintenance());
702 stats = kvs_.GetStorageStats();
703 EXPECT_EQ(stats.in_use_bytes, (192u * kvs_.redundancy()));
704 EXPECT_EQ(stats.reclaimable_bytes, 0u);
705 EXPECT_EQ(stats.writable_bytes, 512u * 3 - (192 * kvs_.redundancy()));
706 EXPECT_EQ(stats.corrupt_sectors_recovered, 0u);
707 EXPECT_EQ(stats.missing_redundant_entries_recovered, 6u);
710 TEST_F(InitializedRedundantMultiMagicKvs, RecoversLossOfSecondSector) {
711 auto stats = kvs_.GetStorageStats();
712 EXPECT_EQ(stats.in_use_bytes, (192u * kvs_.redundancy()));
713 EXPECT_EQ(stats.reclaimable_bytes, 0u);
714 EXPECT_EQ(stats.writable_bytes, 512u * 3 - (192 * kvs_.redundancy()));
715 EXPECT_EQ(stats.corrupt_sectors_recovered, 0u);
716 EXPECT_EQ(stats.missing_redundant_entries_recovered, 0u);
718 EXPECT_EQ(OkStatus(), partition_.Erase(partition_.sector_size_bytes(), 1));
720 ASSERT_CONTAINS_ENTRY("key1", "value1");
721 ASSERT_CONTAINS_ENTRY("k2", "value2");
722 ASSERT_CONTAINS_ENTRY("k3y", "value3");
723 ASSERT_CONTAINS_ENTRY("A Key", "XD");
724 ASSERT_CONTAINS_ENTRY("kee", "O_o");
726 EXPECT_EQ(false, kvs_.error_detected());
728 EXPECT_EQ(OkStatus(), kvs_.Init());
729 stats = kvs_.GetStorageStats();
730 EXPECT_EQ(stats.in_use_bytes, (192u * kvs_.redundancy()));
731 EXPECT_EQ(stats.reclaimable_bytes, 0u);
732 EXPECT_EQ(stats.writable_bytes, 512u * 3 - (192 * kvs_.redundancy()));
733 EXPECT_EQ(stats.corrupt_sectors_recovered, 0u);
734 EXPECT_EQ(stats.missing_redundant_entries_recovered, 0u);
737 TEST_F(InitializedRedundantMultiMagicKvs, SingleReadErrors) {
738 // Inject 2 read errors, so the first read attempt fully fails.
739 flash_.InjectReadError(FlashError::Unconditional(Status::Internal(), 2));
741 flash_.InjectReadError(FlashError::Unconditional(Status::Internal(), 1, 7));
743 ASSERT_CONTAINS_ENTRY("key1", "value1");
744 ASSERT_CONTAINS_ENTRY("k2", "value2");
745 ASSERT_CONTAINS_ENTRY("k3y", "value3");
746 ASSERT_CONTAINS_ENTRY("A Key", "XD");
747 ASSERT_CONTAINS_ENTRY("kee", "O_o");
749 EXPECT_EQ(true, kvs_.error_detected());
751 auto stats = kvs_.GetStorageStats();
752 EXPECT_EQ(stats.in_use_bytes, (192u * kvs_.redundancy()));
753 EXPECT_EQ(stats.reclaimable_bytes, 320u);
754 EXPECT_EQ(stats.writable_bytes, 512u * 2 - (192 * (kvs_.redundancy() - 1)));
755 EXPECT_EQ(stats.corrupt_sectors_recovered, 0u);
756 EXPECT_EQ(stats.missing_redundant_entries_recovered, 0u);
759 TEST_F(InitializedRedundantMultiMagicKvs, SingleWriteError) {
760 flash_.InjectWriteError(FlashError::Unconditional(Status::Internal(), 1, 1));
762 EXPECT_EQ(Status::Internal(), kvs_.Put("new key", bytes::String("abcd?")));
764 EXPECT_EQ(true, kvs_.error_detected());
766 auto stats = kvs_.GetStorageStats();
767 EXPECT_EQ(stats.in_use_bytes, 32 + (192u * kvs_.redundancy()));
768 EXPECT_EQ(stats.reclaimable_bytes, 320u);
769 EXPECT_EQ(stats.writable_bytes,
770 512u * 2 - 32 - (192 * (kvs_.redundancy() - 1)));
771 EXPECT_EQ(stats.corrupt_sectors_recovered, 0u);
772 EXPECT_EQ(stats.missing_redundant_entries_recovered, 0u);
777 kvs_.Get("new key", std::as_writable_bytes(std::span(val))).status());
779 EXPECT_EQ(OkStatus(), kvs_.FullMaintenance());
780 stats = kvs_.GetStorageStats();
781 EXPECT_EQ(stats.in_use_bytes, (224u * kvs_.redundancy()));
782 EXPECT_EQ(stats.reclaimable_bytes, 0u);
783 EXPECT_EQ(stats.writable_bytes, 512u * 3 - (224 * kvs_.redundancy()));
784 EXPECT_EQ(stats.corrupt_sectors_recovered, 0u);
785 EXPECT_EQ(stats.missing_redundant_entries_recovered, 0u);
789 kvs_.Get("new key", std::as_writable_bytes(std::span(val))).status());
792 TEST_F(InitializedRedundantMultiMagicKvs, DataLossAfterLosingBothCopies) {
793 EXPECT_EQ(OkStatus(), partition_.Erase(0, 2));
796 EXPECT_EQ(Status::DataLoss(),
797 kvs_.Get("key1", std::as_writable_bytes(std::span(val))).status());
798 EXPECT_EQ(Status::DataLoss(),
799 kvs_.Get("k2", std::as_writable_bytes(std::span(val))).status());
800 EXPECT_EQ(Status::DataLoss(),
801 kvs_.Get("k3y", std::as_writable_bytes(std::span(val))).status());
802 EXPECT_EQ(Status::DataLoss(),
803 kvs_.Get("A Key", std::as_writable_bytes(std::span(val))).status());
804 EXPECT_EQ(Status::DataLoss(),
805 kvs_.Get("kee", std::as_writable_bytes(std::span(val))).status());
807 EXPECT_EQ(true, kvs_.error_detected());
809 auto stats = kvs_.GetStorageStats();
810 EXPECT_EQ(stats.in_use_bytes, (192u * kvs_.redundancy()));
811 EXPECT_EQ(stats.reclaimable_bytes, 2 * 320u);
812 EXPECT_EQ(stats.writable_bytes, 512u);
813 EXPECT_EQ(stats.corrupt_sectors_recovered, 0u);
814 EXPECT_EQ(stats.missing_redundant_entries_recovered, 0u);
817 TEST_F(InitializedRedundantMultiMagicKvs, PutNewEntry_UsesFirstFormat) {
818 EXPECT_EQ(OkStatus(), kvs_.Put("new key", bytes::String("abcd?")));
820 constexpr auto kNewEntry =
821 MakeValidEntry(kMagic, 129, "new key", bytes::String("abcd?"));
823 std::memcmp(kNewEntry.data(),
824 flash_.buffer().data() + kInitialContents.size(),
826 ASSERT_CONTAINS_ENTRY("new key", "abcd?");
829 TEST_F(InitializedRedundantMultiMagicKvs, PutExistingEntry_UsesFirstFormat) {
830 EXPECT_EQ(OkStatus(), kvs_.Put("A Key", bytes::String("New value!")));
832 constexpr auto kNewEntry =
833 MakeValidEntry(kMagic, 129, "A Key", bytes::String("New value!"));
835 std::memcmp(kNewEntry.data(),
836 flash_.buffer().data() + kInitialContents.size(),
838 ASSERT_CONTAINS_ENTRY("A Key", "New value!");
841 #define ASSERT_KVS_CONTAINS_ENTRY(kvs, key, str_value) \
843 char val[sizeof(str_value)] = {}; \
844 StatusWithSize stat = \
845 kvs.Get(key, std::as_writable_bytes(std::span(val))); \
846 ASSERT_EQ(OkStatus(), stat.status()); \
847 ASSERT_EQ(sizeof(str_value) - 1, stat.size()); \
848 ASSERT_STREQ(str_value, val); \
851 TEST_F(InitializedRedundantMultiMagicKvs, UpdateEntryFormat) {
852 ASSERT_EQ(OkStatus(), kvs_.FullMaintenance());
854 KeyValueStoreBuffer<kMaxEntries, kMaxUsableSectors, 2, 1> local_kvs(
856 {.magic = kMagic, .checksum = &default_checksum},
859 ASSERT_EQ(OkStatus(), local_kvs.Init());
860 EXPECT_EQ(false, local_kvs.error_detected());
861 ASSERT_KVS_CONTAINS_ENTRY(local_kvs, "key1", "value1");
862 ASSERT_KVS_CONTAINS_ENTRY(local_kvs, "k2", "value2");
863 ASSERT_KVS_CONTAINS_ENTRY(local_kvs, "k3y", "value3");
864 ASSERT_KVS_CONTAINS_ENTRY(local_kvs, "A Key", "XD");
865 ASSERT_KVS_CONTAINS_ENTRY(local_kvs, "kee", "O_o");
868 class InitializedMultiMagicKvs : public ::testing::Test {
870 static constexpr auto kInitialContents =
871 bytes::Concat(kNoChecksumEntry, kEntry1, kAltEntry, kEntry2, kEntry3);
873 InitializedMultiMagicKvs()
874 : flash_(internal::Entry::kMinAlignmentBytes),
878 {.magic = kMagic, .checksum = &default_checksum},
879 {.magic = kAltMagic, .checksum = &alt_checksum},
880 {.magic = kNoChecksumMagic, .checksum = nullptr},
882 kRecoveryNoGcOptions) {
884 std::memcpy(flash_.buffer().data(),
885 kInitialContents.data(),
886 kInitialContents.size());
888 EXPECT_EQ(OkStatus(), kvs_.Init());
891 FakeFlashMemoryBuffer<512, 4, 3> flash_;
892 FlashPartition partition_;
893 KeyValueStoreBuffer<kMaxEntries, kMaxUsableSectors, 1, 3> kvs_;
896 // Similar to test for InitializedRedundantMultiMagicKvs. Doing similar test
897 // with different KVS configuration.
898 TEST_F(InitializedMultiMagicKvs, AllEntriesArePresent) {
899 ASSERT_CONTAINS_ENTRY("key1", "value1");
900 ASSERT_CONTAINS_ENTRY("k2", "value2");
901 ASSERT_CONTAINS_ENTRY("k3y", "value3");
902 ASSERT_CONTAINS_ENTRY("A Key", "XD");
903 ASSERT_CONTAINS_ENTRY("kee", "O_o");
906 // Similar to test for InitializedRedundantMultiMagicKvs. Doing similar test
907 // with different KVS configuration.
908 TEST_F(InitializedMultiMagicKvs, UpdateEntryFormat) {
909 ASSERT_EQ(OkStatus(), kvs_.FullMaintenance());
911 KeyValueStoreBuffer<kMaxEntries, kMaxUsableSectors, 1, 1> local_kvs(
913 {.magic = kMagic, .checksum = &default_checksum},
916 ASSERT_EQ(OkStatus(), local_kvs.Init());
917 EXPECT_EQ(false, local_kvs.error_detected());
918 ASSERT_KVS_CONTAINS_ENTRY(local_kvs, "key1", "value1");
919 ASSERT_KVS_CONTAINS_ENTRY(local_kvs, "k2", "value2");
920 ASSERT_KVS_CONTAINS_ENTRY(local_kvs, "k3y", "value3");
921 ASSERT_KVS_CONTAINS_ENTRY(local_kvs, "A Key", "XD");
922 ASSERT_KVS_CONTAINS_ENTRY(local_kvs, "kee", "O_o");
925 class InitializedRedundantLazyRecoveryKvs : public ::testing::Test {
927 static constexpr auto kInitialContents =
928 bytes::Concat(kEntry1, kEntry2, kEntry3, kEntry4);
930 InitializedRedundantLazyRecoveryKvs()
931 : flash_(internal::Entry::kMinAlignmentBytes),
934 {.magic = kMagic, .checksum = &default_checksum},
935 kRecoveryLazyGcOptions) {
937 std::memcpy(flash_.buffer().data(),
938 kInitialContents.data(),
939 kInitialContents.size());
941 EXPECT_EQ(OkStatus(), kvs_.Init());
944 FakeFlashMemoryBuffer<512, 4, 3> flash_;
945 FlashPartition partition_;
946 KeyValueStoreBuffer<kMaxEntries, kMaxUsableSectors, 2> kvs_;
949 TEST_F(InitializedRedundantLazyRecoveryKvs, WriteAfterDataLoss) {
950 EXPECT_EQ(OkStatus(), partition_.Erase(0, 4));
953 EXPECT_EQ(Status::DataLoss(),
954 kvs_.Get("key1", std::as_writable_bytes(std::span(val))).status());
955 EXPECT_EQ(Status::DataLoss(),
956 kvs_.Get("k2", std::as_writable_bytes(std::span(val))).status());
957 EXPECT_EQ(Status::DataLoss(),
958 kvs_.Get("k3y", std::as_writable_bytes(std::span(val))).status());
959 EXPECT_EQ(Status::DataLoss(),
960 kvs_.Get("4k", std::as_writable_bytes(std::span(val))).status());
962 EXPECT_EQ(true, kvs_.error_detected());
964 auto stats = kvs_.GetStorageStats();
965 EXPECT_EQ(stats.in_use_bytes, (128u * kvs_.redundancy()));
966 EXPECT_EQ(stats.reclaimable_bytes, 2 * 384u);
967 EXPECT_EQ(stats.writable_bytes, 512u);
968 EXPECT_EQ(stats.corrupt_sectors_recovered, 0u);
969 EXPECT_EQ(stats.missing_redundant_entries_recovered, 0u);
971 ASSERT_EQ(Status::DataLoss(), kvs_.Put("key1", 1000));
973 EXPECT_EQ(OkStatus(), kvs_.FullMaintenance());
974 stats = kvs_.GetStorageStats();
975 EXPECT_EQ(stats.in_use_bytes, 0u);
976 EXPECT_EQ(stats.reclaimable_bytes, 0u);
977 EXPECT_EQ(stats.writable_bytes, 3 * 512u);
978 EXPECT_EQ(stats.corrupt_sectors_recovered, 0u);
979 EXPECT_EQ(stats.missing_redundant_entries_recovered, 0u);
982 TEST_F(InitializedRedundantLazyRecoveryKvs, TwoSectorsCorruptWithGoodEntries) {
983 ASSERT_CONTAINS_ENTRY("key1", "value1");
984 ASSERT_CONTAINS_ENTRY("k2", "value2");
985 ASSERT_CONTAINS_ENTRY("k3y", "value3");
986 ASSERT_CONTAINS_ENTRY("4k", "value4");
988 EXPECT_EQ(false, kvs_.error_detected());
990 auto stats = kvs_.GetStorageStats();
991 EXPECT_EQ(stats.in_use_bytes, (128u * kvs_.redundancy()));
992 EXPECT_EQ(stats.reclaimable_bytes, 0u);
993 EXPECT_EQ(stats.writable_bytes, 3 * 512u - (128u * kvs_.redundancy()));
994 EXPECT_EQ(stats.corrupt_sectors_recovered, 0u);
995 EXPECT_EQ(stats.missing_redundant_entries_recovered, 0u);
997 // Corrupt all the keys, alternating which copy gets corrupted.
998 flash_.buffer()[0x10] = byte(0xef);
999 flash_.buffer()[0x230] = byte(0xef);
1000 flash_.buffer()[0x50] = byte(0xef);
1001 flash_.buffer()[0x270] = byte(0xef);
1003 ASSERT_CONTAINS_ENTRY("key1", "value1");
1004 ASSERT_CONTAINS_ENTRY("k2", "value2");
1005 ASSERT_CONTAINS_ENTRY("k3y", "value3");
1006 ASSERT_CONTAINS_ENTRY("4k", "value4");
1008 EXPECT_EQ(OkStatus(), kvs_.FullMaintenance());
1009 stats = kvs_.GetStorageStats();
1010 EXPECT_EQ(stats.in_use_bytes, (128u * kvs_.redundancy()));
1011 EXPECT_EQ(stats.reclaimable_bytes, 0u);
1012 EXPECT_EQ(stats.writable_bytes, 3 * 512u - (128u * kvs_.redundancy()));
1013 EXPECT_EQ(stats.corrupt_sectors_recovered, 2u);
1014 EXPECT_EQ(stats.missing_redundant_entries_recovered, 4u);
1017 class InitializedLazyRecoveryKvs : public ::testing::Test {
1019 static constexpr auto kInitialContents =
1020 bytes::Concat(kEntry1, kEntry2, kEntry3, kEntry4);
1022 InitializedLazyRecoveryKvs()
1023 : flash_(internal::Entry::kMinAlignmentBytes),
1024 partition_(&flash_),
1026 {.magic = kMagic, .checksum = &default_checksum},
1027 kRecoveryLazyGcOptions) {
1029 std::memcpy(flash_.buffer().data(),
1030 kInitialContents.data(),
1031 kInitialContents.size());
1033 EXPECT_EQ(OkStatus(), kvs_.Init());
1036 FakeFlashMemoryBuffer<512, 8> flash_;
1037 FlashPartition partition_;
1038 KeyValueStoreBuffer<kMaxEntries, kMaxUsableSectors> kvs_;
1041 // Test a KVS with a number of entries, several sectors that are nearly full
1042 // of stale (reclaimable) space, and not enough writable (free) space to add a
1043 // redundant copy for any of the entries. Tests that the add redundancy step of
1044 // repair is able to use garbage collection to free up space needed for the new
1046 TEST_F(InitializedLazyRecoveryKvs, AddRedundancyToKvsFullOfStaleData) {
1047 // Verify the pre-initialized key are present in the KVS.
1048 ASSERT_CONTAINS_ENTRY("key1", "value1");
1049 ASSERT_CONTAINS_ENTRY("k2", "value2");
1050 ASSERT_CONTAINS_ENTRY("k3y", "value3");
1051 ASSERT_CONTAINS_ENTRY("4k", "value4");
1053 EXPECT_EQ(false, kvs_.error_detected());
1055 auto stats = kvs_.GetStorageStats();
1056 EXPECT_EQ(stats.in_use_bytes, (128u * kvs_.redundancy()));
1057 EXPECT_EQ(stats.reclaimable_bytes, 0u);
1058 EXPECT_EQ(stats.writable_bytes, 7 * 512u - (128u * kvs_.redundancy()));
1059 EXPECT_EQ(stats.corrupt_sectors_recovered, 0u);
1060 EXPECT_EQ(stats.missing_redundant_entries_recovered, 0u);
1062 // Block of data to use for entry value. Sized to 470 so the total entry
1063 // results in the 512 byte sector having 16 bytes remaining.
1064 uint8_t test_data[470] = {1, 2, 3, 4, 5, 6};
1066 // Add a near-sector size key entry to fill the KVS with a valid large entry
1067 // and stale data. Modify the value in between Puts so it actually writes
1068 // (identical value writes are skipped).
1069 EXPECT_EQ(OkStatus(), kvs_.Put("big_key", test_data));
1071 EXPECT_EQ(OkStatus(), kvs_.Put("big_key", test_data));
1073 EXPECT_EQ(OkStatus(), kvs_.Put("big_key", test_data));
1075 EXPECT_EQ(OkStatus(), kvs_.Put("big_key", test_data));
1077 EXPECT_EQ(OkStatus(), kvs_.Put("big_key", test_data));
1079 EXPECT_EQ(OkStatus(), kvs_.Put("big_key", test_data));
1081 // Instantiate a new KVS with redundancy of 2. This KVS should add an extra
1082 // copy of each valid key as part of the init process. Because there is not
1083 // enough writable space, the adding redundancy will need to garbage collect
1085 KeyValueStoreBuffer<kMaxEntries, kMaxUsableSectors, 2> local_kvs(
1087 {.magic = kMagic, .checksum = &default_checksum},
1088 kRecoveryLazyGcOptions);
1089 ASSERT_EQ(OkStatus(), local_kvs.Init());
1091 // Verify no errors found in the new KVS and all the entries are present.
1092 EXPECT_EQ(false, local_kvs.error_detected());
1093 ASSERT_KVS_CONTAINS_ENTRY(local_kvs, "key1", "value1");
1094 ASSERT_KVS_CONTAINS_ENTRY(local_kvs, "k2", "value2");
1095 ASSERT_KVS_CONTAINS_ENTRY(local_kvs, "k3y", "value3");
1096 ASSERT_KVS_CONTAINS_ENTRY(local_kvs, "4k", "value4");
1097 StatusWithSize big_key_size = local_kvs.ValueSize("big_key");
1098 EXPECT_EQ(OkStatus(), big_key_size.status());
1099 EXPECT_EQ(sizeof(test_data), big_key_size.size());
1101 // Verify that storage stats of the new redundant KVS match expected values.
1102 stats = local_kvs.GetStorageStats();
1104 // Expected in-use bytes is size of (pre-init keys + big key) * redundancy.
1105 EXPECT_EQ(stats.in_use_bytes, ((128u + 496u) * local_kvs.redundancy()));
1107 // Expected reclaimable space is number of stale entries remaining for big_key
1108 // (3 after GC to add redundancy) * total sizeof big_key entry (496 bytes).
1110 EXPECT_EQ(stats.reclaimable_bytes, 496u * 3u);
1111 // Expected writable bytes is total writable size (512 * 7) - valid bytes (in
1112 // use) - reclaimable bytes.
1113 EXPECT_EQ(stats.writable_bytes, 848u);
1114 EXPECT_EQ(stats.corrupt_sectors_recovered, 0u);
1115 EXPECT_EQ(stats.missing_redundant_entries_recovered, 0u);
1119 } // namespace pw::kvs