feat(nvs_flash): Added purging of erased items at namespace level

- Added new option NVS_READWRITE_PURGE for nvs_open. All update and erase operations of
    the handle are wiping out the content erased items on top of just marking them erased.
  - Added new API call nvs_purge_all allowing to wipe out the existing erased items
    of the handle.
This commit is contained in:
radek.tandler
2026-03-23 10:39:38 +01:00
parent e31e4c1429
commit a3e65eb7f8
20 changed files with 757 additions and 129 deletions
@@ -15,6 +15,7 @@
#include <unistd.h> // for close(), read(), write(), lseek()
#include "nvs_constants.h" // for NVS_CONST_PAGE_SIZE
#include "esp_log.h" // for ESP_LOGE
#include "nvs.h" // for ESP_ERR_NVS_NOT_FOUND
#define TAG "NVSPartitionTestHelper"
@@ -227,6 +228,48 @@ esp_err_t NVSPartitionTestHelper::load_from_file(const char *part_name, const ch
return ret;
}
esp_err_t NVSPartitionTestHelper::check_marker(const char *part_name, const uint8_t *marker, size_t marker_len)
{
if (marker == nullptr || marker_len == 0) {
return ESP_ERR_INVALID_ARG;
}
// Find the partition by name
const esp_partition_t *part = esp_partition_find_first(
ESP_PARTITION_TYPE_DATA,
ESP_PARTITION_SUBTYPE_DATA_NVS,
part_name);
if (part == nullptr) {
return ESP_ERR_NOT_FOUND;
}
// Allocate buffer for reading partition data
uint8_t *read_buf = (uint8_t*)malloc(part->size);
if (read_buf == nullptr) {
return ESP_ERR_NO_MEM;
}
// Read the entire partition
esp_err_t ret = esp_partition_read(part, 0, read_buf, part->size);
if (ret != ESP_OK) {
free(read_buf);
return ret;
}
// Search for the marker in the partition data
esp_err_t result = ESP_ERR_NVS_NOT_FOUND;
for (size_t i = 0; i <= part->size - marker_len; i++) {
if (memcmp(read_buf + i, marker, marker_len) == 0) {
result = ESP_OK;
break;
}
}
free(read_buf);
return result;
}
void NVSPartitionTestHelper::clear_stats(void)
{
return esp_partition_clear_stats();
@@ -527,6 +570,54 @@ esp_err_t NVSPartitionTestHelper::load_from_file(const char *part_name, const ch
return ret;
}
esp_err_t NVSPartitionTestHelper::check_marker(const char *part_name, const uint8_t *marker, size_t marker_len)
{
if (marker == nullptr || marker_len == 0) {
return ESP_ERR_INVALID_ARG;
}
// Get the BDL handle for the partition
esp_blockdev_handle_t bdl_handle;
esp_err_t ret = esp_partition_get_blockdev(
ESP_PARTITION_TYPE_DATA,
ESP_PARTITION_SUBTYPE_DATA_NVS,
part_name,
&bdl_handle);
if (ret != ESP_OK) {
return ret;
}
// Allocate buffer for reading partition data
size_t read_size = bdl_handle->geometry.disk_size;
uint8_t *read_buf = (uint8_t*)malloc(read_size);
if (read_buf == nullptr) {
return ESP_ERR_NO_MEM;
}
do {
// Read the entire partition
ret = bdl_handle->ops->read(bdl_handle, read_buf, read_size, 0, read_size);
if (ret != ESP_OK) {
break;
}
// Search for the marker in the partition data
ret = ESP_ERR_NVS_NOT_FOUND;
for (size_t i = 0; i <= bdl_handle->geometry.disk_size - marker_len; i++) {
if (memcmp(read_buf + i, marker, marker_len) == 0) {
ret = ESP_OK;
break;
}
}
} while (false);
free(read_buf);
return ret;
}
void NVSPartitionTestHelper::clear_stats(void)
{
// TODO: Once BDL implementation supports statistics, we will need to re-implement this function
@@ -56,6 +56,11 @@ public:
// Load the partition from a file
static esp_err_t load_from_file(const char *part_name, const char *file_name);
// Check if a specific marker (byte pattern) exists in the partition
// Returns ESP_OK if marker is found, ESP_ERR_NOT_FOUND if marker not found,
// or other error codes for technical failures
static esp_err_t check_marker(const char *part_name, const uint8_t *marker, size_t marker_len);
// Set of functions to access partition statistics
// At the moment these functions are global, it means that they do not distinguish between
// different partitions
@@ -32,6 +32,8 @@ using namespace std;
#define WD_PREFIX "./components/nvs_flash/host_test/nvs_host_test/" // path from ci cwd to the location of host test
#define TEST_DEFAULT_PURGE_AFTER_ERASE true // erase with purge after erase
#if defined(SEGGER_H) && defined(GLOBAL_H)
NVS_GUARD_SYSVIEW_MACRO_EXPANSION_PUSH();
#undef U8
@@ -155,6 +157,7 @@ TEST_CASE("Page when writing and erasing, used/erased counts are updated correct
// The remaining, not deleted entry is the namespace entry.
NVSPartitionTestHelper h(TEST_DEFAULT_PARTITION_NAME);
const bool purgeAfterErase = TEST_DEFAULT_PURGE_AFTER_ERASE;
nvs::Page page;
TEST_ESP_OK(page.load(&h, 0));
@@ -165,7 +168,7 @@ TEST_CASE("Page when writing and erasing, used/erased counts are updated correct
CHECK(page.getUsedEntryCount() == 1);
TEST_ESP_OK(page.writeItem(2, "foo1", foo1));
CHECK(page.getUsedEntryCount() == 2);
TEST_ESP_OK(page.eraseItem<uint32_t>(2, "foo1"));
TEST_ESP_OK(page.eraseItem<uint32_t>(2, "foo1", purgeAfterErase));
CHECK(page.getUsedEntryCount() == 1);
CHECK(page.getErasedEntryCount() == 1);
for (size_t i = 0; i < nvs::Page::ENTRY_COUNT - 2; ++i) {
@@ -178,7 +181,7 @@ TEST_CASE("Page when writing and erasing, used/erased counts are updated correct
for (size_t i = 0; i < nvs::Page::ENTRY_COUNT - 2; ++i) {
char name[16];
snprintf(name, sizeof(name), "i%ld", (long int)i);
TEST_ESP_OK(page.eraseItem(1, nvs::itemTypeOf<size_t>(), name));
TEST_ESP_OK(page.eraseItem(1, nvs::itemTypeOf<size_t>(), name, purgeAfterErase));
}
CHECK(page.getUsedEntryCount() == 1);
CHECK(page.getErasedEntryCount() == nvs::Page::ENTRY_COUNT - 1);
@@ -447,13 +450,14 @@ TEST_CASE("storage doesn't add duplicates within one page", "[nvs]")
// - overwriting the same item sets the erased entry count to 1
NVSPartitionTestHelper h(TEST_DEFAULT_PARTITION_NAME);
const bool purgeAfterErase = TEST_DEFAULT_PURGE_AFTER_ERASE;
nvs::Storage storage(&h);
TEST_ESP_OK(storage.init(0, h.get_sectors()));
int bar = 0;
TEST_ESP_OK(storage.writeItem(1, "bar", ++bar));
TEST_ESP_OK(storage.writeItem(1, "bar", ++bar));
TEST_ESP_OK(storage.writeItem(1, "bar", ++bar, purgeAfterErase));
TEST_ESP_OK(storage.writeItem(1, "bar", ++bar, purgeAfterErase));
nvs::Page page;
@@ -468,6 +472,7 @@ TEST_CASE("can write one item a thousand times", "[nvs]")
// TC verifies that Storage.writeItem can write one item a thousand times.
NVSPartitionTestHelper h(TEST_DEFAULT_PARTITION_NAME);
const bool purgeAfterErase = TEST_DEFAULT_PURGE_AFTER_ERASE;
const size_t NO_WRITES = 1000;
nvs::Storage storage(&h);
@@ -477,7 +482,7 @@ TEST_CASE("can write one item a thousand times", "[nvs]")
NVSPartitionTestHelper::clear_stats(); // Clear statistics before writing
for (size_t i = 0; i < NO_WRITES; ++i) {
TEST_ESP_OK(storage.writeItem(1, "i", static_cast<int>(i)));
TEST_ESP_OK(storage.writeItem(1, "i", static_cast<int>(i), purgeAfterErase));
}
s_perf << "Time to write one item a " << NO_WRITES << " times: " << NVSPartitionTestHelper::get_total_time() << " us (" << NVSPartitionTestHelper::get_erase_ops() << " " << NVSPartitionTestHelper::get_write_ops() << " " << NVSPartitionTestHelper::get_read_ops() << " " << NVSPartitionTestHelper::get_write_bytes() << " " << NVSPartitionTestHelper::get_read_bytes() << ")" << std::endl;
}
@@ -493,17 +498,17 @@ TEST_CASE("storage doesn't add duplicates within multiple pages", "[nvs]")
// - reading the item "bar" from the second page works
NVSPartitionTestHelper h(TEST_DEFAULT_PARTITION_NAME);
const bool purgeAfterErase = TEST_DEFAULT_PURGE_AFTER_ERASE;
nvs::Storage storage(&h);
TEST_ESP_OK(storage.init(0, h.get_sectors()));
int bar = 0;
TEST_ESP_OK(storage.writeItem(1, "bar", ++bar));
TEST_ESP_OK(storage.writeItem(1, "bar", ++bar, purgeAfterErase));
for (size_t i = 0; i < nvs::Page::ENTRY_COUNT; ++i) {
TEST_ESP_OK(storage.writeItem(1, "foo", static_cast<int>(++bar)));
TEST_ESP_OK(storage.writeItem(1, "foo", static_cast<int>(++bar), purgeAfterErase));
}
TEST_ESP_OK(storage.writeItem(1, "bar", ++bar));
TEST_ESP_OK(storage.writeItem(1, "bar", ++bar, purgeAfterErase));
nvs::Page page;
TEST_ESP_OK(page.load(&h, 0)); // first page attempt
CHECK(page.findItem(1, nvs::itemTypeOf<int>(), "bar") == ESP_ERR_NVS_NOT_FOUND);
@@ -517,18 +522,19 @@ TEST_CASE("storage can find items on second page if first is not fully written a
// The first page is occupied by the item "1" and "2" which occupy enough of the first page in order to not fit the third item "3".
NVSPartitionTestHelper h(TEST_DEFAULT_PARTITION_NAME);
const bool purgeAfterErase = TEST_DEFAULT_PURGE_AFTER_ERASE;
nvs::Storage storage(&h);
TEST_ESP_OK(storage.init(0, h.get_sectors()));
uint8_t bigdata[(nvs::Page::CHUNK_MAX_SIZE - nvs::Page::ENTRY_SIZE) / 2] = {0};
// write one big chunk of data
ESP_ERROR_CHECK(storage.writeItem(1, nvs::ItemType::BLOB, "1", bigdata, sizeof(bigdata)));
ESP_ERROR_CHECK(storage.writeItem(1, nvs::ItemType::BLOB, "1", bigdata, sizeof(bigdata), purgeAfterErase));
// write another big chunk of data
ESP_ERROR_CHECK(storage.writeItem(1, nvs::ItemType::BLOB, "2", bigdata, sizeof(bigdata)));
ESP_ERROR_CHECK(storage.writeItem(1, nvs::ItemType::BLOB, "2", bigdata, sizeof(bigdata), purgeAfterErase));
// write third one; it will not fit into the first page
ESP_ERROR_CHECK(storage.writeItem(1, nvs::ItemType::BLOB, "3", bigdata, sizeof(bigdata)));
ESP_ERROR_CHECK(storage.writeItem(1, nvs::ItemType::BLOB, "3", bigdata, sizeof(bigdata), purgeAfterErase));
size_t size;
ESP_ERROR_CHECK(storage.getItemDataSize(1, nvs::ItemType::BLOB, "1", size));
@@ -543,6 +549,7 @@ TEST_CASE("can write and read variable length data lots of times", "[nvs]")
// of variable length data interleaved with fixed length data.
NVSPartitionTestHelper h(TEST_DEFAULT_PARTITION_NAME);
const bool purgeAfterErase = TEST_DEFAULT_PURGE_AFTER_ERASE;
const size_t NO_WRITES = 1000;
nvs::Storage storage(&h);
@@ -556,8 +563,8 @@ TEST_CASE("can write and read variable length data lots of times", "[nvs]")
for (size_t i = 0; i < NO_WRITES; ++i) {
CAPTURE(i);
TEST_ESP_OK(storage.writeItem(1, nvs::ItemType::SZ, "foobaar", str, len + 1));
TEST_ESP_OK(storage.writeItem(1, "foo", static_cast<uint32_t>(i)));
TEST_ESP_OK(storage.writeItem(1, nvs::ItemType::SZ, "foobaar", str, len + 1, purgeAfterErase));
TEST_ESP_OK(storage.writeItem(1, "foo", static_cast<uint32_t>(i), purgeAfterErase));
uint32_t value;
TEST_ESP_OK(storage.readItem(1, "foo", value));
@@ -577,6 +584,7 @@ TEST_CASE("can get length of variable length data", "[nvs]")
// - getting the size of a string and blob works correctly and independently between namespaces
NVSPartitionTestHelper h(TEST_DEFAULT_PARTITION_NAME);
const bool purgeAfterErase = TEST_DEFAULT_PURGE_AFTER_ERASE;
TEST_ESP_OK(NVSPartitionTestHelper::randomize_partition(TEST_DEFAULT_PARTITION_NAME, 200));
@@ -586,12 +594,12 @@ TEST_CASE("can get length of variable length data", "[nvs]")
const char str[] = "foobar1234foobar1234foobar1234foobar1234foobar1234foobar1234foobar1234foobar1234";
size_t len = strlen(str);
TEST_ESP_OK(storage.writeItem(1, nvs::ItemType::SZ, "foobaar", str, len + 1)); // namespace 1
TEST_ESP_OK(storage.writeItem(1, nvs::ItemType::SZ, "foobaar", str, len + 1, purgeAfterErase)); // namespace 1
size_t dataSize;
TEST_ESP_OK(storage.getItemDataSize(1, nvs::ItemType::SZ, "foobaar", dataSize)); // namespace 1
CHECK(dataSize == len + 1);
TEST_ESP_OK(storage.writeItem(2, nvs::ItemType::BLOB, "foobaar", str, len)); // namespace 2
TEST_ESP_OK(storage.writeItem(2, nvs::ItemType::BLOB, "foobaar", str, len, purgeAfterErase)); // namespace 2
TEST_ESP_OK(storage.getItemDataSize(2, nvs::ItemType::BLOB, "foobaar", dataSize)); // namespace 2
CHECK(dataSize == len);
}
@@ -626,6 +634,7 @@ TEST_CASE("storage may become full", "[nvs]")
// - writing an item when the storage is full returns ESP_ERR_NVS_NOT_ENOUGH_SPACE
NVSPartitionTestHelper h(TEST_DEFAULT_PARTITION_NAME);
const bool purgeAfterErase = TEST_DEFAULT_PURGE_AFTER_ERASE;
nvs::Storage storage(&h);
@@ -638,9 +647,9 @@ TEST_CASE("storage may become full", "[nvs]")
for (size_t i = 0; i < max_items; ++i) {
char name[nvs::Item::MAX_KEY_LENGTH + 1];
snprintf(name, sizeof(name), "key%05d", static_cast<int>(i));
TEST_ESP_OK(storage.writeItem(1, name, static_cast<int>(i)));
TEST_ESP_OK(storage.writeItem(1, name, static_cast<int>(i), purgeAfterErase));
}
REQUIRE(storage.writeItem(1, "foo", 10) == ESP_ERR_NVS_NOT_ENOUGH_SPACE);
REQUIRE(storage.writeItem(1, "foo", 10, purgeAfterErase) == ESP_ERR_NVS_NOT_ENOUGH_SPACE);
}
TEST_CASE("can reuse the space previously occupied by the overwritten item", "[nvs]")
@@ -651,6 +660,7 @@ TEST_CASE("can reuse the space previously occupied by the overwritten item", "[n
// marked as erased and thus space occupied by some of them will be reclaimed and page will be erased.
NVSPartitionTestHelper h(TEST_DEFAULT_PARTITION_NAME);
const bool purgeAfterErase = TEST_DEFAULT_PURGE_AFTER_ERASE;
nvs::Storage storage(&h);
TEST_ESP_OK(storage.init(0, h.get_sectors()));
@@ -662,7 +672,7 @@ TEST_CASE("can reuse the space previously occupied by the overwritten item", "[n
max_usable_items += 1;
for (size_t i = 0; i < max_usable_items; ++i) {
TEST_ESP_OK(storage.writeItem(1, "foo", 42U));
TEST_ESP_OK(storage.writeItem(1, "foo", 42U, purgeAfterErase));
}
}
@@ -673,6 +683,7 @@ TEST_CASE("erase operations are distributed among sectors", "[nvs]")
// on the same item, and finally checks that erase counts are distributed among the remaining sectors.
NVSPartitionTestHelper h(TEST_DEFAULT_PARTITION_NAME);
const bool purgeAfterErase = TEST_DEFAULT_PURGE_AFTER_ERASE;
// Test parameters
const size_t static_pages = 2; // Number of NVS pages to fill with stable items
@@ -694,14 +705,14 @@ TEST_CASE("erase operations are distributed among sectors", "[nvs]")
for (size_t i = 0; i < static_pages * nvs::Page::ENTRY_COUNT; ++i) {
char name[nvs::Item::MAX_KEY_LENGTH];
snprintf(name, sizeof(name), "static%d", (int) i);
TEST_ESP_OK(storage.writeItem(1, name, i));
TEST_ESP_OK(storage.writeItem(1, name, i, purgeAfterErase));
}
// Calculate how many write operations we need to perform to ensure that every page will be erased
size_t write_ops = (all_pages - static_pages) * nvs::Page::ENTRY_COUNT * erase_count;
for (size_t i = 0; i < write_ops; ++i) {
TEST_ESP_OK(storage.writeItem(1, "value", i));
TEST_ESP_OK(storage.writeItem(1, "value", i, purgeAfterErase));
}
const size_t max_erase_cnt = write_ops / nvs::Page::ENTRY_COUNT / (all_pages - static_pages);
@@ -735,6 +746,7 @@ TEST_CASE("can erase items", "[nvs]")
// - namespace id is respected in eraseItem and readItem
NVSPartitionTestHelper h(TEST_DEFAULT_PARTITION_NAME);
const bool purgeAfterErase = TEST_DEFAULT_PURGE_AFTER_ERASE;
nvs::Storage storage(&h);
TEST_ESP_OK(storage.init(0, h.get_sectors()));
@@ -743,15 +755,15 @@ TEST_CASE("can erase items", "[nvs]")
for (size_t i = 0; i < nvs::Page::ENTRY_COUNT * 2 - 3; ++i) {
char name[nvs::Item::MAX_KEY_LENGTH + 1];
snprintf(name, sizeof(name), "key%05d", static_cast<int>(i));
TEST_ESP_OK(storage.writeItem(3, name, static_cast<int>(i)));
TEST_ESP_OK(storage.writeItem(3, name, static_cast<int>(i), purgeAfterErase));
}
TEST_ESP_OK(storage.writeItem(1, "foo", 32));
TEST_ESP_OK(storage.writeItem(2, "foo", 64));
TEST_ESP_OK(storage.eraseItem(2, "foo"));
TEST_ESP_OK(storage.writeItem(1, "foo", 32, purgeAfterErase));
TEST_ESP_OK(storage.writeItem(2, "foo", 64, purgeAfterErase));
TEST_ESP_OK(storage.eraseItem(2, "foo", purgeAfterErase));
int val;
TEST_ESP_OK(storage.readItem(1, "foo", val));
CHECK(val == 32);
TEST_ESP_OK(storage.eraseNamespace(3));
TEST_ESP_OK(storage.eraseNamespace(3, purgeAfterErase));
CHECK(storage.readItem(2, "foo", val) == ESP_ERR_NVS_NOT_FOUND);
CHECK(storage.readItem(3, "key00222", val) == ESP_ERR_NVS_NOT_FOUND);
}
@@ -2335,6 +2347,7 @@ TEST_CASE("Check that orphaned blobs are erased during init", "[nvs]")
// Use NVSPartitionTestHelper to provide nvs::Partition instance for the test
NVSPartitionTestHelper h(TEST_DEFAULT_PARTITION_NAME);
const bool purgeAfterErase = true;
// Make sure the partition has enough NVS pages
// Requires 1 namespace entry, 1 + 3 metadata entries for the blob,
@@ -2350,13 +2363,13 @@ TEST_CASE("Check that orphaned blobs are erased during init", "[nvs]")
TEST_ESP_OK(storage.init(0, required_sectors));
TEST_ESP_OK(storage.writeItem(1, nvs::ItemType::BLOB, "key", blob, sizeof(blob)));
TEST_ESP_OK(storage.writeItem(1, nvs::ItemType::BLOB, "key", blob, sizeof(blob), purgeAfterErase));
TEST_ESP_OK(storage.init(0, required_sectors));
// Check that multi-page item is still available.
TEST_ESP_OK(storage.readItem(1, nvs::ItemType::BLOB, "key", blob, sizeof(blob)));
TEST_ESP_ERR(storage.writeItem(1, nvs::ItemType::BLOB, "key2", blob, sizeof(blob)), ESP_ERR_NVS_NOT_ENOUGH_SPACE);
TEST_ESP_ERR(storage.writeItem(1, nvs::ItemType::BLOB, "key2", blob, sizeof(blob), purgeAfterErase), ESP_ERR_NVS_NOT_ENOUGH_SPACE);
nvs::Page p;
TEST_ESP_OK(p.load(&h, 3)); // This is where index will be placed.
@@ -2365,7 +2378,7 @@ TEST_CASE("Check that orphaned blobs are erased during init", "[nvs]")
TEST_ESP_OK(storage.init(0, required_sectors));
TEST_ESP_ERR(storage.readItem(1, nvs::ItemType::BLOB, "key", blob, sizeof(blob)), ESP_ERR_NVS_NOT_FOUND);
TEST_ESP_OK(storage.writeItem(1, nvs::ItemType::BLOB, "key3", blob, sizeof(blob)));
TEST_ESP_OK(storage.writeItem(1, nvs::ItemType::BLOB, "key3", blob, sizeof(blob), purgeAfterErase));
}
TEST_CASE("nvs blob fragmentation test", "[nvs]")
@@ -2434,6 +2447,7 @@ TEST_CASE("nvs code handles errors properly when partition is near to full", "[n
// Initialize NVS partition
NVSPartitionTestHelper h(TEST_DEFAULT_PARTITION_NAME);
const bool purgeAfterErase = TEST_DEFAULT_PURGE_AFTER_ERASE;
// This test requires at least 4+1 NVS pages to run.
CHECK(h.get_sectors() >= 5);
@@ -2449,12 +2463,12 @@ TEST_CASE("nvs code handles errors properly when partition is near to full", "[n
// Four pages should fit roughly 12 blobs
for (uint8_t count = 1; count <= 12; count++) {
snprintf(nvs_key, sizeof(nvs_key), "key:%u", count);
TEST_ESP_OK(storage.writeItem(1, nvs::ItemType::BLOB, nvs_key, blob, sizeof(blob)));
TEST_ESP_OK(storage.writeItem(1, nvs::ItemType::BLOB, nvs_key, blob, sizeof(blob), purgeAfterErase));
}
for (uint8_t count = 13; count <= 20; count++) {
snprintf(nvs_key, sizeof(nvs_key), "key:%u", count);
TEST_ESP_ERR(storage.writeItem(1, nvs::ItemType::BLOB, nvs_key, blob, sizeof(blob)), ESP_ERR_NVS_NOT_ENOUGH_SPACE);
TEST_ESP_ERR(storage.writeItem(1, nvs::ItemType::BLOB, nvs_key, blob, sizeof(blob), purgeAfterErase), ESP_ERR_NVS_NOT_ENOUGH_SPACE);
}
}
@@ -2513,6 +2527,7 @@ TEST_CASE("monkey test with old-format blob present", "[nvs][monkey][xxx]")
// Clear partition stats before testing
NVSPartitionTestHelper::clear_stats();
const bool purgeAfterErase = true;
static const size_t smallBlobLen = nvs::Page::CHUNK_MAX_SIZE / 3;
@@ -2531,9 +2546,9 @@ TEST_CASE("monkey test with old-format blob present", "[nvs][monkey][xxx]")
for (uint8_t num = 0; num < sector_count; num++) {
nvs::Page p;
TEST_ESP_OK(p.load(&h, num));
p.eraseItem(1, nvs::ItemType::BLOB, "singlepage", nvs::Item::CHUNK_ANY, nvs::VerOffset::VER_ANY);
p.eraseItem(1, nvs::ItemType::BLOB_IDX, "singlepage", nvs::Item::CHUNK_ANY, nvs::VerOffset::VER_ANY);
p.eraseItem(1, nvs::ItemType::BLOB_DATA, "singlepage", nvs::Item::CHUNK_ANY, nvs::VerOffset::VER_ANY);
p.eraseItem(1, nvs::ItemType::BLOB, "singlepage", purgeAfterErase, nvs::Item::CHUNK_ANY, nvs::VerOffset::VER_ANY);
p.eraseItem(1, nvs::ItemType::BLOB_IDX, "singlepage", purgeAfterErase, nvs::Item::CHUNK_ANY, nvs::VerOffset::VER_ANY);
p.eraseItem(1, nvs::ItemType::BLOB_DATA, "singlepage", purgeAfterErase, nvs::Item::CHUNK_ANY, nvs::VerOffset::VER_ANY);
}
// Now write "singlepage" blob in old format
@@ -3186,6 +3201,7 @@ TEST_CASE("recovery after failure to write data", "[nvs]")
// PartitionTestHelper is used to provide nvs::Partition instance for the test
NVSPartitionTestHelper h(TEST_DEFAULT_PARTITION_NAME);
const bool purgeAfterErase = TEST_DEFAULT_PURGE_AFTER_ERASE;
const char str[] = "value 0123456789abcdef012345678value 0123456789abcdef012345678";
@@ -3195,10 +3211,10 @@ TEST_CASE("recovery after failure to write data", "[nvs]")
nvs::Storage storage(&h);
TEST_ESP_OK(storage.init(0, 3));
TEST_ESP_ERR(storage.writeItem(1, nvs::ItemType::SZ, "key", str, strlen(str)), ESP_ERR_FLASH_OP_FAIL);
TEST_ESP_ERR(storage.writeItem(1, nvs::ItemType::SZ, "key", str, strlen(str), purgeAfterErase), ESP_ERR_FLASH_OP_FAIL);
// check that repeated operations cause an error
TEST_ESP_ERR(storage.writeItem(1, nvs::ItemType::SZ, "key", str, strlen(str)), ESP_ERR_NVS_INVALID_STATE);
TEST_ESP_ERR(storage.writeItem(1, nvs::ItemType::SZ, "key", str, strlen(str), purgeAfterErase), ESP_ERR_NVS_INVALID_STATE);
uint8_t val;
TEST_ESP_ERR(storage.readItem(1, nvs::ItemType::U8, "key", &val, sizeof(val)), ESP_ERR_NVS_NOT_FOUND);
@@ -3226,13 +3242,14 @@ TEST_CASE("crc errors in item header are handled", "[nvs]")
// PartitionTestHelper is used to provide nvs::Partition instance for the test
NVSPartitionTestHelper h(TEST_DEFAULT_PARTITION_NAME);
const bool purgeAfterErase = TEST_DEFAULT_PURGE_AFTER_ERASE;
nvs::Storage storage(&h);
// prepare some data
TEST_ESP_OK(storage.init(0, 3));
TEST_ESP_OK(storage.writeItem(0, "ns1", static_cast<uint8_t>(1)));
TEST_ESP_OK(storage.writeItem(1, "value1", static_cast<uint32_t>(1)));
TEST_ESP_OK(storage.writeItem(1, "value2", static_cast<uint32_t>(2)));
TEST_ESP_OK(storage.writeItem(0, "ns1", static_cast<uint8_t>(1), purgeAfterErase));
TEST_ESP_OK(storage.writeItem(1, "value1", static_cast<uint32_t>(1), purgeAfterErase));
TEST_ESP_OK(storage.writeItem(1, "value2", static_cast<uint32_t>(2), purgeAfterErase));
// corrupt item header
uint32_t val = 0;
@@ -3249,7 +3266,7 @@ TEST_CASE("crc errors in item header are handled", "[nvs]")
for (size_t i = 0; i < nvs::Page::ENTRY_COUNT; ++i) {
char item_name[nvs::Item::MAX_KEY_LENGTH + 1];
snprintf(item_name, sizeof(item_name), "item_%ld", (long int)i);
TEST_ESP_OK(storage.writeItem(1, item_name, static_cast<uint32_t>(i)));
TEST_ESP_OK(storage.writeItem(1, item_name, static_cast<uint32_t>(i), purgeAfterErase));
}
// corrupt another item on the full page
@@ -3316,13 +3333,14 @@ TEST_CASE("zero span in item header with correct crc is handled", "[nvs]")
// PartitionTestHelper is used to provide nvs::Partition instance for the test
NVSPartitionTestHelper h(TEST_DEFAULT_PARTITION_NAME);
const bool purgeAfterErase = TEST_DEFAULT_PURGE_AFTER_ERASE;
nvs::Storage storage(&h);
// prepare some data
TEST_ESP_OK(storage.init(0, 3));
TEST_ESP_OK(storage.writeItem(0, "ns1", static_cast<uint8_t>(1)));
TEST_ESP_OK(storage.writeItem(1, "value1", static_cast<uint32_t>(1)));
TEST_ESP_OK(storage.writeItem(1, "value2", static_cast<uint32_t>(2)));
TEST_ESP_OK(storage.writeItem(0, "ns1", static_cast<uint8_t>(1), purgeAfterErase));
TEST_ESP_OK(storage.writeItem(1, "value1", static_cast<uint32_t>(1), purgeAfterErase));
TEST_ESP_OK(storage.writeItem(1, "value2", static_cast<uint32_t>(2), purgeAfterErase));
// damage item header of value1 to introduce span==0 error, recalculate crc
{
@@ -3357,7 +3375,7 @@ TEST_CASE("zero span in item header with correct crc is handled", "[nvs]")
for (size_t i = 0; i < nvs::Page::ENTRY_COUNT; ++i) {
char item_name[nvs::Item::MAX_KEY_LENGTH + 1];
snprintf(item_name, sizeof(item_name), "item_%ld", (long int)i);
TEST_ESP_OK(storage.writeItem(1, item_name, static_cast<uint32_t>(i)));
TEST_ESP_OK(storage.writeItem(1, item_name, static_cast<uint32_t>(i), purgeAfterErase));
}
// damage item header of item_125 to introduce span==0 error, recalculate crc
@@ -3397,18 +3415,19 @@ TEST_CASE("inconsistent fields in item header with correct crc are handled for s
// PartitionTestHelper is used to provide nvs::Partition instance for the test
NVSPartitionTestHelper h(TEST_DEFAULT_PARTITION_NAME);
const bool purgeAfterErase = TEST_DEFAULT_PURGE_AFTER_ERASE;
nvs::Storage storage(&h);
// prepare some data
TEST_ESP_OK(storage.init(0, 3));
TEST_ESP_OK(storage.writeItem(0, "ns1", static_cast<uint8_t>(1)));
TEST_ESP_OK(storage.writeItem(0, "ns1", static_cast<uint8_t>(1), purgeAfterErase));
const char str[] = "String67890123456789012345678901String67890123456789012345678901String6789012345678901234567890"; // 95 + 1 bytes data occupy 3 entries, overhead 1
TEST_ESP_OK(storage.writeItem(1, nvs::ItemType::SZ, "valuestr1", str, strlen(str)));
TEST_ESP_OK(storage.writeItem(1, nvs::ItemType::SZ, "valuestr2", str, strlen(str)));
TEST_ESP_OK(storage.writeItem(1, nvs::ItemType::SZ, "valuestr3", str, strlen(str)));
TEST_ESP_OK(storage.writeItem(1, nvs::ItemType::SZ, "valuestr4", str, strlen(str)));
TEST_ESP_OK(storage.writeItem(1, "valueu32", static_cast<uint32_t>(2)));
TEST_ESP_OK(storage.writeItem(1, nvs::ItemType::SZ, "valuestr1", str, strlen(str), purgeAfterErase));
TEST_ESP_OK(storage.writeItem(1, nvs::ItemType::SZ, "valuestr2", str, strlen(str), purgeAfterErase));
TEST_ESP_OK(storage.writeItem(1, nvs::ItemType::SZ, "valuestr3", str, strlen(str), purgeAfterErase));
TEST_ESP_OK(storage.writeItem(1, nvs::ItemType::SZ, "valuestr4", str, strlen(str), purgeAfterErase));
TEST_ESP_OK(storage.writeItem(1, "valueu32", static_cast<uint32_t>(2), purgeAfterErase));
// read the values back
char read_str[sizeof(str)] = {0};
@@ -3789,14 +3808,15 @@ TEST_CASE("invalid data type in item header with correct crc is handled", "[nvs]
// PartitionTestHelper is used to provide nvs::Partition instance for the test
NVSPartitionTestHelper h(TEST_DEFAULT_PARTITION_NAME);
const bool purgeAfterErase = TEST_DEFAULT_PURGE_AFTER_ERASE;
nvs::Storage storage(&h);
// prepare some data
TEST_ESP_OK(storage.init(0, 3));
TEST_ESP_OK(storage.writeItem(0, "ns1", static_cast<uint8_t>(1)));
TEST_ESP_OK(storage.writeItem(1, "value1", static_cast<uint32_t>(1)));
TEST_ESP_OK(storage.writeItem(1, "value2", static_cast<uint32_t>(2)));
TEST_ESP_OK(storage.writeItem(1, "value3", static_cast<uint32_t>(3)));
TEST_ESP_OK(storage.writeItem(0, "ns1", static_cast<uint8_t>(1), purgeAfterErase));
TEST_ESP_OK(storage.writeItem(1, "value1", static_cast<uint32_t>(1), purgeAfterErase));
TEST_ESP_OK(storage.writeItem(1, "value2", static_cast<uint32_t>(2), purgeAfterErase));
TEST_ESP_OK(storage.writeItem(1, "value3", static_cast<uint32_t>(3), purgeAfterErase));
// damage item header of value2 to introduce data type error, recalculate crc
{
@@ -4745,6 +4765,249 @@ TEST_CASE("nvs find key tests", "[nvs]")
nvs_close(handle_2);
TEST_ESP_OK(nvs_flash_deinit_partition(TEST_DEFAULT_PARTITION_NAME));
}
TEST_CASE("nvs handle purge test", "[nvs]")
{
// TC validating that erased and overwritten entries are purged from NVS storage based on the mode of handle opening.
// The test covers both modes: with implicit purge enabled and disabled.
// To validate the behavior, the test:
// Writes entries of different types to NVS storage. Entries include: integer, string and multi page blob.
// Inf first round, the data contains initial markers to identify them.
// Then it erases / overwrites the entries one by one and after each erase it checks whether the markers are still present in the storage
// SECTIONS:
// 1. Open writable namespace without implicit purge. Erase data. Should find markers.
// 2/ Open writable namespace without implicit purge. Overwrite data. Should find old markers.
// 3. Open writable namespace with implicit purge. Erase data. Should NOT find markers.
// 4. Open writable namespace with implicit purge. Overwrite data. Should NOT find old markers.
const char* part_name = TEST_DEFAULT_PARTITION_NAME;
const char* ns_name = "ns1";
// Markers and lengths used in the test
// String mrkers are fixed strings at the beginning of the string data as well as at the end of the string data
const char str_key[] = "str_key";
const char str_marker_start[] = "STR_START_";;
const char str_marker_end[] = "_STR_END";
const size_t string_len = 3000; // 4000 is max, 3000 fills almost entire page
// Blob markers are fixed patterns at the beginning of the blob data as well as at the end of the blob data
const char blob_key[] = "blob_key";
const uint8_t blob_marker_start[] = {0xBA, 0xAD, 0xF0, 0x0D};
const uint8_t blob_marker_end[] = {0xD0, 0x0F, 0xDA, 0xAB};
const size_t blob_len = 8192; // 8192 bytes to ensure multipage blob
// uint64_t marker is used for integer data
const char uint_key[] = "uint_key";
const uint64_t uint_marker = 0xDEADBEEFFEEBDAED;
// Overwrite patterns
const char str_overwrite_pattern = 'X';
const uint8_t blob_overwrite_pattern = 0x5A;
const uint64_t uint_overwrite_pattern = 0xA5A5A5A5A5A5A5A5;
// nvs_flash_erase_partition to start with clean partition
TEST_ESP_OK(nvs_flash_erase_partition(part_name));
TEST_ESP_OK(nvs_flash_init_partition(part_name));
// init non-purge handle
nvs_handle_t handle;
TEST_ESP_OK(nvs_open_from_partition(part_name, ns_name, NVS_READWRITE, &handle));
// populate with markers
// string
char* str_data = (char*) malloc(string_len);
CHECK(str_data != nullptr);
memset(str_data, 'A', string_len);
memcpy(str_data, str_marker_start, strlen(str_marker_start));
memcpy(str_data + string_len - (strlen(str_marker_end) + 1), str_marker_end, strlen(str_marker_end));
str_data[string_len - 1] = '\0'; // ensure null termination
TEST_ESP_OK(nvs_set_str(handle, str_key, str_data));
free(str_data);
// blob
uint8_t* blob_data = (uint8_t*) malloc(blob_len);
CHECK(blob_data != nullptr);
memset(blob_data, 0xFF, blob_len);
memcpy(blob_data, blob_marker_start, sizeof(blob_marker_start));
memcpy(blob_data + blob_len - sizeof(blob_marker_end), blob_marker_end, sizeof(blob_marker_end));
TEST_ESP_OK(nvs_set_blob(handle, blob_key, blob_data, blob_len));
free(blob_data);
// uint64_t
TEST_ESP_OK(nvs_set_u64(handle, uint_key, uint_marker));
TEST_ESP_OK(nvs_commit(handle));
// make sure markers are written
// check string marker
TEST_ESP_OK(NVSPartitionTestHelper::check_marker(part_name, (const uint8_t*)str_marker_start, strlen(str_marker_start)));
TEST_ESP_OK(NVSPartitionTestHelper::check_marker(part_name, (const uint8_t*)str_marker_end, strlen(str_marker_end)));
// check blob marker
TEST_ESP_OK(NVSPartitionTestHelper::check_marker(part_name, blob_marker_start, sizeof(blob_marker_start)));
TEST_ESP_OK(NVSPartitionTestHelper::check_marker(part_name, blob_marker_end, sizeof(blob_marker_end)));
// check uint64_t marker
TEST_ESP_OK(NVSPartitionTestHelper::check_marker(part_name, (const uint8_t*)&uint_marker, sizeof(uint_marker)));
// deinit
nvs_close(handle);
SECTION("non-purge handle - erase entries")
{
// init non-purge handle
TEST_ESP_OK(nvs_open_from_partition(part_name, ns_name, NVS_READWRITE, &handle));
// erase / check markers
TEST_ESP_OK(nvs_erase_key(handle, str_key));
TEST_ESP_OK(nvs_erase_key(handle, blob_key));
TEST_ESP_OK(nvs_erase_key(handle, uint_key));
TEST_ESP_OK(nvs_commit(handle));
// this is non implicit purge handle, so markers should be found
// check string marker
TEST_ESP_OK(NVSPartitionTestHelper::check_marker(part_name, (const uint8_t*)str_marker_start, strlen(str_marker_start)));
TEST_ESP_OK(NVSPartitionTestHelper::check_marker(part_name, (const uint8_t*)str_marker_end, strlen(str_marker_end)));
// check blob marker
TEST_ESP_OK(NVSPartitionTestHelper::check_marker(part_name, blob_marker_start, sizeof(blob_marker_start)));
TEST_ESP_OK(NVSPartitionTestHelper::check_marker(part_name, blob_marker_end, sizeof(blob_marker_end)));
// check uint64_t marker
TEST_ESP_OK(NVSPartitionTestHelper::check_marker(part_name, (const uint8_t*)&uint_marker, sizeof(uint_marker)));
// explicitly call purge
TEST_ESP_OK(nvs_purge_all(handle));
// after purge, markers should NOT be found
// check string marker
TEST_ESP_ERR(NVSPartitionTestHelper::check_marker(part_name, (const uint8_t*)str_marker_start, strlen(str_marker_start)), ESP_ERR_NVS_NOT_FOUND);
TEST_ESP_ERR(NVSPartitionTestHelper::check_marker(part_name, (const uint8_t*)str_marker_end, strlen(str_marker_end)), ESP_ERR_NVS_NOT_FOUND);
// check blob marker
TEST_ESP_ERR(NVSPartitionTestHelper::check_marker(part_name, blob_marker_start, sizeof(blob_marker_start)), ESP_ERR_NVS_NOT_FOUND);
TEST_ESP_ERR(NVSPartitionTestHelper::check_marker(part_name, blob_marker_end, sizeof(blob_marker_end)), ESP_ERR_NVS_NOT_FOUND);
// check uint64_t marker
TEST_ESP_ERR(NVSPartitionTestHelper::check_marker(part_name, (const uint8_t*)&uint_marker, sizeof(uint_marker)), ESP_ERR_NVS_NOT_FOUND);
}
SECTION("non-purge handle - overwrite entries")
{
// init non-purge handle
TEST_ESP_OK(nvs_open_from_partition(part_name, ns_name, NVS_READWRITE, &handle));
// overwrite / check markers
// string
char* str_data_ov = (char*) malloc(string_len);
CHECK(str_data_ov != nullptr);
memset(str_data_ov, str_overwrite_pattern, string_len);
str_data_ov[string_len - 1] = '\0'; // ensure null termination
TEST_ESP_OK(nvs_set_str(handle, str_key, str_data_ov));
// blob
uint8_t* blob_data_ov = (uint8_t*) malloc(blob_len);
CHECK(blob_data_ov != nullptr);
memset(blob_data_ov, blob_overwrite_pattern, blob_len);
TEST_ESP_OK(nvs_set_blob(handle, blob_key, blob_data_ov, blob_len));
// uint64_t
TEST_ESP_OK(nvs_set_u64(handle, uint_key, uint_overwrite_pattern));
TEST_ESP_OK(nvs_commit(handle));
free(str_data_ov);
free(blob_data_ov);
// this is non implicit purge handle, so old markers should be found
// check string marker
TEST_ESP_OK(NVSPartitionTestHelper::check_marker(part_name, (const uint8_t*)str_marker_start, strlen(str_marker_start)));
TEST_ESP_OK(NVSPartitionTestHelper::check_marker(part_name, (const uint8_t*)str_marker_end, strlen(str_marker_end)));
// check blob marker
TEST_ESP_OK(NVSPartitionTestHelper::check_marker(part_name, blob_marker_start, sizeof(blob_marker_start)));
TEST_ESP_OK(NVSPartitionTestHelper::check_marker(part_name, blob_marker_end, sizeof(blob_marker_end)));
// check uint64_t marker
TEST_ESP_OK(NVSPartitionTestHelper::check_marker(part_name, (const uint8_t*)&uint_marker, sizeof(uint_marker)));
// explicitly call purge
TEST_ESP_OK(nvs_purge_all(handle));
// after purge, markers should NOT be found
// check string marker
TEST_ESP_ERR(NVSPartitionTestHelper::check_marker(part_name, (const uint8_t*)str_marker_start, strlen(str_marker_start)), ESP_ERR_NVS_NOT_FOUND);
TEST_ESP_ERR(NVSPartitionTestHelper::check_marker(part_name, (const uint8_t*)str_marker_end, strlen(str_marker_end)), ESP_ERR_NVS_NOT_FOUND);
// check blob marker
TEST_ESP_ERR(NVSPartitionTestHelper::check_marker(part_name, blob_marker_start, sizeof(blob_marker_start)), ESP_ERR_NVS_NOT_FOUND);
TEST_ESP_ERR(NVSPartitionTestHelper::check_marker(part_name, blob_marker_end, sizeof(blob_marker_end)), ESP_ERR_NVS_NOT_FOUND);
// check uint64_t marker
TEST_ESP_ERR(NVSPartitionTestHelper::check_marker(part_name, (const uint8_t*)&uint_marker, sizeof(uint_marker)), ESP_ERR_NVS_NOT_FOUND);
}
SECTION("purge handle - erase entries")
{
// init purge handle
TEST_ESP_OK(nvs_open_from_partition(part_name, ns_name, NVS_READWRITE_PURGE, &handle));
// erase / check markers
TEST_ESP_OK(nvs_erase_key(handle, str_key));
TEST_ESP_OK(nvs_erase_key(handle, blob_key));
TEST_ESP_OK(nvs_erase_key(handle, uint_key));
TEST_ESP_OK(nvs_commit(handle));
// this is purge handle, so markers should NOT be found
// check string marker
TEST_ESP_ERR(NVSPartitionTestHelper::check_marker(part_name, (const uint8_t*)str_marker_start, strlen(str_marker_start)), ESP_ERR_NVS_NOT_FOUND);
TEST_ESP_ERR(NVSPartitionTestHelper::check_marker(part_name, (const uint8_t*)str_marker_end, strlen(str_marker_end)), ESP_ERR_NVS_NOT_FOUND);
// check blob marker
TEST_ESP_ERR(NVSPartitionTestHelper::check_marker(part_name, blob_marker_start, sizeof(blob_marker_start)), ESP_ERR_NVS_NOT_FOUND);
TEST_ESP_ERR(NVSPartitionTestHelper::check_marker(part_name, blob_marker_end, sizeof(blob_marker_end)), ESP_ERR_NVS_NOT_FOUND);
// check uint64_t marker
TEST_ESP_ERR(NVSPartitionTestHelper::check_marker(part_name, (const uint8_t*)&uint_marker, sizeof(uint_marker)), ESP_ERR_NVS_NOT_FOUND);
}
SECTION("purge handle - overwrite entries")
{
// init purge handle
TEST_ESP_OK(nvs_open_from_partition(part_name, ns_name, NVS_READWRITE_PURGE, &handle));
// overwrite / check markers
// string
char* str_data_ov = (char*) malloc(string_len);
CHECK(str_data_ov != nullptr);
memset(str_data_ov, str_overwrite_pattern, string_len);
str_data_ov[string_len - 1] = '\0'; // ensure null termination
TEST_ESP_OK(nvs_set_str(handle, str_key, str_data_ov));
// blob
uint8_t* blob_data_ov = (uint8_t*) malloc(blob_len);
CHECK(blob_data_ov != nullptr);
memset(blob_data_ov, blob_overwrite_pattern, blob_len);
TEST_ESP_OK(nvs_set_blob(handle, blob_key, blob_data_ov, blob_len));
// uint64_t
TEST_ESP_OK(nvs_set_u64(handle, uint_key, uint_overwrite_pattern));
TEST_ESP_OK(nvs_commit(handle));
free(str_data_ov);
free(blob_data_ov);
// this is purge handle, so old markers should NOT be found
// check string marker
TEST_ESP_ERR(NVSPartitionTestHelper::check_marker(part_name, (const uint8_t*)str_marker_start, strlen(str_marker_start)), ESP_ERR_NVS_NOT_FOUND);
TEST_ESP_ERR(NVSPartitionTestHelper::check_marker(part_name, (const uint8_t*)str_marker_end, strlen(str_marker_end)), ESP_ERR_NVS_NOT_FOUND);
// check blob marker
TEST_ESP_ERR(NVSPartitionTestHelper::check_marker(part_name, blob_marker_start, sizeof(blob_marker_start)), ESP_ERR_NVS_NOT_FOUND);
TEST_ESP_ERR(NVSPartitionTestHelper::check_marker(part_name, blob_marker_end, sizeof(blob_marker_end)), ESP_ERR_NVS_NOT_FOUND);
// check uint64_t marker
TEST_ESP_ERR(NVSPartitionTestHelper::check_marker(part_name, (const uint8_t*)&uint_marker, sizeof(uint_marker)), ESP_ERR_NVS_NOT_FOUND);
}
// deinit
nvs_close(handle);
// clean up partition
TEST_ESP_OK(nvs_flash_erase_partition(part_name));
}
// Add new tests above
// This test has to be the final one
@@ -12,6 +12,7 @@
using namespace std;
#define TEST_DEFAULT_PARTITION_NAME "nvs"
#define TEST_DEFAULT_PURGE_AFTER_ERASE true // erase with purge after erase
TEST_CASE("Storage iterator recognizes blob with VerOffset::VER_1_OFFSET", "[nvs_storage]")
{
@@ -31,11 +32,10 @@ TEST_CASE("Storage iterator recognizes blob with VerOffset::VER_1_OFFSET", "[nvs
REQUIRE(storage != nullptr);
storage->createOrOpenNamespace("test_ns", true, ns_index);
CHECK(storage->writeItem(ns_index, nvs::ItemType::BLOB, "test_blob", blob, sizeof(blob)) == ESP_OK);
CHECK(storage->writeItem(ns_index, nvs::ItemType::BLOB, "test_blob", blob, sizeof(blob), TEST_DEFAULT_PURGE_AFTER_ERASE) == ESP_OK);
// changing provokes a blob with version offset 1 (VerOffset::VER_1_OFFSET)
CHECK(storage->writeItem(ns_index, nvs::ItemType::BLOB, "test_blob", blob_new, sizeof(blob_new)) == ESP_OK);
CHECK(storage->writeItem(ns_index, nvs::ItemType::BLOB, "test_blob", blob_new, sizeof(blob_new), TEST_DEFAULT_PURGE_AFTER_ERASE) == ESP_OK);
nvs_opaque_iterator_t it;
it.storage = storage;
it.type = NVS_TYPE_ANY;
@@ -1,5 +1,5 @@
/*
* SPDX-FileCopyrightText: 2015-2024 Espressif Systems (Shanghai) CO LTD
* SPDX-FileCopyrightText: 2015-2025 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
@@ -26,6 +26,8 @@ NVS_GUARD_SYSVIEW_MACRO_EXPANSION_PUSH();
using namespace std;
using namespace nvs;
#define DEFAULT_PURGE_AFTER_ERASE false
void test_Page_load_reading_header_fails()
{
PartitionEmulationFixture fix;
@@ -661,7 +663,7 @@ void test_Page_eraseItem__uninitialized()
{
Page page;
TEST_ASSERT_EQUAL(ESP_ERR_NVS_NOT_FOUND, page.eraseItem<uint8_t>(NVSValidPageFixture::NS_INDEX, "test_value"));
TEST_ASSERT_EQUAL(ESP_ERR_NVS_NOT_FOUND, page.eraseItem<uint8_t>(NVSValidPageFixture::NS_INDEX, "test_value", DEFAULT_PURGE_AFTER_ERASE));
}
void test_Page_eraseItem__key_not_found()
@@ -672,7 +674,7 @@ void test_Page_eraseItem__key_not_found()
TEST_ASSERT_EQUAL(Page::PageState::ACTIVE, fix.page.state());
TEST_ASSERT_EQUAL(ESP_ERR_NVS_NOT_FOUND, fix.page.eraseItem<uint8_t>(NVSValidPageFixture::NS_INDEX, "different"));
TEST_ASSERT_EQUAL(ESP_ERR_NVS_NOT_FOUND, fix.page.eraseItem<uint8_t>(NVSValidPageFixture::NS_INDEX, "different", DEFAULT_PURGE_AFTER_ERASE));
TEST_ASSERT_EQUAL(2, fix.page.getUsedEntryCount());
TEST_ASSERT_EQUAL(122, fix.page.getErasedEntryCount());
@@ -692,7 +694,7 @@ void test_Page_eraseItem__write_fail()
// simulated write failure should fail the eraseItem call
fix.fail_write_at(1);
TEST_ASSERT_EQUAL(ESP_ERR_FLASH_OP_FAIL, fix.page.eraseItem<uint8_t>(NVSValidPageFixture::NS_INDEX, "test_value"));
TEST_ASSERT_EQUAL(ESP_ERR_FLASH_OP_FAIL, fix.page.eraseItem<uint8_t>(NVSValidPageFixture::NS_INDEX, "test_value", DEFAULT_PURGE_AFTER_ERASE));
TEST_ASSERT_EQUAL(1, fix.page.getUsedEntryCount());
TEST_ASSERT_EQUAL(123, fix.page.getErasedEntryCount());
@@ -706,7 +708,7 @@ void test_Page_eraseItem__write_succeed()
TEST_ASSERT_EQUAL(2, fix.page.getUsedEntryCount());
TEST_ASSERT_EQUAL(122, fix.page.getErasedEntryCount());
TEST_ASSERT_EQUAL(Page::PageState::ACTIVE, fix.page.state());
TEST_ASSERT_EQUAL(ESP_OK, fix.page.eraseItem<uint8_t>(NVSValidPageFixture::NS_INDEX, "test_value"));
TEST_ASSERT_EQUAL(ESP_OK, fix.page.eraseItem<uint8_t>(NVSValidPageFixture::NS_INDEX, "test_value", DEFAULT_PURGE_AFTER_ERASE));
TEST_ASSERT_EQUAL(1, fix.page.getUsedEntryCount());
TEST_ASSERT_EQUAL(123, fix.page.getErasedEntryCount());
TEST_ASSERT_EQUAL(Page::PageState::ACTIVE, fix.page.state());