pw_kvs: Expand KVS::Init() corruption tests
This change adds tests for space accounting and sector writability in
data corruption cases.
Change-Id: I28fb6aead1be5b53595c083b750a183c1402b59c
diff --git a/pw_kvs/key_value_store_error_handling_test.cc b/pw_kvs/key_value_store_error_handling_test.cc
index cf4fa38..69074d3 100644
--- a/pw_kvs/key_value_store_error_handling_test.cc
+++ b/pw_kvs/key_value_store_error_handling_test.cc
@@ -92,16 +92,23 @@
constexpr uint32_t kMagic = 0xc001beef;
constexpr EntryFormat kFormat{.magic = kMagic, .checksum = &checksum};
+constexpr Options kNoGcOptions{
+ .partial_gc_on_write = false,
+ .verify_on_read = true,
+ .verify_on_write = true,
+};
constexpr auto kEntry1 = MakeValidEntry(kMagic, 1, "key1", ByteStr("value1"));
constexpr auto kEntry2 = MakeValidEntry(kMagic, 3, "k2", ByteStr("value2"));
+constexpr auto kEntry3 = MakeValidEntry(kMagic, 4, "k3y", ByteStr("value3"));
+constexpr auto kEntry4 = MakeValidEntry(kMagic, 5, "4k", ByteStr("value4"));
class KvsErrorHandling : public ::testing::Test {
protected:
KvsErrorHandling()
: flash_(internal::Entry::kMinAlignmentBytes),
partition_(&flash_),
- kvs_(&partition_, kFormat) {}
+ kvs_(&partition_, kFormat, kNoGcOptions) {}
void InitFlashTo(span<const byte> contents) {
partition_.Erase();
@@ -141,9 +148,38 @@
byte buffer[64];
ASSERT_EQ(Status::NOT_FOUND, kvs_.Get("key1", buffer).status());
ASSERT_EQ(Status::OK, kvs_.Get("k2", buffer).status());
+
+ auto stats = kvs_.GetStorageStats();
+ // One valid entry.
+ ASSERT_EQ(32u, stats.in_use_bytes);
+ // Rest of space is reclaimable as the sector is corrupt.
+ ASSERT_EQ(480u, stats.reclaimable_bytes);
}
}
+TEST_F(KvsErrorHandling, Init_CorruptEntry_CorrectlyAccountsForSectorSize) {
+ InitFlashTo(AsBytes(kEntry1, kEntry2, kEntry3, kEntry4));
+
+ // Corrupt the first and third entries.
+ flash_.buffer()[9] = byte(0xef);
+ flash_.buffer()[67] = byte(0xef);
+
+ ASSERT_EQ(Status::DATA_LOSS, kvs_.Init());
+
+ EXPECT_EQ(2u, kvs_.size());
+
+ byte buffer[64];
+ EXPECT_EQ(Status::NOT_FOUND, kvs_.Get("key1", buffer).status());
+ EXPECT_EQ(Status::OK, kvs_.Get("k2", buffer).status());
+ EXPECT_EQ(Status::NOT_FOUND, kvs_.Get("k3y", buffer).status());
+ EXPECT_EQ(Status::OK, kvs_.Get("4k", buffer).status());
+
+ auto stats = kvs_.GetStorageStats();
+ ASSERT_EQ(64u, stats.in_use_bytes);
+ ASSERT_EQ(448u, stats.reclaimable_bytes);
+ ASSERT_EQ(1024u, stats.writable_bytes);
+}
+
TEST_F(KvsErrorHandling, Init_ReadError_IsNotInitialized) {
InitFlashTo(AsBytes(kEntry1, kEntry2));
@@ -154,6 +190,58 @@
EXPECT_FALSE(kvs_.initialized());
}
+TEST_F(KvsErrorHandling, Init_CorruptSectors_ShouldBeUnwritable) {
+ InitFlashTo(AsBytes(kEntry1, kEntry2));
+
+ // Corrupt 3 of the 4 512-byte flash sectors. Corrupt sectors should be
+ // unwritable, and the KVS must maintain one empty sector at all times.
+ // As GC on write is disabled through KVS options, writes should no longer be
+ // possible due to lack of space.
+ flash_.buffer()[1] = byte(0xef);
+ flash_.buffer()[513] = byte(0xef);
+ flash_.buffer()[1025] = byte(0xef);
+
+ ASSERT_EQ(Status::DATA_LOSS, kvs_.Init());
+ EXPECT_EQ(Status::RESOURCE_EXHAUSTED, kvs_.Put("hello", ByteStr("world")));
+ EXPECT_EQ(Status::RESOURCE_EXHAUSTED, kvs_.Put("a", ByteStr("b")));
+
+ // Existing valid entries should still be readable.
+ EXPECT_EQ(1u, kvs_.size());
+ byte buffer[64];
+ EXPECT_EQ(Status::NOT_FOUND, kvs_.Get("key1", buffer).status());
+ EXPECT_EQ(Status::OK, kvs_.Get("k2", buffer).status());
+
+ auto stats = kvs_.GetStorageStats();
+ EXPECT_EQ(32u, stats.in_use_bytes);
+ EXPECT_EQ(480u + 2 * 512u, stats.reclaimable_bytes);
+ EXPECT_EQ(0u, stats.writable_bytes);
+}
+
+TEST_F(KvsErrorHandling, Init_CorruptKey_RevertsToPreviousVersion) {
+ constexpr auto kVersion7 =
+ MakeValidEntry(kMagic, 7, "my_key", ByteStr("version 7"));
+ constexpr auto kVersion8 =
+ MakeValidEntry(kMagic, 8, "my_key", ByteStr("version 8"));
+
+ InitFlashTo(AsBytes(kVersion7, kVersion8));
+
+ // Corrupt a byte of entry version 8 (addresses 32-63).
+ flash_.buffer()[34] = byte(0xef);
+
+ ASSERT_EQ(Status::DATA_LOSS, kvs_.Init());
+
+ char buffer[64] = {};
+
+ EXPECT_EQ(1u, kvs_.size());
+
+ auto result = kvs_.Get("my_key", as_writable_bytes(span(buffer)));
+ EXPECT_EQ(Status::OK, result.status());
+ EXPECT_EQ(sizeof("version 7") - 1, result.size());
+ EXPECT_STREQ("version 7", buffer);
+
+ EXPECT_EQ(32u, kvs_.GetStorageStats().in_use_bytes);
+}
+
TEST_F(KvsErrorHandling, Put_WriteFailure_EntryNotAdded) {
ASSERT_EQ(Status::OK, kvs_.Init());
flash_.InjectWriteError(FlashError::Unconditional(Status::UNAVAILABLE));