blob: 74e33edccc1dfdd47e46da0101f10150712a3997 [file] [log] [blame]
// Copyright 2021 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 <array>
#include <cstring>
#include "gtest/gtest.h"
#include "pw_checksum/crc16_ccitt.h"
#include "pw_kvs/crc16_checksum.h"
#include "pw_kvs/flash_memory.h"
#include "pw_kvs/flash_test_partition.h"
#include "pw_kvs/key_value_store.h"
#include "pw_kvs_private/config.h"
#include "pw_log/log.h"
#include "pw_span/span.h"
#include "pw_status/status.h"
#include "pw_string/string_builder.h"
namespace pw::kvs {
namespace {
using std::byte;
constexpr size_t kMaxEntries = 256;
constexpr size_t kMaxUsableSectors = 1024;
constexpr std::array<const char*, 3> keys{"TestKey1", "Key2", "TestKey3"};
ChecksumCrc16 checksum;
// For KVS magic value always use a random 32 bit integer rather than a
// human readable 4 bytes. See pw_kvs/format.h for more information.
constexpr EntryFormat default_format{.magic = 0x749c361e,
.checksum = &checksum};
} // namespace
TEST(KvsFuzz, FuzzTest) {
FlashPartition& test_partition = FlashTestPartition();
ASSERT_EQ(OkStatus(), test_partition.Erase());
KeyValueStoreBuffer<kMaxEntries, kMaxUsableSectors> kvs_(&test_partition,
default_format);
ASSERT_EQ(OkStatus(), kvs_.Init());
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(OkStatus(), kvs_.Put(key1, buf1));
ASSERT_EQ(OkStatus(), kvs_.Put(key2, buf2));
for (size_t j = 0; j < keys.size(); j++) {
ASSERT_EQ(OkStatus(), 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(OkStatus(), kvs_.Put("some_data", j));
}
// Delete and re-add everything except "some_data"
ASSERT_EQ(OkStatus(), kvs_.Delete(key1));
ASSERT_EQ(OkStatus(), kvs_.Put(key1, span(buf1, size1)));
ASSERT_EQ(OkStatus(), kvs_.Delete(key2));
ASSERT_EQ(OkStatus(), kvs_.Put(key2, span(buf2, size2)));
for (size_t j = 0; j < keys.size(); j++) {
ASSERT_EQ(OkStatus(), kvs_.Delete(keys[j]));
ASSERT_EQ(OkStatus(), kvs_.Put(keys[j], j));
}
// Re-enable and verify
ASSERT_EQ(OkStatus(), kvs_.Init());
static byte buf[4 * 1024];
ASSERT_EQ(OkStatus(), kvs_.Get(key1, span(buf, size1)).status());
ASSERT_EQ(std::memcmp(buf, buf1, size1), 0);
ASSERT_EQ(OkStatus(), 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(OkStatus(), kvs_.Get(keys[j], &ret));
ASSERT_EQ(ret, j);
}
}
}
TEST(KvsFuzz, FuzzTestWithGC) {
FlashPartition& test_partition = FlashTestPartition();
ASSERT_EQ(OkStatus(), test_partition.Erase());
KeyValueStoreBuffer<kMaxEntries, kMaxUsableSectors> kvs_(&test_partition,
default_format);
ASSERT_EQ(OkStatus(), kvs_.Init());
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(OkStatus(), kvs_.Put(key1, buf1));
ASSERT_EQ(OkStatus(), kvs_.Put(key2, buf2));
for (size_t j = 0; j < keys.size(); j++) {
ASSERT_EQ(OkStatus(), 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(OkStatus(), kvs_.Put("some_data", j));
}
// Delete and re-add everything except "some_data".
ASSERT_EQ(OkStatus(), kvs_.Delete(key1));
ASSERT_EQ(OkStatus(), kvs_.Put(key1, span(buf1, size1)));
ASSERT_EQ(OkStatus(), kvs_.Delete(key2));
// Throw some heavy maintenance in the middle to trigger some GC before
// moving forward.
EXPECT_EQ(OkStatus(), kvs_.HeavyMaintenance());
// check for expected stats
KeyValueStore::StorageStats stats = kvs_.GetStorageStats();
EXPECT_GT(stats.sector_erase_count, 1u);
EXPECT_EQ(stats.reclaimable_bytes, 0u);
if (!PW_KVS_REMOVE_DELETED_KEYS_IN_HEAVY_MAINTENANCE) {
PW_LOG_INFO(
"PW_KVS_REMOVE_DELETED_KEYS_IN_HEAVY_MAINTENANCE is "
"disabled; skipping remainder of test");
return;
}
// Write out rotating keyvalue, read it, and delete kMaxEntries * 4.
// This tests whether garbage collection is working on write.
for (size_t j = 0; j < kMaxEntries * 4; j++) {
size_t readj;
StringBuffer<6> keyVal;
keyVal << j;
ASSERT_EQ(OkStatus(), kvs_.Put(keyVal.c_str(), j));
ASSERT_EQ(OkStatus(), kvs_.Get(keyVal.c_str(), &readj));
ASSERT_EQ(j, readj);
ASSERT_EQ(OkStatus(), kvs_.Delete(keyVal.c_str()));
ASSERT_EQ(Status::NotFound(), kvs_.Get(keyVal.c_str(), &readj));
}
// The KVS should contain key1, "some_data", and all of keys[].
ASSERT_EQ(kvs_.size(), 2u + keys.size());
ASSERT_EQ(OkStatus(), kvs_.Put(key2, span(buf2, size2)));
for (size_t j = 0; j < keys.size(); j++) {
ASSERT_EQ(OkStatus(), kvs_.Delete(keys[j]));
ASSERT_EQ(OkStatus(), kvs_.Put(keys[j], j));
}
// Do some more heavy maintenance, ensure we have the right number
// of keys.
EXPECT_EQ(OkStatus(), kvs_.HeavyMaintenance());
// The KVS should contain key1, key2, "some_data", and all of keys[].
ASSERT_EQ(kvs_.size(), 3u + keys.size());
// Re-enable and verify (final check on store).
ASSERT_EQ(OkStatus(), kvs_.Init());
static byte buf[4 * 1024];
ASSERT_EQ(OkStatus(), kvs_.Get(key1, span(buf, size1)).status());
ASSERT_EQ(std::memcmp(buf, buf1, size1), 0);
ASSERT_EQ(OkStatus(), 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(OkStatus(), kvs_.Get(keys[j], &ret));
ASSERT_EQ(ret, j);
}
}
}
} // namespace pw::kvs