mirror of
https://fuchsia.googlesource.com/third_party/pigweed.googlesource.com/pigweed/pigweed
synced 2024-09-21 06:12:09 +00:00
f3884eb9d4
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
1290 lines
43 KiB
C++
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
|