blob: 58b5d8544cf04646345c7493d66d30a4afbd752f [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 "gtest/gtest.h"
#include "pw_kvs/flash_memory.h"
#include "pw_kvs/in_memory_fake_flash.h"
#include "pw_kvs/key_value_store.h"
#include "pw_log/log.h"
namespace pw::kvs {
namespace {
constexpr size_t kTestPartitionSectorSize = 4 * 1024;
constexpr size_t kTestPartitionSectorCount = 6;
constexpr size_t kMaxEntries = 256;
constexpr size_t kMaxUsableSectors = kTestPartitionSectorCount;
typedef KeyValueStoreBuffer<kMaxEntries, kMaxUsableSectors> TestKvs;
// Creates a FakeFlashBuffer that tracks erase count.
template <size_t kSectorSize, size_t kSectorCount, size_t kInjectedErrors = 8>
class FakeFlashBufferWithEraseCount
: public FakeFlashBuffer<kSectorSize, kSectorCount, kInjectedErrors> {
public:
// Creates a flash memory with no data written.
explicit FakeFlashBufferWithEraseCount(
size_t alignment_bytes = InMemoryFakeFlash::kDefaultAlignmentBytes)
: FakeFlashBufferWithEraseCount(std::array<std::byte, 0>{},
alignment_bytes) {}
// Creates a flash memory initialized to the provided contents.
explicit FakeFlashBufferWithEraseCount(
span<const std::byte> contents,
size_t alignment_bytes = InMemoryFakeFlash::kDefaultAlignmentBytes)
: FakeFlashBuffer<kSectorSize, kSectorCount, kInjectedErrors>(
contents, alignment_bytes) {
std::memset(erase_counts_.data(), 0, erase_counts_.size() * sizeof(size_t));
}
// Override the in-memory flash fake to clear the erase counts when the entire
// flash partition is erased.
Status Clear() {
std::memset(erase_counts_.data(), 0, erase_counts_.size() * sizeof(size_t));
return Erase(FlashMemory::start_address(), FlashMemory::sector_count());
}
// Override the in-memory flash fake to track sector erase count.
Status Erase(FlashMemory::Address address, size_t num_sectors) override {
Status status;
if (status = InMemoryFakeFlash::Erase(address, num_sectors); !status.ok()) {
return status;
}
for (size_t i = 0; i < num_sectors; ++i) {
erase_counts_[address / kSectorSize + i]++;
}
return Status::OK;
}
// Reports the erase count of the sector with the least erases.
size_t MinEraseCount() {
size_t min_erases = ~static_cast<size_t>(0);
for (auto sector_erases : erase_counts_) {
if (sector_erases < min_erases) {
min_erases = sector_erases;
}
}
return min_erases;
}
private:
std::array<size_t, kSectorCount> erase_counts_;
};
FakeFlashBufferWithEraseCount<kTestPartitionSectorSize,
kTestPartitionSectorCount>
test_flash(16);
FlashPartition test_partition(&test_flash, 0, test_flash.sector_count());
constexpr EntryFormat format{.magic = 0xBAD'C0D3, .checksum = nullptr};
} // namespace
// Write a large key (i.e. only one entry fits in each sector) enough times to
// fill up the KVS multiple times, and ensure every sector was garbage collected
// multiple additional times.
TEST(WearLeveling, RepeatedLargeEntry) {
// Initialize an empty KVS, erasing flash and all tracked sector erase counts.
test_flash.Clear();
EXPECT_EQ(test_flash.MinEraseCount(), 1u);
TestKvs new_kvs(&test_partition, format);
new_kvs.Init();
// Add enough large entries to fill the entire KVS several times.
std::byte data[kTestPartitionSectorSize / 2] = {std::byte(0)};
for (size_t i = 0; i < kMaxUsableSectors * 10; ++i) {
EXPECT_TRUE(new_kvs.Put("large_entry", span(data)).ok());
}
// Ensure every sector has been erased at several times due to garbage
// collection.
EXPECT_GE(test_flash.MinEraseCount(), 7u);
}
} // namespace pw::kvs