pw_kvs: EntryCache tests
- Expand the EntryCache tests.
- Remove unused kWorkingBufferSize variable
- Remove EntryMetadata::deleted() since it isn't widely used and
overlaps with EntryMetadata::state().
Change-Id: Ic18622acf5ce8f3b303fe9ddd15659163dc2ef55
diff --git a/pw_kvs/BUILD b/pw_kvs/BUILD
index 9700013..76bef4f 100644
--- a/pw_kvs/BUILD
+++ b/pw_kvs/BUILD
@@ -122,7 +122,10 @@
pw_cc_test(
name = "entry_cache_test",
srcs = ["entry_cache_test.cc"],
- deps = [":pw_kvs"],
+ deps = [
+ ":pw_kvs",
+ ":test_utils",
+ ],
)
pw_cc_test(
diff --git a/pw_kvs/BUILD.gn b/pw_kvs/BUILD.gn
index b682455..9c95799 100644
--- a/pw_kvs/BUILD.gn
+++ b/pw_kvs/BUILD.gn
@@ -133,7 +133,10 @@
}
pw_test("entry_cache_test") {
- deps = [ ":pw_kvs" ]
+ deps = [
+ ":pw_kvs",
+ ":test_utils",
+ ]
sources = [ "entry_cache_test.cc" ]
}
diff --git a/pw_kvs/entry_cache.cc b/pw_kvs/entry_cache.cc
index eec4044..64f3bec 100644
--- a/pw_kvs/entry_cache.cc
+++ b/pw_kvs/entry_cache.cc
@@ -73,7 +73,7 @@
// If the key's hash collides with an existing key or if the key is deleted,
// treat it as if it is not in the KVS.
if (status == Status::ALREADY_EXISTS ||
- (status.ok() && metadata->deleted())) {
+ (status.ok() && metadata->state() == EntryState::kDeleted)) {
return Status::NOT_FOUND;
}
return status;
@@ -169,7 +169,6 @@
for (size_t i = 0; i < redundancy(); ++i) {
if (existing[i] == kNoAddress) {
existing[i] = address;
- addresses(descriptor_index);
return;
}
}
diff --git a/pw_kvs/entry_cache_test.cc b/pw_kvs/entry_cache_test.cc
index df1ae90..3b9d1a1 100644
--- a/pw_kvs/entry_cache_test.cc
+++ b/pw_kvs/entry_cache_test.cc
@@ -15,31 +15,274 @@
#include "pw_kvs/internal/entry_cache.h"
#include "gtest/gtest.h"
+#include "pw_kvs/flash_memory.h"
+#include "pw_kvs/in_memory_fake_flash.h"
+#include "pw_kvs/internal/hash.h"
+#include "pw_kvs/internal/key_descriptor.h"
+#include "pw_kvs_private/byte_utils.h"
namespace pw::kvs::internal {
+namespace {
+
+using std::byte;
class EmptyEntryCache : public ::testing::Test {
protected:
+ static constexpr size_t kMaxEntries = 32;
static constexpr size_t kRedundancy = 3;
EmptyEntryCache() : entries_(descriptors_, addresses_, kRedundancy) {}
- Vector<KeyDescriptor, 32> descriptors_;
- EntryCache::AddressList<32, kRedundancy> addresses_;
+ Vector<KeyDescriptor, kMaxEntries> descriptors_;
+ EntryCache::AddressList<kMaxEntries, kRedundancy> addresses_;
EntryCache entries_;
};
+constexpr char kTheKey[] = "The Key";
+
+constexpr KeyDescriptor kDescriptor = {.key_hash = Hash(kTheKey),
+ .transaction_id = 123,
+ .state = EntryState::kValid};
+
TEST_F(EmptyEntryCache, AddNew) {
- EntryMetadata metadata = entries_.AddNew(
- {.key_hash = 1, .transaction_id = 123, .state = EntryState::kValid}, 5);
- EXPECT_EQ(1u, metadata.hash());
- EXPECT_EQ(123u, metadata.transaction_id());
+ EntryMetadata metadata = entries_.AddNew(kDescriptor, 5);
+ EXPECT_EQ(kDescriptor.key_hash, metadata.hash());
+ EXPECT_EQ(kDescriptor.transaction_id, metadata.transaction_id());
+ EXPECT_EQ(kDescriptor.state, metadata.state());
EXPECT_EQ(5u, metadata.first_address());
EXPECT_EQ(1u, metadata.addresses().size());
}
-// TODO(hepler): Expand these tests!
+TEST_F(EmptyEntryCache, EntryMetadata_AddNewAddress) {
+ EntryMetadata metadata = entries_.AddNew(kDescriptor, 100);
+ metadata.AddNewAddress(999);
+
+ EXPECT_EQ(2u, metadata.addresses().size());
+ EXPECT_EQ(100u, metadata.first_address());
+ EXPECT_EQ(100u, metadata.addresses()[0]);
+ EXPECT_EQ(999u, metadata.addresses()[1]);
+}
+
+TEST_F(EmptyEntryCache, EntryMetadata_Reset) {
+ EntryMetadata metadata = entries_.AddNew(kDescriptor, 100);
+ metadata.AddNewAddress(999);
+
+ metadata.Reset(
+ {.key_hash = 987, .transaction_id = 5, .state = EntryState::kDeleted},
+ 8888);
+
+ EXPECT_EQ(987u, metadata.hash());
+ EXPECT_EQ(5u, metadata.transaction_id());
+ EXPECT_EQ(EntryState::kDeleted, metadata.state());
+ EXPECT_EQ(1u, metadata.addresses().size());
+ EXPECT_EQ(8888u, metadata.first_address());
+ EXPECT_EQ(8888u, metadata.addresses()[0]);
+}
+
+TEST_F(EmptyEntryCache, AddNewOrUpdateExisting_NewEntry) {
+ ASSERT_EQ(Status::OK,
+ entries_.AddNewOrUpdateExisting(kDescriptor, 1000, 2000));
+
+ EXPECT_EQ(1u, entries_.present_entries());
+
+ for (const EntryMetadata& entry : entries_) {
+ EXPECT_EQ(1000u, entry.first_address());
+ EXPECT_EQ(kDescriptor.key_hash, entry.hash());
+ EXPECT_EQ(kDescriptor.transaction_id, entry.transaction_id());
+ }
+}
+
+TEST_F(EmptyEntryCache, AddNewOrUpdateExisting_NewEntry_Full) {
+ for (uint32_t i = 0; i < kMaxEntries; ++i) {
+ ASSERT_EQ( // Fill up the cache
+ Status::OK,
+ entries_.AddNewOrUpdateExisting({i, i, EntryState::kValid}, i, 1));
+ }
+ ASSERT_EQ(kMaxEntries, entries_.total_entries());
+ ASSERT_TRUE(entries_.full());
+
+ EXPECT_EQ(Status::RESOURCE_EXHAUSTED,
+ entries_.AddNewOrUpdateExisting(kDescriptor, 1000, 1));
+ EXPECT_EQ(kMaxEntries, entries_.total_entries());
+}
+
+TEST_F(EmptyEntryCache, AddNewOrUpdateExisting_UpdatedEntry) {
+ KeyDescriptor kd = kDescriptor;
+ kd.transaction_id += 3;
+
+ ASSERT_EQ(Status::OK, entries_.AddNewOrUpdateExisting(kd, 3210, 2000));
+
+ EXPECT_EQ(1u, entries_.present_entries());
+
+ for (const EntryMetadata& entry : entries_) {
+ EXPECT_EQ(3210u, entry.first_address());
+ EXPECT_EQ(kDescriptor.key_hash, entry.hash());
+ EXPECT_EQ(kDescriptor.transaction_id + 3, entry.transaction_id());
+ }
+}
+
+TEST_F(EmptyEntryCache, AddNewOrUpdateExisting_AddDuplicateEntry) {
+ ASSERT_EQ(Status::OK,
+ entries_.AddNewOrUpdateExisting(kDescriptor, 1000, 2000));
+ ASSERT_EQ(Status::OK,
+ entries_.AddNewOrUpdateExisting(kDescriptor, 3000, 2000));
+ ASSERT_EQ(Status::OK,
+ entries_.AddNewOrUpdateExisting(kDescriptor, 7000, 2000));
+
+ // Duplicates beyond the redundancy are ignored.
+ ASSERT_EQ(Status::OK,
+ entries_.AddNewOrUpdateExisting(kDescriptor, 9000, 2000));
+
+ EXPECT_EQ(1u, entries_.present_entries());
+
+ for (const EntryMetadata& entry : entries_) {
+ EXPECT_EQ(3u, entry.addresses().size());
+ EXPECT_EQ(1000u, entry.addresses()[0]);
+ EXPECT_EQ(3000u, entry.addresses()[1]);
+ EXPECT_EQ(7000u, entry.addresses()[2]);
+
+ EXPECT_EQ(kDescriptor.key_hash, entry.hash());
+ EXPECT_EQ(kDescriptor.transaction_id, entry.transaction_id());
+ }
+}
+
+TEST_F(EmptyEntryCache, AddNewOrUpdateExisting_AddDuplicateEntryInSameSector) {
+ ASSERT_EQ(Status::OK,
+ entries_.AddNewOrUpdateExisting(kDescriptor, 1000, 1000));
+ EXPECT_EQ(Status::DATA_LOSS,
+ entries_.AddNewOrUpdateExisting(kDescriptor, 1950, 1000));
+
+ EXPECT_EQ(1u, entries_.present_entries());
+
+ for (const EntryMetadata& entry : entries_) {
+ EXPECT_EQ(1u, entry.addresses().size());
+ EXPECT_EQ(1000u, entry.addresses()[0]);
+
+ EXPECT_EQ(kDescriptor.key_hash, entry.hash());
+ EXPECT_EQ(kDescriptor.transaction_id, entry.transaction_id());
+ }
+}
+
+constexpr auto kTheEntry = AsBytes(uint32_t(12345), // magic
+ uint32_t(0), // checksum
+ uint8_t(0), // alignment (16 B)
+ uint8_t(sizeof(kTheKey) - 1), // key length
+ uint16_t(0), // value size
+ uint32_t(123), // transaction ID
+ ByteStr(kTheKey));
+constexpr std::array<byte, 16 - kTheEntry.size() % 16> kPadding1{};
+
+constexpr char kCollision1[] = "9FDC";
+constexpr char kCollision2[] = "axzzK";
+
+constexpr auto kCollisionEntry =
+ AsBytes(uint32_t(12345), // magic
+ uint32_t(0), // checksum
+ uint8_t(0), // alignment (16 B)
+ uint8_t(sizeof(kCollision1) - 1), // key length
+ uint16_t(0), // value size
+ uint32_t(123), // transaction ID
+ ByteStr(kCollision1));
+constexpr std::array<byte, 16 - kCollisionEntry.size() % 16> kPadding2{};
+
+constexpr auto kDeletedEntry =
+ AsBytes(uint32_t(12345), // magic
+ uint32_t(0), // checksum
+ uint8_t(0), // alignment (16 B)
+ uint8_t(sizeof("delorted") - 1), // key length
+ uint16_t(0xffff), // value size (deleted)
+ uint32_t(123), // transaction ID
+ ByteStr("delorted"));
+
+class InitializedEntryCache : public EmptyEntryCache {
+ protected:
+ static_assert(Hash(kCollision1) == Hash(kCollision2));
+
+ InitializedEntryCache()
+ : flash_(AsBytes(
+ kTheEntry, kPadding1, kCollisionEntry, kPadding2, kDeletedEntry)),
+ partition_(&flash_) {
+ entries_.AddNew(kDescriptor, 0);
+ entries_.AddNew({.key_hash = Hash(kCollision1),
+ .transaction_id = 125,
+ .state = EntryState::kDeleted},
+ kTheEntry.size() + kPadding1.size());
+ entries_.AddNew({.key_hash = Hash("delorted"),
+ .transaction_id = 256,
+ .state = EntryState::kDeleted},
+ kTheEntry.size() + kPadding1.size() +
+ kCollisionEntry.size() + kPadding2.size());
+ }
+
+ FakeFlashBuffer<64, 128> flash_;
+ FlashPartition partition_;
+};
+
+TEST_F(InitializedEntryCache, EntryCounts) {
+ EXPECT_EQ(3u, entries_.total_entries());
+ EXPECT_EQ(1u, entries_.present_entries());
+ EXPECT_EQ(kMaxEntries, entries_.max_entries());
+}
+
+TEST_F(InitializedEntryCache, Reset_ClearsEntryCounts) {
+ entries_.Reset();
+
+ EXPECT_EQ(0u, entries_.total_entries());
+ EXPECT_EQ(0u, entries_.present_entries());
+ EXPECT_EQ(kMaxEntries, entries_.max_entries());
+}
+
+TEST_F(InitializedEntryCache, Find_PresentEntry) {
+ EntryMetadata metadata;
+ ASSERT_EQ(Status::OK, entries_.Find(partition_, kTheKey, &metadata));
+ EXPECT_EQ(Hash(kTheKey), metadata.hash());
+ EXPECT_EQ(EntryState::kValid, metadata.state());
+}
+
+TEST_F(InitializedEntryCache, Find_DeletedEntry) {
+ EntryMetadata metadata;
+ ASSERT_EQ(Status::OK, entries_.Find(partition_, "delorted", &metadata));
+ EXPECT_EQ(Hash("delorted"), metadata.hash());
+ EXPECT_EQ(EntryState::kDeleted, metadata.state());
+}
+
+TEST_F(InitializedEntryCache, Find_MissingEntry) {
+ EntryMetadata metadata;
+ ASSERT_EQ(Status::NOT_FOUND, entries_.Find(partition_, "3.141", &metadata));
+}
+
+TEST_F(InitializedEntryCache, Find_Collision) {
+ EntryMetadata metadata;
+ EXPECT_EQ(Status::ALREADY_EXISTS,
+ entries_.Find(partition_, kCollision2, &metadata));
+}
+
+TEST_F(InitializedEntryCache, FindExisting_PresentEntry) {
+ EntryMetadata metadata;
+ ASSERT_EQ(Status::OK, entries_.FindExisting(partition_, kTheKey, &metadata));
+ EXPECT_EQ(Hash(kTheKey), metadata.hash());
+}
+
+TEST_F(InitializedEntryCache, FindExisting_DeletedEntry) {
+ EntryMetadata metadata;
+ ASSERT_EQ(Status::NOT_FOUND,
+ entries_.FindExisting(partition_, "delorted", &metadata));
+}
+
+TEST_F(InitializedEntryCache, FindExisting_MissingEntry) {
+ EntryMetadata metadata;
+ ASSERT_EQ(Status::NOT_FOUND,
+ entries_.FindExisting(partition_, "3.141", &metadata));
+}
+
+TEST_F(InitializedEntryCache, FindExisting_Collision) {
+ EntryMetadata metadata;
+ EXPECT_EQ(Status::NOT_FOUND,
+ entries_.FindExisting(partition_, kCollision2, &metadata));
+}
+
+} // namespace
} // namespace pw::kvs::internal
diff --git a/pw_kvs/key_value_store.cc b/pw_kvs/key_value_store.cc
index 2ad68fa..091c244 100644
--- a/pw_kvs/key_value_store.cc
+++ b/pw_kvs/key_value_store.cc
@@ -173,8 +173,8 @@
DBG("Second pass: Count valid bytes in each sector");
Address newest_key = 0;
- // For every valid key, increment the valid bytes for that sector.
-
+ // For every valid entry, count the valid bytes in that sector. Track which
+ // entry has the newest transaction ID for initializing last_new_sector_.
for (const EntryMetadata& metadata : entry_cache_) {
for (Address address : metadata.addresses()) {
Entry entry;
@@ -364,7 +364,7 @@
KeyValueStore::iterator& KeyValueStore::iterator::operator++() {
// Skip to the next entry that is valid (not deleted).
while (++item_.iterator_ != item_.kvs_.entry_cache_.end() &&
- item_.iterator_->deleted()) {
+ item_.iterator_->state() != EntryState::kValid) {
}
return *this;
}
@@ -372,7 +372,8 @@
KeyValueStore::iterator KeyValueStore::begin() const {
internal::EntryCache::iterator cache_iterator = entry_cache_.begin();
// Skip over any deleted entries at the start of the descriptor list.
- while (cache_iterator != entry_cache_.end() && cache_iterator->deleted()) {
+ while (cache_iterator != entry_cache_.end() &&
+ cache_iterator->state() != EntryState::kValid) {
++cache_iterator;
}
return iterator(*this, cache_iterator);
@@ -1053,7 +1054,7 @@
DBG("Key descriptors: count %zu", entry_cache_.total_entries());
for (auto& metadata : entry_cache_) {
DBG(" - Key: %s, hash %#zx, transaction ID %zu, first address %#zx",
- metadata.deleted() ? "Deleted" : "Valid",
+ metadata.state() == EntryState::kDeleted ? "Deleted" : "Valid",
static_cast<size_t>(metadata.hash()),
static_cast<size_t>(metadata.transaction_id()),
static_cast<size_t>(metadata.first_address()));
diff --git a/pw_kvs/public/pw_kvs/internal/entry_cache.h b/pw_kvs/public/pw_kvs/internal/entry_cache.h
index 5cbef5c..99c2008 100644
--- a/pw_kvs/public/pw_kvs/internal/entry_cache.h
+++ b/pw_kvs/public/pw_kvs/internal/entry_cache.h
@@ -38,12 +38,10 @@
EntryState state() const { return descriptor_->state; }
- bool deleted() const { return state() == EntryState::kDeleted; }
-
// The first known address of this entry.
uint32_t first_address() const { return addresses_[0]; }
- // All addresses for this entry, including redundant entries.
+ // All addresses for this entry, including redundant entries, if any.
const span<Address>& addresses() const { return addresses_; }
// True if the KeyDesctiptor's transaction ID is newer than the specified ID.
@@ -52,8 +50,8 @@
return transaction_id() > other_transaction_id;
}
- // Adds a new address to the entry metadata. MUST NOT be called more than
- // allowed by the redundancy.
+ // Adds a new address to the entry metadata. MUST NOT be called more times
+ // than allowed by the redundancy.
void AddNewAddress(Address address) {
addresses_[addresses_.size()] = address;
addresses_ = span(addresses_.begin(), addresses_.size() + 1);
@@ -92,7 +90,7 @@
addresses_(addresses),
redundancy_(redundancy) {}
- // Clears all
+ // Clears all KeyDescriptors.
void Reset() { descriptors_.clear(); }
// Finds the metadata for an entry matching a particular key. Searches for a
@@ -121,7 +119,8 @@
std::string_view key,
EntryMetadata* metadata) const;
- // Adds a new descriptor to the descriptor list. Must NOT be full!
+ // Adds a new descriptor to the descriptor list. The entry MUST be unique and
+ // the EntryCache must NOT be full!
EntryMetadata AddNew(const KeyDescriptor& entry, Address address);
// Adds a new descriptor, overwrites an existing one, or adds an additional