third_party.pigweed.src/pw_kvs/key_value_store_test.cc
David Rogers f3884eb9d4 pw_kvs: Fix garbage collection for redundant entries
Fix garbage collection when using redudant entries.
- Improve "find sector to write entry to" and "find sector to GC"
  algorithms so they work better with redundant entries.
- Add support in GC to be aware of partially written keys, where the
  first entry is written to flash, but GC is needed to free space for
  the redundant entries to have space to be written.

Tested with kEntryRedundancy up to 2.

Change-Id: Iff88d970714fa1ece634af926a1838a5f7d5d7e7
2020-03-10 23:18:58 +00:00

1290 lines
43 KiB
C++

// Copyright 2020 The Pigweed Authors
//
// Licensed under the Apache License, Version 2.0 (the "License"); you may not
// use this file except in compliance with the License. You may obtain a copy of
// the License at
//
// https://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
// License for the specific language governing permissions and limitations under
// the License.
#define DUMP_KVS_STATE_TO_FILE 0
#define USE_MEMORY_BUFFER 1
#define PW_LOG_USE_ULTRA_SHORT_NAMES 1
#include "pw_kvs/key_value_store.h"
#include <array>
#include <cstdio>
#include <cstring>
#if DUMP_KVS_STATE_TO_FILE
#include <vector>
#endif // DUMP_KVS_STATE_TO_FILE
#include "gtest/gtest.h"
#include "pw_checksum/ccitt_crc16.h"
#include "pw_kvs/crc16_checksum.h"
#include "pw_kvs/flash_memory.h"
#include "pw_kvs/internal/entry.h"
#include "pw_kvs_private/byte_utils.h"
#include "pw_kvs_private/macros.h"
#include "pw_log/log.h"
#include "pw_span/span.h"
#include "pw_status/status.h"
#include "pw_string/string_builder.h"
#if USE_MEMORY_BUFFER
#include "pw_kvs/in_memory_fake_flash.h"
#endif // USE_MEMORY_BUFFER
namespace pw::kvs {
namespace {
using internal::EntryHeader;
using std::byte;
constexpr size_t kMaxEntries = 256;
constexpr size_t kMaxUsableSectors = 256;
// Test the functions in byte_utils.h. Create a byte array with AsBytes and
// ByteStr and check that its contents are correct.
constexpr std::array<char, 2> kTestArray = {'a', 'b'};
constexpr auto kAsBytesTest = AsBytes(
'a', uint16_t(1), uint8_t(23), kTestArray, ByteStr("c"), uint64_t(-1));
static_assert(kAsBytesTest.size() == 15);
static_assert(kAsBytesTest[0] == std::byte{'a'});
static_assert(kAsBytesTest[1] == std::byte{1});
static_assert(kAsBytesTest[2] == std::byte{0});
static_assert(kAsBytesTest[3] == std::byte{23});
static_assert(kAsBytesTest[4] == std::byte{'a'});
static_assert(kAsBytesTest[5] == std::byte{'b'});
static_assert(kAsBytesTest[6] == std::byte{'c'});
static_assert(kAsBytesTest[7] == std::byte{0xff});
static_assert(kAsBytesTest[8] == std::byte{0xff});
static_assert(kAsBytesTest[9] == std::byte{0xff});
static_assert(kAsBytesTest[10] == std::byte{0xff});
static_assert(kAsBytesTest[11] == std::byte{0xff});
static_assert(kAsBytesTest[12] == std::byte{0xff});
static_assert(kAsBytesTest[13] == std::byte{0xff});
static_assert(kAsBytesTest[14] == std::byte{0xff});
// Test that the ConvertsToSpan trait correctly idenitifies types that convert
// to span.
static_assert(!ConvertsToSpan<int>());
static_assert(!ConvertsToSpan<void>());
static_assert(!ConvertsToSpan<std::byte>());
static_assert(!ConvertsToSpan<std::byte*>());
static_assert(ConvertsToSpan<std::array<int, 5>>());
static_assert(ConvertsToSpan<decltype("Hello!")>());
static_assert(ConvertsToSpan<std::string_view>());
static_assert(ConvertsToSpan<std::string_view&>());
static_assert(ConvertsToSpan<std::string_view&&>());
static_assert(ConvertsToSpan<const std::string_view>());
static_assert(ConvertsToSpan<const std::string_view&>());
static_assert(ConvertsToSpan<const std::string_view&&>());
static_assert(ConvertsToSpan<bool[1]>());
static_assert(ConvertsToSpan<char[35]>());
static_assert(ConvertsToSpan<const int[35]>());
static_assert(ConvertsToSpan<span<int>>());
static_assert(ConvertsToSpan<span<byte>>());
static_assert(ConvertsToSpan<span<const int*>>());
static_assert(ConvertsToSpan<span<bool>&&>());
static_assert(ConvertsToSpan<const span<bool>&>());
static_assert(ConvertsToSpan<span<bool>&&>());
// This is a self contained flash unit with both memory and a single partition.
template <uint32_t sector_size_bytes, uint16_t sector_count>
struct FlashWithPartitionFake {
// Default to 16 byte alignment, which is common in practice.
FlashWithPartitionFake() : FlashWithPartitionFake(16) {}
FlashWithPartitionFake(size_t alignment_bytes)
: memory(alignment_bytes), partition(&memory, 0, memory.sector_count()) {}
FakeFlashBuffer<sector_size_bytes, sector_count> memory;
FlashPartition partition;
public:
#if DUMP_KVS_STATE_TO_FILE
Status Dump(const char* filename) {
std::FILE* out_file = std::fopen(filename, "w+");
if (out_file == nullptr) {
PW_LOG_ERROR("Failed to dump to %s", filename);
return Status::DATA_LOSS;
}
std::vector<std::byte> out_vec(memory.size_bytes());
Status status =
memory.Read(0, pw::span<std::byte>(out_vec.data(), out_vec.size()));
if (status != Status::OK) {
fclose(out_file);
return status;
}
size_t written =
std::fwrite(out_vec.data(), 1, memory.size_bytes(), out_file);
if (written != memory.size_bytes()) {
PW_LOG_ERROR("Failed to dump to %s, written=%u",
filename,
static_cast<unsigned>(written));
status = Status::DATA_LOSS;
} else {
PW_LOG_INFO("Dumped to %s", filename);
status = Status::OK;
}
fclose(out_file);
return status;
}
#else
Status Dump(const char*) { return Status::OK; }
#endif // DUMP_KVS_STATE_TO_FILE
};
typedef FlashWithPartitionFake<4 * 128 /*sector size*/, 6 /*sectors*/> Flash;
#if USE_MEMORY_BUFFER
// Although it might be useful to test other configurations, some tests require
// at least 3 sectors; therfore it should have this when checked in.
FakeFlashBuffer<4 * 1024, 6> test_flash(
16); // 4 x 4k sectors, 16 byte alignment
FlashPartition test_partition(&test_flash, 0, test_flash.sector_count());
FakeFlashBuffer<1024, 60> large_test_flash(8);
FlashPartition large_test_partition(&large_test_flash,
0,
large_test_flash.sector_count());
#else // TODO: Test with real flash
FlashPartition& test_partition = FlashExternalTestPartition();
#endif // USE_MEMORY_BUFFER
std::array<byte, 512> buffer;
constexpr std::array<const char*, 3> keys{"TestKey1", "Key2", "TestKey3"};
ChecksumCrc16 checksum;
constexpr EntryFormat format{.magic = 0xBAD'C0D3, .checksum = &checksum};
size_t RoundUpForAlignment(size_t size) {
return AlignUp(size, test_partition.alignment_bytes());
}
// This class gives attributes of KVS that we are testing against
class KvsAttributes {
public:
KvsAttributes(size_t key_size, size_t data_size)
: chunk_header_size_(RoundUpForAlignment(sizeof(EntryHeader))),
data_size_(RoundUpForAlignment(data_size)),
key_size_(RoundUpForAlignment(key_size)),
erase_size_(chunk_header_size_ + key_size_),
min_put_size_(
RoundUpForAlignment(chunk_header_size_ + key_size_ + data_size_)) {}
size_t ChunkHeaderSize() { return chunk_header_size_; }
size_t DataSize() { return data_size_; }
size_t KeySize() { return key_size_; }
size_t EraseSize() { return erase_size_; }
size_t MinPutSize() { return min_put_size_; }
private:
const size_t chunk_header_size_;
const size_t data_size_;
const size_t key_size_;
const size_t erase_size_;
const size_t min_put_size_;
};
class EmptyInitializedKvs : public ::testing::Test {
protected:
EmptyInitializedKvs() : kvs_(&test_partition, format) {
test_partition.Erase();
ASSERT_EQ(Status::OK, kvs_.Init());
}
// Intention of this is to put and erase key-val to fill up sectors. It's a
// helper function in testing how KVS handles cases where flash sector is full
// or near full.
void FillKvs(const char* key, size_t size_to_fill) {
constexpr size_t kTestDataSize = 8;
KvsAttributes kvs_attr(std::strlen(key), kTestDataSize);
const size_t kMaxPutSize =
buffer.size() + kvs_attr.ChunkHeaderSize() + kvs_attr.KeySize();
ASSERT_GE(size_to_fill, kvs_attr.MinPutSize() + kvs_attr.EraseSize());
// Saving enough space to perform erase after loop
size_to_fill -= kvs_attr.EraseSize();
// We start with possible small chunk to prevent too small of a Kvs.Put() at
// the end.
size_t chunk_len =
std::max(kvs_attr.MinPutSize(), size_to_fill % buffer.size());
std::memset(buffer.data(), 0, buffer.size());
while (size_to_fill > 0) {
// Changing buffer value so put actually does something
buffer[0] = static_cast<byte>(static_cast<uint8_t>(buffer[0]) + 1);
ASSERT_EQ(Status::OK,
kvs_.Put(key,
span(buffer.data(),
chunk_len - kvs_attr.ChunkHeaderSize() -
kvs_attr.KeySize())));
size_to_fill -= chunk_len;
chunk_len = std::min(size_to_fill, kMaxPutSize);
}
ASSERT_EQ(Status::OK, kvs_.Delete(key));
}
KeyValueStoreBuffer<kMaxEntries, kMaxUsableSectors> kvs_;
};
} // namespace
TEST_F(EmptyInitializedKvs, Put_SameKeySameValueRepeatedly_AlignedEntries) {
std::array<char, 8> value{'v', 'a', 'l', 'u', 'e', '6', '7', '\0'};
for (int i = 0; i < 1000; ++i) {
ASSERT_EQ(Status::OK, kvs_.Put("The Key!", as_bytes(span(value))));
}
}
TEST_F(EmptyInitializedKvs, Put_SameKeySameValueRepeatedly_UnalignedEntries) {
std::array<char, 7> value{'v', 'a', 'l', 'u', 'e', '6', '\0'};
for (int i = 0; i < 1000; ++i) {
ASSERT_EQ(Status::OK, kvs_.Put("The Key!", as_bytes(span(value))));
}
}
TEST_F(EmptyInitializedKvs, Put_SameKeyDifferentValuesRepeatedly) {
std::array<char, 10> value{'v', 'a', 'l', 'u', 'e', '6', '7', '8', '9', '\0'};
for (int i = 0; i < 100; ++i) {
for (unsigned size = 0; size < value.size(); ++size) {
ASSERT_EQ(Status::OK, kvs_.Put("The Key!", i));
}
}
}
TEST_F(EmptyInitializedKvs, Put_MaxValueSize) {
size_t max_value_size =
test_partition.sector_size_bytes() - sizeof(EntryHeader) - 1;
// Use the large_test_flash as a big chunk of data for the Put statement.
ASSERT_GT(sizeof(large_test_flash), max_value_size + 2 * sizeof(EntryHeader));
auto big_data = as_bytes(span(&large_test_flash, 1));
EXPECT_EQ(Status::OK, kvs_.Put("K", big_data.subspan(0, max_value_size)));
// Larger than maximum is rejected.
EXPECT_EQ(Status::INVALID_ARGUMENT,
kvs_.Put("K", big_data.subspan(0, max_value_size + 1)));
EXPECT_EQ(Status::INVALID_ARGUMENT, kvs_.Put("K", big_data));
}
TEST_F(EmptyInitializedKvs, PutAndGetByValue_ConvertibleToSpan) {
constexpr float input[] = {1.0, -3.5};
ASSERT_EQ(Status::OK, kvs_.Put("key", input));
float output[2] = {};
ASSERT_EQ(Status::OK, kvs_.Get("key", &output));
EXPECT_EQ(input[0], output[0]);
EXPECT_EQ(input[1], output[1]);
}
TEST_F(EmptyInitializedKvs, PutAndGetByValue_Span) {
float input[] = {1.0, -3.5};
ASSERT_EQ(Status::OK, kvs_.Put("key", span(input)));
float output[2] = {};
ASSERT_EQ(Status::OK, kvs_.Get("key", &output));
EXPECT_EQ(input[0], output[0]);
EXPECT_EQ(input[1], output[1]);
}
TEST_F(EmptyInitializedKvs, PutAndGetByValue_NotConvertibleToSpan) {
struct TestStruct {
double a;
bool b;
};
const TestStruct input{-1234.5, true};
ASSERT_EQ(Status::OK, kvs_.Put("key", input));
TestStruct output;
ASSERT_EQ(Status::OK, kvs_.Get("key", &output));
EXPECT_EQ(input.a, output.a);
EXPECT_EQ(input.b, output.b);
}
TEST_F(EmptyInitializedKvs, Get_Simple) {
ASSERT_EQ(Status::OK, kvs_.Put("Charles", as_bytes(span("Mingus"))));
char value[16];
auto result = kvs_.Get("Charles", as_writable_bytes(span(value)));
EXPECT_EQ(Status::OK, result.status());
EXPECT_EQ(sizeof("Mingus"), result.size());
EXPECT_STREQ("Mingus", value);
}
TEST_F(EmptyInitializedKvs, Get_WithOffset) {
ASSERT_EQ(Status::OK, kvs_.Put("Charles", as_bytes(span("Mingus"))));
char value[16];
auto result = kvs_.Get("Charles", as_writable_bytes(span(value)), 4);
EXPECT_EQ(Status::OK, result.status());
EXPECT_EQ(sizeof("Mingus") - 4, result.size());
EXPECT_STREQ("us", value);
}
TEST_F(EmptyInitializedKvs, Get_WithOffset_FillBuffer) {
ASSERT_EQ(Status::OK, kvs_.Put("Charles", as_bytes(span("Mingus"))));
char value[4] = {};
auto result = kvs_.Get("Charles", as_writable_bytes(span(value, 3)), 1);
EXPECT_EQ(Status::RESOURCE_EXHAUSTED, result.status());
EXPECT_EQ(3u, result.size());
EXPECT_STREQ("ing", value);
}
TEST_F(EmptyInitializedKvs, Get_WithOffset_PastEnd) {
ASSERT_EQ(Status::OK, kvs_.Put("Charles", as_bytes(span("Mingus"))));
char value[16];
auto result =
kvs_.Get("Charles", as_writable_bytes(span(value)), sizeof("Mingus") + 1);
EXPECT_EQ(Status::OUT_OF_RANGE, result.status());
EXPECT_EQ(0u, result.size());
}
TEST_F(EmptyInitializedKvs, GetValue) {
ASSERT_EQ(Status::OK, kvs_.Put("key", uint32_t(0xfeedbeef)));
uint32_t value = 0;
EXPECT_EQ(Status::OK, kvs_.Get("key", &value));
EXPECT_EQ(uint32_t(0xfeedbeef), value);
}
TEST_F(EmptyInitializedKvs, GetValue_TooSmall) {
ASSERT_EQ(Status::OK, kvs_.Put("key", uint32_t(0xfeedbeef)));
uint8_t value = 0;
EXPECT_EQ(Status::INVALID_ARGUMENT, kvs_.Get("key", &value));
EXPECT_EQ(0u, value);
}
TEST_F(EmptyInitializedKvs, GetValue_TooLarge) {
ASSERT_EQ(Status::OK, kvs_.Put("key", uint32_t(0xfeedbeef)));
uint64_t value = 0;
EXPECT_EQ(Status::INVALID_ARGUMENT, kvs_.Get("key", &value));
EXPECT_EQ(0u, value);
}
TEST_F(EmptyInitializedKvs, Delete_GetDeletedKey_ReturnsNotFound) {
ASSERT_EQ(Status::OK, kvs_.Put("kEy", as_bytes(span("123"))));
ASSERT_EQ(Status::OK, kvs_.Delete("kEy"));
EXPECT_EQ(Status::NOT_FOUND, kvs_.Get("kEy", {}).status());
EXPECT_EQ(Status::NOT_FOUND, kvs_.ValueSize("kEy").status());
}
TEST_F(EmptyInitializedKvs, Delete_AddBackKey_PersistsAfterInitialization) {
ASSERT_EQ(Status::OK, kvs_.Put("kEy", as_bytes(span("123"))));
ASSERT_EQ(Status::OK, kvs_.Delete("kEy"));
EXPECT_EQ(Status::OK, kvs_.Put("kEy", as_bytes(span("45678"))));
char data[6] = {};
ASSERT_EQ(Status::OK, kvs_.Get("kEy", &data));
EXPECT_STREQ(data, "45678");
// Ensure that the re-added key is still present after reinitialization.
KeyValueStoreBuffer<kMaxEntries, kMaxUsableSectors> new_kvs(&test_partition,
format);
ASSERT_EQ(Status::OK, new_kvs.Init());
EXPECT_EQ(Status::OK, new_kvs.Put("kEy", as_bytes(span("45678"))));
char new_data[6] = {};
EXPECT_EQ(Status::OK, new_kvs.Get("kEy", &new_data));
EXPECT_STREQ(data, "45678");
}
TEST_F(EmptyInitializedKvs, Delete_AllItems_KvsIsEmpty) {
ASSERT_EQ(Status::OK, kvs_.Put("kEy", as_bytes(span("123"))));
ASSERT_EQ(Status::OK, kvs_.Delete("kEy"));
EXPECT_EQ(0u, kvs_.size());
EXPECT_TRUE(kvs_.empty());
}
TEST_F(EmptyInitializedKvs, Collision_WithPresentKey) {
// Both hash to 0x19df36f0.
constexpr std::string_view key1 = "D4";
constexpr std::string_view key2 = "dFU6S";
ASSERT_EQ(Status::OK, kvs_.Put(key1, 1000));
EXPECT_EQ(Status::ALREADY_EXISTS, kvs_.Put(key2, 999));
int value = 0;
EXPECT_EQ(Status::OK, kvs_.Get(key1, &value));
EXPECT_EQ(1000, value);
EXPECT_EQ(Status::NOT_FOUND, kvs_.Get(key2, &value));
EXPECT_EQ(Status::NOT_FOUND, kvs_.ValueSize(key2).status());
EXPECT_EQ(Status::NOT_FOUND, kvs_.Delete(key2));
}
TEST_F(EmptyInitializedKvs, Collision_WithDeletedKey) {
// Both hash to 0x4060f732.
constexpr std::string_view key1 = "1U2";
constexpr std::string_view key2 = "ahj9d";
ASSERT_EQ(Status::OK, kvs_.Put(key1, 1000));
ASSERT_EQ(Status::OK, kvs_.Delete(key1));
// key2 collides with key1's tombstone.
EXPECT_EQ(Status::ALREADY_EXISTS, kvs_.Put(key2, 999));
int value = 0;
EXPECT_EQ(Status::NOT_FOUND, kvs_.Get(key1, &value));
EXPECT_EQ(Status::NOT_FOUND, kvs_.Get(key2, &value));
EXPECT_EQ(Status::NOT_FOUND, kvs_.ValueSize(key2).status());
EXPECT_EQ(Status::NOT_FOUND, kvs_.Delete(key2));
}
TEST_F(EmptyInitializedKvs, Iteration_Empty_ByReference) {
for (const KeyValueStore::Item& entry : kvs_) {
FAIL(); // The KVS is empty; this shouldn't execute.
static_cast<void>(entry);
}
}
TEST_F(EmptyInitializedKvs, Iteration_Empty_ByValue) {
for (KeyValueStore::Item entry : kvs_) {
FAIL(); // The KVS is empty; this shouldn't execute.
static_cast<void>(entry);
}
}
TEST_F(EmptyInitializedKvs, Iteration_OneItem) {
ASSERT_EQ(Status::OK, kvs_.Put("kEy", as_bytes(span("123"))));
for (KeyValueStore::Item entry : kvs_) {
EXPECT_STREQ(entry.key(), "kEy"); // Make sure null-terminated.
char buffer[sizeof("123")] = {};
EXPECT_EQ(Status::OK, entry.Get(&buffer));
EXPECT_STREQ("123", buffer);
}
}
TEST_F(EmptyInitializedKvs, Iteration_GetWithOffset) {
ASSERT_EQ(Status::OK, kvs_.Put("key", as_bytes(span("not bad!"))));
for (KeyValueStore::Item entry : kvs_) {
char buffer[5];
auto result = entry.Get(as_writable_bytes(span(buffer)), 4);
EXPECT_EQ(Status::OK, result.status());
EXPECT_EQ(5u, result.size());
EXPECT_STREQ("bad!", buffer);
}
}
TEST_F(EmptyInitializedKvs, Iteration_GetValue) {
ASSERT_EQ(Status::OK, kvs_.Put("key", uint32_t(0xfeedbeef)));
for (KeyValueStore::Item entry : kvs_) {
uint32_t value = 0;
EXPECT_EQ(Status::OK, entry.Get(&value));
EXPECT_EQ(uint32_t(0xfeedbeef), value);
}
}
TEST_F(EmptyInitializedKvs, Iteration_GetValue_TooSmall) {
ASSERT_EQ(Status::OK, kvs_.Put("key", uint32_t(0xfeedbeef)));
for (KeyValueStore::Item entry : kvs_) {
uint8_t value = 0;
EXPECT_EQ(Status::INVALID_ARGUMENT, entry.Get(&value));
EXPECT_EQ(0u, value);
}
}
TEST_F(EmptyInitializedKvs, Iteration_GetValue_TooLarge) {
ASSERT_EQ(Status::OK, kvs_.Put("key", uint32_t(0xfeedbeef)));
for (KeyValueStore::Item entry : kvs_) {
uint64_t value = 0;
EXPECT_EQ(Status::INVALID_ARGUMENT, entry.Get(&value));
EXPECT_EQ(0u, value);
}
}
TEST_F(EmptyInitializedKvs, Iteration_EmptyAfterDeletion) {
ASSERT_EQ(Status::OK, kvs_.Put("kEy", as_bytes(span("123"))));
ASSERT_EQ(Status::OK, kvs_.Delete("kEy"));
for (KeyValueStore::Item entry : kvs_) {
static_cast<void>(entry);
FAIL();
}
}
TEST_F(EmptyInitializedKvs, FuzzTest) {
if (test_partition.sector_size_bytes() < 4 * 1024 ||
test_partition.sector_count() < 4) {
PW_LOG_INFO("Sectors too small, skipping test.");
return; // TODO: Test could be generalized
}
const char* key1 = "Buf1";
const char* key2 = "Buf2";
const size_t kLargestBufSize = 3 * 1024;
static byte buf1[kLargestBufSize];
static byte buf2[kLargestBufSize];
std::memset(buf1, 1, sizeof(buf1));
std::memset(buf2, 2, sizeof(buf2));
// Start with things in KVS
ASSERT_EQ(Status::OK, kvs_.Put(key1, buf1));
ASSERT_EQ(Status::OK, kvs_.Put(key2, buf2));
for (size_t j = 0; j < keys.size(); j++) {
ASSERT_EQ(Status::OK, kvs_.Put(keys[j], j));
}
for (size_t i = 0; i < 100; i++) {
// Vary two sizes
size_t size1 = (kLargestBufSize) / (i + 1);
size_t size2 = (kLargestBufSize) / (100 - i);
for (size_t j = 0; j < 50; j++) {
// Rewrite a single key many times, can fill up a sector
ASSERT_EQ(Status::OK, kvs_.Put("some_data", j));
}
// Delete and re-add everything
ASSERT_EQ(Status::OK, kvs_.Delete(key1));
ASSERT_EQ(Status::OK, kvs_.Put(key1, span(buf1, size1)));
ASSERT_EQ(Status::OK, kvs_.Delete(key2));
ASSERT_EQ(Status::OK, kvs_.Put(key2, span(buf2, size2)));
for (size_t j = 0; j < keys.size(); j++) {
ASSERT_EQ(Status::OK, kvs_.Delete(keys[j]));
ASSERT_EQ(Status::OK, kvs_.Put(keys[j], j));
}
// Re-enable and verify
ASSERT_EQ(Status::OK, kvs_.Init());
static byte buf[4 * 1024];
ASSERT_EQ(Status::OK, kvs_.Get(key1, span(buf, size1)).status());
ASSERT_EQ(std::memcmp(buf, buf1, size1), 0);
ASSERT_EQ(Status::OK, kvs_.Get(key2, span(buf, size2)).status());
ASSERT_EQ(std::memcmp(buf2, buf2, size2), 0);
for (size_t j = 0; j < keys.size(); j++) {
size_t ret = 1000;
ASSERT_EQ(Status::OK, kvs_.Get(keys[j], &ret));
ASSERT_EQ(ret, j);
}
}
}
TEST_F(EmptyInitializedKvs, Basic) {
// Add some data
uint8_t value1 = 0xDA;
ASSERT_EQ(Status::OK,
kvs_.Put(keys[0], as_bytes(span(&value1, sizeof(value1)))));
uint32_t value2 = 0xBAD0301f;
ASSERT_EQ(Status::OK, kvs_.Put(keys[1], value2));
// Verify data
uint32_t test2;
EXPECT_EQ(Status::OK, kvs_.Get(keys[1], &test2));
uint8_t test1;
ASSERT_EQ(Status::OK, kvs_.Get(keys[0], &test1));
EXPECT_EQ(test1, value1);
EXPECT_EQ(test2, value2);
// Delete a key
EXPECT_EQ(Status::OK, kvs_.Delete(keys[0]));
// Verify it was erased
EXPECT_EQ(kvs_.Get(keys[0], &test1), Status::NOT_FOUND);
test2 = 0;
ASSERT_EQ(
Status::OK,
kvs_.Get(keys[1], span(reinterpret_cast<byte*>(&test2), sizeof(test2)))
.status());
EXPECT_EQ(test2, value2);
// Delete other key
kvs_.Delete(keys[1]);
// Verify it was erased
EXPECT_EQ(kvs_.size(), 0u);
}
#define ASSERT_OK(expr) ASSERT_EQ(Status::OK, expr)
#define EXPECT_OK(expr) EXPECT_EQ(Status::OK, expr)
TEST(InMemoryKvs, WriteOneKeyMultipleTimes) {
// Create and erase the fake flash. It will persist across reloads.
Flash flash;
ASSERT_OK(flash.partition.Erase());
int num_reloads = 2;
for (int reload = 0; reload < num_reloads; ++reload) {
DBG("xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx");
DBG("xxx xxxx");
DBG("xxx Reload %2d xxxx", reload);
DBG("xxx xxxx");
DBG("xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx");
// Create and initialize the KVS.
constexpr EntryFormat format{.magic = 0xBAD'C0D3, .checksum = nullptr};
KeyValueStoreBuffer<kMaxEntries, kMaxUsableSectors> kvs(&flash.partition,
format);
ASSERT_OK(kvs.Init());
// Write the same entry many times.
const char* key = "abcd";
const size_t num_writes = 99;
uint32_t written_value;
EXPECT_EQ(kvs.size(), (reload == 0) ? 0 : 1u);
for (uint32_t i = 0; i < num_writes; ++i) {
DBG("PUT #%zu for key %s with value %zu", size_t(i), key, size_t(i));
written_value = i + 0xfc; // Prevent accidental pass with zero.
EXPECT_OK(kvs.Put(key, written_value));
EXPECT_EQ(kvs.size(), 1u);
}
// Verify that we can read the value back.
DBG("GET final value for key: %s", key);
uint32_t actual_value;
EXPECT_OK(kvs.Get(key, &actual_value));
EXPECT_EQ(actual_value, written_value);
char fname_buf[64] = {'\0'};
snprintf(&fname_buf[0],
sizeof(fname_buf),
"WriteOneKeyMultipleTimes_%d.bin",
reload);
flash.Dump(fname_buf);
}
}
TEST(InMemoryKvs, WritingMultipleKeysIncreasesSize) {
// Create and erase the fake flash.
Flash flash;
ASSERT_OK(flash.partition.Erase());
// Create and initialize the KVS.
constexpr EntryFormat format{.magic = 0xBAD'C0D3, .checksum = nullptr};
KeyValueStoreBuffer<kMaxEntries, kMaxUsableSectors> kvs(&flash.partition,
format);
ASSERT_OK(kvs.Init());
// Write the same entry many times.
const size_t num_writes = 10;
EXPECT_EQ(kvs.size(), 0u);
for (size_t i = 0; i < num_writes; ++i) {
StringBuffer<150> key;
key << "key_" << i;
DBG("PUT #%zu for key %s with value %zu", i, key.c_str(), i);
size_t value = i + 77; // Prevent accidental pass with zero.
EXPECT_OK(kvs.Put(key.view(), value));
EXPECT_EQ(kvs.size(), i + 1);
}
flash.Dump("WritingMultipleKeysIncreasesSize.bin");
}
TEST(InMemoryKvs, WriteAndReadOneKey) {
// Create and erase the fake flash.
Flash flash;
ASSERT_OK(flash.partition.Erase());
// Create and initialize the KVS.
constexpr EntryFormat format{.magic = 0xBAD'C0D3, .checksum = nullptr};
KeyValueStoreBuffer<kMaxEntries, kMaxUsableSectors> kvs(&flash.partition,
format);
ASSERT_OK(kvs.Init());
// Add two entries with different keys and values.
const char* key = "Key1";
DBG("PUT value for key: %s", key);
uint8_t written_value = 0xDA;
ASSERT_OK(kvs.Put(key, written_value));
EXPECT_EQ(kvs.size(), 1u);
DBG("GET value for key: %s", key);
uint8_t actual_value;
ASSERT_OK(kvs.Get(key, &actual_value));
EXPECT_EQ(actual_value, written_value);
EXPECT_EQ(kvs.size(), 1u);
}
TEST(InMemoryKvs, Basic) {
const char* key1 = "Key1";
const char* key2 = "Key2";
// Create and erase the fake flash.
Flash flash;
ASSERT_EQ(Status::OK, flash.partition.Erase());
// Create and initialize the KVS.
constexpr EntryFormat format{.magic = 0xBAD'C0D3, .checksum = nullptr};
KeyValueStoreBuffer<kMaxEntries, kMaxUsableSectors> kvs(&flash.partition,
format);
ASSERT_OK(kvs.Init());
// Add two entries with different keys and values.
uint8_t value1 = 0xDA;
ASSERT_OK(kvs.Put(key1, as_bytes(span(&value1, sizeof(value1)))));
EXPECT_EQ(kvs.size(), 1u);
uint32_t value2 = 0xBAD0301f;
ASSERT_OK(kvs.Put(key2, value2));
EXPECT_EQ(kvs.size(), 2u);
// Verify data
uint32_t test2;
EXPECT_OK(kvs.Get(key2, &test2));
uint8_t test1;
ASSERT_OK(kvs.Get(key1, &test1));
EXPECT_EQ(test1, value1);
EXPECT_EQ(test2, value2);
EXPECT_EQ(kvs.size(), 2u);
}
TEST_F(EmptyInitializedKvs, MaxKeyLength) {
// Add some data
char key[16] = "123456789abcdef"; // key length 15 (without \0)
int value = 1;
ASSERT_EQ(Status::OK, kvs_.Put(key, value));
// Verify data
int test = 0;
ASSERT_EQ(Status::OK, kvs_.Get(key, &test));
EXPECT_EQ(test, value);
// Delete a key
EXPECT_EQ(Status::OK, kvs_.Delete(key));
// Verify it was erased
EXPECT_EQ(kvs_.Get(key, &test), Status::NOT_FOUND);
}
TEST_F(EmptyInitializedKvs, LargeBuffers) {
// Note this assumes that no other keys larger then key0
static_assert(sizeof(keys[0]) >= sizeof(keys[1]) &&
sizeof(keys[0]) >= sizeof(keys[2]));
KvsAttributes kvs_attr(std::strlen(keys[0]), buffer.size());
// Verify the data will fit in this test partition. This checks that all the
// keys chunks will fit and a header for each sector will fit. It requires 1
// empty sector also.
const size_t kMinSize = kvs_attr.MinPutSize() * keys.size();
const size_t kAvailSectorSpace =
test_partition.sector_size_bytes() * (test_partition.sector_count() - 1);
if (kAvailSectorSpace < kMinSize) {
PW_LOG_INFO("KVS too small, skipping test.");
return;
}
// Add and verify
for (unsigned add_idx = 0; add_idx < keys.size(); add_idx++) {
std::memset(buffer.data(), add_idx, buffer.size());
ASSERT_EQ(Status::OK, kvs_.Put(keys[add_idx], buffer));
EXPECT_EQ(kvs_.size(), add_idx + 1);
for (unsigned verify_idx = 0; verify_idx <= add_idx; verify_idx++) {
std::memset(buffer.data(), 0, buffer.size());
ASSERT_EQ(Status::OK, kvs_.Get(keys[verify_idx], buffer).status());
for (unsigned i = 0; i < buffer.size(); i++) {
EXPECT_EQ(static_cast<unsigned>(buffer[i]), verify_idx);
}
}
}
// Erase and verify
for (unsigned erase_idx = 0; erase_idx < keys.size(); erase_idx++) {
ASSERT_EQ(Status::OK, kvs_.Delete(keys[erase_idx]));
EXPECT_EQ(kvs_.size(), keys.size() - erase_idx - 1);
for (unsigned verify_idx = 0; verify_idx < keys.size(); verify_idx++) {
std::memset(buffer.data(), 0, buffer.size());
if (verify_idx <= erase_idx) {
ASSERT_EQ(kvs_.Get(keys[verify_idx], buffer).status(),
Status::NOT_FOUND);
} else {
ASSERT_EQ(Status::OK, kvs_.Get(keys[verify_idx], buffer).status());
for (uint32_t i = 0; i < buffer.size(); i++) {
EXPECT_EQ(buffer[i], static_cast<byte>(verify_idx));
}
}
}
}
}
TEST_F(EmptyInitializedKvs, Enable) {
KvsAttributes kvs_attr(std::strlen(keys[0]), buffer.size());
// Verify the data will fit in this test partition. This checks that all the
// keys chunks will fit and a header for each sector will fit. It requires 1
// empty sector also.
const size_t kMinSize = kvs_attr.MinPutSize() * keys.size();
const size_t kAvailSectorSpace =
test_partition.sector_size_bytes() * (test_partition.sector_count() - 1);
if (kAvailSectorSpace < kMinSize) {
PW_LOG_INFO("KVS too small, skipping test.");
return;
}
// Add some items
for (unsigned add_idx = 0; add_idx < keys.size(); add_idx++) {
std::memset(buffer.data(), add_idx, buffer.size());
ASSERT_EQ(Status::OK, kvs_.Put(keys[add_idx], buffer));
EXPECT_EQ(kvs_.size(), add_idx + 1);
}
// Enable different KVS which should be able to properly setup the same map
// from what is stored in flash.
static KeyValueStoreBuffer<kMaxEntries, kMaxUsableSectors> kvs_local(
&test_partition, format);
ASSERT_EQ(Status::OK, kvs_local.Init());
EXPECT_EQ(kvs_local.size(), keys.size());
// Ensure adding to new KVS works
uint8_t value = 0xDA;
const char* key = "new_key";
ASSERT_EQ(Status::OK, kvs_local.Put(key, value));
uint8_t test;
ASSERT_EQ(Status::OK, kvs_local.Get(key, &test));
EXPECT_EQ(value, test);
EXPECT_EQ(kvs_local.size(), keys.size() + 1);
// Verify previous data
for (unsigned verify_idx = 0; verify_idx < keys.size(); verify_idx++) {
std::memset(buffer.data(), 0, buffer.size());
ASSERT_EQ(Status::OK, kvs_local.Get(keys[verify_idx], buffer).status());
for (uint32_t i = 0; i < buffer.size(); i++) {
EXPECT_EQ(static_cast<unsigned>(buffer[i]), verify_idx);
}
}
}
TEST_F(EmptyInitializedKvs, MultiSector) {
// Calculate number of elements to ensure multiple sectors are required.
uint16_t add_count = (test_partition.sector_size_bytes() / buffer.size()) + 1;
if (kvs_.max_size() < add_count) {
PW_LOG_INFO("Sector size too large, skipping test.");
return; // this chip has very large sectors, test won't work
}
if (test_partition.sector_count() < 3) {
PW_LOG_INFO("Not enough sectors, skipping test.");
return; // need at least 3 sectors for multi-sector test
}
char key[20];
for (unsigned add_idx = 0; add_idx < add_count; add_idx++) {
std::memset(buffer.data(), add_idx, buffer.size());
snprintf(key, sizeof(key), "key_%u", add_idx);
ASSERT_EQ(Status::OK, kvs_.Put(key, buffer));
EXPECT_EQ(kvs_.size(), add_idx + 1);
}
for (unsigned verify_idx = 0; verify_idx < add_count; verify_idx++) {
std::memset(buffer.data(), 0, buffer.size());
snprintf(key, sizeof(key), "key_%u", verify_idx);
ASSERT_EQ(Status::OK, kvs_.Get(key, buffer).status());
for (uint32_t i = 0; i < buffer.size(); i++) {
EXPECT_EQ(static_cast<unsigned>(buffer[i]), verify_idx);
}
}
// Check erase
for (unsigned erase_idx = 0; erase_idx < add_count; erase_idx++) {
snprintf(key, sizeof(key), "key_%u", erase_idx);
ASSERT_EQ(Status::OK, kvs_.Delete(key));
EXPECT_EQ(kvs_.size(), add_count - erase_idx - 1);
}
}
TEST_F(EmptyInitializedKvs, RewriteValue) {
// Write first value
const uint8_t kValue1 = 0xDA;
const uint8_t kValue2 = 0x12;
const char* key = "the_key";
ASSERT_EQ(Status::OK, kvs_.Put(key, as_bytes(span(&kValue1, 1))));
// Verify
uint8_t value;
ASSERT_EQ(Status::OK,
kvs_.Get(key, as_writable_bytes(span(&value, 1))).status());
EXPECT_EQ(kValue1, value);
// Write new value for key
ASSERT_EQ(Status::OK, kvs_.Put(key, as_bytes(span(&kValue2, 1))));
// Verify
ASSERT_EQ(Status::OK,
kvs_.Get(key, as_writable_bytes(span(&value, 1))).status());
EXPECT_EQ(kValue2, value);
// Verify only 1 element exists
EXPECT_EQ(kvs_.size(), 1u);
}
TEST_F(EmptyInitializedKvs, RepeatingValueWithOtherData) {
std::byte set_buf[150];
std::byte get_buf[sizeof(set_buf)];
for (size_t set_index = 0; set_index < sizeof(set_buf); set_index++) {
set_buf[set_index] = static_cast<std::byte>(set_index);
}
StatusWithSize result;
// Test setting the same entry 10 times but varying the amount of data
// that is already in env before each test
for (size_t test_iteration = 0; test_iteration < sizeof(set_buf);
test_iteration++) {
// TOD0: Add KVS erase
// Add a constant unchanging entry so that the updates are not
// the only entries in the env. The size of this initial entry
// we vary between no bytes to sizeof(set_buf).
ASSERT_EQ(Status::OK,
kvs_.Put("const_entry", span(set_buf, test_iteration)));
// The value we read back should be the last value we set
std::memset(get_buf, 0, sizeof(get_buf));
result = kvs_.Get("const_entry", span(get_buf));
ASSERT_EQ(Status::OK, result.status());
ASSERT_EQ(result.size(), test_iteration);
for (size_t j = 0; j < test_iteration; j++) {
EXPECT_EQ(set_buf[j], get_buf[j]);
}
// Update the test entry 5 times
static_assert(sizeof(std::byte) == sizeof(uint8_t));
uint8_t set_entry_buf[]{1, 2, 3, 4, 5, 6, 7, 8};
std::byte* set_entry = reinterpret_cast<std::byte*>(set_entry_buf);
std::byte get_entry_buf[sizeof(set_entry_buf)];
for (size_t i = 0; i < 5; i++) {
set_entry[0] = static_cast<std::byte>(i);
ASSERT_EQ(Status::OK,
kvs_.Put("test_entry", span(set_entry, sizeof(set_entry_buf))));
std::memset(get_entry_buf, 0, sizeof(get_entry_buf));
result = kvs_.Get("test_entry", span(get_entry_buf));
ASSERT_TRUE(result.ok());
ASSERT_EQ(result.size(), sizeof(get_entry_buf));
for (uint32_t j = 0; j < sizeof(set_entry_buf); j++) {
EXPECT_EQ(set_entry[j], get_entry_buf[j]);
}
}
// Check that the const entry is still present and has the right value
std::memset(get_buf, 0, sizeof(get_buf));
result = kvs_.Get("const_entry", span(get_buf));
ASSERT_TRUE(result.ok());
ASSERT_EQ(result.size(), test_iteration);
for (size_t j = 0; j < test_iteration; j++) {
EXPECT_EQ(set_buf[j], get_buf[j]);
}
}
}
TEST_F(EmptyInitializedKvs, OffsetRead) {
const char* key = "the_key";
constexpr size_t kReadSize = 16; // needs to be a multiple of alignment
constexpr size_t kTestBufferSize = kReadSize * 10;
ASSERT_GT(buffer.size(), kTestBufferSize);
ASSERT_LE(kTestBufferSize, 0xFFu);
// Write the entire buffer
for (size_t i = 0; i < kTestBufferSize; i++) {
buffer[i] = byte(i);
}
ASSERT_EQ(Status::OK, kvs_.Put(key, span(buffer.data(), kTestBufferSize)));
EXPECT_EQ(kvs_.size(), 1u);
// Read in small chunks and verify
for (unsigned i = 0; i < kTestBufferSize / kReadSize; i++) {
std::memset(buffer.data(), 0, buffer.size());
StatusWithSize result =
kvs_.Get(key, span(buffer.data(), kReadSize), i * kReadSize);
ASSERT_EQ(kReadSize, result.size());
// Only last iteration is OK since all remaining data was read.
if (i == kTestBufferSize / kReadSize - 1) {
ASSERT_EQ(Status::OK, result.status());
} else { // RESOURCE_EXHAUSTED, since there is still data to read.
ASSERT_EQ(Status::RESOURCE_EXHAUSTED, result.status());
}
for (unsigned j = 0; j < kReadSize; j++) {
ASSERT_EQ(static_cast<unsigned>(buffer[j]), j + i * kReadSize);
}
}
}
TEST_F(EmptyInitializedKvs, MultipleRewrite) {
// Calculate number of elements to ensure multiple sectors are required.
unsigned add_count = (test_partition.sector_size_bytes() / buffer.size()) + 1;
const char* key = "the_key";
constexpr uint8_t kGoodVal = 0x60;
constexpr uint8_t kBadVal = 0xBA;
std::memset(buffer.data(), kBadVal, buffer.size());
for (unsigned add_idx = 0; add_idx < add_count; add_idx++) {
if (add_idx == add_count - 1) { // last value
std::memset(buffer.data(), kGoodVal, buffer.size());
}
ASSERT_EQ(Status::OK, kvs_.Put(key, buffer));
EXPECT_EQ(kvs_.size(), 1u);
}
// Verify
std::memset(buffer.data(), 0, buffer.size());
ASSERT_EQ(Status::OK, kvs_.Get(key, buffer).status());
for (uint32_t i = 0; i < buffer.size(); i++) {
ASSERT_EQ(buffer[i], static_cast<byte>(kGoodVal));
}
}
TEST_F(EmptyInitializedKvs, FillSector) {
ASSERT_EQ(std::strlen(keys[0]), 8U); // Easier for alignment
ASSERT_EQ(std::strlen(keys[2]), 8U); // Easier for alignment
constexpr size_t kTestDataSize = 8;
KvsAttributes kvs_attr(std::strlen(keys[2]), kTestDataSize);
int bytes_remaining = test_partition.sector_size_bytes();
constexpr byte kKey0Pattern = byte{0xBA};
std::memset(
buffer.data(), static_cast<int>(kKey0Pattern), kvs_attr.DataSize());
ASSERT_EQ(Status::OK,
kvs_.Put(keys[0], span(buffer.data(), kvs_attr.DataSize())));
bytes_remaining -= kvs_attr.MinPutSize();
std::memset(buffer.data(), 1, kvs_attr.DataSize());
ASSERT_EQ(Status::OK,
kvs_.Put(keys[2], span(buffer.data(), kvs_attr.DataSize())));
bytes_remaining -= kvs_attr.MinPutSize();
EXPECT_EQ(kvs_.size(), 2u);
ASSERT_EQ(Status::OK, kvs_.Delete(keys[2]));
bytes_remaining -= kvs_attr.EraseSize();
EXPECT_EQ(kvs_.size(), 1u);
// Intentionally adding erase size to trigger sector cleanup
bytes_remaining += kvs_attr.EraseSize();
FillKvs(keys[2], bytes_remaining);
// Verify key[0]
std::memset(buffer.data(), 0, kvs_attr.DataSize());
ASSERT_EQ(
Status::OK,
kvs_.Get(keys[0], span(buffer.data(), kvs_attr.DataSize())).status());
for (uint32_t i = 0; i < kvs_attr.DataSize(); i++) {
EXPECT_EQ(buffer[i], kKey0Pattern);
}
}
TEST_F(EmptyInitializedKvs, Interleaved) {
const uint8_t kValue1 = 0xDA;
const uint8_t kValue2 = 0x12;
uint8_t value;
ASSERT_EQ(Status::OK, kvs_.Put(keys[0], kValue1));
EXPECT_EQ(kvs_.size(), 1u);
ASSERT_EQ(Status::OK, kvs_.Delete(keys[0]));
EXPECT_EQ(kvs_.Get(keys[0], &value), Status::NOT_FOUND);
ASSERT_EQ(Status::OK, kvs_.Put(keys[1], as_bytes(span(&kValue1, 1))));
ASSERT_EQ(Status::OK, kvs_.Put(keys[2], kValue2));
ASSERT_EQ(Status::OK, kvs_.Delete(keys[1]));
EXPECT_EQ(Status::OK, kvs_.Get(keys[2], &value));
EXPECT_EQ(kValue2, value);
EXPECT_EQ(kvs_.size(), 1u);
}
TEST_F(EmptyInitializedKvs, DeleteAndReinitialize) {
// Write value
const uint8_t kValue = 0xDA;
ASSERT_EQ(Status::OK, kvs_.Put(keys[0], kValue));
ASSERT_EQ(Status::OK, kvs_.Delete(keys[0]));
uint8_t value;
ASSERT_EQ(kvs_.Get(keys[0], &value), Status::NOT_FOUND);
// Reset KVS, ensure captured at enable
ASSERT_EQ(Status::OK, kvs_.Init());
ASSERT_EQ(kvs_.Get(keys[0], &value), Status::NOT_FOUND);
}
TEST_F(EmptyInitializedKvs, TemplatedPutAndGet) {
// Store a value with the convenience method.
const uint32_t kValue = 0x12345678;
ASSERT_EQ(Status::OK, kvs_.Put(keys[0], kValue));
// Read it back with the other convenience method.
uint32_t value;
ASSERT_EQ(Status::OK, kvs_.Get(keys[0], &value));
ASSERT_EQ(kValue, value);
// Make sure we cannot get something where size isn't what we expect
const uint8_t kSmallValue = 0xBA;
uint8_t small_value = kSmallValue;
ASSERT_EQ(kvs_.Get(keys[0], &small_value), Status::INVALID_ARGUMENT);
ASSERT_EQ(small_value, kSmallValue);
}
// This test is derived from bug that was discovered. Testing this corner case
// relies on creating a new key-value just under the size that is left over in
// the sector.
TEST_F(EmptyInitializedKvs, FillSector2) {
if (test_partition.sector_count() < 3) {
PW_LOG_INFO("Not enough sectors, skipping test.");
return; // need at least 3 sectors
}
// Start of by filling flash sector to near full
constexpr int kHalfBufferSize = buffer.size() / 2;
const int kSizeToFill = test_partition.sector_size_bytes() - kHalfBufferSize;
constexpr size_t kTestDataSize = 8;
KvsAttributes kvs_attr(std::strlen(keys[2]), kTestDataSize);
FillKvs(keys[2], kSizeToFill);
// Find out how much space is remaining for new key-value and confirm it
// makes sense.
size_t new_keyvalue_size = 0;
size_t alignment = test_partition.alignment_bytes();
// Starts on second sector since it will try to keep first sector free
FlashPartition::Address read_address =
2 * test_partition.sector_size_bytes() - alignment;
for (; read_address > 0; read_address -= alignment) {
bool is_erased = false;
ASSERT_EQ(
Status::OK,
test_partition.IsRegionErased(read_address, alignment, &is_erased));
if (is_erased) {
new_keyvalue_size += alignment;
} else {
break;
}
}
size_t expected_remaining = test_partition.sector_size_bytes() - kSizeToFill;
ASSERT_EQ(new_keyvalue_size, expected_remaining);
const char* kNewKey = "NewKey";
constexpr size_t kValueLessThanChunkHeaderSize = 2;
constexpr auto kTestPattern = byte{0xBA};
new_keyvalue_size -= kValueLessThanChunkHeaderSize;
std::memset(buffer.data(), static_cast<int>(kTestPattern), new_keyvalue_size);
ASSERT_EQ(Status::OK,
kvs_.Put(kNewKey, span(buffer.data(), new_keyvalue_size)));
// In failed corner case, adding new key is deceptively successful. It isn't
// until KVS is disabled and reenabled that issue can be detected.
ASSERT_EQ(Status::OK, kvs_.Init());
// Might as well check that new key-value is what we expect it to be
ASSERT_EQ(Status::OK,
kvs_.Get(kNewKey, span(buffer.data(), new_keyvalue_size)).status());
for (size_t i = 0; i < new_keyvalue_size; i++) {
EXPECT_EQ(buffer[i], kTestPattern);
}
}
TEST_F(EmptyInitializedKvs, ValueSize_Positive) {
constexpr auto kData = AsBytes('h', 'i', '!');
ASSERT_EQ(Status::OK, kvs_.Put("TheKey", kData));
auto result = kvs_.ValueSize("TheKey");
EXPECT_EQ(Status::OK, result.status());
EXPECT_EQ(kData.size(), result.size());
}
TEST_F(EmptyInitializedKvs, ValueSize_Zero) {
ASSERT_EQ(Status::OK, kvs_.Put("TheKey", as_bytes(span("123", 3))));
auto result = kvs_.ValueSize("TheKey");
EXPECT_EQ(Status::OK, result.status());
EXPECT_EQ(3u, result.size());
}
TEST_F(EmptyInitializedKvs, ValueSize_InvalidKey) {
EXPECT_EQ(Status::INVALID_ARGUMENT, kvs_.ValueSize("").status());
}
TEST_F(EmptyInitializedKvs, ValueSize_MissingKey) {
EXPECT_EQ(Status::NOT_FOUND, kvs_.ValueSize("Not in there").status());
}
TEST_F(EmptyInitializedKvs, ValueSize_DeletedKey) {
ASSERT_EQ(Status::OK, kvs_.Put("TheKey", as_bytes(span("123", 3))));
ASSERT_EQ(Status::OK, kvs_.Delete("TheKey"));
EXPECT_EQ(Status::NOT_FOUND, kvs_.ValueSize("TheKey").status());
}
#if USE_MEMORY_BUFFER
class LargeEmptyInitializedKvs : public ::testing::Test {
protected:
LargeEmptyInitializedKvs() : kvs_(&large_test_partition, format) {
ASSERT_EQ(
Status::OK,
large_test_partition.Erase(0, large_test_partition.sector_count()));
ASSERT_EQ(Status::OK, kvs_.Init());
}
KeyValueStoreBuffer<kMaxEntries, kMaxUsableSectors> kvs_;
};
TEST_F(LargeEmptyInitializedKvs, Basic) {
const uint8_t kValue1 = 0xDA;
const uint8_t kValue2 = 0x12;
uint8_t value;
ASSERT_EQ(Status::OK, kvs_.Put(keys[0], kValue1));
EXPECT_EQ(kvs_.size(), 1u);
ASSERT_EQ(Status::OK, kvs_.Delete(keys[0]));
EXPECT_EQ(kvs_.Get(keys[0], &value), Status::NOT_FOUND);
ASSERT_EQ(Status::OK, kvs_.Put(keys[1], kValue1));
ASSERT_EQ(Status::OK, kvs_.Put(keys[2], kValue2));
ASSERT_EQ(Status::OK, kvs_.Delete(keys[1]));
EXPECT_EQ(Status::OK, kvs_.Get(keys[2], &value));
EXPECT_EQ(kValue2, value);
ASSERT_EQ(kvs_.Get(keys[1], &value), Status::NOT_FOUND);
EXPECT_EQ(kvs_.size(), 1u);
}
#endif // USE_MEMORY_BUFFER
TEST_F(EmptyInitializedKvs, CallingEraseTwice_NothingWrittenToFlash) {
const uint8_t kValue = 0xDA;
ASSERT_EQ(Status::OK, kvs_.Put(keys[0], kValue));
ASSERT_EQ(Status::OK, kvs_.Delete(keys[0]));
// Compare before / after checksums to verify that nothing was written.
const uint16_t crc = checksum::CcittCrc16(test_flash.buffer());
EXPECT_EQ(kvs_.Delete(keys[0]), Status::NOT_FOUND);
EXPECT_EQ(crc, checksum::CcittCrc16(test_flash.buffer()));
}
} // namespace pw::kvs