blob: b839d2ee6bcfab63c7a4233106381bc21d1a7444 [file] [log] [blame]
// 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.
#include "pw_kvs/key_value_store.h"
#include <array>
#include <cstdio>
#if defined(__linux__)
#include <vector>
#endif // defined(__linux__)
#include <cstring>
#include <type_traits>
#include "pw_span/span.h"
#define PW_LOG_USE_ULTRA_SHORT_NAMES 1
#include "pw_log/log.h"
#define USE_MEMORY_BUFFER 1
#include "gtest/gtest.h"
#include "pw_checksum/ccitt_crc16.h"
#include "pw_kvs/flash_memory.h"
#include "pw_kvs_private/format.h"
#include "pw_kvs_private/macros.h"
#include "pw_log/log.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 std::byte;
template <typename... Args>
constexpr auto ByteArray(Args... args) {
return std::array<byte, sizeof...(args)>{static_cast<byte>(args)...};
}
// 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<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()) {}
InMemoryFakeFlash<sector_size_bytes, sector_count> memory;
FlashPartition partition;
public:
#if defined(__linux__)
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* filename) {
(void)(filename);
return Status::OK;
}
#endif // defined(__linux__)
};
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.
InMemoryFakeFlash<4 * 1024, 4> test_flash(
16); // 4 x 4k sectors, 16 byte alignment
FlashPartition test_partition(&test_flash, 0, test_flash.sector_count());
InMemoryFakeFlash<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
// TODO: Need a checksum implementation (e.g. CRC16) to use for tests.
constexpr EntryHeaderFormat format{.magic = 0xBAD'C0D3, .checksum = nullptr};
KeyValueStore kvs(&test_partition, format);
// Use test fixture for logging support
class KeyValueStoreTest : public ::testing::Test {
protected:
KeyValueStoreTest() : kvs_local_(&test_partition, format) {}
KeyValueStore kvs_local_;
};
std::array<byte, 512> buffer;
constexpr std::array<const char*, 3> keys{"TestKey1", "Key2", "TestKey3"};
Status PaddedWrite(FlashPartition* partition,
FlashPartition::Address address,
const std::byte* buf,
size_t size) {
static constexpr size_t kMaxAlignmentBytes = 128;
byte alignment_buffer[kMaxAlignmentBytes] = {};
size_t aligned_bytes = size - (size % partition->alignment_bytes());
TRY(partition->Write(address, span(buf, aligned_bytes)));
uint16_t remaining_bytes = size - aligned_bytes;
if (remaining_bytes > 0) {
std::memcpy(alignment_buffer, &buf[aligned_bytes], remaining_bytes);
if (Status status = partition->Write(
address + aligned_bytes,
span(alignment_buffer, partition->alignment_bytes()));
!status.ok()) {
return status;
}
}
return Status::OK;
}
size_t RoundUpForAlignment(size_t size) {
// TODO: THIS IS SO PADDEDWRITE APPEARS USED
(void)PaddedWrite;
uint16_t alignment = test_partition.alignment_bytes();
if (size % alignment != 0) {
return size + alignment - size % alignment;
}
return size;
}
// This class gives attributes of KVS that we are testing against
class KvsAttributes {
public:
KvsAttributes(size_t key_size, size_t data_size)
: sector_header_meta_size_(
RoundUpForAlignment(sizeof(EntryHeader))), // TODO: not correct
sector_header_clean_size_(
RoundUpForAlignment(sizeof(EntryHeader))), // TODO: not correct
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_(chunk_header_size_ + key_size_ + data_size_) {}
size_t SectorHeaderSize() {
return sector_header_meta_size_ + sector_header_clean_size_;
}
size_t SectorHeaderMetaSize() { return sector_header_meta_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 sector_header_meta_size_;
const size_t sector_header_clean_size_;
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_;
};
// 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));
}
uint16_t CalcKvsCrc(const char* key, const void* data, size_t data_len) {
uint16_t crc = checksum::CcittCrc16(as_bytes(span(key, std::strlen(key))));
return checksum::CcittCrc16(span(static_cast<const byte*>(data), data_len),
crc);
}
uint16_t CalcTestPartitionCrc() {
byte buf[16]; // Read as 16 byte chunks
EXPECT_EQ(sizeof(buf) % test_partition.alignment_bytes(), 0u);
EXPECT_EQ(test_partition.size_bytes() % sizeof(buf), 0u);
uint16_t crc = checksum::kCcittCrc16DefaultInitialValue;
for (size_t i = 0; i < test_partition.size_bytes(); i += sizeof(buf)) {
test_partition.Read(i, buf);
crc = checksum::CcittCrc16(as_bytes(span(buf)), crc);
}
return crc;
}
} // namespace
TEST_F(KeyValueStoreTest, Iteration_Empty_ByReference) {
kvs.Init();
for (const KeyValueStore::Entry& entry : kvs) {
FAIL(); // The KVS is empty; this shouldn't execute.
static_cast<void>(entry);
}
}
TEST_F(KeyValueStoreTest, Iteration_Empty_ByValue) {
kvs.Init();
for (KeyValueStore::Entry entry : kvs) {
FAIL(); // The KVS is empty; this shouldn't execute.
static_cast<void>(entry);
}
}
TEST_F(KeyValueStoreTest, DISABLED_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
}
// Erase
ASSERT_EQ(Status::OK, test_partition.Erase(0, test_partition.sector_count()));
// Reset KVS
ASSERT_EQ(Status::OK, kvs.Init());
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(KeyValueStoreTest, DISABLED_Basic) {
// Erase
ASSERT_EQ(Status::OK, test_partition.Erase(0, test_partition.sector_count()));
// Reset KVS
ASSERT_EQ(Status::OK, kvs.Init());
// 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);
// Erase 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);
// Erase 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)
#define AS_SIZE(x) static_cast<size_t>(x)
TEST(InMemoryKvs, DISABLED_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 EntryHeaderFormat format{.magic = 0xBAD'C0D3,
.checksum = nullptr};
KeyValueStore kvs(&flash.partition, format);
ASSERT_OK(kvs.Init());
// Write the same entry many times.
const char* key = "abcd";
const size_t num_writes = 1; // TODO: Make this > 1 when things work.
uint32_t written_value;
EXPECT_EQ(kvs.size(), (reload == 0) ? 0 : 1u);
for (uint32_t i = 0; i < num_writes; ++i) {
INF("PUT #%zu for key %s with value %zu", AS_SIZE(i), key, AS_SIZE(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.
INF("GET final value for key: %s", key);
uint32_t actual_value;
EXPECT_OK(kvs.Get(key, &actual_value));
EXPECT_EQ(actual_value, written_value);
kvs.LogDebugInfo();
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 EntryHeaderFormat format{.magic = 0xBAD'C0D3, .checksum = nullptr};
KeyValueStore 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;
INF("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);
}
kvs.LogDebugInfo();
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 EntryHeaderFormat format{.magic = 0xBAD'C0D3, .checksum = nullptr};
KeyValueStore kvs(&flash.partition, format);
ASSERT_OK(kvs.Init());
// Add two entries with different keys and values.
const char* key = "Key1";
INF("PUT value for key: %s", key);
uint8_t written_value = 0xDA;
ASSERT_OK(kvs.Put(key, written_value));
EXPECT_EQ(kvs.size(), 1u);
INF("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 EntryHeaderFormat format{.magic = 0xBAD'C0D3, .checksum = nullptr};
KeyValueStore kvs(&flash.partition, format);
ASSERT_OK(kvs.Init());
// Add two entries with different keys and values.
INF("PUT first value");
uint8_t value1 = 0xDA;
ASSERT_OK(kvs.Put(key1, as_bytes(span(&value1, sizeof(value1)))));
EXPECT_EQ(kvs.size(), 1u);
INF("PUT second value");
uint32_t value2 = 0xBAD0301f;
ASSERT_OK(kvs.Put(key2, value2));
EXPECT_EQ(kvs.size(), 2u);
INF("--------------------------------");
INF("GET second value");
// Verify data
uint32_t test2;
EXPECT_OK(kvs.Get(key2, &test2));
INF("GET first value");
uint8_t test1;
ASSERT_OK(kvs.Get(key1, &test1));
EXPECT_EQ(test1, value1);
EXPECT_EQ(test2, value2);
EXPECT_EQ(kvs.size(), 2u);
}
TEST_F(KeyValueStoreTest, DISABLED_MaxKeyLength) {
// Erase
ASSERT_EQ(Status::OK, test_partition.Erase(0, test_partition.sector_count()));
// Reset KVS
ASSERT_EQ(Status::OK, kvs.Init());
// 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);
// Erase a key
kvs.Delete(key);
// Verify it was erased
EXPECT_EQ(kvs.Get(key, &test), Status::NOT_FOUND);
}
TEST_F(KeyValueStoreTest, DISABLED_LargeBuffers) {
test_partition.Erase(0, test_partition.sector_count());
// 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 kAllChunkSize = kvs_attr.MinPutSize() * keys.size();
const size_t kAllSectorHeaderSizes =
kvs_attr.SectorHeaderSize() * (test_partition.sector_count() - 1);
const size_t kMinSize = kAllChunkSize + kAllSectorHeaderSizes;
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;
}
// Reset KVS
ASSERT_EQ(Status::OK, kvs.Init());
// 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(KeyValueStoreTest, DISABLED_Enable) {
test_partition.Erase(0, test_partition.sector_count());
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 kAllChunkSize = kvs_attr.MinPutSize() * keys.size();
const size_t kAllSectorHeaderSizes =
kvs_attr.SectorHeaderSize() * (test_partition.sector_count() - 1);
const size_t kMinSize = kAllChunkSize + kAllSectorHeaderSizes;
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;
}
// Reset KVS
ASSERT_EQ(Status::OK, kvs.Init());
// 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.
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(KeyValueStoreTest, DISABLED_MultiSector) {
test_partition.Erase(0, test_partition.sector_count());
// Reset KVS
ASSERT_EQ(Status::OK, kvs.Init());
// 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(KeyValueStoreTest, DISABLED_RewriteValue) {
test_partition.Erase(0, test_partition.sector_count());
// Reset KVS
ASSERT_EQ(Status::OK, kvs.Init());
// 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);
}
#if 0 // Offset reads are not yet supported
TEST_F(KeyValueStoreTest, OffsetRead) {
test_partition.Erase(0, test_partition.sector_count());
// Reset KVS
ASSERT_EQ(Status::OK, kvs.Init());
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, 0xFF);
// 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());
ASSERT_EQ(
Status::OK,
kvs.Get(key, span(buffer.data(), kReadSize), i * kReadSize).status());
for (unsigned j = 0; j < kReadSize; j++) {
ASSERT_EQ(static_cast<unsigned>(buffer[j]), j + i * kReadSize);
}
}
}
#endif
TEST_F(KeyValueStoreTest, DISABLED_MultipleRewrite) {
// Write many large buffers to force moving to new sector.
test_partition.Erase(0, test_partition.sector_count());
// Reset KVS
ASSERT_EQ(Status::OK, kvs.Init());
// 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(KeyValueStoreTest, DISABLED_FillSector) {
// Write key[0], Write/erase Key[2] multiple times to fill sector check
// everything makes sense after.
test_partition.Erase(0, test_partition.sector_count());
// Reset KVS
ASSERT_EQ(Status::OK, kvs.Init());
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() - kvs_attr.SectorHeaderSize();
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(KeyValueStoreTest, DISABLED_Interleaved) {
test_partition.Erase(0, test_partition.sector_count());
// Reset KVS
ASSERT_EQ(Status::OK, kvs.Init());
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(KeyValueStoreTest, DISABLED_BadCrc) {
static constexpr uint32_t kTestPattern = 0xBAD0301f;
// clang-format off
// There is a top and bottom because for each because we don't want to write
// the erase 0xFF, especially on encrypted flash.
static constexpr auto kKvsTestDataAligned1Top = ByteArray(
0xCD, 0xAB, 0x03, 0x00, 0x01, 0x00, 0xFF, 0xFF // Sector Header
);
static constexpr auto kKvsTestDataAligned1Bottom = ByteArray(
0xAA, 0x55, 0xBA, 0xDD, 0x00, 0x00, 0x18, 0x00, // header (BAD CRC)
0x54, 0x65, 0x73, 0x74, 0x4B, 0x65, 0x79, 0x31, // Key (keys[0])
0xDA, // Value
0xAA, 0x55, 0xB5, 0x87, 0x00, 0x00, 0x44, 0x00, // Header (GOOD CRC)
0x4B, 0x65, 0x79, 0x32, // Key (keys[1])
0x1F, 0x30, 0xD0, 0xBA); // Value
static constexpr auto kKvsTestDataAligned2Top = ByteArray(
0xCD, 0xAB, 0x03, 0x00, 0x02, 0x00, 0xFF, 0xFF // Sector Header
);
static constexpr auto kKvsTestDataAligned2Bottom = ByteArray(
0xAA, 0x55, 0xBA, 0xDD, 0x00, 0x00, 0x18, 0x00, // header (BAD CRC)
0x54, 0x65, 0x73, 0x74, 0x4B, 0x65, 0x79, 0x31, // Key (keys[0])
0xDA, 0x00, // Value + padding
0xAA, 0x55, 0xB5, 0x87, 0x00, 0x00, 0x44, 0x00, // Header (GOOD CRC)
0x4B, 0x65, 0x79, 0x32, // Key (keys[1])
0x1F, 0x30, 0xD0, 0xBA // Value
);
static constexpr auto kKvsTestDataAligned8Top = ByteArray(
0xCD, 0xAB, 0x03, 0x00, 0x08, 0x00, 0xFF, 0xFF // Sector Header
);
static constexpr auto kKvsTestDataAligned8Bottom = ByteArray(
0xAA, 0x55, 0xBA, 0xDD, 0x00, 0x00, 0x18, 0x00, // header (BAD CRC)
0x54, 0x65, 0x73, 0x74, 0x4B, 0x65, 0x79, 0x31, // Key (keys[0])
0xDA, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // Value + padding
0xAA, 0x55, 0xB5, 0x87, 0x00, 0x00, 0x44, 0x00, // header (GOOD CRC)
0x4B, 0x65, 0x79, 0x32, 0x00, 0x00, 0x00, 0x00, // Key (keys[1])
0x1F, 0x30, 0xD0, 0xBA, 0x00, 0x00, 0x00, 0x00 // Value + padding
);
static constexpr auto kKvsTestDataAligned16Top = ByteArray(
0xCD, 0xAB, 0x03, 0x00, 0x10, 0x00, 0xFF, 0xFF, // Sector Header
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 // Alignment to 16
);
static constexpr auto kKvsTestDataAligned16Bottom = ByteArray(
0xAA, 0x55, 0xBA, 0xDD, 0x00, 0x00, 0x18, 0x00, // header (BAD CRC)
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // Alignment to 16
0x54, 0x65, 0x73, 0x74, 0x4B, 0x65, 0x79, 0x31, // Key (keys[0])
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // Alignment to 16
0xDA, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // Value + padding
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // Alignment to 16
0xAA, 0x55, 0xB5, 0x87, 0x00, 0x00, 0x44, 0x00, // header (GOOD CRC)
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // Alignment to 16
0x4B, 0x65, 0x79, 0x32, 0x00, 0x00, 0x00, 0x00, // Key (keys[1])
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // Alignment to 16
0x1F, 0x30, 0xD0, 0xBA, 0x00, 0x00, 0x00, 0x00, // Value + padding
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 // Alignment to 16
);
// clang-format on
ASSERT_EQ(Status::OK, test_partition.Erase(0, test_partition.sector_count()));
// We don't actually care about the size values provided, since we are only
// using kvs_attr to get Sector Size
KvsAttributes kvs_attr(8, 8);
if (test_partition.alignment_bytes() == 1) {
ASSERT_EQ(Status::OK,
test_partition.Write(0, kKvsTestDataAligned1Top).status());
ASSERT_EQ(
Status::OK,
test_partition
.Write(kvs_attr.SectorHeaderSize(), kKvsTestDataAligned1Bottom)
.status());
} else if (test_partition.alignment_bytes() == 2) {
ASSERT_EQ(Status::OK,
test_partition.Write(0, kKvsTestDataAligned2Top).status());
ASSERT_EQ(
Status::OK,
test_partition
.Write(kvs_attr.SectorHeaderSize(), kKvsTestDataAligned2Bottom)
.status());
} else if (test_partition.alignment_bytes() == 8) {
ASSERT_EQ(Status::OK,
test_partition.Write(0, kKvsTestDataAligned8Top).status());
ASSERT_EQ(
Status::OK,
test_partition
.Write(kvs_attr.SectorHeaderSize(), kKvsTestDataAligned8Bottom)
.status());
} else if (test_partition.alignment_bytes() == 16) {
ASSERT_EQ(Status::OK,
test_partition.Write(0, kKvsTestDataAligned16Top).status());
ASSERT_EQ(
Status::OK,
test_partition
.Write(kvs_attr.SectorHeaderSize(), kKvsTestDataAligned16Bottom)
.status());
} else {
PW_LOG_ERROR("Test only supports 1, 2, 8 and 16 byte alignments.");
ASSERT_EQ(Status::OK, false);
}
EXPECT_EQ(kvs_local_.Init(), Status::OK);
EXPECT_TRUE(kvs_local_.initialized());
EXPECT_EQ(Status::DATA_LOSS,
kvs_local_.Get(keys[0], span(buffer.data(), 1)).status());
// Value with correct CRC should still be available.
uint32_t test2 = 0;
ASSERT_EQ(Status::OK, kvs_local_.Get(keys[1], &test2));
EXPECT_EQ(kTestPattern, test2);
// Test rewriting over corrupted data.
ASSERT_EQ(Status::OK, kvs_local_.Put(keys[0], kTestPattern));
test2 = 0;
EXPECT_EQ(Status::OK, kvs_local_.Get(keys[0], &test2));
EXPECT_EQ(kTestPattern, test2);
// Check correct when re-enabled
EXPECT_EQ(kvs_local_.Init(), Status::OK);
test2 = 0;
EXPECT_EQ(Status::OK, kvs_local_.Get(keys[0], &test2));
EXPECT_EQ(kTestPattern, test2);
}
TEST_F(KeyValueStoreTest, DISABLED_TestVersion2) {
static constexpr uint32_t kTestPattern = 0xBAD0301f;
// Since this test is not run on encypted flash, we can write the clean
// pending flag for just this test.
static constexpr uint8_t kKvsTestDataAligned1[] = {
0xCD, 0xAB, 0x02, 0x00, 0x00, 0x00, 0xFF, 0xFF, // Sector Header
0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, // Clean pending flag
0xAA, 0x55, 0xB5, 0x87, 0x00, 0x00, 0x44, 0x00, // Header (GOOD CRC)
0x4B, 0x65, 0x79, 0x32, // Key (keys[1])
0x1F, 0x30, 0xD0, 0xBA}; // Value
if (test_partition.alignment_bytes() == 1) {
// Test only runs on 1 byte alignment partitions
test_partition.Erase(0, test_partition.sector_count());
test_partition.Write(0, as_bytes(span(kKvsTestDataAligned1)));
EXPECT_EQ(Status::OK, kvs_local_.Init());
uint32_t test2 = 0;
ASSERT_EQ(
Status::OK,
kvs_local_.Get(keys[1], as_writable_bytes(span(&test2, 1))).status());
EXPECT_EQ(kTestPattern, test2);
}
}
TEST_F(KeyValueStoreTest, DISABLED_ReEnable) {
test_partition.Erase(0, test_partition.sector_count());
// Reset KVS
ASSERT_EQ(Status::OK, kvs.Init());
EXPECT_EQ(Status::OK, kvs_local_.Init());
// Write value
const uint8_t kValue = 0xDA;
ASSERT_EQ(Status::OK, kvs_local_.Put(keys[0], kValue));
uint8_t value;
ASSERT_EQ(Status::OK, kvs_local_.Get(keys[0], &value));
// Verify
EXPECT_EQ(kValue, value);
}
TEST_F(KeyValueStoreTest, DISABLED_Erase) {
test_partition.Erase(0, test_partition.sector_count());
// Reset KVS
ASSERT_EQ(Status::OK, kvs.Init());
// 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(KeyValueStoreTest, DISABLED_TemplatedPutAndGet) {
test_partition.Erase(0, test_partition.sector_count());
// Reset KVS
ASSERT_EQ(Status::OK, kvs.Init());
// 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);
}
TEST_F(KeyValueStoreTest, DISABLED_SameValueRewrite) {
static constexpr uint32_t kTestPattern = 0xBAD0301f;
// clang-format off
static constexpr auto kKvsTestDataAligned1Top = ByteArray(
0xCD, 0xAB, 0x02, 0x00, 0x00, 0x00, 0xFF, 0xFF // Sector Header
);
static constexpr auto kKvsTestDataAligned1Bottom = ByteArray(
0xAA, 0x55, 0xB5, 0x87, 0x00, 0x00, 0x44, 0x00, // Header (GOOD CRC)
0x4B, 0x65, 0x79, 0x32, // Key (keys[1])
0x1F, 0x30, 0xD0, 0xBA // Value
);
static constexpr auto kKvsTestDataAligned2Top = ByteArray(
0xCD, 0xAB, 0x03, 0x00, 0x02, 0x00, 0xFF, 0xFF // Sector Header
);
static constexpr auto kKvsTestDataAligned2Bottom = ByteArray(
0xAA, 0x55, 0xB5, 0x87, 0x00, 0x00, 0x44, 0x00, // Header (GOOD CRC)
0x4B, 0x65, 0x79, 0x32, // Key (keys[1])
0x1F, 0x30, 0xD0, 0xBA // Value
);
static constexpr auto kKvsTestDataAligned8Top = ByteArray(
0xCD, 0xAB, 0x03, 0x00, 0x08, 0x00, 0xFF, 0xFF // Sector Header
);
static constexpr auto kKvsTestDataAligned8Bottom = ByteArray(
0xAA, 0x55, 0xB5, 0x87, 0x00, 0x00, 0x44, 0x00, // header (GOOD CRC)
0x4B, 0x65, 0x79, 0x32, 0x00, 0x00, 0x00, 0x00, // Key (keys[1])
0x1F, 0x30, 0xD0, 0xBA, 0x00, 0x00, 0x00, 0x00 // Value + padding
);
static constexpr auto kKvsTestDataAligned16Top = ByteArray(
0xCD, 0xAB, 0x03, 0x00, 0x10, 0x00, 0xFF, 0xFF, // Sector Header
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 // Alignment to 16
);
static constexpr auto kKvsTestDataAligned16Bottom = ByteArray(
0xAA, 0x55, 0xB5, 0x87, 0x00, 0x00, 0x44, 0x00, // header (GOOD CRC)
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // Alignment to 16
0x4B, 0x65, 0x79, 0x32, 0x00, 0x00, 0x00, 0x00, // Key (keys[1])
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // Alignment to 16
0x1F, 0x30, 0xD0, 0xBA, 0x00, 0x00, 0x00, 0x00, // Value + padding
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 // Alignment to 16
);
// clang-format on
ASSERT_EQ(Status::OK, test_partition.Erase(0, test_partition.sector_count()));
// We don't actually care about the size values provided, since we are only
// using kvs_attr to get Sector Size
KvsAttributes kvs_attr(8, 8);
FlashPartition::Address address = kvs_attr.SectorHeaderSize();
if (test_partition.alignment_bytes() == 1) {
ASSERT_EQ(Status::OK,
test_partition.Write(0, kKvsTestDataAligned1Top).status());
ASSERT_EQ(
Status::OK,
test_partition.Write(address, kKvsTestDataAligned1Bottom).status());
address += sizeof(kKvsTestDataAligned1Bottom);
} else if (test_partition.alignment_bytes() == 2) {
ASSERT_EQ(Status::OK,
test_partition.Write(0, kKvsTestDataAligned2Top).status());
ASSERT_EQ(
Status::OK,
test_partition.Write(address, kKvsTestDataAligned2Bottom).status());
address += sizeof(kKvsTestDataAligned2Bottom);
} else if (test_partition.alignment_bytes() == 8) {
ASSERT_EQ(Status::OK,
test_partition.Write(0, kKvsTestDataAligned8Top).status());
ASSERT_EQ(
Status::OK,
test_partition.Write(address, kKvsTestDataAligned8Bottom).status());
address += sizeof(kKvsTestDataAligned8Bottom);
} else if (test_partition.alignment_bytes() == 16) {
ASSERT_EQ(Status::OK,
test_partition.Write(0, kKvsTestDataAligned16Top).status());
ASSERT_EQ(
Status::OK,
test_partition.Write(address, kKvsTestDataAligned16Bottom).status());
address += sizeof(kKvsTestDataAligned16Bottom);
} else {
PW_LOG_ERROR("Test only supports 1, 2, 8 and 16 byte alignments.");
ASSERT_EQ(true, false);
}
ASSERT_EQ(Status::OK, kvs_local_.Init());
EXPECT_TRUE(kvs_local_.initialized());
// Put in same key/value pair
ASSERT_EQ(Status::OK, kvs_local_.Put(keys[1], kTestPattern));
bool is_erased = false;
ASSERT_EQ(Status::OK,
test_partition.IsRegionErased(
address, test_partition.alignment_bytes(), &is_erased));
EXPECT_EQ(is_erased, true);
}
// 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(KeyValueStoreTest, DISABLED_FillSector2) {
if (test_partition.sector_count() < 3) {
PW_LOG_INFO("Not enough sectors, skipping test.");
return; // need at least 3 sectors
}
// Reset KVS
test_partition.Erase(0, test_partition.sector_count());
ASSERT_EQ(Status::OK, kvs.Init());
// 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() -
kvs_attr.SectorHeaderSize() - 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(KeyValueStoreTest, DISABLED_GetValueSizeTests) {
constexpr uint16_t kSizeOfValueToFill = 20U;
constexpr uint8_t kKey0Pattern = 0xBA;
// Start off with disabled KVS
// kvs.Disable();
// Try getting value when KVS is disabled, expect failure
EXPECT_EQ(kvs.ValueSize(keys[0]).status(), Status::FAILED_PRECONDITION);
// Reset KVS
test_partition.Erase(0, test_partition.sector_count());
ASSERT_EQ(Status::OK, kvs.Init());
// Try some case that are expected to fail
ASSERT_EQ(kvs.ValueSize(keys[0]).status(), Status::NOT_FOUND);
ASSERT_EQ(kvs.ValueSize("").status(), Status::INVALID_ARGUMENT);
// Add key[0] and test we get the right value size for it.
std::memset(buffer.data(), kKey0Pattern, kSizeOfValueToFill);
ASSERT_EQ(Status::OK,
kvs.Put(keys[0], span(buffer.data(), kSizeOfValueToFill)));
ASSERT_EQ(kSizeOfValueToFill, kvs.ValueSize(keys[0]).size());
// Verify after erase key is not found
ASSERT_EQ(Status::OK, kvs.Delete(keys[0]));
ASSERT_EQ(kvs.ValueSize(keys[0]).status(), Status::NOT_FOUND);
}
#if 0 // TODO: not CanFitEntry function yet
TEST_F(KeyValueStoreTest, DISABLED_CanFitEntryTests) {
// Reset KVS
test_partition.Erase(0, test_partition.sector_count());
ASSERT_EQ(Status::OK, kvs.Init());
// Get exactly the number of bytes that can fit in the space remaining for
// a large value, accounting for alignment.
constexpr uint16_t kTestKeySize = 2;
size_t space_remaining =
test_partition.sector_size_bytes() //
- RoundUpForAlignment(sizeof(EntryHeader)) // TODO: Sector Header
- RoundUpForAlignment(sizeof(EntryHeader)) // Cleaning Header
- RoundUpForAlignment(sizeof(EntryHeader)) // TODO: Chunk Header
- RoundUpForAlignment(kTestKeySize);
space_remaining -= test_partition.alignment_bytes() / 2;
space_remaining = RoundUpForAlignment(space_remaining);
EXPECT_TRUE(kvs.CanFitEntry(kTestKeySize, space_remaining));
EXPECT_FALSE(kvs.CanFitEntry(kTestKeySize, space_remaining + 1));
}
#endif
TEST_F(KeyValueStoreTest, DISABLED_DifferentValueSameCrc16) {
test_partition.Erase(0, test_partition.sector_count());
const char kKey[] = "k";
// With the key and our CRC16 algorithm these both have CRC of 0x82AE
// Given they are the same size and same key, the KVS will need to check
// the actual bits to know they are different.
const char kValue1[] = {'d', 'a', 't'};
const char kValue2[] = {'u', 'c', 'd'};
// Verify the CRC matches
ASSERT_EQ(CalcKvsCrc(kKey, kValue1, sizeof(kValue1)),
CalcKvsCrc(kKey, kValue2, sizeof(kValue2)));
// Reset KVS
ASSERT_EQ(Status::OK, kvs.Init());
ASSERT_EQ(Status::OK, kvs.Put(kKey, kValue1));
// Now try to rewrite with the similar value.
ASSERT_EQ(Status::OK, kvs.Put(kKey, kValue2));
// Read it back and check it is correct
char value[3];
ASSERT_EQ(Status::OK, kvs.Get(kKey, &value));
ASSERT_EQ(std::memcmp(value, kValue2, sizeof(value)), 0);
}
TEST_F(KeyValueStoreTest, DISABLED_CallingEraseTwice) {
test_partition.Erase(0, test_partition.sector_count());
// Reset KVS
ASSERT_EQ(Status::OK, kvs.Init());
const uint8_t kValue = 0xDA;
ASSERT_EQ(Status::OK, kvs.Put(keys[0], kValue));
ASSERT_EQ(Status::OK, kvs.Delete(keys[0]));
uint16_t crc = CalcTestPartitionCrc();
EXPECT_EQ(kvs.Delete(keys[0]), Status::NOT_FOUND);
// Verify the flash has not changed
EXPECT_EQ(crc, CalcTestPartitionCrc());
}
void __attribute__((noinline)) StackHeavyPartialClean() {
#if 0 // TODO: No FlashSubPartition
ASSERT_GE(test_partition.sector_count(), 2);
FlashSubPartition test_partition_sector1(&test_partition, 0, 1);
FlashSubPartition test_partition_sector2(&test_partition, 1, 1);
KeyValueStore kvs1(&test_partition_sector1);
KeyValueStore kvs2(&test_partition_sector2);
test_partition.Erase(0, test_partition.sector_count());
ASSERT_EQ(Status::OK, kvs1.Enable());
ASSERT_EQ(Status::OK, kvs2.Enable());
int values1[3] = {100, 101, 102};
ASSERT_EQ(Status::OK, kvs1.Put(keys[0], values1[0]));
ASSERT_EQ(Status::OK, kvs1.Put(keys[1], values1[1]));
ASSERT_EQ(Status::OK, kvs1.Put(keys[2], values1[2]));
int values2[3] = {200, 201, 202};
ASSERT_EQ(Status::OK, kvs2.Put(keys[0], values2[0]));
ASSERT_EQ(Status::OK, kvs2.Put(keys[1], values2[1]));
ASSERT_EQ(Status::OK, kvs2.Delete(keys[1]));
kvs1.Disable();
kvs2.Disable();
// Key 0 is value1 in first sector, value 2 in second
// Key 1 is value1 in first sector, erased in second
// key 2 is only in first sector
uint64_t mark_clean_count = 5;
ASSERT_EQ(Status::OK,
PaddedWrite(&test_partition_sector1,
RoundUpForAlignment(KeyValueStore::kHeaderSize),
reinterpret_cast<uint8_t*>(&mark_clean_count),
sizeof(uint64_t)));
// Reset KVS
kvs.Disable();
ASSERT_EQ(Status::OK, kvs.Init());
int value;
ASSERT_EQ(Status::OK, kvs.Get(keys[0], &value));
ASSERT_EQ(values2[0], value);
ASSERT_EQ(kvs.Get(keys[1], &value), Status::NOT_FOUND);
ASSERT_EQ(Status::OK, kvs.Get(keys[2], &value));
ASSERT_EQ(values1[2], value);
if (test_partition.sector_count() == 2) {
EXPECT_EQ(kvs.PendingCleanCount(), 0u);
// Has forced a clean, mark again for next test
return; // Not enough sectors to test 2 partial cleans.
} else {
EXPECT_EQ(kvs.PendingCleanCount(), 1u);
}
mark_clean_count--;
ASSERT_EQ(Status::OK,
PaddedWrite(&test_partition_sector2,
RoundUpForAlignment(KeyValueStore::kHeaderSize),
reinterpret_cast<uint8_t*>(&mark_clean_count),
sizeof(uint64_t)));
// Reset KVS
kvs.Disable();
ASSERT_EQ(Status::OK, kvs.Init());
EXPECT_EQ(kvs.PendingCleanCount(), 2u);
ASSERT_EQ(Status::OK, kvs.Get(keys[0], &value));
ASSERT_EQ(values1[0], value);
ASSERT_EQ(Status::OK, kvs.Get(keys[1], &value));
ASSERT_EQ(values1[1], value);
ASSERT_EQ(Status::OK, kvs.Get(keys[2], &value));
ASSERT_EQ(values1[2], value);
#endif
}
// TODO: This doesn't do anything, and would be unreliable anyway.
size_t CurrentTaskStackFree() { return -1; }
TEST_F(KeyValueStoreTest, DISABLED_PartialClean) {
if (CurrentTaskStackFree() < sizeof(KeyValueStore) * 2) {
PW_LOG_ERROR("Not enough stack for test, skipping");
return;
}
StackHeavyPartialClean();
}
void __attribute__((noinline)) StackHeavyCleanAll() {
#if 0 // TODO: no FlashSubPartition
ASSERT_GE(test_partition.sector_count(), 2);
FlashSubPartition test_partition_sector1(&test_partition, 0, 1);
KeyValueStore kvs1(&test_partition_sector1);
test_partition.Erase(0, test_partition.sector_count());
ASSERT_EQ(Status::OK, kvs1.Enable());
int values1[3] = {100, 101, 102};
ASSERT_EQ(Status::OK, kvs1.Put(keys[0], values1[0]));
ASSERT_EQ(Status::OK, kvs1.Put(keys[1], values1[1]));
ASSERT_EQ(Status::OK,
kvs1.Put(keys[2], values1[2] - 100)); // force a rewrite
ASSERT_EQ(Status::OK, kvs1.Put(keys[2], values1[2]));
kvs1.Disable();
uint64_t mark_clean_count = 5;
ASSERT_EQ(Status::OK,
PaddedWrite(&test_partition_sector1,
RoundUpForAlignment(KeyValueStore::kHeaderSize),
reinterpret_cast<uint8_t*>(&mark_clean_count),
sizeof(uint64_t)));
// Reset KVS
kvs.Disable();
ASSERT_EQ(Status::OK, kvs.Init());
int value;
EXPECT_EQ(kvs.PendingCleanCount(), 1u);
ASSERT_EQ(Status::OK, kvs.CleanAll());
EXPECT_EQ(kvs.PendingCleanCount(), 0u);
ASSERT_EQ(Status::OK, kvs.Get(keys[0], &value));
ASSERT_EQ(values1[0], value);
ASSERT_EQ(Status::OK, kvs.Get(keys[1], &value));
ASSERT_EQ(values1[1], value);
ASSERT_EQ(Status::OK, kvs.Get(keys[2], &value));
ASSERT_EQ(values1[2], value);
#endif
}
TEST_F(KeyValueStoreTest, DISABLED_CleanAll) {
if (CurrentTaskStackFree() < sizeof(KeyValueStore) * 1) {
PW_LOG_ERROR("Not enough stack for test, skipping");
return;
}
StackHeavyCleanAll();
}
void __attribute__((noinline)) StackHeavyPartialCleanLargeCounts() {
#if 0
ASSERT_GE(test_partition.sector_count(), 2);
FlashSubPartition test_partition_sector1(&test_partition, 0, 1);
FlashSubPartition test_partition_sector2(&test_partition, 1, 1);
KeyValueStore kvs1(&test_partition_sector1);
KeyValueStore kvs2(&test_partition_sector2);
test_partition.Erase(0, test_partition.sector_count());
ASSERT_EQ(Status::OK, kvs1.Enable());
ASSERT_EQ(Status::OK, kvs2.Enable());
int values1[3] = {100, 101, 102};
ASSERT_EQ(Status::OK, kvs1.Put(keys[0], values1[0]));
ASSERT_EQ(Status::OK, kvs1.Put(keys[1], values1[1]));
ASSERT_EQ(Status::OK, kvs1.Put(keys[2], values1[2]));
int values2[3] = {200, 201, 202};
ASSERT_EQ(Status::OK, kvs2.Put(keys[0], values2[0]));
ASSERT_EQ(Status::OK, kvs2.Put(keys[1], values2[1]));
ASSERT_EQ(Status::OK, kvs2.Delete(keys[1]));
kvs1.Disable();
kvs2.Disable();
kvs.Disable();
// Key 0 is value1 in first sector, value 2 in second
// Key 1 is value1 in first sector, erased in second
// key 2 is only in first sector
uint64_t mark_clean_count = 4569877515;
ASSERT_EQ(Status::OK,
PaddedWrite(&test_partition_sector1,
RoundUpForAlignment(KeyValueStore::kHeaderSize),
reinterpret_cast<uint8_t*>(&mark_clean_count),
sizeof(uint64_t)));
// Reset KVS
ASSERT_EQ(Status::OK, kvs.Init());
int value;
ASSERT_EQ(Status::OK, kvs.Get(keys[0], &value));
ASSERT_EQ(values2[0], value);
ASSERT_EQ(kvs.Get(keys[1], &value), Status::NOT_FOUND);
ASSERT_EQ(Status::OK, kvs.Get(keys[2], &value));
ASSERT_EQ(values1[2], value);
if (test_partition.sector_count() == 2) {
EXPECT_EQ(kvs.PendingCleanCount(), 0u);
// Has forced a clean, mark again for next test
// Has forced a clean, mark again for next test
return; // Not enough sectors to test 2 partial cleans.
} else {
EXPECT_EQ(kvs.PendingCleanCount(), 1u);
}
kvs.Disable();
mark_clean_count--;
ASSERT_EQ(Status::OK,
PaddedWrite(&test_partition_sector2,
RoundUpForAlignment(KeyValueStore::kHeaderSize),
reinterpret_cast<uint8_t*>(&mark_clean_count),
sizeof(uint64_t)));
// Reset KVS
ASSERT_EQ(Status::OK, kvs.Init());
EXPECT_EQ(kvs.PendingCleanCount(), 2u);
ASSERT_EQ(Status::OK, kvs.Get(keys[0], &value));
ASSERT_EQ(values1[0], value);
ASSERT_EQ(Status::OK, kvs.Get(keys[1], &value));
ASSERT_EQ(values1[1], value);
ASSERT_EQ(Status::OK, kvs.Get(keys[2], &value));
ASSERT_EQ(values1[2], value);
#endif
}
TEST_F(KeyValueStoreTest, DISABLED_PartialCleanLargeCounts) {
if (CurrentTaskStackFree() < sizeof(KeyValueStore) * 2) {
PW_LOG_ERROR("Not enough stack for test, skipping");
return;
}
StackHeavyPartialCleanLargeCounts();
}
void __attribute__((noinline)) StackHeavyRecoverNoFreeSectors() {
#if 0 // TODO: no FlashSubPartition
ASSERT_GE(test_partition.sector_count(), 2);
FlashSubPartition test_partition_sector1(&test_partition, 0, 1);
FlashSubPartition test_partition_sector2(&test_partition, 1, 1);
FlashSubPartition test_partition_both(&test_partition, 0, 2);
KeyValueStore kvs1(&test_partition_sector1);
KeyValueStore kvs2(&test_partition_sector2);
KeyValueStore kvs_both(&test_partition_both);
test_partition.Erase(0, test_partition.sector_count());
ASSERT_EQ(Status::OK, kvs1.Enable());
ASSERT_EQ(Status::OK, kvs2.Enable());
int values[3] = {100, 101};
ASSERT_EQ(Status::OK, kvs1.Put(keys[0], values[0]));
ASSERT_FALSE(kvs1.HasEmptySector());
ASSERT_EQ(Status::OK, kvs2.Put(keys[1], values[1]));
ASSERT_FALSE(kvs2.HasEmptySector());
kvs1.Disable();
kvs2.Disable();
// Reset KVS
ASSERT_EQ(Status::OK, kvs_both.Enable());
ASSERT_TRUE(kvs_both.HasEmptySector());
int value;
ASSERT_EQ(Status::OK, kvs_both.Get(keys[0], &value));
ASSERT_EQ(values[0], value);
ASSERT_EQ(Status::OK, kvs_both.Get(keys[1], &value));
ASSERT_EQ(values[1], value);
#endif
}
TEST_F(KeyValueStoreTest, RecoverNoFreeSectors) {
if (CurrentTaskStackFree() < sizeof(KeyValueStore) * 3) {
PW_LOG_ERROR("Not enough stack for test, skipping");
return;
}
StackHeavyRecoverNoFreeSectors();
}
void __attribute__((noinline)) StackHeavyCleanOneSector() {
#if 0 // TODO: no FlashSubPartition
ASSERT_GE(test_partition.sector_count(), 2);
FlashSubPartition test_partition_sector1(&test_partition, 0, 1);
FlashSubPartition test_partition_sector2(&test_partition, 1, 1);
KeyValueStore kvs1(&test_partition_sector1);
test_partition.Erase(0, test_partition.sector_count());
ASSERT_EQ(Status::OK, kvs1.Enable());
int values[3] = {100, 101, 102};
ASSERT_EQ(Status::OK, kvs1.Put(keys[0], values[0]));
ASSERT_EQ(Status::OK, kvs1.Put(keys[1], values[1]));
ASSERT_EQ(Status::OK, kvs1.Put(keys[2], values[2]));
kvs1.Disable();
kvs.Disable();
uint64_t mark_clean_count = 1;
ASSERT_EQ(Status::OK,
PaddedWrite(&test_partition_sector1,
RoundUpForAlignment(KeyValueStore::kHeaderSize),
reinterpret_cast<uint8_t*>(&mark_clean_count),
sizeof(uint64_t)));
// Reset KVS
ASSERT_EQ(Status::OK, kvs.Init());
EXPECT_EQ(kvs.PendingCleanCount(), 1u);
bool all_sectors_have_been_cleaned = false;
ASSERT_EQ(Status::OK, kvs.CleanOneSector(&all_sectors_have_been_cleaned));
EXPECT_EQ(all_sectors_have_been_cleaned, true);
EXPECT_EQ(kvs.PendingCleanCount(), 0u);
ASSERT_EQ(Status::OK, kvs.CleanOneSector(&all_sectors_have_been_cleaned));
EXPECT_EQ(all_sectors_have_been_cleaned, true);
int value;
ASSERT_EQ(Status::OK, kvs.Get(keys[0], &value));
ASSERT_EQ(values[0], value);
ASSERT_EQ(Status::OK, kvs.Get(keys[1], &value));
ASSERT_EQ(values[1], value);
ASSERT_EQ(Status::OK, kvs.Get(keys[2], &value));
ASSERT_EQ(values[2], value);
#endif
}
TEST_F(KeyValueStoreTest, DISABLED_CleanOneSector) {
if (CurrentTaskStackFree() < sizeof(KeyValueStore)) {
PW_LOG_ERROR("Not enough stack for test, skipping");
return;
}
StackHeavyCleanOneSector();
}
#if USE_MEMORY_BUFFER
TEST_F(KeyValueStoreTest, DISABLED_LargePartition) {
if (CurrentTaskStackFree() < sizeof(KeyValueStore)) {
PW_LOG_ERROR("Not enough stack for test, skipping");
return;
}
large_test_partition.Erase(0, large_test_partition.sector_count());
KeyValueStore large_kvs(&large_test_partition, format);
// Reset KVS
ASSERT_EQ(Status::OK, large_kvs.Init());
const uint8_t kValue1 = 0xDA;
const uint8_t kValue2 = 0x12;
uint8_t value;
ASSERT_EQ(Status::OK, large_kvs.Put(keys[0], kValue1));
EXPECT_EQ(large_kvs.size(), 1u);
ASSERT_EQ(Status::OK, large_kvs.Delete(keys[0]));
EXPECT_EQ(large_kvs.Get(keys[0], &value), Status::NOT_FOUND);
ASSERT_EQ(Status::OK, large_kvs.Put(keys[1], kValue1));
ASSERT_EQ(Status::OK, large_kvs.Put(keys[2], kValue2));
ASSERT_EQ(Status::OK, large_kvs.Delete(keys[1]));
EXPECT_EQ(Status::OK, large_kvs.Get(keys[2], &value));
EXPECT_EQ(kValue2, value);
ASSERT_EQ(large_kvs.Get(keys[1], &value), Status::NOT_FOUND);
EXPECT_EQ(large_kvs.size(), 1u);
}
#endif // USE_MEMORY_BUFFER
TEST(KeyValueStoreEntryHeader, KeyValueSizes) {
EntryHeader header;
header.set_key_length(9u);
EXPECT_EQ(header.key_length(), 9u);
header.set_value_length(11u);
EXPECT_EQ(header.value_length(), 11u);
header.set_key_length(6u);
header.set_value_length(100u);
EXPECT_EQ(header.key_length(), 6u);
EXPECT_EQ(header.value_length(), 100u);
header.set_value_length(10u);
EXPECT_EQ(header.key_length(), 6u);
EXPECT_EQ(header.value_length(), 10u);
header.set_key_length(3u);
header.set_value_length(4000u);
EXPECT_EQ(header.key_length(), 3u);
EXPECT_EQ(header.value_length(), 4000u);
}
} // namespace pw::kvs