diff --git a/components/nvs_flash/.build-test-rules.yml b/components/nvs_flash/.build-test-rules.yml index 61085c2382e..d8274373819 100644 --- a/components/nvs_flash/.build-test-rules.yml +++ b/components/nvs_flash/.build-test-rules.yml @@ -1,8 +1,8 @@ components/nvs_flash/host_test: depends_components: - - spi_flash - nvs_flash - nvs_sec_provider + - esp_blockdev - esp_partition - esp_blockdev enable: diff --git a/components/nvs_flash/host_test/nvs_host_test/main/CMakeLists.txt b/components/nvs_flash/host_test/nvs_host_test/main/CMakeLists.txt index d7260af4850..b8018982287 100644 --- a/components/nvs_flash/host_test/nvs_host_test/main/CMakeLists.txt +++ b/components/nvs_flash/host_test/nvs_host_test/main/CMakeLists.txt @@ -1,9 +1,11 @@ -idf_component_register(SRCS "test_nvs.cpp" +idf_component_register(SRCS "bdl_ramdisk.cpp" "test_nvs.cpp" "test_partition_manager.cpp" "test_nvs_cxx_api.cpp" "test_nvs_handle.cpp" "test_nvs_initialization.cpp" "test_nvs_storage.cpp" + "test_fixtures.cpp" + "bdl_ramdisk.cpp" INCLUDE_DIRS "../../../src" "../../../private_include" diff --git a/components/nvs_flash/host_test/nvs_host_test/main/bdl_ramdisk.cpp b/components/nvs_flash/host_test/nvs_host_test/main/bdl_ramdisk.cpp new file mode 100644 index 00000000000..795918523fa --- /dev/null +++ b/components/nvs_flash/host_test/nvs_host_test/main/bdl_ramdisk.cpp @@ -0,0 +1,202 @@ +/* + * SPDX-FileCopyrightText: 2025 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include "bdl_ramdisk.hpp" +#include +#include +#include + +static esp_err_t ramdisk_dev_read(esp_blockdev_handle_t dev_handle, uint8_t* dst_buf, size_t dst_buf_size, uint64_t src_addr, size_t data_read_len) +{ + if (!dev_handle || !dst_buf) { + return ESP_ERR_INVALID_ARG; + } + + bdl_ramdisk_ctx_t* ctx = (bdl_ramdisk_ctx_t*)dev_handle->ctx; + + if (dst_buf_size < data_read_len) { + return ESP_ERR_INVALID_ARG; + } + + if (src_addr + data_read_len > ctx->total_size) { + return ESP_ERR_INVALID_SIZE; + } + + // Check alignment for read_size + if (dev_handle->geometry.read_size > 0) { + if (src_addr % dev_handle->geometry.read_size != 0 || + data_read_len % dev_handle->geometry.read_size != 0) { + return ESP_ERR_INVALID_SIZE; + } + } + + memcpy(dst_buf, ctx->data + src_addr, data_read_len); + return ESP_OK; +} + +static esp_err_t ramdisk_dev_write(esp_blockdev_handle_t dev_handle, const uint8_t* src_buf, uint64_t dst_addr, size_t data_write_len) +{ + if (!dev_handle || !src_buf) { + return ESP_ERR_INVALID_ARG; + } + + if (dev_handle->device_flags.read_only) { + return ESP_ERR_NOT_SUPPORTED; + } + + bdl_ramdisk_ctx_t* ctx = (bdl_ramdisk_ctx_t*)dev_handle->ctx; + + if (dst_addr + data_write_len > ctx->total_size) { + return ESP_ERR_INVALID_SIZE; + } + + // Check alignment for write_size + if (dev_handle->geometry.write_size > 0) { + if (dst_addr % dev_handle->geometry.write_size != 0 || + data_write_len % dev_handle->geometry.write_size != 0) { + return ESP_ERR_INVALID_SIZE; + } + } + + memcpy(ctx->data + dst_addr, src_buf, data_write_len); + return ESP_OK; +} + +static esp_err_t ramdisk_dev_erase(esp_blockdev_handle_t dev_handle, uint64_t start_addr, size_t erase_len) +{ + if (!dev_handle) { + return ESP_ERR_INVALID_ARG; + } + + if (dev_handle->device_flags.read_only) { + return ESP_ERR_NOT_SUPPORTED; + } + + bdl_ramdisk_ctx_t* ctx = (bdl_ramdisk_ctx_t*)dev_handle->ctx; + + if (start_addr + erase_len > ctx->total_size) { + return ESP_ERR_INVALID_SIZE; + } + + // Check alignment for erase_size + if (dev_handle->geometry.erase_size > 0) { + if (start_addr % dev_handle->geometry.erase_size != 0 || + erase_len % dev_handle->geometry.erase_size != 0) { + return ESP_ERR_INVALID_SIZE; + } + } + + // Fill with default erase value (0xFF for flash-like behavior) + uint8_t erase_value = dev_handle->device_flags.default_val_after_erase ? 0xFF : 0x00; + memset(ctx->data + start_addr, erase_value, erase_len); + return ESP_OK; +} + +static esp_err_t ramdisk_dev_sync(esp_blockdev_handle_t dev_handle) +{ + // RAM disk doesn't need sync - all operations are immediate + (void)dev_handle; + return ESP_OK; +} + +static esp_err_t ramdisk_dev_ioctl(esp_blockdev_handle_t dev_handle, const uint8_t cmd, void* args) +{ + if (dev_handle == nullptr || args == nullptr) { + return ESP_ERR_INVALID_ARG; + } + + switch (cmd) { + case ESP_BLOCKDEV_CMD_MARK_DELETED: + case ESP_BLOCKDEV_CMD_ERASE_CONTENTS: + // For RAM disk, mark as deleted is equivalent to erase + if (args) { + esp_blockdev_cmd_arg_erase_t* erase_args = (esp_blockdev_cmd_arg_erase_t*)args; + return ramdisk_dev_erase(dev_handle, erase_args->start_addr, erase_args->erase_len); + } + return ESP_ERR_INVALID_ARG; + + default: + return ESP_ERR_NOT_SUPPORTED; + } +} + +esp_err_t ramdisk_dev_release(esp_blockdev_handle_t dev_handle) +{ + if (!dev_handle) { + return ESP_ERR_INVALID_ARG; + } + + bdl_ramdisk_ctx_t* ctx = (bdl_ramdisk_ctx_t*)dev_handle->ctx; + if (ctx && ctx->data) { + free(ctx->data); + } + free(ctx); + free(dev_handle); + return ESP_OK; +} + +// Const operations structure - shared by all instances +static const esp_blockdev_ops_t s_bdl_ramdisk_ops = { + .read = ramdisk_dev_read, + .write = ramdisk_dev_write, + .erase = ramdisk_dev_erase, + .sync = ramdisk_dev_sync, + .ioctl = ramdisk_dev_ioctl, + .release = ramdisk_dev_release +}; + +esp_err_t ramdisk_dev_get_blockdev(esp_blockdev_handle_t* blockdev_handle_out, const size_t total_size, const size_t erase_size) +{ + if (total_size == 0 || erase_size == 0 || total_size % erase_size != 0) { + return ESP_ERR_INVALID_ARG; + } + + // Allocate the block device structure + esp_blockdev_t* bdl = (esp_blockdev_t*)calloc(1, sizeof(esp_blockdev_t)); + if (!bdl) { + return ESP_ERR_NO_MEM; + } + + // Allocate the context + bdl_ramdisk_ctx_t* ctx = (bdl_ramdisk_ctx_t*)calloc(1, sizeof(bdl_ramdisk_ctx_t)); + if (!ctx) { + free(bdl); + return ESP_ERR_NO_MEM; + } + + // Allocate the RAM storage + ctx->data = (uint8_t*)malloc(total_size); + if (!ctx->data) { + free(ctx); + free(bdl); + return ESP_ERR_NO_MEM; + } + + // Initialize with erased state (0xFF) + memset(ctx->data, 0xFF, total_size); + + ctx->total_size = total_size; + ctx->erase_size = erase_size; + + // Setup block device structure + bdl->ctx = ctx; + bdl->ops = &s_bdl_ramdisk_ops; + + // Configure device flags for flash-like behavior + ESP_BLOCKDEV_FLAGS_INST_CONFIG_DEFAULT(bdl->device_flags); + + // Configure geometry + bdl->geometry.disk_size = total_size; + bdl->geometry.read_size = 1; // Can read single bytes + bdl->geometry.write_size = 1; // Can write single bytes + bdl->geometry.erase_size = erase_size; // Erase block size + bdl->geometry.recommended_read_size = 32; // Optimal read size + bdl->geometry.recommended_write_size = 32; // Optimal write size + bdl->geometry.recommended_erase_size = erase_size; + + *blockdev_handle_out = bdl; + return ESP_OK; +} diff --git a/components/nvs_flash/host_test/nvs_host_test/main/bdl_ramdisk.hpp b/components/nvs_flash/host_test/nvs_host_test/main/bdl_ramdisk.hpp new file mode 100644 index 00000000000..e9da6eabbb5 --- /dev/null +++ b/components/nvs_flash/host_test/nvs_host_test/main/bdl_ramdisk.hpp @@ -0,0 +1,75 @@ +/* + * SPDX-FileCopyrightText: 2025 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#pragma once + +#include "esp_blockdev.h" +#include +#include + +#ifdef __cplusplus +extern "C" { +#endif + +/** + * @brief RAM disk context structure + */ +typedef struct { + uint8_t *data; /*!< Pointer to RAM storage */ + size_t total_size; /*!< Total size of the RAM disk */ + size_t erase_size; /*!< Erase block size */ +} bdl_ramdisk_ctx_t; + +/** + * @brief Create a new RAM disk block device + * + * @param blockdev_handle_out Pointer to store the created block device handle + * @param total_size Total size of the RAM disk in bytes + * @param erase_size Erase block size in bytes + * @return esp_err_t ESP_OK on success, or an error code on failure + */ + esp_err_t ramdisk_dev_get_blockdev(esp_blockdev_handle_t* blockdev_handle_out, const size_t total_size, const size_t erase_size); + +/** + * @brief Release a RAM disk block device + * + * @param handle Handle to the RAM disk to release + * @return esp_err_t ESP_OK on success + */ +esp_err_t ramdisk_dev_release(esp_blockdev_handle_t handle); + +#ifdef __cplusplus +} + +/** + * @brief C++ wrapper class for RAM disk + */ +class bdl_ramdisk { +public: + bdl_ramdisk(const size_t total_size, const size_t erase_size) { + handle = nullptr; + ramdisk_dev_get_blockdev(&handle, total_size, erase_size); + } + + ~bdl_ramdisk() { + if (handle) { + ramdisk_dev_release(handle); + } + } + + operator esp_blockdev_handle_t() const { + return handle; + } + + esp_blockdev_handle_t get_handle() const { + return handle; + } + +private: + esp_blockdev_handle_t handle; +}; + +#endif diff --git a/components/nvs_flash/host_test/nvs_host_test/main/test_fixtures.cpp b/components/nvs_flash/host_test/nvs_host_test/main/test_fixtures.cpp new file mode 100644 index 00000000000..1a8e33c7cf5 --- /dev/null +++ b/components/nvs_flash/host_test/nvs_host_test/main/test_fixtures.cpp @@ -0,0 +1,584 @@ +/* + * SPDX-FileCopyrightText: 2025 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include "sdkconfig.h" // for CONFIG_NVS_BDL_STACK +#include // for Catch2 test macros +#include "test_fixtures.hpp" // for test fixtures class definition +#include "nvs_partition.hpp" // for NVSPartition class +#include "esp_partition.h" // for BDL and native esp_partition related functions +#include "esp_private/partition_linux.h" // for partition_linux functions like statistics and emulated sector size +#include // for std::random_device, std::mt19937, std::generate_n +#include // for open() +#include // for close(), read(), write(), lseek() +#include "nvs_constants.h" // for NVS_CONST_PAGE_SIZE +#include "esp_log.h" // for ESP_LOGE + +#define TAG "NVSPartitionTestHelper" + +// Initialize the static variable(s) of NVSPartitionTestHelper +int NVSPartitionTestHelper::s_instance_count = 0; + +#ifndef CONFIG_NVS_BDL_STACK +NVSPartitionTestHelper::NVSPartitionTestHelper(const char *part_name, const bool erase_partition) : + // First call to the esp_partition_find_first maps the partition memory + nvs::NVSPartition(esp_partition_find_first( + ESP_PARTITION_TYPE_DATA, + ESP_PARTITION_SUBTYPE_DATA_NVS, + part_name)) +{ + // Increase the instance count + s_instance_count++; + + CHECK(mESPPartition != nullptr); + + // Remember the erase size of the BDL + mOriginalEraseSize = ((esp_partition_t*) mESPPartition)->erase_size; + + if(erase_partition) + { + // erase the partition before testing + CHECK(erase_range(0, get_size()) == ESP_OK); + } +} + +NVSPartitionTestHelper::~NVSPartitionTestHelper() +{ + // Decrease the instance count + s_instance_count--; + + // reset the erase size to the original value + set_erase_size(mOriginalEraseSize); +} + +esp_err_t NVSPartitionTestHelper::load_from_file(const char *file_name) +{ + return load_from_file(get_partition_name(), file_name); +} + +uint32_t NVSPartitionTestHelper::get_sectors() +{ + return get_size() / NVS_CONST_PAGE_SIZE; +} + +esp_err_t NVSPartitionTestHelper::set_erase_size(const size_t size) +{ + if(mESPPartition == nullptr) { + return ESP_ERR_INVALID_STATE; + } + ((esp_partition_t*) mESPPartition)->erase_size = size; + return ESP_OK; +} + +size_t NVSPartitionTestHelper::get_first_emulated_sector_index() +{ + if (mESPPartition == nullptr) { + return 0; + } + return ((esp_partition_t*) mESPPartition)->address / ESP_PARTITION_EMULATED_SECTOR_SIZE; +} + +size_t NVSPartitionTestHelper::get_sector_erase_count(size_t nvs_sector_index) +{ + // The esp_partition_get_sector_erase_count function is global across all partitions + // it's sector index is relative to the emulated flash and the unit of the sector is defined by ESP_PARTITION_EMULATED_SECTOR_SIZE. + // nvs_sector_index parameter is the index of the sector in the partition and the unit of the sector is defined by NVS_CONST_PAGE_SIZE. + // Therefore, we need to convert the sector index to the emulated flash sector index. + if (mESPPartition == nullptr || nvs_sector_index >= get_sectors()) { + return 0; // Invalid sector index + } + // To get the correct sector index, we need to calculate the offset of the first sector + size_t first_sector = get_first_emulated_sector_index(); + size_t emulated_sector_index = (nvs_sector_index * NVS_CONST_PAGE_SIZE) / ESP_PARTITION_EMULATED_SECTOR_SIZE; + + return esp_partition_get_sector_erase_count(emulated_sector_index + first_sector); +} + +// Static methods to make testing easier + +esp_err_t NVSPartitionTestHelper::randomize_partition(const char *part_name, const uint32_t seed) +{ + // Erase the whole partition before randomizing + esp_err_t ret = erase_partition(part_name); + if (ret != ESP_OK) { + return ret; + } + + // 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; + } + + std::random_device rd; + std::mt19937 gen(rd()); + gen.seed(seed); + + uint8_t buf[NVS_CONST_PAGE_SIZE]; + + // loop over all sectors in the partition + // and fill them with random data + for (uint32_t i = 0; i < part->size / NVS_CONST_PAGE_SIZE; i++) + { + // fill the buffer with random data + std::generate_n(buf, NVS_CONST_PAGE_SIZE, gen); + + ret = esp_partition_write(part, i * NVS_CONST_PAGE_SIZE, buf, NVS_CONST_PAGE_SIZE); + + if (ret != ESP_OK) { + break; + } + } + return ret; +} + +// Erase the whole partition +esp_err_t NVSPartitionTestHelper::erase_partition(const char *part_name) +{ + // 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; + } + // Erase the whole partition + return esp_partition_erase_range(part, 0, part->size); +} + +// Get number of NVS sectors in the partition +uint32_t NVSPartitionTestHelper::get_sectors(const char *part_name) +{ + // 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 0; + } + + return part->size / NVS_CONST_PAGE_SIZE; +} + +// Load the partition from a file +esp_err_t NVSPartitionTestHelper::load_from_file(const char *part_name, const char *file_name) +{ + // 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; + } + + // Load the partition from file + int file_fd = open(file_name, O_RDONLY); + if (file_fd == -1) { + return ESP_ERR_NOT_FOUND; + } + + off_t size = lseek(file_fd, 0L, SEEK_END); + if (size < 0) { + close(file_fd); + return ESP_ERR_INVALID_SIZE; + } + + // Check if file fits into the partition + if (size > part->size) { + close(file_fd); + return ESP_ERR_INVALID_SIZE; + } + + void *p_buff = malloc((size_t)size); + if (p_buff == nullptr) { + close(file_fd); + return ESP_ERR_NO_MEM; + } + + lseek(file_fd, 0L, SEEK_SET); + ssize_t read_size = ::read(file_fd, p_buff, size); + close(file_fd); + + if (read_size != size) { + free(p_buff); + return ESP_ERR_INVALID_SIZE; + } + + esp_err_t ret = esp_partition_erase_range(part, 0, part->size); + if (ret != ESP_OK) { + free(p_buff); + return ret; + } + + ret = esp_partition_write(part, 0, p_buff, size); + free(p_buff); + return ret; +} + +void NVSPartitionTestHelper::clear_stats(void) +{ + return esp_partition_clear_stats(); +} + +size_t NVSPartitionTestHelper::get_read_ops(void) +{ + return esp_partition_get_read_ops(); +} + +size_t NVSPartitionTestHelper::get_write_ops(void) +{ + return esp_partition_get_write_ops(); +} + +size_t NVSPartitionTestHelper::get_erase_ops(void) +{ + return esp_partition_get_erase_ops(); +} + +size_t NVSPartitionTestHelper::get_read_bytes(void) +{ + return esp_partition_get_read_bytes(); +} + +size_t NVSPartitionTestHelper::get_write_bytes(void) +{ + return esp_partition_get_write_bytes(); +} + +size_t NVSPartitionTestHelper::get_total_time(void) +{ + return esp_partition_get_total_time(); +} + +void NVSPartitionTestHelper::fail_after(size_t count, uint8_t mode) +{ + esp_partition_fail_after(count, mode); +} + +#else // CONFIG_NVS_BDL_STACK +// BDL implementation of NVSPartitionTestHelper +NVSPartitionTestHelper::NVSPartitionTestHelper(const char *part_name, const bool erase_partition) : + // Call parent constructor with non-zero though invalid pointer to BDL handle, we will retrieve the correct one later + nvs::NVSPartition(part_name, (esp_blockdev_handle_t)0x1 , false) +{ + // Retrieve the BDL handle for the partition + esp_err_t err = esp_partition_get_blockdev( + ESP_PARTITION_TYPE_DATA, + ESP_PARTITION_SUBTYPE_DATA_NVS, + part_name, + &mBDL); + + if (err != ESP_OK) { + ESP_LOGE(TAG, "Failed to get BDL handle for NVS partition %s: %s", part_name, esp_err_to_name(err)); + std::abort(); + } + + CHECK(mBDL != nullptr); + + // Increase the instance count + s_instance_count++; + + // Remember the erase size of the BDL + mOriginalEraseSize = mBDL->geometry.erase_size; + + if(erase_partition) + { + // erase the partition before testing + CHECK(erase_range(0, get_size()) == ESP_OK); + } +} + +NVSPartitionTestHelper::~NVSPartitionTestHelper() +{ + // reset the erase size to the original value + set_erase_size(mOriginalEraseSize); + + // Decrease the instance count + s_instance_count--; +} + +esp_err_t NVSPartitionTestHelper::load_from_file(const char *file_name) +{ + return load_from_file(get_partition_name(), file_name); +} + +uint32_t NVSPartitionTestHelper::get_sectors() +{ + return get_size() / NVS_CONST_PAGE_SIZE; +} + +esp_err_t NVSPartitionTestHelper::set_erase_size(const size_t size) +{ + // Set the new erase size in the BDL geometry + mBDL->geometry.erase_size = size; + + // We have to set also the erase size in the esp_partition_t structure + const esp_partition_t* part = esp_partition_find_first( + ESP_PARTITION_TYPE_DATA, + ESP_PARTITION_SUBTYPE_DATA_NVS, + get_partition_name()); + + if(part != nullptr) { + ((esp_partition_t*)part)->erase_size = size; + return ESP_OK; + } + + return ESP_ERR_NOT_FOUND; +} + +size_t NVSPartitionTestHelper::get_first_emulated_sector_index() +{ + // TODO: Change this implementation to reflect the concept of sectors counting for statistics once it is + // implemented in BDL. + + // Current implementation assumes the esp_partition API and it's linux implementation evaluates statistics + // based on the emulated flash sectors, which are defined by ESP_PARTITION_EMULATED_SECTOR_SIZE. + // In order to evaluate the statistics correctly, we need to return index of the first sector number this + // partition resides in the emulated flash. + + const esp_partition_t* part = esp_partition_find_first( + ESP_PARTITION_TYPE_DATA, + ESP_PARTITION_SUBTYPE_DATA_NVS, + get_partition_name()); + + CHECK(part != nullptr); + + return part->address / ESP_PARTITION_EMULATED_SECTOR_SIZE; +} + +size_t NVSPartitionTestHelper::get_sector_erase_count(size_t nvs_sector_index) +{ + // TODO: Once BDL implementation supports sector erase count tracking, we will need to re-implement + + // The esp_partition_get_sector_erase_count function is global across all partitions + // it's sector index is relative to the emulated flash and the unit of the sector is defined by ESP_PARTITION_EMULATED_SECTOR_SIZE. + // nvs_sector_index parameter is the index of the sector in the partition and the unit of the sector is defined by NVS_CONST_PAGE_SIZE. + // Therefore, we need to convert the sector index to the emulated flash sector index. + + CHECK(nvs_sector_index < get_sectors()); + + // The statistics are based on the emulated flash sectors, which are defined by ESP_PARTITION_EMULATED_SECTOR_SIZE. + // Therefore, we need to convert the sector index to the emulated flash sector index. + + // Get the first sector index of the partition in the emulated flash + size_t first_sector = get_first_emulated_sector_index(); + + // Calculate the emulated sector index based on the NVS sector index (the unis are defined by NVS_CONST_PAGE_SIZE) + size_t emulated_sector_index = (nvs_sector_index * NVS_CONST_PAGE_SIZE) / ESP_PARTITION_EMULATED_SECTOR_SIZE; + + // Call the esp_partition_get_sector_erase_count function with the emulated sector index + return esp_partition_get_sector_erase_count(emulated_sector_index + first_sector); +} + +// Static methods to make testing easier + +esp_err_t NVSPartitionTestHelper::randomize_partition(const char *part_name, const uint32_t seed) +{ + // Erase the whole partition before randomizing + esp_err_t ret = erase_partition(part_name); + if (ret != ESP_OK) { + return ret; + } + + esp_blockdev_handle_t bdl_handle; + + // get the BDL handle for the partition + ret = esp_partition_get_blockdev( + ESP_PARTITION_TYPE_DATA, + ESP_PARTITION_SUBTYPE_DATA_NVS, + part_name, + &bdl_handle); + + if (ret != ESP_OK) { + return ret; + } + + std::random_device rd; + std::mt19937 gen(rd()); + gen.seed(seed); + + uint8_t buf[NVS_CONST_PAGE_SIZE]; + + // loop over all sectors in the partition + // and fill them with random data + for (uint32_t i = 0; i < bdl_handle->geometry.disk_size / NVS_CONST_PAGE_SIZE; i++) + { + // fill the buffer with random data + std::generate_n(buf, NVS_CONST_PAGE_SIZE, gen); + + // Write the random data to the partition using the BDL handle + ret = bdl_handle->ops->write(bdl_handle, buf, i * NVS_CONST_PAGE_SIZE, NVS_CONST_PAGE_SIZE); + + if (ret != ESP_OK) { + break; + } + } + return ret; +} + +// Erase the whole partition +esp_err_t NVSPartitionTestHelper::erase_partition(const char *part_name) +{ + // 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; + } + + // Erase the whole partition + return bdl_handle->ops->erase(bdl_handle, 0, bdl_handle->geometry.disk_size); +} + +// Get number of NVS sectors in the partition +uint32_t NVSPartitionTestHelper::get_sectors(const char *part_name) +{ + // 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 0; + } + + return bdl_handle->geometry.disk_size / NVS_CONST_PAGE_SIZE; +} + +// Load the partition from a file +esp_err_t NVSPartitionTestHelper::load_from_file(const char *part_name, const char *file_name) +{ + // 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; + } + + // Load the partition from file + int file_fd = open(file_name, O_RDONLY); + if (file_fd == -1) { + return ESP_ERR_NOT_FOUND; + } + + off_t size = lseek(file_fd, 0L, SEEK_END); + if (size < 0) { + close(file_fd); + return ESP_ERR_INVALID_SIZE; + } + + // Check if file fits into the partition + if (size > bdl_handle->geometry.disk_size) { + close(file_fd); + return ESP_ERR_INVALID_SIZE; + } + + void *p_buff = malloc((size_t)size); + if (p_buff == nullptr) { + close(file_fd); + return ESP_ERR_NO_MEM; + } + + lseek(file_fd, 0L, SEEK_SET); + ssize_t read_size = ::read(file_fd, p_buff, size); + close(file_fd); + + if (read_size != size) { + free(p_buff); + return ESP_ERR_INVALID_SIZE; + } + + ret = erase_partition(part_name); + if (ret != ESP_OK) { + free(p_buff); + return ret; + } + + ret = bdl_handle->ops->write(bdl_handle, (const uint8_t*) p_buff, 0, size); + free(p_buff); + return ret; +} + +void NVSPartitionTestHelper::clear_stats(void) +{ + // TODO: Once BDL implementation supports statistics, we will need to re-implement this function + + return esp_partition_clear_stats(); +} + +size_t NVSPartitionTestHelper::get_read_ops(void) +{ + // TODO: Once BDL implementation supports statistics, we will need to re-implement this function + + return esp_partition_get_read_ops(); +} + +size_t NVSPartitionTestHelper::get_write_ops(void) +{ + // TODO: Once BDL implementation supports statistics, we will need to re-implement this function + + return esp_partition_get_write_ops(); +} + +size_t NVSPartitionTestHelper::get_erase_ops(void) +{ + // TODO: Once BDL implementation supports statistics, we will need to re-implement this function + + return esp_partition_get_erase_ops(); +} + +size_t NVSPartitionTestHelper::get_read_bytes(void) +{ + // TODO: Once BDL implementation supports statistics, we will need to re-implement this function + return esp_partition_get_read_bytes(); +} + +size_t NVSPartitionTestHelper::get_write_bytes(void) +{ + // TODO: Once BDL implementation supports statistics, we will need to re-implement this function + + return esp_partition_get_write_bytes(); +} + +size_t NVSPartitionTestHelper::get_total_time(void) +{ + // TODO: Once BDL implementation supports statistics, we will need to re-implement this function + + return esp_partition_get_total_time(); +} + +void NVSPartitionTestHelper::fail_after(size_t count, uint8_t mode) +{ + // TODO: Once BDL implementation supports programmed failures, we will need to re-implement this function + + esp_partition_fail_after(count, mode); +} +#endif diff --git a/components/nvs_flash/host_test/nvs_host_test/main/test_fixtures.hpp b/components/nvs_flash/host_test/nvs_host_test/main/test_fixtures.hpp index 51d30420c73..580fd5a82e9 100644 --- a/components/nvs_flash/host_test/nvs_host_test/main/test_fixtures.hpp +++ b/components/nvs_flash/host_test/nvs_host_test/main/test_fixtures.hpp @@ -1,206 +1,75 @@ /* - * SPDX-FileCopyrightText: 2015-2024 Espressif Systems (Shanghai) CO LTD + * SPDX-FileCopyrightText: 2015-2025 Espressif Systems (Shanghai) CO LTD * * SPDX-License-Identifier: Apache-2.0 */ -#include "nvs_partition.hpp" -#include "esp_private/partition_linux.h" -#include "nvs.h" -#include -#include -#include -#include "esp_partition.h" -class PartitionEmulationFixture { +#pragma once + +#include "nvs_partition.hpp" // for NVSPartition class +#include "esp_err.h" // for esp_err_t +#include "esp_private/partition_linux.h" // for constants of the fail_after() ESP_PARTITION_FAIL_AFTER_MODE_BOTH + +class NVSPartitionTestHelper : public nvs::NVSPartition { public: - PartitionEmulationFixture( uint32_t start_sector = 0, - uint32_t sector_size = 1, - const char *partition_name = NVS_DEFAULT_PART_NAME) : - esp_partition() - { + // Constructor uses esp_partition API to initialize the partition + // Parameters: + // - part_name: name of the partition to be used for testing + // - erase_partition: if true, the partition will be erased before testing + NVSPartitionTestHelper(const char *part_name, const bool erase_partition = true); - if (esp_partition_file_mmap((const uint8_t **) &p_part_desc_addr_start) != ESP_OK) { - FAIL("Failed to initialize esp_partition_file_mmap"); - } + // Destructor decreases the instance count + // and unmaps the partition memory if this is the last instance + virtual ~NVSPartitionTestHelper(); - const uint32_t sec_size = esp_partition_get_main_flash_sector_size(); - esp_partition.address = start_sector * sec_size; - esp_partition.size = (start_sector + sector_size) * sec_size; - esp_partition.erase_size = ESP_PARTITION_EMULATED_SECTOR_SIZE; - esp_partition.type = ESP_PARTITION_TYPE_DATA; - esp_partition.subtype = ESP_PARTITION_SUBTYPE_DATA_NVS; - strncpy(esp_partition.label, partition_name, NVS_PART_NAME_MAX_SIZE); - p_part = new (std::nothrow) nvs::NVSPartition(&esp_partition); - REQUIRE(p_part != nullptr); - } + // Load the partition from a file + esp_err_t load_from_file(const char *file_name); - // initializes the partition and loads partition binary file into it - PartitionEmulationFixture( uint32_t start_sector, - uint32_t sector_size, - const char *partition_name, - const char *partition_binary) : PartitionEmulationFixture(start_sector, sector_size, partition_name) - { - int file_fd = -1; - off_t size = -1; - void *p_buff = nullptr; - char const *fail_msg = nullptr; - const uint32_t sec_size = esp_partition_get_main_flash_sector_size(); + // Return the number of NVS sectors in the partition + uint32_t get_sectors(); - do { - // get file size - file_fd = open(partition_binary, O_RDONLY); - if (file_fd == -1) { - fail_msg = "Failed to open file with partition content"; - break; - } - size = lseek(file_fd, 0L, SEEK_END); - if (size < 0) { - fail_msg = "failed to seek in file with partition content"; - break; - } + // Overrides the erase size to the specified size + esp_err_t set_erase_size(const size_t size); - // check if file fits into the partitiion - if (size > sector_size * sec_size) { - fail_msg = "file with partition content doesn't fit into the partition"; - break; - } + // Function returns the starting sector index of the partition in the emulated flash + // The size of emulated sector is defined by ESP_PARTITION_EMULATED_SECTOR_SIZE + // First emulated sector of the partition is any number, it depends on the partition location + // in the emulated flash + size_t get_first_emulated_sector_index(); - // allocate local buffer - p_buff = malloc((size_t) size); - if (p_buff == nullptr) { - fail_msg = "unable to allocate buffer for reading file with partition content"; - break; - } + // Get the erase count of a specific NVS sector within the partition + // The size of NVS sector is defined by NVS_CONST_PAGE_SIZE + // First sector index is 0 + size_t get_sector_erase_count(size_t nvs_sector_index); - // laoad file into local buffer - int res = lseek(file_fd, 0L, SEEK_SET); - if (res < 0) { - fail_msg = "failed to seek in file with partition content"; - break; - } - size = read(file_fd, p_buff, size); - if (size < 0) { - fail_msg = "cannot read file with partition content"; - break; - } + //--------------- Static helper methods to make testing easier -------------------------- - // erase whole partition - if (ESP_OK != esp_partition_erase_range(&esp_partition, 0, sector_size * sec_size)) { - fail_msg = "cannot erase partition prior to write partition binary from file"; - break; - } + // Randomize the whole partition with a given seed + static esp_err_t randomize_partition(const char *part_name, const uint32_t seed); - // write local buffer to the partition - if (ESP_OK != esp_partition_write_raw(&esp_partition, 0, p_buff, size)) { - fail_msg = "cannot write to the partition"; - break; - } - } while (false); + // Erase the whole partition + static esp_err_t erase_partition(const char *part_name); - // close file - if (file_fd >= 0) { - close(file_fd); - } + // Get number of NVS sectors in the partition + static uint32_t get_sectors(const char *part_name); - // deallocate buffer - if (p_buff != nullptr) { - free(p_buff); - } + // Load the partition from a file + static esp_err_t load_from_file(const char *part_name, const char *file_name); - if(fail_msg != nullptr) { - FAIL(fail_msg); - } - } - - void randomize(uint32_t seed) - { - std::random_device rd; - std::mt19937 gen(rd()); - gen.seed(seed); - - esp_partition_file_mmap_ctrl_t *p_ctrl = esp_partition_get_file_mmap_ctrl_act(); - REQUIRE(p_ctrl != nullptr); - std::generate_n(p_part_desc_addr_start, p_ctrl->flash_file_size, gen); - } - - // absolute sectorNumber is used here - bool erase(size_t sectorNumber) - { - const uint32_t sec_size = esp_partition_get_main_flash_sector_size(); - size_t offset = sectorNumber * sec_size; - - // check the upper bound - esp_partition_file_mmap_ctrl_t *p_ctrl = esp_partition_get_file_mmap_ctrl_act(); - REQUIRE(p_ctrl != nullptr); - if (offset > p_ctrl->flash_file_size) { - return false; - } - - // esp_partition_erase_range uses offset relative to the beginning of partition - return (esp_partition_erase_range(&esp_partition, - offset - esp_partition.address, - sec_size) == ESP_OK); - } - - ~PartitionEmulationFixture() - { - delete p_part; - - // ensure underlying mapped file gets deleted after unmap. - esp_partition_file_mmap_ctrl_t *p_ctrl = esp_partition_get_file_mmap_ctrl_input(); - p_ctrl->remove_dump = true; - esp_partition_file_munmap(); - } - - nvs::NVSPartition *part() - { - return p_part; - } - - const esp_partition_t *get_esp_partition() const - { - return &esp_partition; - } - - nvs::NVSPartition *p_part; - esp_partition_t esp_partition; - uint8_t *p_part_desc_addr_start; -}; - -// fixture with 2 partitions -class PartitionEmulationFixture2 : public PartitionEmulationFixture { -public: - PartitionEmulationFixture2( uint32_t start_sector1 = 0, - uint32_t sector_size1 = 1, - const char *partition_name1 = "nvs1", - uint32_t start_sector2 = 1, - uint32_t sector_size2 = 1, - const char *partition_name2 = "nvs2" - ) : - PartitionEmulationFixture(start_sector1, sector_size1, partition_name1), esp_partition2() - { - const uint32_t sec_size = esp_partition_get_main_flash_sector_size(); - // for 2nd partition - esp_partition2.address = start_sector2 * sec_size; - esp_partition2.size = (start_sector2 + sector_size2) * sec_size; - esp_partition2.erase_size = ESP_PARTITION_EMULATED_SECTOR_SIZE; - esp_partition2.type = ESP_PARTITION_TYPE_DATA; - esp_partition2.subtype = ESP_PARTITION_SUBTYPE_DATA_NVS; - strncpy(esp_partition2.label, partition_name2, NVS_PART_NAME_MAX_SIZE); - p_part2 = new (std::nothrow) nvs::NVSPartition(&esp_partition2); - REQUIRE(p_part2 != nullptr); - } - - ~PartitionEmulationFixture2() - { - delete p_part2; - } - - nvs::NVSPartition *part2() - { - return p_part2; - } - - nvs::NVSPartition *p_part2; - esp_partition_t esp_partition2; + // Set of functions to access partition statistics + // At the moment these functions are global, it means that they do not distinguish between + // different partitions + // Be aware that host tests mixing multiple partitions + // will not work correctly, as the statistics will be summed up for all partitions + static void clear_stats(void); + static size_t get_read_ops(void); + static size_t get_write_ops(void); + static size_t get_erase_ops(void); + static size_t get_read_bytes(void); + static size_t get_write_bytes(void); + static size_t get_total_time(void); + static void fail_after(size_t count, uint8_t mode); +private: + static int s_instance_count; // Static variable to track instance count + size_t mOriginalEraseSize; // Erase size of the Partition at the moment of the object creation }; diff --git a/components/nvs_flash/host_test/nvs_host_test/main/test_nvs.cpp b/components/nvs_flash/host_test/nvs_host_test/main/test_nvs.cpp index c9d174da7e8..b4cca87cd33 100644 --- a/components/nvs_flash/host_test/nvs_host_test/main/test_nvs.cpp +++ b/components/nvs_flash/host_test/nvs_host_test/main/test_nvs.cpp @@ -3,6 +3,7 @@ * * SPDX-License-Identifier: Apache-2.0 */ + #include #include "nvs.hpp" #include "sdkconfig.h" @@ -22,11 +23,13 @@ using namespace std; +#define TEST_DEFAULT_PARTITION_NAME "nvs" // Default partition name used in the tests - 10 sectors +#define TEST_SECONDARY_PARTITION_NAME "nvs_sec" // Secondary partition name used in the tests - 10 sectors +#define TEST_3SEC_PARTITION_NAME "nvs_3sec" // Partition used in the space constrained tests - 3 sectors + #define TEST_ESP_ERR(rc, res) CHECK((rc) == (res)) #define TEST_ESP_OK(rc) CHECK((rc) == ESP_OK) -#define TEMPORARILY_DISABLED(x) - #define WD_PREFIX "./components/nvs_flash/host_test/nvs_host_test/" // path from ci cwd to the location of host test #if defined(SEGGER_H) && defined(GLOBAL_H) @@ -45,6 +48,13 @@ stringstream s_perf; TEST_CASE("crc32 behaves as expected", "[nvs]") { + // TC verifies that crc32 is calculated correctly for Item. + // It verifies that: + // - crc32 is calculated correctly for Item with all fields set to 0xff + // - changing any field changes the crc32 + // - changing nsIndex, datatype, key or chunkIndex changes the crc32 + // - changing data changes the crc32 + nvs::Item item1; item1.datatype = nvs::ItemType::I32; item1.nsIndex = 1; @@ -71,22 +81,32 @@ TEST_CASE("crc32 behaves as expected", "[nvs]") item2 = item1; strncpy(item2.key, "foo", nvs::Item::MAX_KEY_LENGTH); CHECK(crc32_1 != item2.calculateCrc32()); + + item2 = item1; + fill_n(item2.data, sizeof(item2.data), 0xab); + CHECK(crc32_1 != item2.calculateCrc32()); } TEST_CASE("Page starting with empty flash is in uninitialized state", "[nvs]") { - PartitionEmulationFixture f; + // TC verifies that Page starts in INVALID state and after loading it from an empty partition, it becomes UNINITIALIZED. + + NVSPartitionTestHelper h(TEST_DEFAULT_PARTITION_NAME); + nvs::Page page; CHECK(page.state() == nvs::Page::PageState::INVALID); - TEST_ESP_OK(page.load(f.part(), 0)); + TEST_ESP_OK(page.load(&h, 0)); CHECK(page.state() == nvs::Page::PageState::UNINITIALIZED); } TEST_CASE("Page can distinguish namespaces", "[nvs]") { - PartitionEmulationFixture f; + // TC verifies that Page.writeItem and readItem respects namespace parameter. + + NVSPartitionTestHelper h(TEST_DEFAULT_PARTITION_NAME); + nvs::Page page; - TEST_ESP_OK(page.load(f.part(), 0)); + TEST_ESP_OK(page.load(&h, 0)); int32_t val1 = 0x12345678; TEST_ESP_OK(page.writeItem(1, nvs::ItemType::I32, "intval1", &val1, sizeof(val1))); int32_t val2 = 0x23456789; @@ -99,9 +119,12 @@ TEST_CASE("Page can distinguish namespaces", "[nvs]") TEST_CASE("Page reading with different type causes type mismatch error", "[nvs]") { - PartitionEmulationFixture f; + // TC verifies that Page.readItem returns ESP_ERR_NVS_TYPE_MISMATCH when reading with different type than written. + + NVSPartitionTestHelper h(TEST_DEFAULT_PARTITION_NAME); + nvs::Page page; - TEST_ESP_OK(page.load(f.part(), 0)); + TEST_ESP_OK(page.load(&h, 0)); int32_t val = 0x12345678; TEST_ESP_OK(page.writeItem(1, nvs::ItemType::I32, "intval1", &val, sizeof(val))); CHECK(page.readItem(1, nvs::ItemType::U32, "intval1", &val, sizeof(val)) == ESP_ERR_NVS_TYPE_MISMATCH); @@ -109,9 +132,12 @@ TEST_CASE("Page reading with different type causes type mismatch error", "[nvs]" TEST_CASE("Page when erased, it's state becomes UNINITIALIZED", "[nvs]") { - PartitionEmulationFixture f; + // TC verifies that Page.erase() sets the state to UNINITIALIZED. + + NVSPartitionTestHelper h(TEST_DEFAULT_PARTITION_NAME); + nvs::Page page; - TEST_ESP_OK(page.load(f.part(), 0)); + TEST_ESP_OK(page.load(&h, 0)); int32_t val = 0x12345678; TEST_ESP_OK(page.writeItem(1, nvs::ItemType::I32, "intval1", &val, sizeof(val))); TEST_ESP_OK(page.erase()); @@ -120,9 +146,18 @@ TEST_CASE("Page when erased, it's state becomes UNINITIALIZED", "[nvs]") TEST_CASE("Page when writing and erasing, used/erased counts are updated correctly", "[nvs]") { - PartitionEmulationFixture f; + // TC verifies that Page.writeItem and eraseItem update used/erased entry counts correctly. + // The test verifies following: + // - writing an item increases used entry count + // - overwriting and item increases used entry count + // - erasing an item increases erased entry count and decreases used entry count + // - completely erasinf the page sets used entry count to 1 and erased entry count to Page::ENTRY_COUNT - 1 + // The remaining, not deleted entry is the namespace entry. + + NVSPartitionTestHelper h(TEST_DEFAULT_PARTITION_NAME); + nvs::Page page; - TEST_ESP_OK(page.load(f.part(), 0)); + TEST_ESP_OK(page.load(&h, 0)); CHECK(page.getUsedEntryCount() == 0); CHECK(page.getErasedEntryCount() == 0); uint32_t foo1 = 0; @@ -151,9 +186,13 @@ TEST_CASE("Page when writing and erasing, used/erased counts are updated correct TEST_CASE("Page when page is full, adding an element fails", "[nvs]") { - PartitionEmulationFixture f; + // TC verifies that Page.writeItem returns ESP_ERR_NVS_PAGE_FULL when the page is full + // and writeItem is called. + + NVSPartitionTestHelper h(TEST_DEFAULT_PARTITION_NAME); + nvs::Page page; - TEST_ESP_OK(page.load(f.part(), 0)); + TEST_ESP_OK(page.load(&h, 0)); for (size_t i = 0; i < nvs::Page::ENTRY_COUNT; ++i) { char name[16]; snprintf(name, sizeof(name), "i%ld", (long int)i); @@ -162,19 +201,25 @@ TEST_CASE("Page when page is full, adding an element fails", "[nvs]") CHECK(page.writeItem(1, "foo", 64UL) == ESP_ERR_NVS_PAGE_FULL); } -TEST_CASE("Page maintains its seq number") +TEST_CASE("Page maintains its seq number", "[nvs]") { - PartitionEmulationFixture f; + // TC verifies that Page.setSeqNumber and getSeqNumber works as expected. + // The test verifies following: + // - after loading the page, seq number is 0 + // - after setting the seq number, it is persisted + // - after loading the page again, the seq number is the same as set before + + NVSPartitionTestHelper h(TEST_DEFAULT_PARTITION_NAME); { nvs::Page page; - TEST_ESP_OK(page.load(f.part(), 0)); + TEST_ESP_OK(page.load(&h, 0)); TEST_ESP_OK(page.setSeqNumber(123)); int32_t val = 42; TEST_ESP_OK(page.writeItem(1, nvs::ItemType::I32, "dummy", &val, sizeof(val))); } { nvs::Page page; - TEST_ESP_OK(page.load(f.part(), 0)); + TEST_ESP_OK(page.load(&h, 0)); uint32_t seqno; TEST_ESP_OK(page.getSeqNumber(seqno)); CHECK(seqno == 123); @@ -183,9 +228,15 @@ TEST_CASE("Page maintains its seq number") TEST_CASE("Page can write and read variable length data", "[nvs]") { - PartitionEmulationFixture f; + // TC verifies that Page.writeItem and readItem works with variable length data. + // The test verifies following: + // - writing a variable length data works if performed in the series of fixed data writes + // - reading a variable length data after some fixed length data writes works + + NVSPartitionTestHelper h(TEST_DEFAULT_PARTITION_NAME); + nvs::Page page; - TEST_ESP_OK(page.load(f.part(), 0)); + TEST_ESP_OK(page.load(&h, 0)); const char str[] = "foobar1234foobar1234foobar1234foobar1234foobar1234foobar1234foobar1234foobar1234"; size_t len = strlen(str); TEST_ESP_OK(page.writeItem(1, "stuff1", 42)); @@ -217,9 +268,16 @@ TEST_CASE("Page can write and read variable length data", "[nvs]") TEST_CASE("Page different key names are distinguished even if the pointer is the same", "[nvs]") { - PartitionEmulationFixture f; + // TC verifies that Page.writeItem and readItem uses content of the buffer as key name + // and not the pointer to the buffer. + // The test verifies following: + // - writing two items with the same pointer to buffer containing always different key creates 2 different entries + // - reading the items with the same pointer to buffer containing always different key returns the correct values + + NVSPartitionTestHelper h(TEST_DEFAULT_PARTITION_NAME); + nvs::Page page; - TEST_ESP_OK(page.load(f.part(), 0)); + TEST_ESP_OK(page.load(&h, 0)); TEST_ESP_OK(page.writeItem(1, "i1", 1)); TEST_ESP_OK(page.writeItem(1, "i2", 2)); int32_t value; @@ -236,9 +294,13 @@ TEST_CASE("Page different key names are distinguished even if the pointer is the TEST_CASE("Page validates key size", "[nvs]") { - PartitionEmulationFixture f(0, 4); + // TC verifies that Page.writeItem returns ESP_ERR_NVS_KEY_TOO_LONG when the key length exceed the limit. + // The limit is 15 characters plus null terminator, so 16-character key should fail. + + NVSPartitionTestHelper h(TEST_DEFAULT_PARTITION_NAME); + nvs::Page page; - TEST_ESP_OK(page.load(f.part(), 0)); + TEST_ESP_OK(page.load(&h, 0)); // 16-character key fails TEST_ESP_ERR(page.writeItem(1, "0123456789123456", 1), ESP_ERR_NVS_KEY_TOO_LONG); // 15-character key is okay @@ -247,9 +309,14 @@ TEST_CASE("Page validates key size", "[nvs]") TEST_CASE("Page validates blob size", "[nvs]") { - PartitionEmulationFixture f(0, 4); + // TC verifies that Page.writeItem returns ESP_ERR_NVS_VALUE_TOO_LONG when the blob size exceeds the limit. + // The technical limit at the Page level is the actual number of available entries in the page - 1 (for BLOB metadata) + // The enforced limit assuming the complete page is available for BLOBs is 4096 bytes. + // In this test, both limits are actually the same (4096 bytes). + NVSPartitionTestHelper h(TEST_DEFAULT_PARTITION_NAME); + nvs::Page page; - TEST_ESP_OK(page.load(f.part(), 0)); + TEST_ESP_OK(page.load(&h, 0)); char buf[4096] = { 0 }; // There are two potential errors here: @@ -272,7 +339,15 @@ public: TEST_CASE("HashList is cleaned up as soon as items are erased", "[nvs]") { + // TC verifies that HashList is cleaned up as soon as items are erased. + // The test verifies following: + // - adding items increases the block count + // - removing items decreases the block count + // - adding items again after removing them increases the block count again + // - removing items in the same order works + HashListTestHelper hashlist; + // Add items const size_t count = 128; for (size_t i = 0; i < count; ++i) { @@ -305,28 +380,42 @@ TEST_CASE("HashList is cleaned up as soon as items are erased", "[nvs]") TEST_CASE("can init PageManager in empty flash", "[nvs]") { - PartitionEmulationFixture f(0, 4); + // TC verifies that PageManager can be initialized in empty flash. + NVSPartitionTestHelper h(TEST_DEFAULT_PARTITION_NAME); + nvs::PageManager pm; - TEST_ESP_OK(pm.load(f.part(), 0, 4)); + TEST_ESP_OK(pm.load(&h, 0, h.get_sectors())); } TEST_CASE("PageManager adds page in the correct order", "[nvs]") { - const size_t pageCount = 8; - PartitionEmulationFixture f(0, pageCount); - uint32_t pageNo[pageCount] = { -1U, 50, 11, -1U, 23, 22, 24, 49}; + // TC verifies that PageManager adds pages in the correct order. + // The test verifies following: + // - PageManager iterates through pages in the order of their seq number regardless of the order they were added + NVSPartitionTestHelper h(TEST_DEFAULT_PARTITION_NAME); + const size_t pageCount = 10; + + // We have to make sure that the PageManager is initialized with the same number of pages as the partition has. + CHECK(pageCount == h.get_sectors()); + + // The page numbers are set in such a way that the seq numbers are not in order. + uint32_t pageNo[pageCount] = { -1U, 50, 11, -1U, 23, 22, 24, 49, 31, 32}; + + // Populate the storage with pages in random order. for (uint32_t i = 0; i < pageCount; ++i) { nvs::Page p; - TEST_ESP_OK(p.load(f.part(), i)); + TEST_ESP_OK(p.load(&h, i)); if (pageNo[i] != -1U) { p.setSeqNumber(pageNo[i]); p.writeItem(1, "foo", 10U); } } + // Load the PageManager from the same partition and check that the pages + // are iterated in the order of their seq numbers. nvs::PageManager pageManager; - TEST_ESP_OK(pageManager.load(f.part(), 0, pageCount)); + TEST_ESP_OK(pageManager.load(&h, 0, h.get_sectors())); uint32_t lastSeqNo = 0; for (auto it = std::begin(pageManager); it != std::end(pageManager); ++it) { @@ -338,47 +427,76 @@ TEST_CASE("PageManager adds page in the correct order", "[nvs]") TEST_CASE("can init storage in empty flash", "[nvs]") { - PartitionEmulationFixture f(0, 8); - nvs::Storage storage(f.part()); - TEMPORARILY_DISABLED(f.emu.setBounds(4, 8);) - TEST_ESP_OK(storage.init(4, 4)); - s_perf << "Time to init empty storage (4 sectors): " << esp_partition_get_total_time() << " us" << std::endl; + // TC verifies that Storage can be initialized in empty flash. + NVSPartitionTestHelper h(TEST_DEFAULT_PARTITION_NAME); + + nvs::Storage storage(&h); + + NVSPartitionTestHelper::clear_stats(); // Clear statistics before init + + TEST_ESP_OK(storage.init(0, h.get_sectors())); + + s_perf << "Time to init empty storage (" << h.get_sectors() << " sectors): " << NVSPartitionTestHelper::get_total_time() << " us" << std::endl; } TEST_CASE("storage doesn't add duplicates within one page", "[nvs]") { - PartitionEmulationFixture f(0, 8); - nvs::Storage storage(f.part()); - TEMPORARILY_DISABLED(f.emu.setBounds(4, 8);) - TEST_ESP_OK(storage.init(4, 4)); + // TC verifies that Storage.writeItem doesn't add duplicates within one page. + // The test verifies following: + // - writing the same item twice doesn't increase used entry count + // - overwriting the same item sets the erased entry count to 1 + + NVSPartitionTestHelper h(TEST_DEFAULT_PARTITION_NAME); + + 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)); nvs::Page page; - TEST_ESP_OK(page.load(f.part(), 4)); + + // We are assuming that sector 0 is used for the first page. + TEST_ESP_OK(page.load(&h, 0)); CHECK(page.getUsedEntryCount() == 1); CHECK(page.getErasedEntryCount() == 1); } TEST_CASE("can write one item a thousand times", "[nvs]") { - PartitionEmulationFixture f(0, 8); - nvs::Storage storage(f.part()); - TEMPORARILY_DISABLED(f.emu.setBounds(4, 8);) - TEST_ESP_OK(storage.init(4, 4)); - for (size_t i = 0; i < nvs::Page::ENTRY_COUNT * 4 * 2; ++i) { + // TC verifies that Storage.writeItem can write one item a thousand times. + + NVSPartitionTestHelper h(TEST_DEFAULT_PARTITION_NAME); + + const size_t NO_WRITES = 1000; + nvs::Storage storage(&h); + + TEST_ESP_OK(storage.init(0, h.get_sectors())); + + 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(i))); } - s_perf << "Time to write one item a thousand times: " << esp_partition_get_total_time() << " us (" << esp_partition_get_erase_ops() << " " << esp_partition_get_write_ops() << " " << esp_partition_get_read_ops() << " " << esp_partition_get_write_bytes() << " " << esp_partition_get_read_bytes() << ")" << std::endl; + 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; } TEST_CASE("storage doesn't add duplicates within multiple pages", "[nvs]") { - PartitionEmulationFixture f(0, 8); - nvs::Storage storage(f.part()); - TEMPORARILY_DISABLED(f.emu.setBounds(4, 8);) - TEST_ESP_OK(storage.init(4, 4)); + // TC verifies that Storage.writeItem doesn't add duplicates within multiple pages. + // The test writes the item "bar" to the first page of the partition, + // Then it writes the item "foo" so many times that it doesn't fit into the first page. + // At the end it writes the item "bar" again, this time to the second page. + // The test verifies following: + // - attempt to read the item "bar" from the first page returns ESP_ERR_NVS_NOT_FOUND + + // - reading the item "bar" from the second page works + NVSPartitionTestHelper h(TEST_DEFAULT_PARTITION_NAME); + + nvs::Storage storage(&h); + + TEST_ESP_OK(storage.init(0, h.get_sectors())); int bar = 0; TEST_ESP_OK(storage.writeItem(1, "bar", ++bar)); for (size_t i = 0; i < nvs::Page::ENTRY_COUNT; ++i) { @@ -387,43 +505,56 @@ TEST_CASE("storage doesn't add duplicates within multiple pages", "[nvs]") TEST_ESP_OK(storage.writeItem(1, "bar", ++bar)); nvs::Page page; - TEST_ESP_OK(page.load(f.part(), 4)); + TEST_ESP_OK(page.load(&h, 0)); // first page attempt CHECK(page.findItem(1, nvs::itemTypeOf(), "bar") == ESP_ERR_NVS_NOT_FOUND); - TEST_ESP_OK(page.load(f.part(), 5)); + TEST_ESP_OK(page.load(&h, 1)); // second page attempt TEST_ESP_OK(page.findItem(1, nvs::itemTypeOf(), "bar")); } TEST_CASE("storage can find items on second page if first is not fully written and has cached search data", "[nvs]") { - PartitionEmulationFixture f(0, 3); - nvs::Storage storage(f.part()); - TEST_ESP_OK(storage.init(0, 3)); + // TC verifies that Storage.findItem can find items on the second page if the first page is not fully written + // 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); + + 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(0, nvs::ItemType::BLOB, "1", bigdata, sizeof(bigdata))); + ESP_ERROR_CHECK(storage.writeItem(1, nvs::ItemType::BLOB, "1", bigdata, sizeof(bigdata))); // write another big chunk of data - ESP_ERROR_CHECK(storage.writeItem(0, nvs::ItemType::BLOB, "2", bigdata, sizeof(bigdata))); + ESP_ERROR_CHECK(storage.writeItem(1, nvs::ItemType::BLOB, "2", bigdata, sizeof(bigdata))); // write third one; it will not fit into the first page - ESP_ERROR_CHECK(storage.writeItem(0, nvs::ItemType::BLOB, "3", bigdata, sizeof(bigdata))); + ESP_ERROR_CHECK(storage.writeItem(1, nvs::ItemType::BLOB, "3", bigdata, sizeof(bigdata))); size_t size; - ESP_ERROR_CHECK(storage.getItemDataSize(0, nvs::ItemType::BLOB, "1", size)); + ESP_ERROR_CHECK(storage.getItemDataSize(1, nvs::ItemType::BLOB, "1", size)); CHECK(size == sizeof(bigdata)); - ESP_ERROR_CHECK(storage.getItemDataSize(0, nvs::ItemType::BLOB, "3", size)); + ESP_ERROR_CHECK(storage.getItemDataSize(1, nvs::ItemType::BLOB, "3", size)); CHECK(size == sizeof(bigdata)); } TEST_CASE("can write and read variable length data lots of times", "[nvs]") { - PartitionEmulationFixture f(0, 8); - nvs::Storage storage(f.part()); - TEMPORARILY_DISABLED(f.emu.setBounds(4, 8);) - TEST_ESP_OK(storage.init(4, 4)); + // TC verifies that Storage.writeItem and readItem works with large amounts + // of variable length data interleaved with fixed length data. + + NVSPartitionTestHelper h(TEST_DEFAULT_PARTITION_NAME); + + const size_t NO_WRITES = 1000; + nvs::Storage storage(&h); + + TEST_ESP_OK(storage.init(0, h.get_sectors())); const char str[] = "foobar1234foobar1234foobar1234foobar1234foobar1234foobar1234foobar1234foobar1234"; char buf[sizeof(str) + 16]; size_t len = strlen(str); - for (size_t i = 0; i < nvs::Page::ENTRY_COUNT * 4 * 2; ++i) { + + NVSPartitionTestHelper::clear_stats(); // Clear statistics before writing + + 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(i))); @@ -436,50 +567,75 @@ TEST_CASE("can write and read variable length data lots of times", "[nvs]") TEST_ESP_OK(storage.readItem(1, nvs::ItemType::SZ, "foobaar", buf, sizeof(buf))); CHECK(memcmp(buf, str, strlen(str) + 1) == 0); } - s_perf << "Time to write one string and one integer a thousand times: " << esp_partition_get_total_time() << " us (" << esp_partition_get_erase_ops() << " " << esp_partition_get_write_ops() << " " << esp_partition_get_read_ops() << " " << esp_partition_get_write_bytes() << " " << esp_partition_get_read_bytes() << ")" << std::endl; + s_perf << "Time to write one string and one integer 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; } TEST_CASE("can get length of variable length data", "[nvs]") { - PartitionEmulationFixture f(0, 8); - f.randomize(200); - nvs::Storage storage(f.part()); - TEMPORARILY_DISABLED(f.emu.setBounds(4, 8);) - TEST_ESP_OK(storage.init(4, 4)); + // TC verifies that Storage.getItemDataSize works with variable length data. + // The test verifies following: + // - getting the size of a string and blob works correctly and independently between namespaces + + NVSPartitionTestHelper h(TEST_DEFAULT_PARTITION_NAME); + + TEST_ESP_OK(NVSPartitionTestHelper::randomize_partition(TEST_DEFAULT_PARTITION_NAME, 200)); + + nvs::Storage storage(&h); + + TEST_ESP_OK(storage.init(0, h.get_sectors())); + const char str[] = "foobar1234foobar1234foobar1234foobar1234foobar1234foobar1234foobar1234foobar1234"; size_t len = strlen(str); - TEST_ESP_OK(storage.writeItem(1, nvs::ItemType::SZ, "foobaar", str, len + 1)); + TEST_ESP_OK(storage.writeItem(1, nvs::ItemType::SZ, "foobaar", str, len + 1)); // namespace 1 size_t dataSize; - TEST_ESP_OK(storage.getItemDataSize(1, nvs::ItemType::SZ, "foobaar", 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)); - TEST_ESP_OK(storage.getItemDataSize(2, nvs::ItemType::BLOB, "foobaar", dataSize)); + TEST_ESP_OK(storage.writeItem(2, nvs::ItemType::BLOB, "foobaar", str, len)); // namespace 2 + TEST_ESP_OK(storage.getItemDataSize(2, nvs::ItemType::BLOB, "foobaar", dataSize)); // namespace 2 CHECK(dataSize == len); } TEST_CASE("can create namespaces", "[nvs]") { - PartitionEmulationFixture f(0, 8); - nvs::Storage storage(f.part()); - TEMPORARILY_DISABLED(f.emu.setBounds(4, 8);) - TEST_ESP_OK(storage.init(4, 4)); + // TC verifies that Storage.createOrOpenNamespace works as expected. + // The test verifies following: + // - creating a namespace fails if it doesn't exist and parameter allowing it's creation is false + // - creating a namespace works if it doesn't exist and parameter allowing it's creation is true + + NVSPartitionTestHelper h(TEST_DEFAULT_PARTITION_NAME); + + nvs::Storage storage(&h); + + TEST_ESP_OK(storage.init(0, h.get_sectors())); uint8_t nsi; CHECK(storage.createOrOpenNamespace("wifi", false, nsi) == ESP_ERR_NVS_NOT_FOUND); TEST_ESP_OK(storage.createOrOpenNamespace("wifi", true, nsi)); nvs::Page page; - TEST_ESP_OK(page.load(f.part(), 4)); + TEST_ESP_OK(page.load(&h, 0)); TEST_ESP_OK(page.findItem(nvs::Page::NS_INDEX, nvs::ItemType::U8, "wifi")); } TEST_CASE("storage may become full", "[nvs]") { - PartitionEmulationFixture f(0, 8); - nvs::Storage storage(f.part()); - TEMPORARILY_DISABLED(f.emu.setBounds(4, 8);) - TEST_ESP_OK(storage.init(4, 4)); - for (size_t i = 0; i < nvs::Page::ENTRY_COUNT * 3; ++i) { + // TC verifies that Storage.writeItem returns ESP_ERR_NVS_NOT_ENOUGH_SPACE when the storage is full. + // The test fills the storage with items until it becomes full. + // The test verifies following: + // - writing an item when the storage is not full works + // - writing an item when the storage is full returns ESP_ERR_NVS_NOT_ENOUGH_SPACE + + NVSPartitionTestHelper h(TEST_DEFAULT_PARTITION_NAME); + + nvs::Storage storage(&h); + + TEST_ESP_OK(storage.init(0, h.get_sectors())); + + // Calculate how many items can be written to the storage. + // One page (sector) has to be reserved for space reclaim operations, + size_t max_items = nvs::Page::ENTRY_COUNT * (h.get_sectors() - 1); + + 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(i)); TEST_ESP_OK(storage.writeItem(1, name, static_cast(i))); @@ -487,54 +643,103 @@ TEST_CASE("storage may become full", "[nvs]") REQUIRE(storage.writeItem(1, "foo", 10) == ESP_ERR_NVS_NOT_ENOUGH_SPACE); } -TEST_CASE("can modify an item on a page which will be erased", "[nvs]") +TEST_CASE("can reuse the space previously occupied by the overwritten item", "[nvs]") { - PartitionEmulationFixture f(0, 8); - nvs::Storage storage(f.part()); - TEST_ESP_OK(storage.init(0, 2)); - for (size_t i = 0; i < nvs::Page::ENTRY_COUNT * 3 + 1; ++i) { + // TC verifies that Storage.writeItem will reuse the space previously occupied by the overwritten item. + // The test fills the complete storage with items having same key until it becomes full. + // Then it writes one more item which may not fail as all previously overwritten items were + // 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); + + nvs::Storage storage(&h); + TEST_ESP_OK(storage.init(0, h.get_sectors())); + + // Calculate how many items can be written to the storage. + // One page (sector) has to be reserved for space reclaim operations, + size_t max_usable_items = nvs::Page::ENTRY_COUNT * (h.get_sectors() - 1); + // Add one more item to the count + max_usable_items += 1; + + for (size_t i = 0; i < max_usable_items; ++i) { TEST_ESP_OK(storage.writeItem(1, "foo", 42U)); } } TEST_CASE("erase operations are distributed among sectors", "[nvs]") { - const size_t sectors = 6; - PartitionEmulationFixture f(0, sectors); - nvs::Storage storage(f.part()); - TEST_ESP_OK(storage.init(0, sectors)); + // This TC verifies inherent wear levelling of the NVS storage. + // The test fills the storage with static items occupying 2 sectors, then performs many re-write operations + // on the same item, and finally checks that erase counts are distributed among the remaining sectors. - /* Reset statistics */ - esp_partition_clear_stats(); + NVSPartitionTestHelper h(TEST_DEFAULT_PARTITION_NAME); - /* Fill some part of storage with static values */ - const size_t static_sectors = 2; - for (size_t i = 0; i < static_sectors * nvs::Page::ENTRY_COUNT; ++i) { + // Test parameters + const size_t static_pages = 2; // Number of NVS pages to fill with stable items + const size_t erase_count = 4; // Preset number of required erase cycles on NVS pages containing dynamic items + + // One page is reserved for space reclaim operations + size_t all_pages = h.get_sectors(); + size_t usable_pages = all_pages -1; + REQUIRE(usable_pages > static_pages); + + nvs::Storage storage(&h); + TEST_ESP_OK(storage.init(0, all_pages)); + + // Reset statistics + NVSPartitionTestHelper::clear_stats(); + + // Fill some part of storage with static values + + 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)); } - /* Now perform many write operations */ - const size_t write_ops = 2000; + // 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)); } - /* Check that erase counts are distributed among the remaining sectors */ - const size_t max_erase_cnt = write_ops / nvs::Page::ENTRY_COUNT / (sectors - static_sectors) + 1; - for (size_t i = 0; i < sectors; ++i) { - auto erase_cnt = esp_partition_get_sector_erase_count(i); - INFO("Sector " << i << " erased " << erase_cnt); - CHECK(erase_cnt <= max_erase_cnt); + const size_t max_erase_cnt = write_ops / nvs::Page::ENTRY_COUNT / (all_pages - static_pages); + + for (size_t i = 0; i < all_pages; ++i) { + + auto erase_cnt = h.get_sector_erase_count(i); + INFO("Page " << i << " erased " << erase_cnt); + + // If the page is within the static pages, it should not be erased + if (i < static_pages) { + CHECK(erase_cnt == 0); + continue; + } + + // Pages with dynamic items should be erased at most max_erase_cnt times, may be one less + auto delta = max_erase_cnt - erase_cnt; + CHECK(delta <= 1); } } TEST_CASE("can erase items", "[nvs]") { - PartitionEmulationFixture f(0, 8); - nvs::Storage storage(f.part()); - TEST_ESP_OK(storage.init(0, 3)); + // TC verifies that Storage.eraseItem works as expected. + // The test verifies following: + // - erasing an item works + // - reading an erased item returns ESP_ERR_NVS_NOT_FOUND + // - erasing a namespace works + // - reading an item from erased namespace returns ESP_ERR_NVS_NOT_FOUND + // - reading an item from another namespace works + // - namespace id is respected in eraseItem and readItem + + NVSPartitionTestHelper h(TEST_DEFAULT_PARTITION_NAME); + + nvs::Storage storage(&h); + TEST_ESP_OK(storage.init(0, h.get_sectors())); + + // Fill the storage with some items 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(i)); @@ -553,64 +758,67 @@ TEST_CASE("can erase items", "[nvs]") TEST_CASE("readonly handle fails on writing", "[nvs]") { - PartitionEmulationFixture f(0, 10); + // TC verifies that readonly handle fails on writing. + // The test verifies following: + // - creating a namespace in read-write mode creates it + // - opening a namespace in read-only mode fails on writing + + // Clear the partition before testing + TEST_ESP_OK(nvs_flash_erase_partition(TEST_3SEC_PARTITION_NAME)); + const char *str = "value 0123456789abcdef0123456789abcdef"; const uint8_t blob[8] = {0x0, 0x1, 0x2, 0x3, 0x4, 0x5, 0x6, 0x7}; - nvs_handle_t handle_1; - const uint32_t NVS_FLASH_SECTOR = 6; - const uint32_t NVS_FLASH_SECTOR_COUNT_MIN = 3; - TEMPORARILY_DISABLED(f.emu.setBounds(NVS_FLASH_SECTOR, NVS_FLASH_SECTOR + NVS_FLASH_SECTOR_COUNT_MIN);) - TEST_ESP_OK(nvs::NVSPartitionManager::get_instance()->init_custom(f.part(), - NVS_FLASH_SECTOR, - NVS_FLASH_SECTOR_COUNT_MIN)); + TEST_ESP_OK(nvs_flash_init_partition(TEST_3SEC_PARTITION_NAME)); // first, creating namespace... - TEST_ESP_OK(nvs_open("ro_ns", NVS_READWRITE, &handle_1)); + TEST_ESP_OK(nvs_open_from_partition(TEST_3SEC_PARTITION_NAME, "ro_ns", NVS_READWRITE, &handle_1)); nvs_close(handle_1); - TEST_ESP_OK(nvs_open("ro_ns", NVS_READONLY, &handle_1)); + TEST_ESP_OK(nvs_open_from_partition(TEST_3SEC_PARTITION_NAME, "ro_ns", NVS_READONLY, &handle_1)); TEST_ESP_ERR(nvs_set_i32(handle_1, "key", 47), ESP_ERR_NVS_READ_ONLY); TEST_ESP_ERR(nvs_set_str(handle_1, "key", str), ESP_ERR_NVS_READ_ONLY); TEST_ESP_ERR(nvs_set_blob(handle_1, "key", blob, 8), ESP_ERR_NVS_READ_ONLY); nvs_close(handle_1); - // without deinit it affects "nvs api tests" - TEST_ESP_OK(nvs_flash_deinit_partition(NVS_DEFAULT_PART_NAME)); + TEST_ESP_OK(nvs_flash_deinit_partition(TEST_3SEC_PARTITION_NAME)); } TEST_CASE("nvs api tests", "[nvs]") { - PartitionEmulationFixture f(0, 10); - f.randomize(100); + // TC verifies that NVS API works as expected. + + // Fill the partition with random data before testing + TEST_ESP_OK(NVSPartitionTestHelper::randomize_partition(TEST_3SEC_PARTITION_NAME, 100)); nvs_handle_t handle_1; - const uint32_t NVS_FLASH_SECTOR = 6; - const uint32_t NVS_FLASH_SECTOR_COUNT_MIN = 3; - TEMPORARILY_DISABLED(f.emu.setBounds(NVS_FLASH_SECTOR, NVS_FLASH_SECTOR + NVS_FLASH_SECTOR_COUNT_MIN);) - TEST_ESP_ERR(nvs_open("namespace1", NVS_READWRITE, &handle_1), ESP_ERR_NVS_NOT_INITIALIZED); - for (uint16_t i = NVS_FLASH_SECTOR; i < NVS_FLASH_SECTOR + NVS_FLASH_SECTOR_COUNT_MIN; ++i) { - f.erase(i); - } - TEST_ESP_OK(nvs::NVSPartitionManager::get_instance()->init_custom(f.part(), - NVS_FLASH_SECTOR, - NVS_FLASH_SECTOR_COUNT_MIN)); + // Opening a namespace before initializing NVS should fail + TEST_ESP_ERR(nvs_open_from_partition(TEST_3SEC_PARTITION_NAME, "namespace1", NVS_READWRITE, &handle_1), ESP_ERR_NVS_NOT_INITIALIZED); - TEST_ESP_ERR(nvs_open("namespace1", NVS_READONLY, &handle_1), ESP_ERR_NVS_NOT_FOUND); + // Clear the partition before initializing NVS + TEST_ESP_OK(nvs_flash_erase_partition(TEST_3SEC_PARTITION_NAME)); - TEST_ESP_OK(nvs_open("namespace1", NVS_READWRITE, &handle_1)); + // Initialize NVS partition + TEST_ESP_OK(nvs_flash_init_partition(TEST_3SEC_PARTITION_NAME)); + + // Now opening a non existent namespace in read-only mode should fail + TEST_ESP_ERR(nvs_open_from_partition(TEST_3SEC_PARTITION_NAME, "namespace1", NVS_READONLY, &handle_1), ESP_ERR_NVS_NOT_FOUND); + + // Opening a non existent namespace in read-write mode should create it + TEST_ESP_OK(nvs_open_from_partition(TEST_3SEC_PARTITION_NAME, "namespace1", NVS_READWRITE, &handle_1)); TEST_ESP_OK(nvs_set_i32(handle_1, "foo", 0x12345678)); TEST_ESP_OK(nvs_set_i32(handle_1, "foo", 0x23456789)); nvs_handle_t handle_2; - TEST_ESP_OK(nvs_open("namespace2", NVS_READWRITE, &handle_2)); + TEST_ESP_OK(nvs_open_from_partition(TEST_3SEC_PARTITION_NAME, "namespace2", NVS_READWRITE, &handle_2)); TEST_ESP_OK(nvs_set_i32(handle_2, "foo", 0x3456789a)); char str[] = "value 0123456789abcdef0123456789abcdef"; TEST_ESP_OK(nvs_set_str(handle_2, "key", str)); + // Now we can read the values back int32_t v1; TEST_ESP_OK(nvs_get_i32(handle_1, "foo", &v1)); CHECK(0x23456789 == v1); @@ -622,59 +830,65 @@ TEST_CASE("nvs api tests", "[nvs]") char buf[sizeof(str)]; size_t buf_len = sizeof(buf); + // get str returns the length of the string when the buffer is NULL size_t buf_len_needed; TEST_ESP_OK(nvs_get_str(handle_2, "key", NULL, &buf_len_needed)); CHECK(buf_len_needed == buf_len); + // attempt to get string into the buffer which is too short should fail size_t buf_len_short = buf_len - 1; TEST_ESP_ERR(ESP_ERR_NVS_INVALID_LENGTH, nvs_get_str(handle_2, "key", buf, &buf_len_short)); CHECK(buf_len_short == buf_len); + // attempt to get string into the buffer which is larger than the value should succeed size_t buf_len_long = buf_len + 1; TEST_ESP_OK(nvs_get_str(handle_2, "key", buf, &buf_len_long)); CHECK(buf_len_long == buf_len); + // attempt to get string into the buffer which matches the size of the value should succeed TEST_ESP_OK(nvs_get_str(handle_2, "key", buf, &buf_len)); CHECK(0 == strcmp(buf, str)); nvs_close(handle_1); nvs_close(handle_2); - TEST_ESP_OK(nvs_flash_deinit_partition(NVS_DEFAULT_PART_NAME)); + TEST_ESP_OK(nvs_flash_deinit_partition(TEST_3SEC_PARTITION_NAME)); } TEST_CASE("deinit partition doesn't affect other partition's open handles", "[nvs]") { - const char *OTHER_PARTITION_NAME = "other_part"; - PartitionEmulationFixture f(0, 10); - PartitionEmulationFixture f_other(0, 10, OTHER_PARTITION_NAME); + // TC verifies that deinitializing one NVS partition doesn't affect open handles in another partition. + // The test verifies following: + // - deinitializing one partition doesn't affect open handles in another partition + // - writing to the open handle in the other partition works after deinitialization of the first partition + + // Clear both partitions before initializing NVS + TEST_ESP_OK(nvs_flash_erase_partition(TEST_3SEC_PARTITION_NAME)); + TEST_ESP_OK(nvs_flash_erase_partition(TEST_SECONDARY_PARTITION_NAME)); + + + // Initialize both NVS partitions + TEST_ESP_OK(nvs_flash_init_partition(TEST_3SEC_PARTITION_NAME)); + TEST_ESP_OK(nvs_flash_init_partition(TEST_SECONDARY_PARTITION_NAME)); nvs_handle_t handle_1; - const uint32_t NVS_FLASH_SECTOR = 6; - const uint32_t NVS_FLASH_SECTOR_COUNT_MIN = 3; - TEMPORARILY_DISABLED(f.emu.setBounds(NVS_FLASH_SECTOR, NVS_FLASH_SECTOR + NVS_FLASH_SECTOR_COUNT_MIN);) - TEMPORARILY_DISABLED(f_other.emu.setBounds(NVS_FLASH_SECTOR, NVS_FLASH_SECTOR + NVS_FLASH_SECTOR_COUNT_MIN);) - TEST_ESP_OK(nvs::NVSPartitionManager::get_instance()->init_custom(f.part(), - NVS_FLASH_SECTOR, - NVS_FLASH_SECTOR_COUNT_MIN)); - TEST_ESP_OK(nvs::NVSPartitionManager::get_instance()->init_custom(f_other.part(), - NVS_FLASH_SECTOR, - NVS_FLASH_SECTOR_COUNT_MIN)); - - TEST_ESP_OK(nvs_open_from_partition(OTHER_PARTITION_NAME, "ns", NVS_READWRITE, &handle_1)); + // Open a namespace in the secondary partition + TEST_ESP_OK(nvs_open_from_partition(TEST_SECONDARY_PARTITION_NAME, "ns", NVS_READWRITE, &handle_1)); // Deinitializing must not interfere with the open handle from the other partition. - TEST_ESP_OK(nvs_flash_deinit_partition(NVS_DEFAULT_PART_NAME)); + TEST_ESP_OK(nvs_flash_deinit_partition(TEST_3SEC_PARTITION_NAME)); TEST_ESP_OK(nvs_set_i32(handle_1, "foo", 0x3456789a)); nvs_close(handle_1); - TEST_ESP_OK(nvs_flash_deinit_partition(OTHER_PARTITION_NAME)); + TEST_ESP_OK(nvs_flash_deinit_partition(TEST_SECONDARY_PARTITION_NAME)); } TEST_CASE("nvs iterator nvs_entry_find invalid parameter test", "[nvs]") { + // TC verifies that nvs_entry_find returns ESP_ERR_INVALID_ARG on invalid parameters. + nvs_iterator_t it = reinterpret_cast(0xbeef); CHECK(nvs_entry_find(nullptr, NULL, NVS_TYPE_ANY, &it) == ESP_ERR_INVALID_ARG); CHECK(nvs_entry_find("nvs", NULL, NVS_TYPE_ANY, nullptr) == ESP_ERR_INVALID_ARG); @@ -682,6 +896,10 @@ TEST_CASE("nvs iterator nvs_entry_find invalid parameter test", "[nvs]") TEST_CASE("nvs iterator nvs_entry_find doesn't change iterator on parameter error", "[nvs]") { + // TC verifies that nvs_entry_find doesn't change iterator on parameter error. + // The test verifies following: + // - if the iterator is not NULL, it should not be changed on parameter error + // - if the iterator is NULL, it should remain NULL on parameter error nvs_iterator_t it = reinterpret_cast(0xbeef); REQUIRE(nvs_entry_find(nullptr, NULL, NVS_TYPE_ANY, &it) == ESP_ERR_INVALID_ARG); CHECK(it == reinterpret_cast(0xbeef)); @@ -693,11 +911,20 @@ TEST_CASE("nvs iterator nvs_entry_find doesn't change iterator on parameter erro TEST_CASE("nvs_entry_next return ESP_ERR_INVALID_ARG on parameter is NULL", "[nvs]") { + // TC verifies that nvs_entry_next returns ESP_ERR_INVALID_ARG on NULL iterator. + // The test verifies following: + // - if the iterator is NULL, nvs_entry_next should return ESP_ERR_INVALID_ARG + CHECK(nvs_entry_next(nullptr) == ESP_ERR_INVALID_ARG); } TEST_CASE("nvs_entry_info fails with ESP_ERR_INVALID_ARG if a parameter is NULL", "[nvs]") { + // TC verifies that nvs_entry_info returns ESP_ERR_INVALID_ARG on NULL parameters. + // The test verifies following: + // - if the iterator is NULL, nvs_entry_info should return ESP_ERR_INVALID_ARG + // - if the info pointer is NULL, nvs_entry_info should return ESP_ERR_INVALID + nvs_iterator_t it = reinterpret_cast(0xbeef); nvs_entry_info_t info; CHECK(nvs_entry_info(it, nullptr) == ESP_ERR_INVALID_ARG); @@ -706,6 +933,11 @@ TEST_CASE("nvs_entry_info fails with ESP_ERR_INVALID_ARG if a parameter is NULL" TEST_CASE("nvs_entry_info doesn't change iterator on parameter error", "[nvs]") { + // TC verifies that nvs_entry_info doesn't change iterator on parameter error. + // The test verifies following: + // - if the iterator is not NULL and the info pointer is NULL, iterator should not be changed + // - if the iterator is NULL and the info pointer is not NULL, iterator should remain NULL + nvs_iterator_t it = reinterpret_cast(0xbeef); REQUIRE(nvs_entry_info(it, nullptr) == ESP_ERR_INVALID_ARG); CHECK(it == reinterpret_cast(0xbeef)); @@ -717,18 +949,11 @@ TEST_CASE("nvs_entry_info doesn't change iterator on parameter error", "[nvs]") TEST_CASE("nvs iterators tests", "[nvs]") { - PartitionEmulationFixture f(0, 5); + // Clear the partition before testing + TEST_ESP_OK(nvs_flash_erase_partition(TEST_DEFAULT_PARTITION_NAME)); - const uint32_t NVS_FLASH_SECTOR = 0; - const uint32_t NVS_FLASH_SECTOR_COUNT_MIN = 5; - TEMPORARILY_DISABLED(f.emu.setBounds(NVS_FLASH_SECTOR, NVS_FLASH_SECTOR + NVS_FLASH_SECTOR_COUNT_MIN);) - - for (uint16_t i = NVS_FLASH_SECTOR; i < NVS_FLASH_SECTOR + NVS_FLASH_SECTOR_COUNT_MIN; ++i) { - f.erase(i); - } - TEST_ESP_OK(nvs::NVSPartitionManager::get_instance()->init_custom(f.part(), - NVS_FLASH_SECTOR, - NVS_FLASH_SECTOR_COUNT_MIN)); + // Initialize NVS partition + TEST_ESP_OK(nvs_flash_init_partition(TEST_DEFAULT_PARTITION_NAME)); nvs_iterator_t it; nvs_entry_info_t info; @@ -737,8 +962,9 @@ TEST_CASE("nvs iterators tests", "[nvs]") const uint32_t blob = 0x11223344; const char *name_1 = "namespace1"; const char *name_2 = "namespace2"; - TEST_ESP_OK(nvs_open(name_1, NVS_READWRITE, &handle_1)); - TEST_ESP_OK(nvs_open(name_2, NVS_READWRITE, &handle_2)); + + TEST_ESP_OK(nvs_open_from_partition(TEST_DEFAULT_PARTITION_NAME, name_1, NVS_READWRITE, &handle_1)); + TEST_ESP_OK(nvs_open_from_partition(TEST_DEFAULT_PARTITION_NAME, name_2, NVS_READWRITE, &handle_2)); TEST_ESP_OK(nvs_set_i8(handle_1, "value1", -11)); TEST_ESP_OK(nvs_set_u8(handle_1, "value2", 11)); @@ -789,43 +1015,43 @@ TEST_CASE("nvs iterators tests", "[nvs]") } SECTION("No matching namespace found return ESP_ERR_NVS_NOT_FOUND") { - CHECK(nvs_entry_find(NVS_DEFAULT_PART_NAME, "nonexistent", NVS_TYPE_ANY, &it) == ESP_ERR_NVS_NOT_FOUND); + CHECK(nvs_entry_find(TEST_DEFAULT_PARTITION_NAME, "nonexistent", NVS_TYPE_ANY, &it) == ESP_ERR_NVS_NOT_FOUND); } SECTION("nvs_entry_find sets iterator to null if no matching element found") { it = reinterpret_cast(0xbeef); - REQUIRE(nvs_entry_find(NVS_DEFAULT_PART_NAME, "nonexistent", NVS_TYPE_I16, &it) == ESP_ERR_NVS_NOT_FOUND); + REQUIRE(nvs_entry_find(TEST_DEFAULT_PARTITION_NAME, "nonexistent", NVS_TYPE_I16, &it) == ESP_ERR_NVS_NOT_FOUND); CHECK(it == nullptr); } SECTION("Finding iterator means iterator is valid") { it = nullptr; - TEST_ESP_OK(nvs_entry_find(NVS_DEFAULT_PART_NAME, nullptr, NVS_TYPE_ANY, &it)); + TEST_ESP_OK(nvs_entry_find(TEST_DEFAULT_PARTITION_NAME, nullptr, NVS_TYPE_ANY, &it)); CHECK(it != nullptr); nvs_release_iterator(it); } SECTION("Return ESP_ERR_NVS_NOT_FOUND after iterating over last matching element") { it = nullptr; - REQUIRE(nvs_entry_find(NVS_DEFAULT_PART_NAME, name_1, NVS_TYPE_I16, &it) == ESP_OK); + REQUIRE(nvs_entry_find(TEST_DEFAULT_PARTITION_NAME, name_1, NVS_TYPE_I16, &it) == ESP_OK); REQUIRE(it != nullptr); CHECK(nvs_entry_next(&it) == ESP_ERR_NVS_NOT_FOUND); } SECTION("Set iterator to NULL after iterating over last matching element") { it = nullptr; - REQUIRE(nvs_entry_find(NVS_DEFAULT_PART_NAME, name_1, NVS_TYPE_I16, &it) == ESP_OK); + REQUIRE(nvs_entry_find(TEST_DEFAULT_PARTITION_NAME, name_1, NVS_TYPE_I16, &it) == ESP_OK); REQUIRE(it != nullptr); REQUIRE(nvs_entry_next(&it) == ESP_ERR_NVS_NOT_FOUND); CHECK(it == nullptr); } SECTION("Number of entries found for specified namespace and type is correct") { - CHECK(entry_count(NVS_DEFAULT_PART_NAME, NULL, NVS_TYPE_ANY) == 15); - CHECK(entry_count(NVS_DEFAULT_PART_NAME, name_1, NVS_TYPE_ANY) == 11); - CHECK(entry_count(NVS_DEFAULT_PART_NAME, name_1, NVS_TYPE_I32) == 3); - CHECK(entry_count(NVS_DEFAULT_PART_NAME, NULL, NVS_TYPE_I32) == 5); - CHECK(entry_count(NVS_DEFAULT_PART_NAME, NULL, NVS_TYPE_U64) == 1); + CHECK(entry_count(TEST_DEFAULT_PARTITION_NAME, NULL, NVS_TYPE_ANY) == 15); + CHECK(entry_count(TEST_DEFAULT_PARTITION_NAME, name_1, NVS_TYPE_ANY) == 11); + CHECK(entry_count(TEST_DEFAULT_PARTITION_NAME, name_1, NVS_TYPE_I32) == 3); + CHECK(entry_count(TEST_DEFAULT_PARTITION_NAME, NULL, NVS_TYPE_I32) == 5); + CHECK(entry_count(TEST_DEFAULT_PARTITION_NAME, NULL, NVS_TYPE_U64) == 1); } SECTION("Number of entries found for specified handle and type is correct") { @@ -837,20 +1063,20 @@ TEST_CASE("nvs iterators tests", "[nvs]") } SECTION("New entry is not created when existing key-value pair is set") { - CHECK(entry_count(NVS_DEFAULT_PART_NAME, name_2, NVS_TYPE_ANY) == 4); + CHECK(entry_count(TEST_DEFAULT_PARTITION_NAME, name_2, NVS_TYPE_ANY) == 4); TEST_ESP_OK(nvs_set_i32(handle_2, "value1", -222)); - CHECK(entry_count(NVS_DEFAULT_PART_NAME, name_2, NVS_TYPE_ANY) == 4); + CHECK(entry_count(TEST_DEFAULT_PARTITION_NAME, name_2, NVS_TYPE_ANY) == 4); } SECTION("Number of entries found decrease when entry is erased") { - CHECK(entry_count(NVS_DEFAULT_PART_NAME, NULL, NVS_TYPE_U64) == 1); + CHECK(entry_count(TEST_DEFAULT_PARTITION_NAME, NULL, NVS_TYPE_U64) == 1); TEST_ESP_OK(nvs_erase_key(handle_2, "value4")); - CHECK(entry_count(NVS_DEFAULT_PART_NAME, "", NVS_TYPE_U64) == 0); + CHECK(entry_count(TEST_DEFAULT_PARTITION_NAME, "", NVS_TYPE_U64) == 0); } SECTION("All fields of nvs_entry_info_t structure are correct") { it = nullptr; - esp_err_t res = nvs_entry_find(NVS_DEFAULT_PART_NAME, name_1, NVS_TYPE_I32, &it); + esp_err_t res = nvs_entry_find(TEST_DEFAULT_PARTITION_NAME, name_1, NVS_TYPE_I32, &it); REQUIRE(res == ESP_OK); string key = "value5"; while (res == ESP_OK) { @@ -872,7 +1098,7 @@ TEST_CASE("nvs iterators tests", "[nvs]") SECTION("Entry info is not affected by subsequent erase") { nvs_entry_info_t info_after_erase; - TEST_ESP_OK(nvs_entry_find(NVS_DEFAULT_PART_NAME, name_1, NVS_TYPE_ANY, &it)); + TEST_ESP_OK(nvs_entry_find(TEST_DEFAULT_PARTITION_NAME, name_1, NVS_TYPE_ANY, &it)); REQUIRE(nvs_entry_info(it, &info) == ESP_OK); TEST_ESP_OK(nvs_erase_key(handle_1, "value1")); REQUIRE(nvs_entry_info(it, &info_after_erase) == ESP_OK); @@ -883,7 +1109,7 @@ TEST_CASE("nvs iterators tests", "[nvs]") SECTION("Entry info is not affected by subsequent set") { nvs_entry_info_t info_after_set; - TEST_ESP_OK(nvs_entry_find(NVS_DEFAULT_PART_NAME, name_1, NVS_TYPE_ANY, &it)); + TEST_ESP_OK(nvs_entry_find(TEST_DEFAULT_PARTITION_NAME, name_1, NVS_TYPE_ANY, &it)); REQUIRE(nvs_entry_info(it, &info) == ESP_OK); TEST_ESP_OK(nvs_set_u8(handle_1, info.key, 44)); REQUIRE(nvs_entry_info(it, &info_after_set) == ESP_OK); @@ -896,14 +1122,14 @@ TEST_CASE("nvs iterators tests", "[nvs]") const char *name_3 = "namespace3"; const int entries_created = 250; - TEST_ESP_OK(nvs_open(name_3, NVS_READWRITE, &handle_3)); + TEST_ESP_OK(nvs_open_from_partition(TEST_DEFAULT_PARTITION_NAME, name_3, NVS_READWRITE, &handle_3)); for (size_t i = 0; i < entries_created; i++) { TEST_ESP_OK(nvs_set_u8(handle_3, to_string(i).c_str(), 123)); } int entries_found = 0; it = nullptr; - esp_err_t res = nvs_entry_find(NVS_DEFAULT_PART_NAME, name_3, NVS_TYPE_ANY, &it); + esp_err_t res = nvs_entry_find(TEST_DEFAULT_PARTITION_NAME, name_3, NVS_TYPE_ANY, &it); while (res == ESP_OK) { entries_found++; res = nvs_entry_next(&it); @@ -923,12 +1149,12 @@ TEST_CASE("nvs iterators tests", "[nvs]") const int NUMBER_OF_ENTRIES_PER_PAGE = 125; size_t occupied_entries; - TEST_ESP_OK(nvs_open(name_3, NVS_READWRITE, &handle_3)); + TEST_ESP_OK(nvs_open_from_partition(TEST_DEFAULT_PARTITION_NAME, name_3, NVS_READWRITE, &handle_3)); TEST_ESP_OK(nvs_set_blob(handle_3, "blob", multipage_blob, sizeof(multipage_blob))); TEST_ESP_OK(nvs_get_used_entry_count(handle_3, &occupied_entries)); CHECK(occupied_entries > NUMBER_OF_ENTRIES_PER_PAGE * 2); - CHECK(entry_count(NVS_DEFAULT_PART_NAME, name_3, NVS_TYPE_BLOB) == 1); + CHECK(entry_count(TEST_DEFAULT_PARTITION_NAME, name_3, NVS_TYPE_BLOB) == 1); nvs_close(handle_3); } @@ -936,70 +1162,76 @@ TEST_CASE("nvs iterators tests", "[nvs]") nvs_close(handle_1); nvs_close(handle_2); - TEST_ESP_OK(nvs_flash_deinit_partition(NVS_DEFAULT_PART_NAME)); + TEST_ESP_OK(nvs_flash_deinit_partition(TEST_DEFAULT_PARTITION_NAME)); } TEST_CASE("Iterator with not matching type iterates correctly", "[nvs]") { - PartitionEmulationFixture f(0, 5); + // TC verifies that nvs_entry_find iterates correctly when the type doesn't match. + // The test verifies following: + // - nvs_entry_find with type NVS_TYPE_I32 returns ESP_ERR_NVS_NOT_FOUND if no entry with that type exists + // - nvs_entry_find with type NVS_TYPE_STR returns ESP_OK if an entry with that type exists + // - after writing nvs_entry with type NVS_TYPE_I32 nvs_entry_find with type NVS_TYPE_I32 returns ESP_OK + + // Clear the partition before testing + TEST_ESP_OK(nvs_flash_erase_partition(TEST_DEFAULT_PARTITION_NAME)); + + // Initialize NVS partition + TEST_ESP_OK(nvs_flash_init_partition(TEST_DEFAULT_PARTITION_NAME)); + nvs_iterator_t it; nvs_handle_t my_handle; const char *NAMESPACE = "test_ns_4"; - const uint32_t NVS_FLASH_SECTOR = 0; - const uint32_t NVS_FLASH_SECTOR_COUNT_MIN = 5; - TEMPORARILY_DISABLED(f.emu.setBounds(NVS_FLASH_SECTOR, NVS_FLASH_SECTOR + NVS_FLASH_SECTOR_COUNT_MIN);) - - for (uint16_t i = NVS_FLASH_SECTOR; i < NVS_FLASH_SECTOR + NVS_FLASH_SECTOR_COUNT_MIN; ++i) { - f.erase(i); - } - TEST_ESP_OK(nvs::NVSPartitionManager::get_instance()->init_custom(f.part(), - NVS_FLASH_SECTOR, - NVS_FLASH_SECTOR_COUNT_MIN)); - // writing string to namespace (a type which spans multiple entries) - TEST_ESP_OK(nvs_open(NAMESPACE, NVS_READWRITE, &my_handle)); + TEST_ESP_OK(nvs_open_from_partition(TEST_DEFAULT_PARTITION_NAME, NAMESPACE, NVS_READWRITE, &my_handle)); TEST_ESP_OK(nvs_set_str(my_handle, "test-string", "InitString0")); TEST_ESP_OK(nvs_commit(my_handle)); nvs_close(my_handle); - CHECK(nvs_entry_find(NVS_DEFAULT_PART_NAME, NAMESPACE, NVS_TYPE_I32, &it) == ESP_ERR_NVS_NOT_FOUND); + // attempt to find an entry with type NVS_TYPE_I32 should return ESP_ERR_NVS_NOT_FOUND + CHECK(nvs_entry_find(TEST_DEFAULT_PARTITION_NAME, NAMESPACE, NVS_TYPE_I32, &it) == ESP_ERR_NVS_NOT_FOUND); - // re-init to trigger cleaning up of broken items -> a corrupted string will be erased - TEST_ESP_OK(nvs_flash_deinit()); - TEST_ESP_OK(nvs::NVSPartitionManager::get_instance()->init_custom(f.part(), - NVS_FLASH_SECTOR, - NVS_FLASH_SECTOR_COUNT_MIN)); - - TEST_ESP_OK(nvs_entry_find(NVS_DEFAULT_PART_NAME, NAMESPACE, NVS_TYPE_STR, &it)); + // attempt to find an entry with type NVS_TYPE_STR should return ESP_OK and iterator should be valid + TEST_ESP_OK(nvs_entry_find(TEST_DEFAULT_PARTITION_NAME, NAMESPACE, NVS_TYPE_STR, &it)); nvs_release_iterator(it); - // without deinit it affects "nvs api tests" - TEST_ESP_OK(nvs_flash_deinit_partition(NVS_DEFAULT_PART_NAME)); + // Write an entry with type NVS_TYPE_I32 + TEST_ESP_OK(nvs_open_from_partition(TEST_DEFAULT_PARTITION_NAME, NAMESPACE, NVS_READWRITE, &my_handle)); + TEST_ESP_OK(nvs_set_i32(my_handle, "test-int32", 42)); + TEST_ESP_OK(nvs_commit(my_handle)); + nvs_close(my_handle); + + // attempt to find an entry with type NVS_TYPE_I32 should return ESP_OK and iterator should be valid + TEST_ESP_OK(nvs_entry_find(TEST_DEFAULT_PARTITION_NAME, NAMESPACE, NVS_TYPE_I32, &it)); + nvs_release_iterator(it); + + TEST_ESP_OK(nvs_flash_deinit_partition(TEST_DEFAULT_PARTITION_NAME)); } TEST_CASE("wifi test", "[nvs]") { - PartitionEmulationFixture f(0, 10); - f.randomize(10); + // TC verifies that NVS API works as expected for WiFi related items. - const uint32_t NVS_FLASH_SECTOR = 5; - const uint32_t NVS_FLASH_SECTOR_COUNT_MIN = 3; - TEMPORARILY_DISABLED(f.emu.setBounds(NVS_FLASH_SECTOR, NVS_FLASH_SECTOR + NVS_FLASH_SECTOR_COUNT_MIN);) - TEST_ESP_OK(nvs::NVSPartitionManager::get_instance()->init_custom(f.part(), - NVS_FLASH_SECTOR, - NVS_FLASH_SECTOR_COUNT_MIN)); + // Fill the partition with random data before testing + TEST_ESP_OK(NVSPartitionTestHelper::randomize_partition(TEST_3SEC_PARTITION_NAME, 10)); + + NVSPartitionTestHelper::clear_stats(); // Clear statistics before init + + // Initialize NVS partition + TEST_ESP_OK(nvs_flash_init_partition(TEST_3SEC_PARTITION_NAME)); nvs_handle_t misc_handle; - TEST_ESP_OK(nvs_open("nvs.net80211", NVS_READWRITE, &misc_handle)); + TEST_ESP_OK(nvs_open_from_partition(TEST_3SEC_PARTITION_NAME, "nvs.net80211", NVS_READWRITE, &misc_handle)); char log[33]; size_t log_size = sizeof(log); TEST_ESP_ERR(nvs_get_str(misc_handle, "log", log, &log_size), ESP_ERR_NVS_NOT_FOUND); strcpy(log, "foobarbazfizzz"); TEST_ESP_OK(nvs_set_str(misc_handle, "log", log)); + nvs_close(misc_handle); nvs_handle_t net80211_handle; - TEST_ESP_OK(nvs_open("nvs.net80211", NVS_READWRITE, &net80211_handle)); + TEST_ESP_OK(nvs_open_from_partition(TEST_3SEC_PARTITION_NAME, "nvs.net80211", NVS_READWRITE, &net80211_handle)); uint8_t opmode = 2; TEST_ESP_ERR(nvs_get_u8(net80211_handle, "wifi.opmode", &opmode), ESP_ERR_NVS_NOT_FOUND); @@ -1116,9 +1348,11 @@ TEST_CASE("wifi test", "[nvs]") TEST_ESP_ERR(nvs_get_u8(net80211_handle, "bcn_interval", &bcn_interval), ESP_ERR_NVS_NOT_FOUND); TEST_ESP_OK(nvs_set_u8(net80211_handle, "bcn_interval", bcn_interval)); - s_perf << "Time to simulate nvs init with wifi libs: " << esp_partition_get_total_time() << " us (" << esp_partition_get_erase_ops() << "E " << esp_partition_get_write_ops() << "W " << esp_partition_get_read_ops() << "R " << esp_partition_get_write_bytes() << "Wb " << esp_partition_get_read_bytes() << "Rb)" << std::endl; + nvs_close(net80211_handle); - TEST_ESP_OK(nvs_flash_deinit_partition(NVS_DEFAULT_PART_NAME)); + s_perf << "Time to simulate nvs init with wifi libs: " << NVSPartitionTestHelper::get_total_time() << " us (" << NVSPartitionTestHelper::get_erase_ops() << "E " << NVSPartitionTestHelper::get_write_ops() << "W " << NVSPartitionTestHelper::get_read_ops() << "R " << NVSPartitionTestHelper::get_write_bytes() << "Wb " << NVSPartitionTestHelper::get_read_bytes() << "Rb)" << std::endl; + + TEST_ESP_OK(nvs_flash_deinit_partition(TEST_3SEC_PARTITION_NAME)); } extern "C" void nvs_dump(const char *partName); @@ -1211,6 +1445,28 @@ public: return true; } } + + // if we are here, the read operation failed or returned unexpected value + auto print_bytes = [](const void* ptr, size_t len) -> std::string { + if (!ptr) return "null"; + std::ostringstream oss; + oss << "0x"; + const uint8_t* bytes = reinterpret_cast(ptr); + for (size_t i = 0; i < len; ++i) { + oss << std::hex << std::setw(2) << std::setfill('0') << static_cast(bytes[i]); + } + return oss.str(); + }; + + std::cout << "Error at delayCount: " << delayCount << std::endl + << "read_result: " << esp_err_to_name(read_result) << std::endl + << "read_type: " << getTypeDesc(read_type) << std::endl + << "written: " << (written ? "true" : "false") << std::endl + << "potentially_written: " << (potentially_written ? "true" : "false") << std::endl + << "len: " << len << std::endl + << "buff: " << print_bytes(buff, len) << std::endl + << "value: " << print_bytes(value, len) << std::endl + << "future_value: " << print_bytes(future_value, len) << std::endl; return false; } @@ -1270,13 +1526,14 @@ public: } else { blobBufLen = largeBlobLen ; } - uint8_t* buf = new uint8_t[blobBufLen]; + + uint8_t* buf = static_cast(malloc(blobBufLen)); memset(buf, 0, blobBufLen); size_t len = blobBufLen; auto err = nvs_get_blob(handle, keys[index], buf, &len); - auto eval_result = evaluate(delayCount, err, types[index], written[index], potentially_written[index], buf, values[index], future_values[index], blobBufLen); - delete [] buf; + auto eval_result = evaluate(delayCount, err, types[index], written[index], potentially_written[index], buf, values[index], future_values[index], len); + free(buf); REQUIRE(eval_result == true); break; @@ -1373,7 +1630,10 @@ public: } else { blobBufLen = largeBlobLen ; } - uint8_t* buf = new uint8_t[blobBufLen]; + uint8_t* buf = static_cast(malloc(blobBufLen)); + if(!buf) { + return ESP_ERR_NO_MEM; + } memset(buf, 0, blobBufLen); size_t blobLen = gen() % blobBufLen; std::generate_n(buf, blobLen, [&]() -> uint8_t { @@ -1388,19 +1648,19 @@ public: if (err == ESP_ERR_FLASH_OP_FAIL) { // mark potentially written potentially_written[index] = true; - delete [] buf; + free(buf); return err; } if (err == ESP_ERR_NVS_REMOVE_FAILED) { written[index] = true; memcpy(reinterpret_cast(values[index]), buf, blobBufLen); - delete [] buf; + free(buf); return ESP_ERR_FLASH_OP_FAIL; } REQUIRE(err == ESP_OK); written[index] = true; memcpy(reinterpret_cast(values[index]), buf, blobBufLen); - delete [] buf; + free(buf); break; } @@ -1431,7 +1691,7 @@ public: esp_err_t handleExternalWriteAtIndex(uint8_t index, const void *value, const size_t len) { - if (index == 9) { /* This is only done for small-page blobs for now*/ + if (index == 9) { // This is only done for small-page blobs for now if (len > smallBlobLen) { return ESP_FAIL; } @@ -1446,60 +1706,71 @@ public: TEST_CASE("monkey test", "[nvs][monkey]") { - std::random_device rd; - std::mt19937 gen(rd()); + // TC verifies that NVS API works as expected for random read/write operations. + // This is very long test, which is intended to be run manually + + // Test parameters + size_t count = 1000; uint32_t seed = 3; - gen.seed(seed); - PartitionEmulationFixture f(0, 10); - f.randomize(seed); - esp_partition_clear_stats(); + // Randomize the partition before testing + TEST_ESP_OK(NVSPartitionTestHelper::randomize_partition(TEST_DEFAULT_PARTITION_NAME, seed)); - const uint32_t NVS_FLASH_SECTOR = 2; - const uint32_t NVS_FLASH_SECTOR_COUNT_MIN = 8; - TEMPORARILY_DISABLED(f.emu.setBounds(NVS_FLASH_SECTOR, NVS_FLASH_SECTOR + NVS_FLASH_SECTOR_COUNT_MIN);) - - TEST_ESP_OK(nvs::NVSPartitionManager::get_instance()->init_custom(f.part(), - NVS_FLASH_SECTOR, - NVS_FLASH_SECTOR_COUNT_MIN)); + // Initialize NVS partition + TEST_ESP_OK(nvs_flash_init_partition(TEST_DEFAULT_PARTITION_NAME)); nvs_handle_t handle; - TEST_ESP_OK(nvs_open("namespace1", NVS_READWRITE, &handle)); + TEST_ESP_OK(nvs_open_from_partition(TEST_DEFAULT_PARTITION_NAME, "namespace1", NVS_READWRITE, &handle)); RandomTest test; - size_t count = 1000; + + std::random_device rd; + std::mt19937 gen(rd()); + gen.seed(seed); + + NVSPartitionTestHelper::clear_stats(); // Clear statistics + TEST_ESP_OK(test.doRandomThings(handle, gen, count)); - s_perf << "Monkey test: nErase=" << esp_partition_get_erase_ops() << " nWrite=" << esp_partition_get_write_ops() << std::endl; + s_perf << "Monkey test: nErase=" << NVSPartitionTestHelper::get_erase_ops() << " nWrite=" << NVSPartitionTestHelper::get_write_ops() << std::endl; - TEST_ESP_OK(nvs_flash_deinit_partition(NVS_DEFAULT_PART_NAME)); + TEST_ESP_OK(nvs_flash_deinit_partition(TEST_DEFAULT_PARTITION_NAME)); } TEST_CASE("test for memory leaks in open/set", "[leaks]") { - PartitionEmulationFixture f(0, 10); - const uint32_t NVS_FLASH_SECTOR = 6; - const uint32_t NVS_FLASH_SECTOR_COUNT_MIN = 3; - TEMPORARILY_DISABLED(f.emu.setBounds(NVS_FLASH_SECTOR, NVS_FLASH_SECTOR + NVS_FLASH_SECTOR_COUNT_MIN);) - TEST_ESP_OK(nvs::NVSPartitionManager::get_instance()->init_custom(f.part(), - NVS_FLASH_SECTOR, - NVS_FLASH_SECTOR_COUNT_MIN)); + // TC verifies that NVS API does not leak memory when opening and setting values. + // The test verifies following: + // - nvs_open and nvs_set_blob are called in a loop + // - nvs_flash_deinit_partition is called at the end of the test + + // Erase the partition before testing + TEST_ESP_OK(nvs_flash_erase_partition(TEST_3SEC_PARTITION_NAME)); + + // Initialize NVS partition + TEST_ESP_OK(nvs_flash_init_partition(TEST_3SEC_PARTITION_NAME)); for (int i = 0; i < 100000; ++i) { nvs_handle_t light_handle = 0; char lightbulb[1024] = {12, 13, 14, 15, 16}; - TEST_ESP_OK(nvs_open("light", NVS_READWRITE, &light_handle)); + TEST_ESP_OK(nvs_open_from_partition(TEST_3SEC_PARTITION_NAME, "light", NVS_READWRITE, &light_handle)); TEST_ESP_OK(nvs_set_blob(light_handle, "key", lightbulb, sizeof(lightbulb))); TEST_ESP_OK(nvs_commit(light_handle)); nvs_close(light_handle); } - TEST_ESP_OK(nvs_flash_deinit_partition(NVS_DEFAULT_PART_NAME)); + TEST_ESP_OK(nvs_flash_deinit_partition(TEST_3SEC_PARTITION_NAME)); } TEST_CASE("read/write failure (TW8406)", "[nvs]") { - PartitionEmulationFixture f(0, 3); - nvs::NVSPartitionManager::get_instance()->init_custom(f.part(), 0, 3); + // TC verifies that NVS API works as expected when writing and reading values. + + // Erase the partition before testing + TEST_ESP_OK(nvs_flash_erase_partition(TEST_3SEC_PARTITION_NAME)); + + // Initialize NVS partition + TEST_ESP_OK(nvs_flash_init_partition(TEST_3SEC_PARTITION_NAME)); + for (int attempts = 0; attempts < 3; ++attempts) { int i = 0; nvs_handle_t light_handle = 0; @@ -1508,7 +1779,7 @@ TEST_CASE("read/write failure (TW8406)", "[nvs]") uint8_t number = 20; size_t data_len = sizeof(data); - ESP_ERROR_CHECK(nvs_open("LIGHT", NVS_READWRITE, &light_handle)); + ESP_ERROR_CHECK(nvs_open_from_partition(TEST_3SEC_PARTITION_NAME, "LIGHT", NVS_READWRITE, &light_handle)); ESP_ERROR_CHECK(nvs_set_u8(light_handle, "RecordNum", number)); for (i = 0; i < number; ++i) { snprintf(key, sizeof(key), "light%d", i); @@ -1527,17 +1798,32 @@ TEST_CASE("read/write failure (TW8406)", "[nvs]") nvs_close(light_handle); } - TEST_ESP_OK(nvs_flash_deinit_partition(NVS_DEFAULT_PART_NAME)); + TEST_ESP_OK(nvs_flash_deinit_partition(TEST_3SEC_PARTITION_NAME)); } TEST_CASE("nvs_flash_init checks for an empty page", "[nvs]") { + // TC verifies that nvs_flash_init_partition checks for an empty page in the partition. + // This test is to ensure that the NVS partition manager does not initialize + // the partition if it does not have at least one empty page available. + // The test verifies following: + // - nvs_flash_init_partition returns ESP_ERR_NVS_NO_FREE_PAGES if there are no free pages available + // - nvs_flash_init_partition returns ESP_OK if there is at least one free page available + + + // Initialize NVS partition + NVSPartitionTestHelper h(TEST_DEFAULT_PARTITION_NAME); + + // We need at least 5 sectors to be able to run this test + CHECK(h.get_sectors() >= 5); + const size_t blob_size = nvs::Page::CHUNK_MAX_SIZE; uint8_t blob[blob_size] = {0}; - PartitionEmulationFixture f(0, 8); - TEST_ESP_OK(nvs::NVSPartitionManager::get_instance()->init_custom(f.part(), 0, 5)); + + // Initialize NVS partition with 5 pages + TEST_ESP_OK(nvs::NVSPartitionManager::get_instance()->init_custom(&h, 0, 5)); nvs_handle_t handle; - TEST_ESP_OK(nvs_open("test", NVS_READWRITE, &handle)); + TEST_ESP_OK(nvs_open_from_partition(TEST_DEFAULT_PARTITION_NAME, "test", NVS_READWRITE, &handle)); // Fill first page TEST_ESP_OK(nvs_set_blob(handle, "1a", blob, blob_size)); // Fill second page @@ -1546,45 +1832,79 @@ TEST_CASE("nvs_flash_init checks for an empty page", "[nvs]") TEST_ESP_OK(nvs_set_blob(handle, "3a", blob, blob_size)); TEST_ESP_OK(nvs_commit(handle)); nvs_close(handle); - TEST_ESP_OK(nvs_flash_deinit_partition(NVS_DEFAULT_PART_NAME)); - // first two pages are now full, third one is writable, last two are empty - // init should fail - TEST_ESP_ERR(nvs::NVSPartitionManager::get_instance()->init_custom(f.part(), 0, 3), + TEST_ESP_OK(nvs_flash_deinit_partition(TEST_DEFAULT_PARTITION_NAME)); + + // first three pages are now full, fourth one is active, last one is empty + // init custom with explicitly reducing the number of pages to 4 should fail + TEST_ESP_ERR(nvs::NVSPartitionManager::get_instance()->init_custom(&h, 0, 4), ESP_ERR_NVS_NO_FREE_PAGES); // in case this test fails, to not affect other tests - nvs_flash_deinit_partition(NVS_DEFAULT_PART_NAME); + nvs_flash_deinit_partition(TEST_DEFAULT_PARTITION_NAME); } TEST_CASE("nvs page selection takes into account free entries also not just erased entries", "[nvs]") { + // TC verifies that potential next available NVS Page selection takes into account remaining free entries + // in the partition, not just erased entries. + // This test is to ensure that the NVS partition manager doesn't raise false not enough space errors + // when there are enough free entries available in the partition, which is not at the moment the active page. + + // The test verifies the goal following way: + // - The partition contains just three pages + // - nvs_flash_init_partition initializes the partition with enough free entries + // - nvs_set_blob writes enough data to fill the first page up to the point where the next item + // would not fit into the first page, but would fit into the second page. + // - nvs_set_blob writes the next items which should completely fill the second page. + // - nvs_set_blob writes the next item which should fit into the remaining space in the first page. + + // Erase the partition before testing + TEST_ESP_OK(nvs_flash_erase_partition(TEST_3SEC_PARTITION_NAME)); + + // Initialize NVS partition + TEST_ESP_OK(nvs_flash_init_partition(TEST_3SEC_PARTITION_NAME)); + const size_t blob_size = nvs::Page::CHUNK_MAX_SIZE / 2; uint8_t blob[blob_size] = {0}; - PartitionEmulationFixture f(0, 3); - TEST_ESP_OK(nvs::NVSPartitionManager::get_instance()->init_custom(f.part(), 0, 3)); nvs_handle_t handle; - TEST_ESP_OK(nvs_open("test", NVS_READWRITE, &handle)); - // Fill first page + + TEST_ESP_OK(nvs_open_from_partition(TEST_3SEC_PARTITION_NAME, "test", NVS_READWRITE, &handle)); + // Fill first page, leave some space for small entry, but not enough for the next full entry TEST_ESP_OK(nvs_set_blob(handle, "1a", blob, blob_size / 3)); TEST_ESP_OK(nvs_set_blob(handle, "1b", blob, blob_size)); - // Fill second page + // Activate and fill second page TEST_ESP_OK(nvs_set_blob(handle, "2a", blob, blob_size)); TEST_ESP_OK(nvs_set_blob(handle, "2b", blob, blob_size)); - // The item below should be able to fit the first page. + // The item below should be able to fit the remaining space in the first page. TEST_ESP_OK(nvs_set_blob(handle, "3a", blob, 4)); TEST_ESP_OK(nvs_commit(handle)); nvs_close(handle); - TEST_ESP_OK(nvs_flash_deinit_partition(f.part()->get_partition_name())); + TEST_ESP_OK(nvs_flash_deinit_partition(TEST_3SEC_PARTITION_NAME)); } TEST_CASE("calculate used and free space", "[nvs]") { + // TC verifies that NVS API works as expected when calculating used and free space in the partition. + // The test verifies following: + // - nvs_get_stats returns the correct number of total, free, available and used entries + // in the partition. + // - nvs_get_stats returns the correct number of namespaces in the partition + // - nvs_get_used_entry_count returns the correct number of used entries in the namespace + // - nvs_get_used_entry_count returns ESP_ERR_NVS_INVALID_HANDLE if the handle is invalid + // - nvs_get_stats returns ESP_ERR_NVS_NOT_INITIALIZED if the partition is not initialized + // - nvs_get_stats returns ESP_ERR_INVALID_ARG if the partition name is NULL + // Expected values are documented in the comments below. + + // Erase the partition before testing + TEST_ESP_OK(nvs_flash_erase_partition(TEST_DEFAULT_PARTITION_NAME)); + size_t consumed_entries = 0; - PartitionEmulationFixture f(0, 6); - nvs_flash_deinit(); + // Make sure the partition is not initialized + nvs_flash_deinit_partition(TEST_DEFAULT_PARTITION_NAME); + TEST_ESP_ERR(nvs_get_stats(NULL, NULL), ESP_ERR_INVALID_ARG); nvs_stats_t stat1; nvs_stats_t stat2; @@ -1599,8 +1919,12 @@ TEST_CASE("calculate used and free space", "[nvs]") TEST_ESP_ERR(nvs_get_used_entry_count(handle, &h_count_entries), ESP_ERR_NVS_INVALID_HANDLE); CHECK(h_count_entries == 0); - // init nvs - TEST_ESP_OK(nvs::NVSPartitionManager::get_instance()->init_custom(f.part(), 0, 6)); + // Initialize NVS partition + TEST_ESP_OK(nvs_flash_init_partition(TEST_DEFAULT_PARTITION_NAME)); + + // Retrieve the partition size in nvs pages + size_t total_sectors = NVSPartitionTestHelper::get_sectors(TEST_DEFAULT_PARTITION_NAME); + CHECK(total_sectors > 0); TEST_ESP_ERR(nvs_get_used_entry_count(handle, &h_count_entries), ESP_ERR_NVS_INVALID_HANDLE); CHECK(h_count_entries == 0); @@ -1609,7 +1933,7 @@ TEST_CASE("calculate used and free space", "[nvs]") TEST_ESP_OK(nvs_get_stats(NULL, &stat1)); CHECK(stat1.free_entries != 0); CHECK(stat1.namespace_count == 0); - CHECK(stat1.total_entries == 6 * nvs::Page::ENTRY_COUNT); + CHECK(stat1.total_entries == total_sectors * nvs::Page::ENTRY_COUNT); CHECK(stat1.used_entries == 0); // namespace test_k1 @@ -1618,7 +1942,7 @@ TEST_CASE("calculate used and free space", "[nvs]") // create namespace consumed_entries = 1; // should consume one entry - TEST_ESP_OK(nvs_open("test_k1", NVS_READWRITE, &handle_1)); + TEST_ESP_OK(nvs_open_from_partition(TEST_DEFAULT_PARTITION_NAME, "test_k1", NVS_READWRITE, &handle_1)); TEST_ESP_OK(nvs_get_stats(NULL, &stat2)); CHECK(stat2.free_entries + consumed_entries == stat1.free_entries); CHECK(stat2.namespace_count == 1); @@ -1670,7 +1994,7 @@ TEST_CASE("calculate used and free space", "[nvs]") // create namespace consumed_entries = 1; // should consume one entry - TEST_ESP_OK(nvs_open("test_k2", NVS_READWRITE, &handle_2)); + TEST_ESP_OK(nvs_open_from_partition(TEST_DEFAULT_PARTITION_NAME, "test_k2", NVS_READWRITE, &handle_2)); TEST_ESP_OK(nvs_get_stats(NULL, &stat2)); CHECK(stat2.free_entries + consumed_entries == stat1.free_entries); CHECK(stat2.namespace_count == 2); @@ -1713,7 +2037,7 @@ TEST_CASE("calculate used and free space", "[nvs]") // create namespace consumed_entries = 1; // should consume one entry - TEST_ESP_OK(nvs_open("test_k3", NVS_READWRITE, &handle_3)); + TEST_ESP_OK(nvs_open_from_partition(TEST_DEFAULT_PARTITION_NAME, "test_k3", NVS_READWRITE, &handle_3)); TEST_ESP_OK(nvs_get_stats(NULL, &stat2)); CHECK(stat2.free_entries + consumed_entries == stat1.free_entries); CHECK(stat2.namespace_count == 3); @@ -1756,63 +2080,116 @@ TEST_CASE("calculate used and free space", "[nvs]") nvs_close(handle_3); - TEST_ESP_OK(nvs_flash_deinit_partition(f.part()->get_partition_name())); + TEST_ESP_OK(nvs_flash_deinit_partition(TEST_DEFAULT_PARTITION_NAME)); } TEST_CASE("Multi-page blobs are supported", "[nvs]") { + // TC verifies that NVS API works as expected when storing multi-page blobs. + // The test verifies following: + // - nvs_set_blob can store blobs larger than a single page + + // Erase the partition before testing + TEST_ESP_OK(nvs_flash_erase_partition(TEST_DEFAULT_PARTITION_NAME)); + + // Initialize NVS partition + TEST_ESP_OK(nvs_flash_init_partition(TEST_DEFAULT_PARTITION_NAME)); + const size_t blob_size = nvs::Page::CHUNK_MAX_SIZE * 2; uint8_t blob[blob_size] = {0}; - PartitionEmulationFixture f(0, 5); - TEST_ESP_OK(nvs::NVSPartitionManager::get_instance()->init_custom(f.part(), 0, 5)); + nvs_handle_t handle; - TEST_ESP_OK(nvs_open("test", NVS_READWRITE, &handle)); + TEST_ESP_OK(nvs_open_from_partition(TEST_DEFAULT_PARTITION_NAME, "test", NVS_READWRITE, &handle)); TEST_ESP_OK(nvs_set_blob(handle, "abc", blob, blob_size)); TEST_ESP_OK(nvs_commit(handle)); nvs_close(handle); - TEST_ESP_OK(nvs_flash_deinit_partition(f.part()->get_partition_name())); + TEST_ESP_OK(nvs_flash_deinit_partition(TEST_DEFAULT_PARTITION_NAME)); } TEST_CASE("Failures are handled while storing multi-page blobs", "[nvs]") { + // TC verifies that NVS API works as expected when storing multi-page blobs. + // The test verifies following: + // - nvs_set_blob returns ESP_ERR_NVS_VALUE_TOO_LONG if the blob is larger than the maximum available space + + // Use NVSPartitionTestHelper to wrap the partition operations and allow custom initialization + NVSPartitionTestHelper h(TEST_DEFAULT_PARTITION_NAME); + + // Initialize NVS partition by using only 5 pages + CHECK(h.get_sectors() >= 5); + TEST_ESP_OK(nvs::NVSPartitionManager::get_instance()->init_custom(&h, 0, 5)); + + // Requires at least 7 pages plus one entry for namespace and 1 + 7 metadata entries + // for the blob. Cannot fit into 4 + 1 // pages. const size_t blob_size = nvs::Page::CHUNK_MAX_SIZE * 7; uint8_t blob[blob_size] = {0}; - PartitionEmulationFixture f(0, 5); - TEST_ESP_OK(nvs::NVSPartitionManager::get_instance()->init_custom(f.part(), 0, 5)); + nvs_handle_t handle; - TEST_ESP_OK(nvs_open("test", NVS_READWRITE, &handle)); + TEST_ESP_OK(nvs_open_from_partition(TEST_DEFAULT_PARTITION_NAME, "test", NVS_READWRITE, &handle)); TEST_ESP_ERR(nvs_set_blob(handle, "abc", blob, blob_size), ESP_ERR_NVS_VALUE_TOO_LONG); TEST_ESP_OK(nvs_set_blob(handle, "abc", blob, nvs::Page::CHUNK_MAX_SIZE * 2)); TEST_ESP_OK(nvs_commit(handle)); nvs_close(handle); - TEST_ESP_OK(nvs_flash_deinit_partition(f.part()->get_partition_name())); + TEST_ESP_OK(nvs_flash_deinit_partition(h.get_partition_name())); } TEST_CASE("Reading multi-page blobs", "[nvs]") { + // TC verifies that NVS API works as expected when reading multi-page blobs. + // The test verifies following: + // - nvs_set blob can store blobs larger than a single page + // - nvs_get_blob can read blobs larger than a single page + + // Erase the partition before testing + TEST_ESP_OK(nvs_flash_erase_partition(TEST_DEFAULT_PARTITION_NAME)); + + // Initialize NVS partition + TEST_ESP_OK(nvs_flash_init_partition(TEST_DEFAULT_PARTITION_NAME)); + + // Ensure that the partition has enough pages + // 1 entry for namespace, 1 entry for blob metadata, 3 pages for blob data -> 4 + 1 (spare) NVS pages + CHECK(NVSPartitionTestHelper::get_sectors(TEST_DEFAULT_PARTITION_NAME) >= 5); + const size_t blob_size = nvs::Page::CHUNK_MAX_SIZE * 3; uint8_t blob[blob_size]; uint8_t blob_read[blob_size]; size_t read_size = blob_size; - PartitionEmulationFixture f(0, 5); - TEST_ESP_OK(nvs::NVSPartitionManager::get_instance()->init_custom(f.part(), 0, 5)); + nvs_handle_t handle; memset(blob, 0x11, blob_size); memset(blob_read, 0xee, blob_size); - TEST_ESP_OK(nvs_open("readTest", NVS_READWRITE, &handle)); + TEST_ESP_OK(nvs_open_from_partition(TEST_DEFAULT_PARTITION_NAME, "readTest", NVS_READWRITE, &handle)); TEST_ESP_OK(nvs_set_blob(handle, "abc", blob, blob_size)); TEST_ESP_OK(nvs_get_blob(handle, "abc", blob_read, &read_size)); CHECK(memcmp(blob, blob_read, blob_size) == 0); TEST_ESP_OK(nvs_commit(handle)); nvs_close(handle); - TEST_ESP_OK(nvs_flash_deinit_partition(f.part()->get_partition_name())); + TEST_ESP_OK(nvs_flash_deinit_partition(TEST_DEFAULT_PARTITION_NAME)); } TEST_CASE("Modification of values for Multi-page blobs are supported", "[nvs]") { + // TC verifies that NVS API works as expected when modifying multi-page blobs. + // The test verifies following: + // - nvs_set_blob can overwrite multi-page blobs with the same key + // - nvs_get_blob can read the modified multi-page blob + + // Erase the partition before testing + TEST_ESP_OK(nvs_flash_erase_partition(TEST_DEFAULT_PARTITION_NAME)); + + // Initialize NVS partition + TEST_ESP_OK(nvs_flash_init_partition(TEST_DEFAULT_PARTITION_NAME)); + + // Ensure that the partition has enough NVS pages + // Requires 1 namespace entry, 1 + 2 metadata entries for the blob, and 2 pages for blob data. + // To be able to overwrite the blob with the same key, we need additional 1 + 2 metadata entries + // and 2 pages for blob data. + // It means 5 + 1 (spare) NVS pages are required to run this test. + CHECK(NVSPartitionTestHelper::get_sectors(TEST_DEFAULT_PARTITION_NAME) >= 6); + const size_t blob_size = nvs::Page::CHUNK_MAX_SIZE * 2; uint8_t blob[blob_size] = {0}; uint8_t blob_read[blob_size] = {0xfe};; @@ -1820,15 +2197,14 @@ TEST_CASE("Modification of values for Multi-page blobs are supported", "[nvs]") uint8_t blob3[blob_size] = {0x22}; uint8_t blob4[blob_size] = { 0x33}; size_t read_size = blob_size; - PartitionEmulationFixture f(0, 6); - TEST_ESP_OK(nvs::NVSPartitionManager::get_instance()->init_custom(f.part(), 0, 6)); + nvs_handle_t handle; memset(blob, 0x11, blob_size); memset(blob2, 0x22, blob_size); memset(blob3, 0x33, blob_size); memset(blob4, 0x44, blob_size); memset(blob_read, 0xff, blob_size); - TEST_ESP_OK(nvs_open("test", NVS_READWRITE, &handle)); + TEST_ESP_OK(nvs_open_from_partition(TEST_DEFAULT_PARTITION_NAME, "test", NVS_READWRITE, &handle)); TEST_ESP_OK(nvs_set_blob(handle, "abc", blob, blob_size)); TEST_ESP_OK(nvs_set_blob(handle, "abc", blob2, blob_size)); TEST_ESP_OK(nvs_set_blob(handle, "abc", blob3, blob_size)); @@ -1838,39 +2214,74 @@ TEST_CASE("Modification of values for Multi-page blobs are supported", "[nvs]") TEST_ESP_OK(nvs_commit(handle)); nvs_close(handle); - TEST_ESP_OK(nvs_flash_deinit_partition(f.part()->get_partition_name())); + TEST_ESP_OK(nvs_flash_deinit_partition(TEST_DEFAULT_PARTITION_NAME)); } TEST_CASE("Modification from single page blob to multi-page", "[nvs]") { + // TC verifies that NVS API works as expected when modifying blob originally stored as single page blob to multi-page blob. + // The test verifies following: + // - nvs_set_blob can overwrite single page blob with a multi-page blob + // - nvs_get_blob can read the modified multi-page blob + + // Erase the partition before testing + TEST_ESP_OK(nvs_flash_erase_partition(TEST_DEFAULT_PARTITION_NAME)); + + // Initialize NVS partition + TEST_ESP_OK(nvs_flash_init_partition(TEST_DEFAULT_PARTITION_NAME)); + + // Ensure that the partition has enough NVS pages + // Requires 1 namespace entry, 1 + 1 metadata entries for the blob, and 1/2 of the page for the blob data. + // To be able to overwrite the blob with the same key and data occupying 3 full NVS pages, we need + // additional 1 + 3 metadata entries and 3 whole pages for blob data. + // It means 4 + 1 (spare) NVS pages are required to run this test. + CHECK(NVSPartitionTestHelper::get_sectors(TEST_DEFAULT_PARTITION_NAME) >= 5); + + const size_t blob_size = nvs::Page::CHUNK_MAX_SIZE * 3; uint8_t blob[blob_size] = {0}; uint8_t blob_read[blob_size] = {0xff}; size_t read_size = blob_size; - PartitionEmulationFixture f(0, 5); - TEST_ESP_OK(nvs::NVSPartitionManager::get_instance()->init_custom(f.part(), 0, 5)); + nvs_handle_t handle; - TEST_ESP_OK(nvs_open("Test", NVS_READWRITE, &handle)); - TEST_ESP_OK(nvs_set_blob(handle, "abc", blob, nvs::Page::CHUNK_MAX_SIZE / 2)); + TEST_ESP_OK(nvs_open_from_partition(TEST_DEFAULT_PARTITION_NAME, "Test", NVS_READWRITE, &handle)); + TEST_ESP_OK(nvs_set_blob(handle, "abc", blob, nvs::Page::CHUNK_MAX_SIZE / 2)); // Set single page blob TEST_ESP_OK(nvs_set_blob(handle, "abc", blob, blob_size)); TEST_ESP_OK(nvs_get_blob(handle, "abc", blob_read, &read_size)); CHECK(memcmp(blob, blob_read, blob_size) == 0); TEST_ESP_OK(nvs_commit(handle)); nvs_close(handle); - TEST_ESP_OK(nvs_flash_deinit_partition(f.part()->get_partition_name())); + TEST_ESP_OK(nvs_flash_deinit_partition(TEST_DEFAULT_PARTITION_NAME)); } TEST_CASE("Modification from multi-page to single page", "[nvs]") { + // TC verifies that NVS API works as expected when modifying blob originally stored as multi-page blob to single page blob. + // The test verifies following: + // - nvs_set_blob can overwrite multi-page blob with a single page blob + // - nvs_get_blob can read the modified single page blob + + // Erase the partition before testing + TEST_ESP_OK(nvs_flash_erase_partition(TEST_DEFAULT_PARTITION_NAME)); + + // Initialize NVS partition + TEST_ESP_OK(nvs_flash_init_partition(TEST_DEFAULT_PARTITION_NAME)); + // Ensure that the partition has enough NVS pages + // Requires 1 namespace entry, 1 + 2 metadata entries for the blob, + // and 3 pages for blob data. To be able to overwrite the blob with the + // same key and data occupying 1/2 of the page, we need additional + // 1 + 1 metadata entries and 1/2 of the page for blob data. + // It means 4 + 1 (spare) NVS pages are required to run this test. + CHECK(NVSPartitionTestHelper::get_sectors(TEST_DEFAULT_PARTITION_NAME) >= 5); + const size_t blob_size = nvs::Page::CHUNK_MAX_SIZE * 3; uint8_t blob[blob_size] = {0}; uint8_t blob_read[blob_size] = {0xff}; size_t read_size = blob_size; - PartitionEmulationFixture f(0, 5); - TEST_ESP_OK(nvs::NVSPartitionManager::get_instance()->init_custom(f.part(), 0, 5)); + nvs_handle_t handle; - TEST_ESP_OK(nvs_open("Test", NVS_READWRITE, &handle)); + TEST_ESP_OK(nvs_open_from_partition(TEST_DEFAULT_PARTITION_NAME, "Test", NVS_READWRITE, &handle)); TEST_ESP_OK(nvs_set_blob(handle, "abc", blob, blob_size)); TEST_ESP_OK(nvs_set_blob(handle, "abc", blob, nvs::Page::CHUNK_MAX_SIZE / 2)); TEST_ESP_OK(nvs_set_blob(handle, "abc2", blob, blob_size)); @@ -1879,49 +2290,79 @@ TEST_CASE("Modification from multi-page to single page", "[nvs]") TEST_ESP_OK(nvs_commit(handle)); nvs_close(handle); - TEST_ESP_OK(nvs_flash_deinit_partition(f.part()->get_partition_name())); + TEST_ESP_OK(nvs_flash_deinit_partition(TEST_DEFAULT_PARTITION_NAME)); } TEST_CASE("Multi-page blob erased using nvs_erase_key should not be found when probed for just length", "[nvs]") { + // TC verifies that NVS API works as expected when erasing multi-page blobs. + // The test verifies following: + // - nvs_erase_key can erase multi-page blobs + // - nvs_get_blob returns ESP_ERR_NVS_NOT_FOUND when probing for length of a multi-page blob that has been erased + + // Erase the partition before testing + TEST_ESP_OK(nvs_flash_erase_partition(TEST_DEFAULT_PARTITION_NAME)); + + // Initialize NVS partition + TEST_ESP_OK(nvs_flash_init_partition(TEST_DEFAULT_PARTITION_NAME)); + // Ensure that the partition has enough NVS pages + // Requires 1 namespace entry, 1 + 3 metadata entries for the blob, + // and 3 pages for blob data. + // It means 4 + 1 (spare) NVS pages are required to run this test. + CHECK(NVSPartitionTestHelper::get_sectors(TEST_DEFAULT_PARTITION_NAME) >= 5); + const size_t blob_size = nvs::Page::CHUNK_MAX_SIZE * 3; uint8_t blob[blob_size] = {0}; size_t read_size = blob_size; - PartitionEmulationFixture f(0, 5); - TEST_ESP_OK(nvs::NVSPartitionManager::get_instance()->init_custom(f.part(), 0, 5)); + nvs_handle_t handle; - TEST_ESP_OK(nvs_open("Test", NVS_READWRITE, &handle)); + TEST_ESP_OK(nvs_open_from_partition(TEST_DEFAULT_PARTITION_NAME, "Test", NVS_READWRITE, &handle)); TEST_ESP_OK(nvs_set_blob(handle, "abc", blob, blob_size)); TEST_ESP_OK(nvs_erase_key(handle, "abc")); TEST_ESP_ERR(nvs_get_blob(handle, "abc", NULL, &read_size), ESP_ERR_NVS_NOT_FOUND); TEST_ESP_OK(nvs_commit(handle)); nvs_close(handle); - TEST_ESP_OK(nvs_flash_deinit_partition(f.part()->get_partition_name())); + TEST_ESP_OK(nvs_flash_deinit_partition(TEST_DEFAULT_PARTITION_NAME)); } TEST_CASE("Check that orphaned blobs are erased during init", "[nvs]") { - const size_t blob_size = nvs::Page::CHUNK_MAX_SIZE * 3 ; - uint8_t blob[blob_size] = {0x11}; - PartitionEmulationFixture f(0, 5); - nvs::Storage storage(f.part()); + // TC verifies that NVS API works as expected when initializing with orphaned blobs left in the partition. + // The test verifies following: + // - nvs_init can initialize the partition with orphaned blobs left in the partition + // - nvs_init erases orphaned blobs during initialization - TEST_ESP_OK(storage.init(0, 5)); + // Use NVSPartitionTestHelper to provide nvs::Partition instance for the test + NVSPartitionTestHelper h(TEST_DEFAULT_PARTITION_NAME); + + // Make sure the partition has enough NVS pages + // Requires 1 namespace entry, 1 + 3 metadata entries for the blob, + // and 3 pages for blob data. + // 4 + 1 (spare) NVS pages are required to run this test. + const size_t required_sectors = 5; + CHECK(NVSPartitionTestHelper::get_sectors(TEST_DEFAULT_PARTITION_NAME) >= required_sectors); + + const size_t blob_size = nvs::Page::CHUNK_MAX_SIZE * 3; + uint8_t blob[blob_size] = {0x11}; + + nvs::Storage storage(&h); + + 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.init(0, 5)); - /* Check that multi-page item is still available.**/ + 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); nvs::Page p; - TEST_ESP_OK(p.load(f.part(), 3)); // This is where index will be placed. + TEST_ESP_OK(p.load(&h, 3)); // This is where index will be placed. TEST_ESP_OK(p.erase()); - TEST_ESP_OK(storage.init(0, 5)); + 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))); @@ -1929,39 +2370,83 @@ TEST_CASE("Check that orphaned blobs are erased during init", "[nvs]") TEST_CASE("nvs blob fragmentation test", "[nvs]") { - PartitionEmulationFixture f(0, 4); - TEST_ESP_OK(nvs::NVSPartitionManager::get_instance()->init_custom(f.part(), 0, 4)); + // TC verifies that NVS API works as expected when writing and updating large blobs. + // The test verifies following: + // - nvs_set_blob can write large blobs that span multiple pages + // - nvs_set_blob can update large blobs that span multiple pages + // - nvs_set_blob can handle fragmentation when updating large blobs + + // In each loop cycle, the test overwrites one u32 key and one blob key. On top of overwrites, it adds + // one more additional u32 key to enforce fragmentation on subsequent updates. + + // Initialize NVS partition + NVSPartitionTestHelper h(TEST_DEFAULT_PARTITION_NAME); + + // We need at least 4 sectors to be able to run this test + CHECK(h.get_sectors() >= 4); + + // Initialize NVS partition limited to 4 pages + TEST_ESP_OK(nvs::NVSPartitionManager::get_instance()->init_custom(&h, 0, 4)); + + // Blob definition const size_t BLOB_SIZE = 3500; uint8_t *blob = (uint8_t *) malloc(BLOB_SIZE); CHECK(blob != NULL); memset(blob, 0xEE, BLOB_SIZE); - const uint32_t magic = 0xff33eaeb; - nvs_handle_t h; - TEST_ESP_OK(nvs_open("blob_tests", NVS_READWRITE, &h)); + + uint32_t magic = 0xff33eaeb; + + nvs_handle_t handle; + TEST_ESP_OK(nvs_open_from_partition(TEST_DEFAULT_PARTITION_NAME, "blob_tests", NVS_READWRITE, &handle)); + for (int i = 0; i < 128; i++) { INFO("Iteration " << i << "...\n"); - TEST_ESP_OK(nvs_set_u32(h, "magic", magic)); - TEST_ESP_OK(nvs_set_blob(h, "blob", blob, BLOB_SIZE)); + + // Modify the magic and the content of the buffer to force the value updates + magic += i; + blob[0] = (uint8_t)(i & 0xFF); + + // Update magic key + TEST_ESP_OK(nvs_set_u32(handle, "magic", magic)); + + // Update blob key + TEST_ESP_OK(nvs_set_blob(handle, "blob", blob, BLOB_SIZE)); + + // Create extra uint32 key to force record shifting on update and thus fragmentation char seq_buf[16]; snprintf(seq_buf, sizeof(seq_buf), "seq%d", i); - TEST_ESP_OK(nvs_set_u32(h, seq_buf, i)); + TEST_ESP_OK(nvs_set_u32(handle, seq_buf, i)); } + + nvs_close(handle); free(blob); - TEST_ESP_OK(nvs_flash_deinit_partition(f.part()->get_partition_name())); + TEST_ESP_OK(nvs_flash_deinit_partition(TEST_DEFAULT_PARTITION_NAME)); } TEST_CASE("nvs code handles errors properly when partition is near to full", "[nvs]") { + // TC verifies that NVS API works as expected when the partition is near to full. + // The test verifies following: + // - nvs_writeItem returns ESP_ERR_NVS_NOT_ENOUGH_SPACE when trying to write an item that does not fit into the partition + // - nvs_writeItem can write items that fit into the partition + // - nvs_writeItem can write items that fit into the remaining space in the partition + + // Initialize NVS partition + NVSPartitionTestHelper h(TEST_DEFAULT_PARTITION_NAME); + + // This test requires at least 4+1 NVS pages to run. + CHECK(h.get_sectors() >= 5); + const size_t blob_size = nvs::Page::CHUNK_MAX_SIZE * 0.3 ; uint8_t blob[blob_size] = {0x11}; - PartitionEmulationFixture f(0, 5); - nvs::Storage storage(f.part()); + + nvs::Storage storage(&h); char nvs_key[16] = ""; TEST_ESP_OK(storage.init(0, 5)); - /* Four pages should fit roughly 12 blobs*/ + // 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))); @@ -1975,63 +2460,86 @@ TEST_CASE("nvs code handles errors properly when partition is near to full", "[n TEST_CASE("Check for nvs version incompatibility", "[nvs]") { - PartitionEmulationFixture f(0, 3); + // TC verifies that NVS API works as expected when the partition has an old version of NVS. + // The test verifies following: + // - nvs_init returns ESP_ERR_NVS_NEW_VERSION_FOUND when the partition has an old version of NVS + // - nvs_init does not initialize the partition with an old version of NVS + + // Initialize NVS partition + NVSPartitionTestHelper h(TEST_DEFAULT_PARTITION_NAME); + + // This test requires at least 3 NVS pages to run. + CHECK(h.get_sectors() >= 3); int32_t val1 = 0x12345678; nvs::Page p; - TEST_ESP_OK(p.load(f.part(), 0)); + TEST_ESP_OK(p.load(&h, 0)); TEST_ESP_OK(p.setVersion(nvs::Page::NVS_VERSION - 1)); TEST_ESP_OK(p.writeItem(1, nvs::ItemType::I32, "foo", &val1, sizeof(val1))); - TEST_ESP_ERR(nvs::NVSPartitionManager::get_instance()->init_custom(f.part(), 0, 3), + TEST_ESP_ERR(nvs::NVSPartitionManager::get_instance()->init_custom(&h, 0, 3), ESP_ERR_NVS_NEW_VERSION_FOUND); // if something went wrong, clean up - nvs_flash_deinit_partition(f.part()->get_partition_name()); + nvs_flash_deinit_partition(TEST_DEFAULT_PARTITION_NAME); } -// TODO: leaks memory -TEST_CASE("monkey test with old-format blob present", "[nvs][monkey]") +TEST_CASE("monkey test with old-format blob present", "[nvs][monkey][xxx]") { + // TC verifies that NVS API works as expected when the partition has an old-format blob present. + // The test verifies following: + // - nvs_init can initialize the partition with an old-format blob present + // - NVS operations can handle write operations on other items with an old-format blob present + // - If NVS updates the old-format blob, it should not leave both versions present in the partition + + // PartitionTestHelper is used to provide nvs::Partition instance for the test + NVSPartitionTestHelper h(TEST_DEFAULT_PARTITION_NAME); + + const uint32_t seed = 3; + const uint32_t NVS_FLASH_SECTOR_COUNT_MIN = 8; // Minimal required NVS pages for this test + + uint32_t sector_count = h.get_sectors(); + + // Make sure the partition has enough NVS pages + CHECK(sector_count >= NVS_FLASH_SECTOR_COUNT_MIN); + + // Fill the partition with random data before testing + TEST_ESP_OK(NVSPartitionTestHelper::randomize_partition(TEST_DEFAULT_PARTITION_NAME, seed)); + + // Prepare random data generator std::random_device rd; std::mt19937 gen(rd()); - uint32_t seed = 3; gen.seed(seed); - PartitionEmulationFixture f(0, 10); - f.randomize(seed); - esp_partition_clear_stats(); + // Clear partition stats before testing + NVSPartitionTestHelper::clear_stats(); - const uint32_t NVS_FLASH_SECTOR = 2; - const uint32_t NVS_FLASH_SECTOR_COUNT_MIN = 8; static const size_t smallBlobLen = nvs::Page::CHUNK_MAX_SIZE / 3; - TEMPORARILY_DISABLED(f.emu.setBounds(NVS_FLASH_SECTOR, NVS_FLASH_SECTOR + NVS_FLASH_SECTOR_COUNT_MIN);) - - TEST_ESP_OK(nvs::NVSPartitionManager::get_instance()->init_custom(f.part(), - NVS_FLASH_SECTOR, - NVS_FLASH_SECTOR_COUNT_MIN)); + TEST_ESP_OK(nvs::NVSPartitionManager::get_instance()->init_custom(&h, + 0, + sector_count)); nvs_handle_t handle; - TEST_ESP_OK(nvs_open("namespace1", NVS_READWRITE, &handle)); + TEST_ESP_OK(nvs_open_from_partition(TEST_DEFAULT_PARTITION_NAME, "namespace1", NVS_READWRITE, &handle)); RandomTest test; for (uint8_t it = 0; it < 10; it++) { size_t count = 200; - /* Erase index and chunks for the blob with "singlepage" key, do not care about errorcodes */ - for (uint8_t num = NVS_FLASH_SECTOR; num < NVS_FLASH_SECTOR + NVS_FLASH_SECTOR_COUNT_MIN; num++) { + // Erase index and chunks for the blob with "singlepage" key, do not care about errorcodes + for (uint8_t num = 0; num < sector_count; num++) { nvs::Page p; - TEST_ESP_OK(p.load(f.part(), num)); + 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); } - /* Now write "singlepage" blob in old format*/ - for (uint8_t num = NVS_FLASH_SECTOR; num < NVS_FLASH_SECTOR + NVS_FLASH_SECTOR_COUNT_MIN; num++) { + // Now write "singlepage" blob in old format + for (uint8_t num = 0; num < sector_count; num++) { nvs::Page p; - TEST_ESP_OK(p.load(f.part(), num)); + TEST_ESP_OK(p.load(&h, num)); if (p.state() == nvs::Page::PageState::ACTIVE) { uint8_t buf[smallBlobLen]; size_t blobLen = gen() % smallBlobLen; @@ -2052,27 +2560,27 @@ TEST_CASE("monkey test with old-format blob present", "[nvs][monkey]") } } - TEST_ESP_OK(nvs_flash_deinit_partition(f.part()->get_partition_name())); - /* Initialize again */ - TEST_ESP_OK(nvs::NVSPartitionManager::get_instance()->init_custom(f.part(), - NVS_FLASH_SECTOR, - NVS_FLASH_SECTOR_COUNT_MIN)); - TEST_ESP_OK(nvs_open("namespace1", NVS_READWRITE, &handle)); + TEST_ESP_OK(nvs_flash_deinit_partition(h.get_partition_name())); + // Initialize again + TEST_ESP_OK(nvs::NVSPartitionManager::get_instance()->init_custom(&h, + 0, + sector_count)); + TEST_ESP_OK(nvs_open_from_partition(TEST_DEFAULT_PARTITION_NAME, "namespace1", NVS_READWRITE, &handle)); - /* Perform random things */ + // Perform random things auto res = test.doRandomThings(handle, gen, count); if (res != ESP_OK) { - nvs_dump(NVS_DEFAULT_PART_NAME); + nvs_dump(TEST_DEFAULT_PARTITION_NAME); CHECK(0); } - /* Check that only one version is present for "singlepage". Its possible that last iteration did not write - * anything for "singlepage". So either old version or new version should be present.*/ + // Check that only one version is present for "singlepage". Its possible that last iteration did not write + // anything for "singlepage". So either old version or new version should be present. bool oldVerPresent = false, newVerPresent = false; - for (uint8_t num = NVS_FLASH_SECTOR; num < NVS_FLASH_SECTOR + NVS_FLASH_SECTOR_COUNT_MIN; num++) { + for (uint8_t num = 0; num < sector_count; num++) { nvs::Page p; - TEST_ESP_OK(p.load(f.part(), num)); + TEST_ESP_OK(p.load(&h, num)); if (!oldVerPresent && p.findItem(1, nvs::ItemType::BLOB, "singlepage", nvs::Item::CHUNK_ANY, nvs::VerOffset::VER_ANY) == ESP_OK) { oldVerPresent = true; } @@ -2081,201 +2589,255 @@ TEST_CASE("monkey test with old-format blob present", "[nvs][monkey]") newVerPresent = true; } } - CHECK(oldVerPresent != newVerPresent); + CHECK(oldVerPresent != newVerPresent); // Only one version should be present, either old or new + CHECK((oldVerPresent || newVerPresent) == true); // At least one version should be present } - TEST_ESP_OK(nvs_flash_deinit_partition(f.part()->get_partition_name())); - s_perf << "Monkey test: nErase=" << esp_partition_get_erase_ops() << " nWrite=" << esp_partition_get_write_ops() << std::endl; + TEST_ESP_OK(nvs_flash_deinit_partition(h.get_partition_name())); + + s_perf << "Monkey test: nErase=" << NVSPartitionTestHelper::get_erase_ops() << " nWrite=" << NVSPartitionTestHelper::get_write_ops() << std::endl; } TEST_CASE("Recovery from power-off during modification of blob present in old-format (same page)", "[nvs]") { - std::random_device rd; - std::mt19937 gen(rd()); - uint32_t seed = 3; - gen.seed(seed); + // TC verifies that NVS API works as expected when the power is lost during modification of a blob + // that is present in the old-format. + // The test verifies following: + // - nvs_init can initialize the partition with an old-format blob present together with the updated blob value + // stored in new-format + // - power-off in this test case means that the new-format blob is written, but the old-format blob is not removed + // - nvs_init removes the old-format blob when the new-format blob is present + // - nvs_get_blob can read the new format blob - PartitionEmulationFixture f(0, 3); - esp_partition_clear_stats(); + const uint32_t NVS_FLASH_SECTOR_COUNT = 3; - TEST_ESP_OK(nvs::NVSPartitionManager::get_instance()->init_custom(f.part(), 0, 3)); + // PartitionTestHelper is used to provide nvs::Partition instance for the test + NVSPartitionTestHelper h(TEST_DEFAULT_PARTITION_NAME); + + // This test requires at least 3 NVS pages to run. + CHECK(h.get_sectors() >= NVS_FLASH_SECTOR_COUNT); + + // Clear partition stats before testing + NVSPartitionTestHelper::clear_stats(); + + TEST_ESP_OK(nvs::NVSPartitionManager::get_instance()->init_custom(&h, 0, NVS_FLASH_SECTOR_COUNT)); nvs_handle_t handle; - TEST_ESP_OK(nvs_open("namespace1", NVS_READWRITE, &handle)); + TEST_ESP_OK(nvs_open_from_partition(TEST_DEFAULT_PARTITION_NAME, "namespace1", NVS_READWRITE, &handle)); uint8_t hexdata[] = {0x01, 0x02, 0x03, 0xab, 0xcd, 0xef}; uint8_t hexdata_old[] = {0x11, 0x12, 0x13, 0xbb, 0xcc, 0xee}; size_t buflen = sizeof(hexdata); uint8_t buf[nvs::Page::CHUNK_MAX_SIZE]; - /* Power-off when blob was being written on the same page where its old version in old format - * was present*/ nvs::Page p; - TEST_ESP_OK(p.load(f.part(), 0)); - /* Write blob in old-format*/ + TEST_ESP_OK(p.load(&h, 0)); + // Write blob in old-format TEST_ESP_OK(p.writeItem(1, nvs::ItemType::BLOB, "singlepage", hexdata_old, sizeof(hexdata_old))); - /* Write blob in new format*/ + // Write blob in new format + // Write BLOB_DATA item TEST_ESP_OK(p.writeItem(1, nvs::ItemType::BLOB_DATA, "singlepage", hexdata, sizeof(hexdata), 0)); - /* All pages are stored. Now store the index.*/ + // BLOB_DATA item is stored. Now store the index. nvs::Item item; item.blobIndex.dataSize = sizeof(hexdata); item.blobIndex.chunkCount = 1; item.blobIndex.chunkStart = nvs::VerOffset::VER_0_OFFSET; - TEST_ESP_OK(p.writeItem(1, nvs::ItemType::BLOB_IDX, "singlepage", item.data, sizeof(item.data))); + // We have written the new-format blob and its index after the old-format blob + // So the old-format blob should still be present on the page TEST_ESP_OK(p.findItem(1, nvs::ItemType::BLOB, "singlepage")); - TEST_ESP_OK(nvs_flash_deinit_partition(f.part()->get_partition_name())); - /* Initialize again */ - TEST_ESP_OK(nvs::NVSPartitionManager::get_instance()->init_custom(f.part(), 0, 3)); - TEST_ESP_OK(nvs_open("namespace1", NVS_READWRITE, &handle)); + TEST_ESP_OK(nvs_flash_deinit_partition(TEST_DEFAULT_PARTITION_NAME)); + // Initialize again + TEST_ESP_OK(nvs::NVSPartitionManager::get_instance()->init_custom(&h, 0, NVS_FLASH_SECTOR_COUNT)); + TEST_ESP_OK(nvs_open_from_partition(TEST_DEFAULT_PARTITION_NAME, "namespace1", NVS_READWRITE, &handle)); + // The blob should be present in the new format TEST_ESP_OK(nvs_get_blob(handle, "singlepage", buf, &buflen)); CHECK(memcmp(buf, hexdata, buflen) == 0); + // Low level check to ensure the old-format blob is not present at all nvs::Page p2; - TEST_ESP_OK(p2.load(f.part(), 0)); + TEST_ESP_OK(p2.load(&h, 0)); + + // After the power-off, the old-format blob should not be present + // Search on the page for BLOB will find BLOB_INDEX and indicate the expected type mismatch TEST_ESP_ERR(p2.findItem(1, nvs::ItemType::BLOB, "singlepage"), ESP_ERR_NVS_TYPE_MISMATCH); - TEST_ESP_OK(nvs_flash_deinit_partition(f.part()->get_partition_name())); + TEST_ESP_OK(nvs_flash_deinit_partition(TEST_DEFAULT_PARTITION_NAME)); } TEST_CASE("Recovery from power-off during modification of blob present in old-format (different page)", "[nvs]") { - std::random_device rd; - std::mt19937 gen(rd()); - uint32_t seed = 3; - gen.seed(seed); + // TC verifies that NVS API works as expected when the power is lost during modification of a blob + // that is present in the old-format. + // The test verifies following: + // - nvs_init can initialize the partition with an old-format blob present together with the updated blob value + // stored in new-format + // - power-off in this test case means that the new-format blob is written, but the old-format blob is not removed + // - nvs_init removes the old-format blob when the new-format blob is present + // - nvs_get_blob can read the new format blob - PartitionEmulationFixture f(0, 3); - esp_partition_clear_stats(); + const uint32_t NVS_FLASH_SECTOR_COUNT = 3; - TEST_ESP_OK(nvs::NVSPartitionManager::get_instance()->init_custom(f.part(), 0, 3)); + // PartitionTestHelper is used to provide nvs::Partition instance for the test + NVSPartitionTestHelper h(TEST_DEFAULT_PARTITION_NAME); + + // This test requires at least 3 NVS pages to run. + CHECK(h.get_sectors() >= NVS_FLASH_SECTOR_COUNT); + + // Clear partition stats before testing + NVSPartitionTestHelper::clear_stats(); + + TEST_ESP_OK(nvs::NVSPartitionManager::get_instance()->init_custom(&h, 0, NVS_FLASH_SECTOR_COUNT)); nvs_handle_t handle; - TEST_ESP_OK(nvs_open("namespace1", NVS_READWRITE, &handle)); + TEST_ESP_OK(nvs_open_from_partition(TEST_DEFAULT_PARTITION_NAME, "namespace1", NVS_READWRITE, &handle)); uint8_t hexdata[] = {0x01, 0x02, 0x03, 0xab, 0xcd, 0xef}; uint8_t hexdata_old[] = {0x11, 0x12, 0x13, 0xbb, 0xcc, 0xee}; size_t buflen = sizeof(hexdata); uint8_t buf[nvs::Page::CHUNK_MAX_SIZE]; - /* Power-off when blob was being written on the different page where its old version in old format - * was present*/ + // Power-off when blob was being written on the different page where its old version in old format + // was present nvs::Page p; - TEST_ESP_OK(p.load(f.part(), 0)); - /* Write blob in old-format*/ + TEST_ESP_OK(p.load(&h, 0)); + // Write blob in old-format TEST_ESP_OK(p.writeItem(1, nvs::ItemType::BLOB, "singlepage", hexdata_old, sizeof(hexdata_old))); - /* Write blob in new format*/ + // Write blob in new format TEST_ESP_OK(p.writeItem(1, nvs::ItemType::BLOB_DATA, "singlepage", hexdata, sizeof(hexdata), 0)); - /* All pages are stored. Now store the index.*/ + // All pages are stored. Now store the index, to the next page. nvs::Item item; item.blobIndex.dataSize = sizeof(hexdata); item.blobIndex.chunkCount = 1; item.blobIndex.chunkStart = nvs::VerOffset::VER_0_OFFSET; TEST_ESP_OK(p.markFull()); nvs::Page p2; - TEST_ESP_OK(p2.load(f.part(), 1)); + + // p2 is loaded from the next page + TEST_ESP_OK(p2.load(&h, 1)); TEST_ESP_OK(p2.setSeqNumber(1)); TEST_ESP_OK(p2.writeItem(1, nvs::ItemType::BLOB_IDX, "singlepage", item.data, sizeof(item.data))); + // Old format blob should still be present on the first page TEST_ESP_OK(p.findItem(1, nvs::ItemType::BLOB, "singlepage")); - TEST_ESP_OK(nvs_flash_deinit_partition(f.part()->get_partition_name())); - /* Initialize again */ - TEST_ESP_OK(nvs::NVSPartitionManager::get_instance()->init_custom(f.part(), 0, 3)); - TEST_ESP_OK(nvs_open("namespace1", NVS_READWRITE, &handle)); + TEST_ESP_OK(nvs_flash_deinit_partition(TEST_DEFAULT_PARTITION_NAME)); + // Initialize again + TEST_ESP_OK(nvs::NVSPartitionManager::get_instance()->init_custom(&h, 0, NVS_FLASH_SECTOR_COUNT)); + TEST_ESP_OK(nvs_open_from_partition(TEST_DEFAULT_PARTITION_NAME, "namespace1", NVS_READWRITE, &handle)); + // API should return the new format blob TEST_ESP_OK(nvs_get_blob(handle, "singlepage", buf, &buflen)); CHECK(memcmp(buf, hexdata, buflen) == 0); + // Low level check to ensure the old-format blob is not present at all + // Search on the page for BLOB will find BLOB_INDEX and indicate the expected type mismatch + // p3 is loaded from the first page, where old-format blob was present before initialization + // but it should not be present after initialization as the new-format blob is present nvs::Page p3; - TEST_ESP_OK(p3.load(f.part(), 0)); + TEST_ESP_OK(p3.load(&h, 0)); TEST_ESP_ERR(p3.findItem(1, nvs::ItemType::BLOB, "singlepage"), ESP_ERR_NVS_NOT_FOUND); - TEST_ESP_OK(nvs_flash_deinit_partition(f.part()->get_partition_name())); + TEST_ESP_OK(nvs_flash_deinit_partition(TEST_DEFAULT_PARTITION_NAME)); } TEST_CASE("Page handles invalid CRC of variable length items", "[nvs][cur]") { - PartitionEmulationFixture f(0, 4); - { - nvs::Page p; - TEST_ESP_OK(p.load(f.part(), 0)); - char buf[128] = {0}; - TEST_ESP_OK(p.writeItem(1, nvs::ItemType::BLOB, "1", buf, sizeof(buf))); - } + // TC verifies that NVS Page works as expected when the page has an invalid CRC for variable length items. + // The test verifies following: + // - NVS Page load does not crash when the NVS item has an invalid CRC + + const uint32_t NVS_FLASH_SECTOR_COUNT = 3; + + // PartitionTestHelper is used to provide nvs::Partition instance for the test + NVSPartitionTestHelper h(TEST_DEFAULT_PARTITION_NAME); + + // This test requires at least 3 NVS pages to run. + CHECK(h.get_sectors() >= NVS_FLASH_SECTOR_COUNT); + + // Write the BLOB item + nvs::Page p; + TEST_ESP_OK(p.load(&h, 0)); + char buf[128] = {0}; + TEST_ESP_OK(p.writeItem(1, nvs::ItemType::BLOB, "1", buf, sizeof(buf))); + // corrupt header of the item (64 is the offset of the first item in page) uint32_t overwrite_buf = 0; - TEST_ESP_OK(esp_partition_write(&f.esp_partition, 64, &overwrite_buf, 4)); + TEST_ESP_OK(h.write_raw(64, &overwrite_buf, sizeof(overwrite_buf))); + // load page again - { - nvs::Page p1; - TEST_ESP_OK(p1.load(f.part(), 0)); - } + nvs::Page p1; + TEST_ESP_OK(p1.load(&h, 0)); } TEST_CASE("namespace name is deep copy", "[nvs]") { + // TC verifies that NVS API works as expected when the namespace name is passed as a pointer. + // The test verifies following: + // - nvs_open can open a namespace with a name passed via a pointer + // - nvs_open does not modify the namespace name passed as a pointer + char ns_name[16]; strcpy(ns_name, "const_name"); nvs_handle_t handle_1; nvs_handle_t handle_2; - const uint32_t NVS_FLASH_SECTOR = 6; - const uint32_t NVS_FLASH_SECTOR_COUNT_MIN = 3; - PartitionEmulationFixture f(NVS_FLASH_SECTOR, NVS_FLASH_SECTOR_COUNT_MIN); + TEST_ESP_OK(nvs_flash_erase_partition(TEST_3SEC_PARTITION_NAME)); - TEMPORARILY_DISABLED(f.emu.setBounds(NVS_FLASH_SECTOR, NVS_FLASH_SECTOR + NVS_FLASH_SECTOR_COUNT_MIN);) + // Initialize NVS partition + TEST_ESP_OK(nvs_flash_init_partition(TEST_3SEC_PARTITION_NAME)); - TEST_ESP_OK(nvs::NVSPartitionManager::get_instance()->init_custom(f.part(), - NVS_FLASH_SECTOR, - NVS_FLASH_SECTOR_COUNT_MIN)); + // Open (and create) a namespace with a name passed via a pointer + TEST_ESP_OK(nvs_open_from_partition(TEST_3SEC_PARTITION_NAME, ns_name, NVS_READWRITE, &handle_1)); - TEST_ESP_OK(nvs_open("const_name", NVS_READWRITE, &handle_1)); + // Modify the namespace name in the variable used as a parameter before strcpy(ns_name, "just_kidding"); - - CHECK(nvs_open("just_kidding", NVS_READONLY, &handle_2) == ESP_ERR_NVS_NOT_FOUND); + // Try to open the namespace using pointer to the same variable + // This time intentionally in ReadOnly mode + // NVS API should not find (and may not create) the namespace + // because the name is not the same as the one used in nvs_open above + CHECK(nvs_open_from_partition(TEST_3SEC_PARTITION_NAME, ns_name, NVS_READONLY, &handle_2) == ESP_ERR_NVS_NOT_FOUND); nvs_close(handle_1); nvs_close(handle_2); - nvs_flash_deinit_partition(NVS_DEFAULT_PART_NAME); + nvs_flash_deinit_partition(TEST_3SEC_PARTITION_NAME); } TEST_CASE("multiple partitions access check", "[nvs]") { - const uint32_t NVS_FLASH_SECTOR_BEGIN1 = 0; - const uint32_t NVS_FLASH_SECTOR_SIZE1 = 5; - const char *NVS_FLASH_PARTITION1 = "nvs1"; - const uint32_t NVS_FLASH_SECTOR_BEGIN2 = 5; - const uint32_t NVS_FLASH_SECTOR_SIZE2 = 5; - const char *NVS_FLASH_PARTITION2 = "nvs2"; + // TC verifies that NVS API works as expected when accessing multiple partitions. + // The test verifies following: + // - nvs_open_from_partition can open same namespace from different partitions + // - nvs_set_i32 can write different values to the same key in different partitions + // - nvs_get_i32 can read different values from the same key in different partitions + // - nvs_flash_init_partition can initialize multiple partitions independently - PartitionEmulationFixture2 f(NVS_FLASH_SECTOR_BEGIN1, - NVS_FLASH_SECTOR_SIZE1, - NVS_FLASH_PARTITION1, - NVS_FLASH_SECTOR_BEGIN2, - NVS_FLASH_SECTOR_SIZE2, - NVS_FLASH_PARTITION2 - ); - TEST_ESP_OK(nvs::NVSPartitionManager::get_instance()->init_custom(f.part(), - NVS_FLASH_SECTOR_BEGIN1, - NVS_FLASH_SECTOR_SIZE1)); + // Erase the partitions before testing + TEST_ESP_OK(nvs_flash_erase_partition(TEST_DEFAULT_PARTITION_NAME)); + TEST_ESP_OK(nvs_flash_erase_partition(TEST_SECONDARY_PARTITION_NAME)); - TEST_ESP_OK(nvs::NVSPartitionManager::get_instance()->init_custom(f.part2(), - NVS_FLASH_SECTOR_BEGIN2, - NVS_FLASH_SECTOR_SIZE2)); + // Initialize NVS partitions + TEST_ESP_OK(nvs_flash_init_partition(TEST_DEFAULT_PARTITION_NAME)); + TEST_ESP_OK(nvs_flash_init_partition(TEST_SECONDARY_PARTITION_NAME)); nvs_handle_t handle1, handle2; - TEST_ESP_OK(nvs_open_from_partition("nvs1", "test", NVS_READWRITE, &handle1)); - TEST_ESP_OK(nvs_open_from_partition("nvs2", "test", NVS_READWRITE, &handle2)); + + // Open namespaces in both partitions + // and write different values to the same key in both partitions + // Check that they are stored correctly + // and can be read back correctly + // This verifies that NVS can handle multiple partitions independently + TEST_ESP_OK(nvs_open_from_partition(TEST_DEFAULT_PARTITION_NAME, "test", NVS_READWRITE, &handle1)); + TEST_ESP_OK(nvs_open_from_partition(TEST_SECONDARY_PARTITION_NAME, "test", NVS_READWRITE, &handle2)); TEST_ESP_OK(nvs_set_i32(handle1, "foo", 0xdeadbeef)); TEST_ESP_OK(nvs_set_i32(handle2, "foo", 0xcafebabe)); int32_t v1, v2; @@ -2283,137 +2845,173 @@ TEST_CASE("multiple partitions access check", "[nvs]") TEST_ESP_OK(nvs_get_i32(handle2, "foo", &v2)); CHECK(v1 == 0xdeadbeef); CHECK(v2 == 0xcafebabe); - TEST_ESP_OK(nvs_flash_deinit_partition(NVS_FLASH_PARTITION1)); - TEST_ESP_OK(nvs_flash_deinit_partition(NVS_FLASH_PARTITION2)); + + // Deinitialize partitions + TEST_ESP_OK(nvs_flash_deinit_partition(TEST_DEFAULT_PARTITION_NAME)); + TEST_ESP_OK(nvs_flash_deinit_partition(TEST_SECONDARY_PARTITION_NAME)); } TEST_CASE("writing the identical content does not write or erase", "[nvs]") { - PartitionEmulationFixture f(0, 20); + // TC verifies that NVS API works as expected when writing the identical content. + // The test verifies following: + // - nvs_set_u8 does not write or erase when writing the same value + // - nvs_set_str does not write or erase when writing the same string + // - nvs_set_blob does not write or erase when writing the same blob - const uint32_t NVS_FLASH_SECTOR = 5; - const uint32_t NVS_FLASH_SECTOR_COUNT_MIN = 10; - TEMPORARILY_DISABLED(f.emu.setBounds(NVS_FLASH_SECTOR, NVS_FLASH_SECTOR + NVS_FLASH_SECTOR_COUNT_MIN);) - TEST_ESP_OK(nvs::NVSPartitionManager::get_instance()->init_custom(f.part(), - NVS_FLASH_SECTOR, - NVS_FLASH_SECTOR_COUNT_MIN)); + // Erase the partition before testing + TEST_ESP_OK(nvs_flash_erase_partition(TEST_DEFAULT_PARTITION_NAME)); + + // Initialize NVS partition + TEST_ESP_OK(nvs_flash_init_partition(TEST_DEFAULT_PARTITION_NAME)); nvs_handle_t misc_handle; - TEST_ESP_OK(nvs_open("test", NVS_READWRITE, &misc_handle)); + TEST_ESP_OK(nvs_open_from_partition(TEST_DEFAULT_PARTITION_NAME, "test", NVS_READWRITE, &misc_handle)); // Test writing a u8 twice, then changing it nvs_set_u8(misc_handle, "test_u8", 8); - esp_partition_clear_stats(); + NVSPartitionTestHelper::clear_stats(); nvs_set_u8(misc_handle, "test_u8", 8); - CHECK(esp_partition_get_write_ops() == 0); - CHECK(esp_partition_get_erase_ops() == 0); - CHECK(esp_partition_get_read_ops() != 0); - esp_partition_clear_stats(); + CHECK(NVSPartitionTestHelper::get_write_ops() == 0); + CHECK(NVSPartitionTestHelper::get_erase_ops() == 0); + CHECK(NVSPartitionTestHelper::get_read_ops() != 0); + NVSPartitionTestHelper::clear_stats(); nvs_set_u8(misc_handle, "test_u8", 9); - CHECK(esp_partition_get_write_ops() != 0); - CHECK(esp_partition_get_read_ops() != 0); + CHECK(NVSPartitionTestHelper::get_write_ops() != 0); + CHECK(NVSPartitionTestHelper::get_read_ops() != 0); // Test writing a string twice, then changing it static const char *test[2] = {"Hello world.", "Hello world!"}; nvs_set_str(misc_handle, "test_str", test[0]); - esp_partition_clear_stats(); + NVSPartitionTestHelper::clear_stats(); nvs_set_str(misc_handle, "test_str", test[0]); - CHECK(esp_partition_get_write_ops() == 0); - CHECK(esp_partition_get_erase_ops() == 0); - CHECK(esp_partition_get_read_ops() != 0); - esp_partition_clear_stats(); + CHECK(NVSPartitionTestHelper::get_write_ops() == 0); + CHECK(NVSPartitionTestHelper::get_erase_ops() == 0); + CHECK(NVSPartitionTestHelper::get_read_ops() != 0); + NVSPartitionTestHelper::clear_stats(); nvs_set_str(misc_handle, "test_str", test[1]); - CHECK(esp_partition_get_write_ops() != 0); - CHECK(esp_partition_get_read_ops() != 0); + CHECK(NVSPartitionTestHelper::get_write_ops() != 0); + CHECK(NVSPartitionTestHelper::get_read_ops() != 0); // Test writing a multi-page blob, then changing it uint8_t blob[nvs::Page::CHUNK_MAX_SIZE * 3] = {0}; memset(blob, 1, sizeof(blob)); nvs_set_blob(misc_handle, "test_blob", blob, sizeof(blob)); - esp_partition_clear_stats(); + NVSPartitionTestHelper::clear_stats(); nvs_set_blob(misc_handle, "test_blob", blob, sizeof(blob)); - CHECK(esp_partition_get_write_ops() == 0); - CHECK(esp_partition_get_erase_ops() == 0); - CHECK(esp_partition_get_read_ops() != 0); + CHECK(NVSPartitionTestHelper::get_write_ops() == 0); + CHECK(NVSPartitionTestHelper::get_erase_ops() == 0); + CHECK(NVSPartitionTestHelper::get_read_ops() != 0); blob[sizeof(blob) - 1]++; - esp_partition_clear_stats(); + NVSPartitionTestHelper::clear_stats(); nvs_set_blob(misc_handle, "test_blob", blob, sizeof(blob)); - CHECK(esp_partition_get_write_ops() != 0); - CHECK(esp_partition_get_read_ops() != 0); + CHECK(NVSPartitionTestHelper::get_write_ops() != 0); + CHECK(NVSPartitionTestHelper::get_read_ops() != 0); - TEST_ESP_OK(nvs_flash_deinit_partition(NVS_DEFAULT_PART_NAME)); + TEST_ESP_OK(nvs_flash_deinit_partition(TEST_DEFAULT_PARTITION_NAME)); } TEST_CASE("can init storage from flash with random contents", "[nvs]") { - PartitionEmulationFixture f(0, 10); - f.randomize(42); + // TC verifies that NVS API can be initialized from a partition with random contents. + // The test verifies following: + // - nvs_flash_init_partition can initialize the partition with random contents + // - nvs_open_from_partition can open a namespace in the partition with random contents + // - nvs_get_u8 reports the not found key as ESP_ERR_NVS_NOT_FOUND + // - nvs_set_u8 can set a key in the partition with random contents + + // Randomize the partition contents + TEST_ESP_OK(NVSPartitionTestHelper::randomize_partition(TEST_3SEC_PARTITION_NAME, 42)); + + // Initialize NVS partition + TEST_ESP_OK(nvs_flash_init_partition(TEST_3SEC_PARTITION_NAME)); nvs_handle_t handle; - const uint32_t NVS_FLASH_SECTOR = 5; - const uint32_t NVS_FLASH_SECTOR_COUNT_MIN = 3; - TEMPORARILY_DISABLED(f.emu.setBounds(NVS_FLASH_SECTOR, NVS_FLASH_SECTOR + NVS_FLASH_SECTOR_COUNT_MIN);) - TEST_ESP_OK(nvs::NVSPartitionManager::get_instance()->init_custom(f.part(), - NVS_FLASH_SECTOR, - NVS_FLASH_SECTOR_COUNT_MIN)); - TEST_ESP_OK(nvs_open("nvs.net80211", NVS_READWRITE, &handle)); + // Open the namespace + TEST_ESP_OK(nvs_open_from_partition(TEST_3SEC_PARTITION_NAME, "nvs.net80211", NVS_READWRITE, &handle)); uint8_t opmode = 2; - if (nvs_get_u8(handle, "wifi.opmode", &opmode) != ESP_OK) { - TEST_ESP_OK(nvs_set_u8(handle, "wifi.opmode", opmode)); - } - TEST_ESP_OK(nvs_flash_deinit_partition(NVS_DEFAULT_PART_NAME)); + // Verify the key "wifi.opmode" is not found + TEST_ESP_ERR(nvs_get_u8(handle, "wifi.opmode", &opmode), ESP_ERR_NVS_NOT_FOUND); + + // Set the key "wifi.opmode" to a value + TEST_ESP_OK(nvs_set_u8(handle, "wifi.opmode", opmode)); + + TEST_ESP_OK(nvs_flash_deinit_partition(TEST_3SEC_PARTITION_NAME)); } TEST_CASE("nvs api tests, starting with random data in flash", "[nvs][long][.]") { + // TC verifies that NVS API works as expected when the partition is initialized with random data. + // The test verifies following: + // - nvs_flash_init_partition can initialize the partition with random data + // - nvs_open_from_partition can open a namespace in the partition with random data + // - nvs_set_i32 can set a key in the initialized partition + // - nvs_get_i32 can get a key from the initialized partition + // - nvs_set_str can set a string in the initialized partition + // - nvs_get_str can get a string from the initialized partition + // - two independen t namespaces can be created and used in the same partition + + // Number of iterations for the test const size_t testIters = 3000; + int lastPercent = -1; for (size_t count = 0; count < testIters; ++count) { + // Progress reporting int percentDone = (int)(count * 100 / testIters); if (percentDone != lastPercent) { lastPercent = percentDone; printf("%d%%\n", percentDone); } - PartitionEmulationFixture f(0, 10); - f.randomize(static_cast(count)); - const uint32_t NVS_FLASH_SECTOR = 6; - const uint32_t NVS_FLASH_SECTOR_COUNT_MIN = 3; - TEMPORARILY_DISABLED(f.emu.setBounds(NVS_FLASH_SECTOR, NVS_FLASH_SECTOR + NVS_FLASH_SECTOR_COUNT_MIN);) + // Randomize the partition contents, the random seed is based on the iteration cycle + TEST_ESP_OK(NVSPartitionTestHelper::randomize_partition(TEST_DEFAULT_PARTITION_NAME, static_cast(count))) ; - TEST_ESP_OK(nvs::NVSPartitionManager::get_instance()->init_custom(f.part(), - NVS_FLASH_SECTOR, - NVS_FLASH_SECTOR_COUNT_MIN)); + // Initialize NVS partition + TEST_ESP_OK(nvs_flash_init_partition(TEST_DEFAULT_PARTITION_NAME)); nvs_handle_t handle_1; - TEST_ESP_ERR(nvs_open("namespace1", NVS_READONLY, &handle_1), ESP_ERR_NVS_NOT_FOUND); + // Readonly open should fail, as the namespace does not exist yet + TEST_ESP_ERR(nvs_open_from_partition(TEST_DEFAULT_PARTITION_NAME, "namespace1", NVS_READONLY, &handle_1), ESP_ERR_NVS_NOT_FOUND); - TEST_ESP_OK(nvs_open("namespace1", NVS_READWRITE, &handle_1)); + // Open the namespace in read-write mode, this should create the namespace + // and allow writing to it + TEST_ESP_OK(nvs_open_from_partition(TEST_DEFAULT_PARTITION_NAME, "namespace1", NVS_READWRITE, &handle_1)); + + // Set a key in the first namespace + // This should succeed and create the key in the namespace TEST_ESP_OK(nvs_set_i32(handle_1, "foo", 0x12345678)); for (size_t i = 0; i < 500; ++i) { nvs_handle_t handle_2; - TEST_ESP_OK(nvs_open("namespace2", NVS_READWRITE, &handle_2)); + + // Open the second namespace, this should create it + // and allow writing to it + TEST_ESP_OK(nvs_open_from_partition(TEST_DEFAULT_PARTITION_NAME, "namespace2", NVS_READWRITE, &handle_2)); + // Set a key in the second namespace TEST_ESP_OK(nvs_set_i32(handle_1, "foo", 0x23456789 % (i + 1))); + // Overwrite the key in the second namespace TEST_ESP_OK(nvs_set_i32(handle_2, "foo", static_cast(i))); const char *str = "value 0123456789abcdef0123456789abcdef %09d"; char str_buf[128]; snprintf(str_buf, sizeof(str_buf), str, i + count * 1024); + // Set a string in the second namespace TEST_ESP_OK(nvs_set_str(handle_2, "key", str_buf)); int32_t v1; + // Get the key from the first namespace TEST_ESP_OK(nvs_get_i32(handle_1, "foo", &v1)); CHECK(0x23456789 % (i + 1) == v1); int32_t v2; + // Get the key from the second namespace TEST_ESP_OK(nvs_get_i32(handle_2, "foo", &v2)); CHECK(static_cast(i) == v2); char buf[128]; size_t buf_len = sizeof(buf); - + // Get the string from the second namespace TEST_ESP_OK(nvs_get_str(handle_2, "key", buf, &buf_len)); CHECK(0 == strcmp(buf, str_buf)); @@ -2421,37 +3019,58 @@ TEST_CASE("nvs api tests, starting with random data in flash", "[nvs][long][.]") } nvs_close(handle_1); - TEST_ESP_OK(nvs_flash_deinit_partition(f.part()->get_partition_name())); + TEST_ESP_OK(nvs_flash_deinit_partition(TEST_DEFAULT_PARTITION_NAME)); } } TEST_CASE("test recovery from sudden poweroff", "[long][nvs][recovery][monkey][.]") { + // TC verifies that NVS API works as expected when the power is lost during random operations. + // The test verifies following: + // - nvs_flash_init_partition can initialize the partition with random contents + // - nvs_open_from_partition can open a namespace in the partition with random contents + // - random operation using nvs API can be performed + // - once the power-off emulation is triggered, the NVS can be re-initialized and + // all random operations can be can be performed without errors + + // Note: This test is designed to run for a long time, and it takes more than one hour to complete. + + // Random device setup std::random_device rd; std::mt19937 gen(rd()); uint32_t seed = 3; gen.seed(seed); + + // Set the number of iterations for the test const size_t iter_count = 2000; + // Total operations counter holding the number of erase and 1/4 write operations performed size_t totalOps = 0; + + // Counter for progress reporting int lastPercent = -1; + + // Main loop counter for the test + // It controls the number of operations before the next power-off simulation uint32_t errDelay; for (errDelay = 0; ; ++errDelay) { INFO(errDelay); - PartitionEmulationFixture f(0, 10); - const uint32_t NVS_FLASH_SECTOR = 2; - const uint32_t NVS_FLASH_SECTOR_COUNT_MIN = 8; - TEMPORARILY_DISABLED(f.emu.setBounds(NVS_FLASH_SECTOR, NVS_FLASH_SECTOR + NVS_FLASH_SECTOR_COUNT_MIN);) + // Randomize the partition contents + TEST_ESP_OK(NVSPartitionTestHelper::randomize_partition(TEST_DEFAULT_PARTITION_NAME, seed)); - f.randomize(seed); - esp_partition_clear_stats(); - esp_partition_fail_after(errDelay, ESP_PARTITION_FAIL_AFTER_MODE_BOTH); + NVSPartitionTestHelper::clear_stats(); + + // Step 1 of the cycle is to initialize the power-off emulation + NVSPartitionTestHelper::fail_after(errDelay, ESP_PARTITION_FAIL_AFTER_MODE_BOTH); RandomTest test; + // Report progress if (totalOps != 0) { int percent = errDelay * 100 / totalOps; + + // Report progress every increment by 1% if (percent > lastPercent) { printf("%d/%d (%d%%)\r\n", errDelay, static_cast(totalOps), percent); lastPercent = percent; @@ -2459,50 +3078,73 @@ TEST_CASE("test recovery from sudden poweroff", "[long][nvs][recovery][monkey][. } nvs_handle_t handle; - size_t count = iter_count; + size_t count = iter_count; // We need local variable for count, as it is referenced in the test.doRandomThings() method - if (nvs::NVSPartitionManager::get_instance()->init_custom(f.part(), - NVS_FLASH_SECTOR, - NVS_FLASH_SECTOR_COUNT_MIN) == ESP_OK) { + // Step 2 tries to perform the random operations + // As the moment of the power-off is driven by the errDelay which gradually increases, + // it is expected that the test will fail continuously until the errDelay is high enough + // to not trigger the power-off emulation. + // We are observing the result of the test.doRandomThings() method to determine if the power-off emulation was triggered. + // Once we reach the point where the errDelay is high enough to not trigger the power-off emulation, + // the loop will break and the test will finish successfully. + if(nvs_flash_init_partition(TEST_DEFAULT_PARTITION_NAME) == ESP_OK) + { + // If the partition was initialized successfully, we can proceed with the test esp_err_t res = ESP_ERR_FLASH_OP_FAIL; - if (nvs_open("namespace1", NVS_READWRITE, &handle) == ESP_OK) { + if (nvs_open_from_partition(TEST_DEFAULT_PARTITION_NAME, "namespace1", NVS_READWRITE, &handle) == ESP_OK) { res = test.doRandomThings(handle, gen, count, errDelay); nvs_close(handle); } - TEST_ESP_OK(nvs_flash_deinit_partition(NVS_DEFAULT_PART_NAME)); + TEST_ESP_OK(nvs_flash_deinit_partition(TEST_DEFAULT_PARTITION_NAME)); + + // As the res was previously set to ESP_ERR_FLASH_OP_FAIL, we check if it was changed + // If the res remains the same, it means that the test encountered and emulated power-off and we can if (res != ESP_ERR_FLASH_OP_FAIL) { - // This means we got to the end without an error due to f.emu.failAfter(), therefore errDelay + // This means we got to the end without an error due to NVSPartitionTestHelper::fail_after(), therefore errDelay // is high enough that we're not triggering it any more, therefore we're done break; } } - TEST_ESP_OK(nvs::NVSPartitionManager::get_instance()->init_custom(f.part(), - NVS_FLASH_SECTOR, - NVS_FLASH_SECTOR_COUNT_MIN)); - TEST_ESP_OK(nvs_open("namespace1", NVS_READWRITE, &handle)); + // Step 3 tries to recover the partition after the power-off emulation + // Note: the power-off won't trigger at this point, as it is deactiavated after the first occurrence + // of the ESP_ERR_FLASH_OP_FAIL error. + TEST_ESP_OK(nvs_flash_init_partition(TEST_DEFAULT_PARTITION_NAME)); + + TEST_ESP_OK(nvs_open_from_partition(TEST_DEFAULT_PARTITION_NAME, "namespace1", NVS_READWRITE, &handle)); esp_err_t res; res = test.doRandomThings(handle, gen, count, errDelay); + + // We expect the test.doRandomThings() to return ESP_OK at this point, + // If not, we dump the partition contents and fail the test if (res != ESP_OK) { - nvs_dump(NVS_DEFAULT_PART_NAME); + nvs_dump(TEST_DEFAULT_PARTITION_NAME); CHECK(0); } nvs_close(handle); - totalOps = esp_partition_get_erase_ops() + esp_partition_get_write_bytes() / 4; + totalOps = NVSPartitionTestHelper::get_erase_ops() + NVSPartitionTestHelper::get_write_bytes() / 4; - TEST_ESP_OK(nvs_flash_deinit_partition(NVS_DEFAULT_PART_NAME)); + TEST_ESP_OK(nvs_flash_deinit_partition(TEST_DEFAULT_PARTITION_NAME)); } } TEST_CASE("duplicate items are removed", "[nvs][dupes]") { - PartitionEmulationFixture f(0, 3); + // TC verifies that NVS API works as expected when duplicate items are present. + // The test verifies following: + // - nvs::Page can handle duplicate items + // - nvs::Storage can handle duplicate items + // - nvs::Storage can read the correct value after duplicates are removed + // - nvs::Page can report the correct number of used and erased entries after duplicates are removed + + // PartitionTestHelper is used to provide nvs::Partition instance for the test + NVSPartitionTestHelper h(TEST_DEFAULT_PARTITION_NAME); { // create one item nvs::Page p; - TEST_ESP_OK(p.load(f.part(), 0)); + TEST_ESP_OK(p.load(&h, 0)); TEST_ESP_OK(p.writeItem(1, "opmode", 3)); } { @@ -2511,16 +3153,16 @@ TEST_CASE("duplicate items are removed", "[nvs][dupes]") item.data[0] = 2; item.crc32 = item.calculateCrc32(); - TEST_ESP_OK(esp_partition_write(f.get_esp_partition(), 3 * 32, reinterpret_cast(&item), sizeof(item))); - TEST_ESP_OK(esp_partition_write(f.get_esp_partition(), 4 * 32, reinterpret_cast(&item), sizeof(item))); + TEST_ESP_OK(h.write_raw(3 * 32, reinterpret_cast(&item), sizeof(item))); + TEST_ESP_OK(h.write_raw(4 * 32, reinterpret_cast(&item), sizeof(item))); uint32_t mask = 0xFFFFFFEA; - TEST_ESP_OK(esp_partition_write(f.get_esp_partition(), 32, &mask, 4)); + TEST_ESP_OK(h.write_raw(32, &mask, 4)); } { // load page and check that second item persists - nvs::Storage s(f.part()); + nvs::Storage s(&h); TEST_ESP_OK(s.init(0, 3)); uint8_t val; ESP_ERROR_CHECK(s.readItem(1, "opmode", val)); @@ -2528,21 +3170,29 @@ TEST_CASE("duplicate items are removed", "[nvs][dupes]") } { nvs::Page p; - TEST_ESP_OK(p.load(f.part(), 0)); + TEST_ESP_OK(p.load(&h, 0)); CHECK(p.getErasedEntryCount() == 2); CHECK(p.getUsedEntryCount() == 1); } } TEST_CASE("recovery after failure to write data", "[nvs]") -{ - PartitionEmulationFixture f(0, 3); +{ // TC verifies that NVS API works as expected when the power is lost during writing data. + // The test verifies following: + // - nvs::Storage can recover from a failure to write data + // - nvs::Page can handle the situation when the data is not written correctly + // - nvs::Storage can read the correct value after recovery + // - nvs::Page can report the correct number of used and erased entries after recovery + + // PartitionTestHelper is used to provide nvs::Partition instance for the test + NVSPartitionTestHelper h(TEST_DEFAULT_PARTITION_NAME); + const char str[] = "value 0123456789abcdef012345678value 0123456789abcdef012345678"; // make flash write fail exactly in nvs::Page::writeEntryData - esp_partition_fail_after(17, ESP_PARTITION_FAIL_AFTER_MODE_BOTH); + NVSPartitionTestHelper::fail_after(17, ESP_PARTITION_FAIL_AFTER_MODE_BOTH); { - nvs::Storage storage(f.part()); + 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); @@ -2556,7 +3206,7 @@ TEST_CASE("recovery after failure to write data", "[nvs]") { // load page and check that data was erased nvs::Page p; - TEST_ESP_OK(p.load(f.part(), 0)); + TEST_ESP_OK(p.load(&h, 0)); CHECK(p.getErasedEntryCount() == 3); CHECK(p.getUsedEntryCount() == 0); @@ -2567,8 +3217,17 @@ TEST_CASE("recovery after failure to write data", "[nvs]") TEST_CASE("crc errors in item header are handled", "[nvs]") { - PartitionEmulationFixture f(0, 3); - nvs::Storage storage(f.part()); + // TC verifies that NVS API works as expected when the item header has a CRC error. + // The test verifies following: + // - nvs::Storage can recover from a CRC error in the item header + // - nvs::Page can handle the situation when the item header has a CRC error + // - nvs::Storage can recover from a CRC error in the item header on a full page + // - nvs::Storage can read the correct value after recovery + + // PartitionTestHelper is used to provide nvs::Partition instance for the test + NVSPartitionTestHelper h(TEST_DEFAULT_PARTITION_NAME); + + nvs::Storage storage(&h); // prepare some data TEST_ESP_OK(storage.init(0, 3)); TEST_ESP_OK(storage.writeItem(0, "ns1", static_cast(1))); @@ -2577,7 +3236,7 @@ TEST_CASE("crc errors in item header are handled", "[nvs]") // corrupt item header uint32_t val = 0; - TEST_ESP_OK(esp_partition_write(f.get_esp_partition(), 32 * 3, &val, 4)); + TEST_ESP_OK(h.write_raw(32 * 3 + 4, &val, 4)); // overwrite crc32 // check that storage can recover TEST_ESP_OK(storage.init(0, 3)); @@ -2595,7 +3254,7 @@ TEST_CASE("crc errors in item header are handled", "[nvs]") // corrupt another item on the full page val = 0; - TEST_ESP_OK(esp_partition_write(f.get_esp_partition(), 32 * 4, &val, 4)); + TEST_ESP_OK(h.write_raw(32 * 4, &val, 4)); // check that storage can recover TEST_ESP_OK(storage.init(0, 3)); @@ -2605,13 +3264,21 @@ TEST_CASE("crc errors in item header are handled", "[nvs]") TEST_CASE("crc error in variable length item is handled", "[nvs]") { - PartitionEmulationFixture f(0, 3); + // TC verifies that NVS API works as expected when the variable length item has a CRC error. + // The test verifies following: + // - nvs::Page can handle the situation when the variable length item has a CRC error + // - nvs::Page can report the correct number of used and erased entries after recovery + // - nvs::Page can read the correct value after recovery + + // PartitionTestHelper is used to provide nvs::Partition instance for the test + NVSPartitionTestHelper h(TEST_DEFAULT_PARTITION_NAME); + const uint64_t before_val = 0xbef04e; const uint64_t after_val = 0xaf7e4; // write some data { nvs::Page p; - TEST_ESP_OK(p.load(f.part(), 0)); + TEST_ESP_OK(p.load(&h, 0)); TEST_ESP_OK(p.writeItem(0, "before", before_val)); const char *str = "foobar"; TEST_ESP_OK(p.writeItem(0, nvs::ItemType::SZ, "key", str, strlen(str))); @@ -2619,13 +3286,13 @@ TEST_CASE("crc error in variable length item is handled", "[nvs]") } // corrupt some data uint32_t w; - TEST_ESP_OK(esp_partition_read(f.get_esp_partition(), 32 * 3 + 8, &w, sizeof(w))); + TEST_ESP_OK(h.read_raw(32 * 3 + 8, &w, sizeof(w))); w &= 0xf000000f; - TEST_ESP_OK(esp_partition_write(f.get_esp_partition(), 32 * 3 + 8, &w, sizeof(w))); + TEST_ESP_OK(h.write_raw(32 * 3 + 8, &w, sizeof(w))); // load and check { nvs::Page p; - TEST_ESP_OK(p.load(f.part(), 0)); + TEST_ESP_OK(p.load(&h, 0)); CHECK(p.getUsedEntryCount() == 2); CHECK(p.getErasedEntryCount() == 2); @@ -2641,8 +3308,16 @@ TEST_CASE("crc error in variable length item is handled", "[nvs]") // handle damaged item header's span=0 even if crc is correct TEST_CASE("zero span in item header with correct crc is handled", "[nvs]") { - PartitionEmulationFixture f(0, 3); - nvs::Storage storage(f.part()); + // TC verifies that NVS API works as expected when the item header has span=0. + // The test verifies following: + // - nvs::Storage can recover from a span=0 error in the item header on a non-full page + // - nvs::Storage can recover from a span=0 error in the item header on full page + // - nvs::Storage reports non-existing removed item as ESP_ERR_NVS_NOT_FOUND + + // PartitionTestHelper is used to provide nvs::Partition instance for the test + NVSPartitionTestHelper h(TEST_DEFAULT_PARTITION_NAME); + + nvs::Storage storage(&h); // prepare some data TEST_ESP_OK(storage.init(0, 3)); TEST_ESP_OK(storage.writeItem(0, "ns1", static_cast(1))); @@ -2654,7 +3329,7 @@ TEST_CASE("zero span in item header with correct crc is handled", "[nvs]") uint8_t new_span = 0; size_t entry_offset = 32 * 3; // 2x page header + 1x ns1 uint8_t buff[sizeof(nvs::Item)] = {0}; - TEST_ESP_OK(esp_partition_read(f.get_esp_partition(), entry_offset, &buff, sizeof(buff))); + TEST_ESP_OK(h.read_raw(entry_offset, &buff, sizeof(buff))); nvs::Item* item = (nvs::Item*)buff; @@ -2663,10 +3338,10 @@ TEST_CASE("zero span in item header with correct crc is handled", "[nvs]") // recalculate crc same way as nvs::Item::calculateCrc32 does item->crc32 = ((nvs::Item*)buff)->calculateCrc32(); - // allow overwriting smaller portion of flash than whole 4k page - ((esp_partition_t*) f.get_esp_partition())->erase_size = 1; - TEST_ESP_OK(esp_partition_erase_range(f.get_esp_partition(), entry_offset, sizeof(buff))); - TEST_ESP_OK(esp_partition_write(f.get_esp_partition(), entry_offset, &buff, sizeof(buff))); + // allow erase smaller portion of flash than whole 4k page + TEST_ESP_OK(h.set_erase_size(1)); + TEST_ESP_OK(h.erase_range(entry_offset, sizeof(buff))); + TEST_ESP_OK(h.write_raw(entry_offset, &buff, sizeof(buff))); } // check that storage can recover @@ -2690,7 +3365,7 @@ TEST_CASE("zero span in item header with correct crc is handled", "[nvs]") uint8_t new_span = 0; size_t entry_offset = 32 * (2 + 1 + 1 + 128) ; // 2x page header + 1x ns1 + 1x value1 + whole page uint8_t buff[sizeof(nvs::Item)] = {0}; - TEST_ESP_OK(esp_partition_read(f.get_esp_partition(), entry_offset, &buff, sizeof(buff))); + TEST_ESP_OK(h.read_raw(entry_offset, &buff, sizeof(buff))); nvs::Item* item = (nvs::Item*)buff; // set span to 0 @@ -2698,10 +3373,10 @@ TEST_CASE("zero span in item header with correct crc is handled", "[nvs]") // recalculate crc same way as nvs::Item::calculateCrc32 does item->crc32 = ((nvs::Item*)buff)->calculateCrc32(); - // allow overwriting smaller portion of flash than whole 4k page - ((esp_partition_t*) f.get_esp_partition())->erase_size = 1; - TEST_ESP_OK(esp_partition_erase_range(f.get_esp_partition(), entry_offset, sizeof(buff))); - TEST_ESP_OK(esp_partition_write(f.get_esp_partition(), entry_offset, &buff, sizeof(buff))); + // allow erase smaller portion of flash than whole 4k page + TEST_ESP_OK(h.set_erase_size(1)); + TEST_ESP_OK(h.erase_range(entry_offset, sizeof(buff))); + TEST_ESP_OK(h.write_raw(entry_offset, &buff, sizeof(buff))); } // check that storage can recover @@ -2710,15 +3385,20 @@ TEST_CASE("zero span in item header with correct crc is handled", "[nvs]") TEST_ESP_ERR(ESP_ERR_NVS_NOT_FOUND, storage.readItem(1, "item_125", val)); } -// test case for damaged item header with correct crc using string. -// first sub-case, span goes over the remaining entries in the page -// second sub-case, span goes over the number of entries required to store the string -// third sub-case, span goes below the number of entries required to store the string -// fourth sub-case, indicated variable data length goes over the remaining space in the page + TEST_CASE("inconsistent fields in item header with correct crc are handled for string", "[nvs]") { - PartitionEmulationFixture f(0, 3); - nvs::Storage storage(f.part()); + // TC testing damaged item header with correct crc using string. + // first section, span goes over the remaining entries in the page + // second section, span goes over the number of entries required to store the string + // third section, span goes below the number of entries required to store the string + // fourth section, indicated variable data length goes over the remaining space in the page + // After the sections, the common evaluation is done to check that the storage can recover from the damaged item header. + + // PartitionTestHelper is used to provide nvs::Partition instance for the test + NVSPartitionTestHelper h(TEST_DEFAULT_PARTITION_NAME); + + nvs::Storage storage(&h); // prepare some data TEST_ESP_OK(storage.init(0, 3)); TEST_ESP_OK(storage.writeItem(0, "ns1", static_cast(1))); @@ -2754,7 +3434,7 @@ TEST_CASE("inconsistent fields in item header with correct crc are handled for s CHECK(val == 2); // allow overwriting smaller portion of flash than whole 4k page - ((esp_partition_t*) f.get_esp_partition())->erase_size = 1; + h.set_erase_size(1); SECTION("damage item header of valuestr1 to introduce span exceeding remaining page size error") { // span of the valstr1 is 4, there are 126 - 1 = 125 entries left in the page, set damaged span to 126 @@ -2764,7 +3444,7 @@ TEST_CASE("inconsistent fields in item header with correct crc are handled for s size_t entry_offset = 32 * entry_index; uint8_t buff[sizeof(nvs::Item)] = {0}; - TEST_ESP_OK(esp_partition_read(f.get_esp_partition(), entry_offset, &buff, sizeof(buff))); + TEST_ESP_OK(h.read_raw(entry_offset, &buff, sizeof(buff))); nvs::Item* item = (nvs::Item*)buff; @@ -2772,8 +3452,8 @@ TEST_CASE("inconsistent fields in item header with correct crc are handled for s // recalculate crc same way as nvs::Item::calculateCrc32 does item->crc32 = ((nvs::Item*)buff)->calculateCrc32(); - TEST_ESP_OK(esp_partition_erase_range(f.get_esp_partition(), entry_offset, sizeof(buff))); - TEST_ESP_OK(esp_partition_write(f.get_esp_partition(), entry_offset, &buff, sizeof(buff))); + TEST_ESP_OK(h.erase_range(entry_offset, sizeof(buff))); + TEST_ESP_OK(h.write_raw(entry_offset, &buff, sizeof(buff))); // expect error when trying to read the item exp_err_str1 = ESP_ERR_NVS_NOT_FOUND; @@ -2787,7 +3467,7 @@ TEST_CASE("inconsistent fields in item header with correct crc are handled for s size_t entry_offset = 32 * entry_index; uint8_t buff[sizeof(nvs::Item)] = {0}; - TEST_ESP_OK(esp_partition_read(f.get_esp_partition(), entry_offset, &buff, sizeof(buff))); + TEST_ESP_OK(h.read_raw(entry_offset, &buff, sizeof(buff))); nvs::Item* item = (nvs::Item*)buff; @@ -2795,8 +3475,8 @@ TEST_CASE("inconsistent fields in item header with correct crc are handled for s // recalculate crc same way as nvs::Item::calculateCrc32 does item->crc32 = ((nvs::Item*)buff)->calculateCrc32(); - TEST_ESP_OK(esp_partition_erase_range(f.get_esp_partition(), entry_offset, sizeof(buff))); - TEST_ESP_OK(esp_partition_write(f.get_esp_partition(), entry_offset, &buff, sizeof(buff))); + TEST_ESP_OK(h.erase_range(entry_offset, sizeof(buff))); + TEST_ESP_OK(h.write_raw(entry_offset, &buff, sizeof(buff))); // expect error when trying to read the item exp_err_str2 = ESP_ERR_NVS_NOT_FOUND; @@ -2810,7 +3490,7 @@ TEST_CASE("inconsistent fields in item header with correct crc are handled for s size_t entry_offset = 32 * entry_index; uint8_t buff[sizeof(nvs::Item)] = {0}; - TEST_ESP_OK(esp_partition_read(f.get_esp_partition(), entry_offset, &buff, sizeof(buff))); + TEST_ESP_OK(h.read_raw(entry_offset, &buff, sizeof(buff))); nvs::Item* item = (nvs::Item*)buff; @@ -2818,8 +3498,8 @@ TEST_CASE("inconsistent fields in item header with correct crc are handled for s // recalculate crc same way as nvs::Item::calculateCrc32 does item->crc32 = ((nvs::Item*)buff)->calculateCrc32(); - TEST_ESP_OK(esp_partition_erase_range(f.get_esp_partition(), entry_offset, sizeof(buff))); - TEST_ESP_OK(esp_partition_write(f.get_esp_partition(), entry_offset, &buff, sizeof(buff))); + TEST_ESP_OK(h.erase_range(entry_offset, sizeof(buff))); + TEST_ESP_OK(h.write_raw(entry_offset, &buff, sizeof(buff))); // expect error when trying to read the item exp_err_str3 = ESP_ERR_NVS_NOT_FOUND; @@ -2833,7 +3513,7 @@ TEST_CASE("inconsistent fields in item header with correct crc are handled for s uint16_t new_size = 112 * 32; uint8_t buff[sizeof(nvs::Item)] = {0}; - TEST_ESP_OK(esp_partition_read(f.get_esp_partition(), entry_offset, &buff, sizeof(buff))); + TEST_ESP_OK(h.read_raw(entry_offset, &buff, sizeof(buff))); nvs::Item* item = (nvs::Item*)buff; @@ -2841,8 +3521,8 @@ TEST_CASE("inconsistent fields in item header with correct crc are handled for s // recalculate crc same way as nvs::Item::calculateCrc32 does item->crc32 = ((nvs::Item*)buff)->calculateCrc32(); - TEST_ESP_OK(esp_partition_erase_range(f.get_esp_partition(), entry_offset, sizeof(buff))); - TEST_ESP_OK(esp_partition_write(f.get_esp_partition(), entry_offset, &buff, sizeof(buff))); + TEST_ESP_OK(h.erase_range(entry_offset, sizeof(buff))); + TEST_ESP_OK(h.write_raw(entry_offset, &buff, sizeof(buff))); // expect error when trying to read the item exp_err_str4 = ESP_ERR_NVS_NOT_FOUND; @@ -2862,23 +3542,26 @@ TEST_CASE("inconsistent fields in item header with correct crc are handled for s TEST_ESP_ERR(exp_err_str4, storage.readItem(1, nvs::ItemType::SZ, "valuestr4", read_buff, sizeof(read_buff))); } -// Inconsistent fields in item header with correct crc are handled for blobs -// Before each sub case damaging the blob with key = "valueblob1", following scheme will be used to store the blob and do the test -// Page 1 -// 1x namespace entry -// 1x U32 entry, key = "valueu32_1" before data -// 124x BLOB_DATA entries, key = "valueblob1", chunk index = 0 containing 123*32 bytes of data - -// Page 2 -// 3x BLOB_DATA entries, key = "valueblob1", chunk index = 1 containing 2*32 bytes of data -// 1x BLOB_INDEX entry key = "valueblob1", chunkVersion = 0, chunkCount = 2 -// 3x BLOB_DATA entries, key = "valueblob2", chunk index = 0 containing 2*32 bytes of data -// 1x BLOB_INDEX entry key = "valueblob2", chunkVersion = 0, chunkCount = 1 -// 1x U32 entry key = "valueu32_2" after data TEST_CASE("inconsistent fields in item header with correct crc are handled for multi page blob", "[nvs]") { - PartitionEmulationFixture f(0, 3); + // TC testing inconsistent fields in item header with correct crc are handled for blobs + + // Before each sub case damaging the blob with key = "valueblob1", following scheme will be used to store the blob and do the test + // Page 1 + // 1x namespace entry + // 1x U32 entry, key = "valueu32_1" before data + // 124x BLOB_DATA entries, key = "valueblob1", chunk index = 0 containing 123*32 bytes of data + + // Page 2 + // 3x BLOB_DATA entries, key = "valueblob1", chunk index = 1 containing 2*32 bytes of data + // 1x BLOB_INDEX entry key = "valueblob1", chunkVersion = 0, chunkCount = 2 + // 3x BLOB_DATA entries, key = "valueblob2", chunk index = 0 containing 2*32 bytes of data + // 1x BLOB_INDEX entry key = "valueblob2", chunkVersion = 0, chunkCount = 1 + // 1x U32 entry key = "valueu32_2" after data + + // Please note that the PartitionTestHelper is kept in the scope block to ensure it's destructor is called prior the + // nvs_flash_deinit_partition(TEST_DEFAULT_PARTITION_NAME) in the evaluation section. // keys char ukey1[] = "valueu32_1"; @@ -2935,143 +3618,150 @@ TEST_CASE("inconsistent fields in item header with correct crc are handled for m memset(read_blob1, 0, read_blob1_size); memset(read_blob2, 0, read_blob2_size); - // Write initial data to the nvs partition + // Initialisation part. Write initial data to the nvs partition { - TEST_ESP_OK(nvs::NVSPartitionManager::get_instance()->init_custom(f.part(), 0, 3)); - + TEST_ESP_OK(nvs_flash_erase_partition(TEST_DEFAULT_PARTITION_NAME)); + TEST_ESP_OK(nvs_flash_init_partition(TEST_DEFAULT_PARTITION_NAME)); nvs_handle_t handle; - TEST_ESP_OK(nvs_open("test", NVS_READWRITE, &handle)); + TEST_ESP_OK(nvs_open_from_partition(TEST_DEFAULT_PARTITION_NAME, "test", NVS_READWRITE, &handle)); TEST_ESP_OK(nvs_set_u32(handle, ukey1, uval1)); TEST_ESP_OK(nvs_set_blob(handle, blob_key1, blob_data1, blob_data1_len)); TEST_ESP_OK(nvs_set_blob(handle, blob_key2, blob_data2, blob_data2_len)); TEST_ESP_OK(nvs_set_u32(handle, ukey2, uval2)); nvs_close(handle); - TEST_ESP_OK(nvs_flash_deinit_partition(f.part()->get_partition_name())); + TEST_ESP_OK(nvs_flash_deinit_partition(TEST_DEFAULT_PARTITION_NAME)); } - // during the test, we will make changes of the data in more granular way than the whole 4k page - // allow overwriting smaller portion of flash than whole 4k page - ((esp_partition_t*) f.get_esp_partition())->erase_size = 1; + { // Do not delete the block as it defines scope of the NVSPartitionTestHelper - // Sub test cases - SECTION("damage BLOB_IDX - span set to 0") { - // Damage BLOB_IDX of valueblob1, modify span == 1 to 0 - size_t entry_offset = blob_index_offset; - uint8_t expected_span = 1; - uint8_t new_span = 0; + // PartitionTestHelper is used to provide nvs::Partition instance for the test + // second parameter is set to false, so the partition is not erased as a part of the helper constructor + NVSPartitionTestHelper h(TEST_DEFAULT_PARTITION_NAME, false); - uint8_t buff[sizeof(nvs::Item)] = {0}; - TEST_ESP_OK(esp_partition_read(f.get_esp_partition(), entry_offset, &buff, sizeof(buff))); - nvs::Item* item = (nvs::Item*)buff; + // during the test, we will make changes of the data in more granular way than the whole 4k page + // allow overwriting smaller portion of flash than whole 4k page + h.set_erase_size(1); - CHECK(item->span == expected_span); - item->span = new_span; - // recalculate crc same way as nvs::Item::calculateCrc32 does - item->crc32 = ((nvs::Item*)buff)->calculateCrc32(); - TEST_ESP_OK(esp_partition_erase_range(f.get_esp_partition(), entry_offset, sizeof(buff))); - TEST_ESP_OK(esp_partition_write(f.get_esp_partition(), entry_offset, &buff, sizeof(buff))); - } + // Sub test cases + SECTION("damage BLOB_IDX - span set to 0") { + // Damage BLOB_IDX of valueblob1, modify span == 1 to 0 + size_t entry_offset = blob_index_offset; + uint8_t expected_span = 1; + uint8_t new_span = 0; - SECTION("damage BLOB_IDX - chunkIndex set to 111 instead of CHUNK_ANY") { - // Damage BLOB_IDX of valueblob1, modify chunkIndex == 111 - size_t entry_offset = blob_index_offset; - uint8_t expected_chunk_index = nvs::Item::CHUNK_ANY; - uint8_t new_chunk_index = 111; + uint8_t buff[sizeof(nvs::Item)] = {0}; + TEST_ESP_OK(h.read_raw(entry_offset, &buff, sizeof(buff))); + nvs::Item* item = (nvs::Item*)buff; - uint8_t buff[sizeof(nvs::Item)] = {0}; - TEST_ESP_OK(esp_partition_read(f.get_esp_partition(), entry_offset, &buff, sizeof(buff))); - nvs::Item* item = (nvs::Item*)buff; + CHECK(item->span == expected_span); + item->span = new_span; + // recalculate crc same way as nvs::Item::calculateCrc32 does + item->crc32 = ((nvs::Item*)buff)->calculateCrc32(); + TEST_ESP_OK(h.erase_range(entry_offset, sizeof(buff))); + TEST_ESP_OK(h.write_raw(entry_offset, &buff, sizeof(buff))); + } - CHECK(item->chunkIndex == expected_chunk_index); - item->chunkIndex = new_chunk_index; - // recalculate crc same way as nvs::Item::calculateCrc32 does - item->crc32 = ((nvs::Item*)buff)->calculateCrc32(); - TEST_ESP_OK(esp_partition_erase_range(f.get_esp_partition(), entry_offset, sizeof(buff))); - TEST_ESP_OK(esp_partition_write(f.get_esp_partition(), entry_offset, &buff, sizeof(buff))); - } + SECTION("damage BLOB_IDX - chunkIndex set to 111 instead of CHUNK_ANY") { + // Damage BLOB_IDX of valueblob1, modify chunkIndex == 111 + size_t entry_offset = blob_index_offset; + uint8_t expected_chunk_index = nvs::Item::CHUNK_ANY; + uint8_t new_chunk_index = 111; - SECTION("damage BLOB_IDX - blobIndex.dataSize over theoretical limit of 32*125*127 (entry size * max payload entries in page * max # of chunks)") { - // Damage BLOB_IDX of valueblob1, modify blobIndex.dataSize - size_t entry_offset = blob_index_offset; - uint32_t expected_data_size = blob_data_chunk0_len + blob_data_chunk1_len; - uint32_t new_data_size = (32 * 125 * 127) + 1; + uint8_t buff[sizeof(nvs::Item)] = {0}; + TEST_ESP_OK(h.read_raw(entry_offset, &buff, sizeof(buff))); + nvs::Item* item = (nvs::Item*)buff; - uint8_t buff[sizeof(nvs::Item)] = {0}; - TEST_ESP_OK(esp_partition_read(f.get_esp_partition(), entry_offset, &buff, sizeof(buff))); - nvs::Item* item = (nvs::Item*)buff; + CHECK(item->chunkIndex == expected_chunk_index); + item->chunkIndex = new_chunk_index; + // recalculate crc same way as nvs::Item::calculateCrc32 does + item->crc32 = ((nvs::Item*)buff)->calculateCrc32(); + TEST_ESP_OK(h.erase_range(entry_offset, sizeof(buff))); + TEST_ESP_OK(h.write_raw(entry_offset, &buff, sizeof(buff))); + } - CHECK(item->blobIndex.dataSize == expected_data_size); - item->chunkIndex = new_data_size; - // recalculate crc same way as nvs::Item::calculateCrc32 does - item->crc32 = ((nvs::Item*)buff)->calculateCrc32(); - TEST_ESP_OK(esp_partition_erase_range(f.get_esp_partition(), entry_offset, sizeof(buff))); - TEST_ESP_OK(esp_partition_write(f.get_esp_partition(), entry_offset, &buff, sizeof(buff))); - } + SECTION("damage BLOB_IDX - blobIndex.dataSize over theoretical limit of 32*125*127 (entry size * max payload entries in page * max # of chunks)") { + // Damage BLOB_IDX of valueblob1, modify blobIndex.dataSize + size_t entry_offset = blob_index_offset; + uint32_t expected_data_size = blob_data_chunk0_len + blob_data_chunk1_len; + uint32_t new_data_size = (32 * 125 * 127) + 1; - SECTION("damage BLOB_DATA - chunkIndex set to invalid value of CHUNK_ANY") { - // Damage BLOB_DATA of valueblob1, modify chunkIndex to CHUNK_ANY - size_t entry_offset = blob_data_chunk0_offset; - uint8_t unexpected_chunk_index = nvs::Item::CHUNK_ANY; - uint8_t new_chunk_index = nvs::Item::CHUNK_ANY; + uint8_t buff[sizeof(nvs::Item)] = {0}; + TEST_ESP_OK(h.read_raw(entry_offset, &buff, sizeof(buff))); + nvs::Item* item = (nvs::Item*)buff; - uint8_t buff[sizeof(nvs::Item)] = {0}; - TEST_ESP_OK(esp_partition_read(f.get_esp_partition(), entry_offset, &buff, sizeof(buff))); - nvs::Item* item = (nvs::Item*)buff; + CHECK(item->blobIndex.dataSize == expected_data_size); + item->chunkIndex = new_data_size; + // recalculate crc same way as nvs::Item::calculateCrc32 does + item->crc32 = ((nvs::Item*)buff)->calculateCrc32(); + TEST_ESP_OK(h.erase_range(entry_offset, sizeof(buff))); + TEST_ESP_OK(h.write_raw(entry_offset, &buff, sizeof(buff))); + } - CHECK(item->chunkIndex != unexpected_chunk_index); - item->chunkIndex = new_chunk_index; + SECTION("damage BLOB_DATA - chunkIndex set to invalid value of CHUNK_ANY") { + // Damage BLOB_DATA of valueblob1, modify chunkIndex to CHUNK_ANY + size_t entry_offset = blob_data_chunk0_offset; + uint8_t unexpected_chunk_index = nvs::Item::CHUNK_ANY; + uint8_t new_chunk_index = nvs::Item::CHUNK_ANY; - // recalculate crc same way as nvs::Item::calculateCrc32 does - item->crc32 = ((nvs::Item*)buff)->calculateCrc32(); - TEST_ESP_OK(esp_partition_erase_range(f.get_esp_partition(), entry_offset, sizeof(buff))); - TEST_ESP_OK(esp_partition_write(f.get_esp_partition(), entry_offset, &buff, sizeof(buff))); - } + uint8_t buff[sizeof(nvs::Item)] = {0}; + TEST_ESP_OK(h.read_raw(entry_offset, &buff, sizeof(buff))); + nvs::Item* item = (nvs::Item*)buff; - SECTION("damage BLOB_DATA - varLength.dataSize is exceeding the maximum capacity available till the end of the page") { - // Damage BLOB_DATA of valueblob1, set new data size to be beyond remaining capacity in the page. - // Even if all entries are used for payload, there will be one overhead entry required - size_t entry_offset = blob_data_chunk1_offset; // let's take chunk1 for this test - uint16_t expected_data_size = blob_data_chunk1_len; - uint16_t new_data_size = 32 * 126; + CHECK(item->chunkIndex != unexpected_chunk_index); + item->chunkIndex = new_chunk_index; - uint8_t buff[sizeof(nvs::Item)] = {0}; - TEST_ESP_OK(esp_partition_read(f.get_esp_partition(), entry_offset, &buff, sizeof(buff))); - nvs::Item* item = (nvs::Item*)buff; + // recalculate crc same way as nvs::Item::calculateCrc32 does + item->crc32 = ((nvs::Item*)buff)->calculateCrc32(); + TEST_ESP_OK(h.erase_range(entry_offset, sizeof(buff))); + TEST_ESP_OK(h.write_raw(entry_offset, &buff, sizeof(buff))); + } - CHECK(item->varLength.dataSize == expected_data_size); - item->varLength.dataSize = new_data_size; + SECTION("damage BLOB_DATA - varLength.dataSize is exceeding the maximum capacity available till the end of the page") { + // Damage BLOB_DATA of valueblob1, set new data size to be beyond remaining capacity in the page. + // Even if all entries are used for payload, there will be one overhead entry required + size_t entry_offset = blob_data_chunk1_offset; // let's take chunk1 for this test + uint16_t expected_data_size = blob_data_chunk1_len; + uint16_t new_data_size = 32 * 126; - // recalculate crc same way as nvs::Item::calculateCrc32 does - item->crc32 = ((nvs::Item*)buff)->calculateCrc32(); - TEST_ESP_OK(esp_partition_erase_range(f.get_esp_partition(), entry_offset, sizeof(buff))); - TEST_ESP_OK(esp_partition_write(f.get_esp_partition(), entry_offset, &buff, sizeof(buff))); - } + uint8_t buff[sizeof(nvs::Item)] = {0}; + TEST_ESP_OK(h.read_raw(entry_offset, &buff, sizeof(buff))); + nvs::Item* item = (nvs::Item*)buff; - SECTION("damage BLOB_DATA - span > maxAvailablePageSpan") { - // Damage BLOB_DATA of valueblob1, set span to the value larger than the rest of entries in the page but smaller than maximum of entries . - // page can accommodate - // chunk0 starts at entry index 2 so max span is 126 - 2 = 124. Use 125 - size_t entry_offset = blob_data_chunk0_offset; - uint8_t expected_span = (blob_data_chunk0_len + 31) / 32 + 1; - uint8_t new_span = 125; + CHECK(item->varLength.dataSize == expected_data_size); + item->varLength.dataSize = new_data_size; - uint8_t buff[sizeof(nvs::Item)] = {0}; - TEST_ESP_OK(esp_partition_read(f.get_esp_partition(), entry_offset, &buff, sizeof(buff))); - nvs::Item* item = (nvs::Item*)buff; + // recalculate crc same way as nvs::Item::calculateCrc32 does + item->crc32 = ((nvs::Item*)buff)->calculateCrc32(); + TEST_ESP_OK(h.erase_range(entry_offset, sizeof(buff))); + TEST_ESP_OK(h.write_raw(entry_offset, &buff, sizeof(buff))); + } - CHECK(item->span == expected_span); - item->span = new_span; + SECTION("damage BLOB_DATA - span > maxAvailablePageSpan") { + // Damage BLOB_DATA of valueblob1, set span to the value larger than the rest of entries in the page but smaller than maximum of entries . + // page can accommodate + // chunk0 starts at entry index 2 so max span is 126 - 2 = 124. Use 125 + size_t entry_offset = blob_data_chunk0_offset; + uint8_t expected_span = (blob_data_chunk0_len + 31) / 32 + 1; + uint8_t new_span = 125; - // recalculate crc same way as nvs::Item::calculateCrc32 does - item->crc32 = ((nvs::Item*)buff)->calculateCrc32(); - TEST_ESP_OK(esp_partition_erase_range(f.get_esp_partition(), entry_offset, sizeof(buff))); - TEST_ESP_OK(esp_partition_write(f.get_esp_partition(), entry_offset, &buff, sizeof(buff))); - } + uint8_t buff[sizeof(nvs::Item)] = {0}; + TEST_ESP_OK(h.read_raw(entry_offset, &buff, sizeof(buff))); + nvs::Item* item = (nvs::Item*)buff; - // check that storage can recover and validate the remaining data + CHECK(item->span == expected_span); + item->span = new_span; + + // recalculate crc same way as nvs::Item::calculateCrc32 does + item->crc32 = ((nvs::Item*)buff)->calculateCrc32(); + TEST_ESP_OK(h.erase_range(entry_offset, sizeof(buff))); + TEST_ESP_OK(h.write_raw(entry_offset, &buff, sizeof(buff))); + } + } // end of block keeping the helper scope + + // Evaluation part. Check that storage can recover and validate the remaining data { - TEST_ESP_OK(nvs::NVSPartitionManager::get_instance()->init_custom(f.part(), 0, 3)); + TEST_ESP_OK(nvs_flash_init_partition(TEST_DEFAULT_PARTITION_NAME)); nvs_handle_t handle; TEST_ESP_OK(nvs_open("test", NVS_READWRITE, &handle)); @@ -3080,7 +3770,7 @@ TEST_CASE("inconsistent fields in item header with correct crc are handled for m TEST_ESP_OK(nvs_get_blob(handle, blob_key2, read_blob2, &read_blob2_size)); TEST_ESP_OK(nvs_get_u32(handle, ukey2, &read_u32_2)); nvs_close(handle); - TEST_ESP_OK(nvs_flash_deinit_partition(f.part()->get_partition_name())); + TEST_ESP_OK(nvs_flash_deinit_partition(TEST_DEFAULT_PARTITION_NAME)); // validate the data CHECK(read_u32_1 == uval1); @@ -3090,12 +3780,17 @@ TEST_CASE("inconsistent fields in item header with correct crc are handled for m } } -// header entry of u32 entry will be damaged in a way that data type will be invalid -// while crc will be correct + TEST_CASE("invalid data type in item header with correct crc is handled", "[nvs]") { - PartitionEmulationFixture f(0, 3); - nvs::Storage storage(f.part()); + // TC testing invalid data type in item header with correct crc is handled + // header entry of u32 entry will be damaged in a way that data type will be invalid + // while crc will be correct + + // PartitionTestHelper is used to provide nvs::Partition instance for the test + NVSPartitionTestHelper h(TEST_DEFAULT_PARTITION_NAME); + + nvs::Storage storage(&h); // prepare some data TEST_ESP_OK(storage.init(0, 3)); TEST_ESP_OK(storage.writeItem(0, "ns1", static_cast(1))); @@ -3108,7 +3803,7 @@ TEST_CASE("invalid data type in item header with correct crc is handled", "[nvs] nvs::ItemType new_data_type = (nvs::ItemType) 0xfe; size_t entry_offset = 32 * 4; // 2x page header + 1x ns1 + 1x value1 uint8_t buff[sizeof(nvs::Item)] = {0}; - TEST_ESP_OK(esp_partition_read(f.get_esp_partition(), entry_offset, &buff, sizeof(buff))); + TEST_ESP_OK(h.read_raw(entry_offset, &buff, sizeof(buff))); nvs::Item* item = (nvs::Item*)buff; @@ -3118,9 +3813,9 @@ TEST_CASE("invalid data type in item header with correct crc is handled", "[nvs] item->crc32 = ((nvs::Item*)buff)->calculateCrc32(); // allow overwriting smaller portion of flash than whole 4k page - ((esp_partition_t*) f.get_esp_partition())->erase_size = 1; - TEST_ESP_OK(esp_partition_erase_range(f.get_esp_partition(), entry_offset, sizeof(buff))); - TEST_ESP_OK(esp_partition_write(f.get_esp_partition(), entry_offset, &buff, sizeof(buff))); + h.set_erase_size(1); + TEST_ESP_OK(h.erase_range(entry_offset, sizeof(buff))); + TEST_ESP_OK(h.write_raw(entry_offset, &buff, sizeof(buff))); } // check that storage can recover @@ -3139,22 +3834,33 @@ TEST_CASE("invalid data type in item header with correct crc is handled", "[nvs] // leaks memory TEST_CASE("Recovery from power-off when the entry being erased is not on active page", "[nvs]") { + // TC testing recovery from power-off when the entry being erased is not on active page + // This test will write some data to the partition, then erase a key and power-off + // while the entry is not on the active page. The test will check that the erased + // key is not present after re-initialization of the partition. + + // Erase the partition before testing + TEST_ESP_OK(nvs_flash_erase_partition(TEST_3SEC_PARTITION_NAME)); + const size_t blob_size = nvs::Page::CHUNK_MAX_SIZE / 2 ; size_t read_size = blob_size; uint8_t blob[blob_size] = {0x11}; - PartitionEmulationFixture f(0, 3); - TEST_ESP_OK(nvs::NVSPartitionManager::get_instance()->init_custom(f.part(), 0, 3)); - nvs_handle_t handle; - TEST_ESP_OK(nvs_open("test", NVS_READWRITE, &handle)); - esp_partition_clear_stats(); - esp_partition_fail_after(nvs::Page::CHUNK_MAX_SIZE / 4 + 75, ESP_PARTITION_FAIL_AFTER_MODE_BOTH); + TEST_ESP_OK(nvs_flash_init_partition(TEST_3SEC_PARTITION_NAME)); + + nvs_handle_t handle; + TEST_ESP_OK(nvs_open_from_partition(TEST_3SEC_PARTITION_NAME, "test", NVS_READWRITE, &handle)); + + NVSPartitionTestHelper::clear_stats(); + NVSPartitionTestHelper::fail_after(nvs::Page::CHUNK_MAX_SIZE / 4 + 75, ESP_PARTITION_FAIL_AFTER_MODE_BOTH); TEST_ESP_OK(nvs_set_blob(handle, "1a", blob, blob_size)); TEST_ESP_OK(nvs_set_blob(handle, "1b", blob, blob_size)); TEST_ESP_ERR(nvs_erase_key(handle, "1a"), ESP_ERR_FLASH_OP_FAIL); - TEST_ESP_OK(nvs::NVSPartitionManager::get_instance()->init_custom(f.part(), 0, 3)); + // re-initialize the partition + TEST_ESP_OK(nvs_flash_init_partition(TEST_3SEC_PARTITION_NAME)); + TEST_ESP_OK(nvs_open_from_partition(TEST_3SEC_PARTITION_NAME, "test", NVS_READWRITE, &handle)); // Check 1a is erased fully TEST_ESP_ERR(nvs_get_blob(handle, "1a", blob, &read_size), ESP_ERR_NVS_NOT_FOUND); @@ -3164,19 +3870,28 @@ TEST_CASE("Recovery from power-off when the entry being erased is not on active nvs_close(handle); - TEST_ESP_OK(nvs_flash_deinit_partition(f.part()->get_partition_name())); + TEST_ESP_OK(nvs_flash_deinit_partition(TEST_3SEC_PARTITION_NAME)); } // leaks memory TEST_CASE("Recovery from power-off when page is being freed", "[nvs]") { + // TC testing recovery from power-off when page is being freed + // This test will write some data to the partition, then erase a key and power-off while freeing the page. + // The test will check that the erased key is not present after re-initialization of the partition. + + // Use only 3 sectors partition for the test to induce the page freeing + // Erase the partition before testing + TEST_ESP_OK(nvs_flash_erase_partition(TEST_3SEC_PARTITION_NAME)); + const size_t blob_size = (nvs::Page::ENTRY_COUNT - 3) * nvs::Page::ENTRY_SIZE; size_t read_size = blob_size / 2; uint8_t blob[blob_size] = {0}; - PartitionEmulationFixture f(0, 3); - TEST_ESP_OK(nvs::NVSPartitionManager::get_instance()->init_custom(f.part(), 0, 3)); + + TEST_ESP_OK(nvs_flash_init_partition(TEST_3SEC_PARTITION_NAME)); + nvs_handle_t handle; - TEST_ESP_OK(nvs_open("test", NVS_READWRITE, &handle)); + TEST_ESP_OK(nvs_open_from_partition(TEST_3SEC_PARTITION_NAME, "test", NVS_READWRITE, &handle)); // Fill first page TEST_ESP_OK(nvs_set_blob(handle, "1a", blob, blob_size / 3)); TEST_ESP_OK(nvs_set_blob(handle, "1b", blob, blob_size / 3)); @@ -3187,11 +3902,13 @@ TEST_CASE("Recovery from power-off when page is being freed", "[nvs]") TEST_ESP_OK(nvs_erase_key(handle, "1c")); - esp_partition_clear_stats(); - esp_partition_fail_after(6 * nvs::Page::ENTRY_COUNT, ESP_PARTITION_FAIL_AFTER_MODE_BOTH); + NVSPartitionTestHelper::clear_stats(); + NVSPartitionTestHelper::fail_after(6 * nvs::Page::ENTRY_COUNT, ESP_PARTITION_FAIL_AFTER_MODE_BOTH); TEST_ESP_ERR(nvs_set_blob(handle, "1d", blob, blob_size / 4), ESP_ERR_FLASH_OP_FAIL); - TEST_ESP_OK(nvs::NVSPartitionManager::get_instance()->init_custom(f.part(), 0, 3)); + // re -initialize the partition + TEST_ESP_OK(nvs_flash_init_partition(TEST_3SEC_PARTITION_NAME)); + TEST_ESP_OK(nvs_open_from_partition(TEST_3SEC_PARTITION_NAME, "test", NVS_READWRITE, &handle)); read_size = blob_size / 3; TEST_ESP_OK(nvs_get_blob(handle, "1a", blob, &read_size)); @@ -3208,20 +3925,34 @@ TEST_CASE("Recovery from power-off when page is being freed", "[nvs]") TEST_ESP_OK(nvs_commit(handle)); nvs_close(handle); - TEST_ESP_OK(nvs_flash_deinit_partition(f.part()->get_partition_name())); + TEST_ESP_OK(nvs_flash_deinit_partition(TEST_3SEC_PARTITION_NAME)); } TEST_CASE("Check that NVS supports old blob format without blob index", "[nvs]") { - // initialize the fixture with nvs binary loaded - PartitionEmulationFixture f(0, 2, "nvs", WD_PREFIX "../../nvs_partition_generator/part_old_blob_format.bin"); + // TC testing that NVS supports old blob format without blob index + // This test will load a partition with pre-generated binary image containing blobs in old format + // and then modify one of the blobs to be stored in new format with blob index. + // The test will check that the blob is read correctly in both formats and that the blob index is present + // for the modified blob. + + // PartitionTestHelper is used to provide nvs::Partition instance for the test + NVSPartitionTestHelper h(TEST_3SEC_PARTITION_NAME); + + // Populate the partition with pre-generated file with blob in old format + // Binary image contains 2 blobs: + // - dummyHex2BinKey: hex data in old format without blob index + // - dummyBase64Key: base64 data in old format without blob index + TEST_ESP_OK(h.load_from_file(WD_PREFIX "../../nvs_partition_generator/part_old_blob_format.bin")); + + TEST_ESP_OK(nvs_flash_init_partition(TEST_3SEC_PARTITION_NAME)); - TEST_ESP_OK(nvs::NVSPartitionManager::get_instance()->init_custom(f.part(), 0, 2)); nvs_handle_t handle; - TEST_ESP_OK(nvs_open_from_partition("nvs", "dummyNamespace", NVS_READWRITE, &handle)); + TEST_ESP_OK(nvs_open_from_partition(TEST_3SEC_PARTITION_NAME, "dummyNamespace", NVS_READWRITE, &handle)); esp_err_t ret = ESP_OK; + // Validate the contents of the blobs to make sure the right binary was loaded char buf[64] = {0}; size_t buflen = 64; uint8_t hexdata[] = {0x01, 0x02, 0x03, 0xab, 0xcd, 0xef}; @@ -3239,121 +3970,85 @@ TEST_CASE("Check that NVS supports old blob format without blob index", "[nvs]") CHECK(memcmp(buf, base64data, len) == 0); } + // Use direct access to the page data using nvs::Page to perform exact item type checks nvs::Page p; - TEST_ESP_OK(p.load(f.part(), 0)); + TEST_ESP_OK(p.load(&h, 0)); - /* Check that item is stored in old format without blob index*/ + // Check that item is stored in old format without blob index TEST_ESP_OK(p.findItem(1, nvs::ItemType::BLOB, "dummyHex2BinKey")); - /* Modify the blob so that it is stored in the new format*/ + // Modify the blob so that it is now stored in the new format hexdata[0] = hexdata[1] = hexdata[2] = 0x99; TEST_ESP_OK(nvs_set_blob(handle, "dummyHex2BinKey", hexdata, sizeof(hexdata))); nvs::Page p2; - TEST_ESP_OK(p2.load(f.part(), 0)); + TEST_ESP_OK(p2.load(&h, 0)); - /* Check the type of the blob. Expect type mismatch since the blob is stored in new format*/ + // Check the type of the blob. Expect type mismatch since the blob is stored in new format TEST_ESP_ERR(p2.findItem(1, nvs::ItemType::BLOB, "dummyHex2BinKey"), ESP_ERR_NVS_TYPE_MISMATCH); - /* Check that index is present for the modified blob according to new format*/ + // Check that index is present for the modified blob according to new format TEST_ESP_OK(p2.findItem(1, nvs::ItemType::BLOB_IDX, "dummyHex2BinKey")); - /* Read the blob in new format and check the contents*/ + // Read the blob in new format and check the contents buflen = 64; TEST_ESP_OK(ret = nvs_get_blob(handle, "dummyBase64Key", buf, &buflen)); if (ret == ESP_OK) { size_t len = buflen <= sizeof(base64data) ? buflen : sizeof(base64data); CHECK(memcmp(buf, base64data, len) == 0); } - TEST_ESP_OK(nvs_flash_deinit_partition(f.part()->get_partition_name())); + TEST_ESP_OK(nvs_flash_deinit_partition(TEST_3SEC_PARTITION_NAME)); } -static void check_nvs_part_gen_args(char const *flash_binary_filename, // name of binary containing emulated flash content - char const *part_name, // name of partition - int size, // required size of partition in sectors - char const *filename) // file with blob for matching data from NVS calls -{ - nvs_handle_t handle; - - // initialize the fixture with nvs binary loaded - PartitionEmulationFixture f(0, size, part_name, flash_binary_filename); - TEST_ESP_OK(nvs::NVSPartitionManager::get_instance()->init_custom(f.part(), 0, size)); - - TEST_ESP_OK(nvs_open_from_partition(part_name, "dummyNamespace", NVS_READONLY, &handle)); - uint8_t u8v; - TEST_ESP_OK(nvs_get_u8(handle, "dummyU8Key", &u8v)); - CHECK(u8v == 127); - int8_t i8v; - TEST_ESP_OK(nvs_get_i8(handle, "dummyI8Key", &i8v)); - CHECK(i8v == -128); - uint16_t u16v; - TEST_ESP_OK(nvs_get_u16(handle, "dummyU16Key", &u16v)); - CHECK(u16v == 32768); - uint32_t u32v; - TEST_ESP_OK(nvs_get_u32(handle, "dummyU32Key", &u32v)); - CHECK(u32v == 4294967295); - int32_t i32v; - TEST_ESP_OK(nvs_get_i32(handle, "dummyI32Key", &i32v)); - CHECK(i32v == -2147483648); - - char string_buf[256]; - const char test_str[] = "Lorem ipsum dolor sit amet, consectetur adipiscing elit.\n" +// Expected content of the string kye dummyStringKey in the tests using nvs_partition_generator utility +const char test_str_long[] = "Lorem ipsum dolor sit amet, consectetur adipiscing elit.\n" "Fusce quis risus justo.\n" "Suspendisse egestas in nisi sit amet auctor.\n" "Pellentesque rhoncus dictum sodales.\n" "In justo erat, viverra at interdum eget, interdum vel dui."; - size_t str_len = sizeof(test_str); - TEST_ESP_OK(nvs_get_str(handle, "dummyStringKey", string_buf, &str_len)); - CHECK(strncmp(string_buf, test_str, str_len) == 0); - char buf[64] = {0}; - uint8_t hexdata[] = {0x01, 0x02, 0x03, 0xab, 0xcd, 0xef}; - size_t buflen = 64; - TEST_ESP_OK(nvs_get_blob(handle, "dummyHex2BinKey", buf, &buflen)); - CHECK(memcmp(buf, hexdata, buflen) == 0); +// Expected content of the string kye dummyStringKey in the tests using manufacturing utility +const char test_str_short[] = "0A:0B:0C:0D:0E:0F"; - uint8_t base64data[] = {'1', '2', '3', 'a', 'b', 'c'}; - TEST_ESP_OK(nvs_get_blob(handle, "dummyBase64Key", buf, &buflen)); - CHECK(memcmp(buf, base64data, buflen) == 0); - buflen = 64; - uint8_t hexfiledata[] = {0x01, 0x23, 0x45, 0x67, 0x89, 0xab, 0xcd, 0xef}; - TEST_ESP_OK(nvs_get_blob(handle, "hexFileKey", buf, &buflen)); - CHECK(memcmp(buf, hexfiledata, buflen) == 0); - - buflen = 64; - uint8_t strfiledata[64] = "abcdefghijklmnopqrstuvwxyz\0"; - TEST_ESP_OK(nvs_get_str(handle, "stringFileKey", buf, &buflen)); - CHECK(memcmp(buf, strfiledata, buflen) == 0); - - char bin_data[5200]; - size_t bin_len = sizeof(bin_data); - char binfiledata[5200]; - ifstream file; - file.open(filename); - file.read(binfiledata, 5200); - TEST_ESP_OK(nvs_get_blob(handle, "binFileKey", bin_data, &bin_len)); - CHECK(memcmp(bin_data, binfiledata, bin_len) == 0); - - file.close(); - - nvs_close(handle); - - TEST_ESP_OK(nvs_flash_deinit_partition(part_name)); -} - -static void check_nvs_part_gen_args_mfg(char const *flash_binary_filename, // name of binary containing emulated flash content - char const *part_name, // name of partition - int size, // required size of partition in sectors - char const *filename) // file with blob for matching data from NVS calls +static void check_nvs_part_gen_args(char const *flash_binary_filename, // name of binary containing emulated flash content + char const *part_name, // name of partition + int size, // required size of partition in sectors + char const *blob_filename, // file with expected contents of blob binFileKey + const char *expected_string) // expected string for dummyStringKey { + // Supplementary function to check NVS partition with given arguments + // This function loads the partition from the binary file, initializes it, + // and checks the values stored in the NVS partition against expected values. + // The function expects the partition to contain the following keys and values: + + // Hardcoded expected values: + // "dummyU8Key", uint8_t, 127 + // "dummyI8Key", int8_t, -128 + // "dummyU16Key", uint16_t, 32768 + // "dummyU32Key", uint32_t, 4294967295 + // "dummyI32Key", int32_t, -2147483648 + // "dummyHex2BinKey", blob, {0x01, 0x02, 0x03, 0xab, 0xcd, 0xef} + // "dummyBase64Key", blob, {'1', '2', '3', 'a', 'b', 'c'} + // "hexFileKey", blob, {0x01, 0x23, 0x45, 0x67, 0x89, 0xab, 0xcd, 0xef} + // "stringFileKey", string, "abcdefghijklmnopqrstuvwxyz\0" + + // Parametrised values: + // "dummyStringKey", string, expected_string (function parameter, up to 256 bytes) + // "binFileKey", blob, contents of blob_filename (function parameter, up to 5200 bytes) + nvs_handle_t handle; - // initialize the fixture with nvs binary loaded - PartitionEmulationFixture f(0, size, part_name, flash_binary_filename); - TEST_ESP_OK(nvs::NVSPartitionManager::get_instance()->init_custom(f.part(), 0, size)); + // Validate that the partition is large enough + CHECK(NVSPartitionTestHelper::get_sectors(part_name) >= size); + // Load the partition from the binary file + TEST_ESP_OK(NVSPartitionTestHelper::load_from_file(part_name, flash_binary_filename)); + + TEST_ESP_OK(nvs_flash_init_partition(part_name)); TEST_ESP_OK(nvs_open_from_partition(part_name, "dummyNamespace", NVS_READONLY, &handle)); + + // Check that the partition contains the expected keys and values uint8_t u8v; TEST_ESP_OK(nvs_get_u8(handle, "dummyU8Key", &u8v)); CHECK(u8v == 127); @@ -3370,13 +4065,16 @@ static void check_nvs_part_gen_args_mfg(char const *flash_binary_filename, // n TEST_ESP_OK(nvs_get_i32(handle, "dummyI32Key", &i32v)); CHECK(i32v == -2147483648); - char buf[64] = {0}; - size_t buflen = 64; - TEST_ESP_OK(nvs_get_str(handle, "dummyStringKey", buf, &buflen)); - CHECK(strncmp(buf, "0A:0B:0C:0D:0E:0F", buflen) == 0); + char string_buf[256]; + size_t str_len = strlen(expected_string) + 1; // +1 for null terminator + CHECK(sizeof(string_buf) >= str_len); + TEST_ESP_OK(nvs_get_str(handle, "dummyStringKey", string_buf, &str_len)); + CHECK(strncmp(string_buf, expected_string, str_len) == 0); + + char buf[64] = {0}; uint8_t hexdata[] = {0x01, 0x02, 0x03, 0xab, 0xcd, 0xef}; - buflen = 64; + size_t buflen = 64; TEST_ESP_OK(nvs_get_blob(handle, "dummyHex2BinKey", buf, &buflen)); CHECK(memcmp(buf, hexdata, buflen) == 0); @@ -3398,23 +4096,26 @@ static void check_nvs_part_gen_args_mfg(char const *flash_binary_filename, // n size_t bin_len = sizeof(bin_data); char binfiledata[5200]; ifstream file; - file.open(filename); + file.open(blob_filename); file.read(binfiledata, 5200); TEST_ESP_OK(nvs_get_blob(handle, "binFileKey", bin_data, &bin_len)); CHECK(memcmp(bin_data, binfiledata, bin_len) == 0); file.close(); - nvs_close(handle); - TEST_ESP_OK(nvs_flash_deinit_partition(part_name)); } TEST_CASE("check and read data from partition generated via partition generation utility with multipage blob support disabled", "[nvs_part_gen]") { + // TC testing partition generation utility with multipage blob support disabled + // This test will generate a partition with blob stored in single page format (without index) + // and then read the data from the partition to check that it is correct. + int status; int childpid = fork(); if (childpid == 0) { + // Copy testdata directory to the current working directory exit(execlp("cp", " cp", "-rf", WD_PREFIX "../../nvs_partition_generator/testdata", @@ -3428,6 +4129,8 @@ TEST_CASE("check and read data from partition generated via partition generation childpid = fork(); if (childpid == 0) { + // Generate partition with single page blob + // The partition will contain a single page blob and data as specified in the sample_singlepage_blob.csv file exit(execlp("python", "python", WD_PREFIX "../../nvs_partition_generator/nvs_partition_gen.py", "generate", @@ -3446,13 +4149,16 @@ TEST_CASE("check and read data from partition generated via partition generation } } + // Check the genetated partition binary using nvs flash API check_nvs_part_gen_args(WD_PREFIX "../../nvs_partition_generator/partition_single_page.bin", - "test", + TEST_3SEC_PARTITION_NAME, 3, - WD_PREFIX "../../nvs_partition_generator/testdata/sample_singlepage_blob.bin"); + WD_PREFIX "../../nvs_partition_generator/testdata/sample_singlepage_blob.bin", + test_str_long); childpid = fork(); if (childpid == 0) { + // Clean up testdata directory and generated partition binary exit(execlp("bash", "bash", "-c", "rm -rf testdata && \ @@ -3467,9 +4173,13 @@ TEST_CASE("check and read data from partition generated via partition generation TEST_CASE("check and read data from partition generated via partition generation utility with multipage blob support enabled", "[nvs_part_gen]") { + // TC testing partition generation utility with multipage blob support enabled + // This test will generate a partition with blob stored in multipage format (with index) + // and then read the data from the partition to check that it is correct. int status; int childpid = fork(); if (childpid == 0) { + // Copy testdata directory to the current working directory exit(execlp("cp", " cp", "-rf", WD_PREFIX "../../nvs_partition_generator/testdata", @@ -3482,6 +4192,9 @@ TEST_CASE("check and read data from partition generated via partition generation childpid = fork(); if (childpid == 0) { + // Generate partition with multipage blob + // The partition will contain a multipage blob and data as specified in the sample_multipage_blob.csv file + // The partition will be generated with multipage blob support enabled exit(execlp("python", "python", WD_PREFIX "../../nvs_partition_generator/nvs_partition_gen.py", "generate", @@ -3499,13 +4212,16 @@ TEST_CASE("check and read data from partition generated via partition generation } } + // Check the generated partition binary using nvs flash API check_nvs_part_gen_args(WD_PREFIX "../../nvs_partition_generator/partition_multipage_blob.bin", - "test", + TEST_DEFAULT_PARTITION_NAME, 4, - WD_PREFIX "../../nvs_partition_generator/testdata/sample_multipage_blob.bin"); + WD_PREFIX "../../nvs_partition_generator/testdata/sample_multipage_blob.bin", + test_str_long); childpid = fork(); if (childpid == 0) { + // Clean up testdata directory and generated partition binary exit(execlp("bash", "bash", "-c", "rm -rf testdata && \ @@ -3519,10 +4235,17 @@ TEST_CASE("check and read data from partition generated via partition generation TEST_CASE("check and read data from partition generated via manufacturing utility with multipage blob support disabled", "[mfg_gen]") { + // TC testing manufacturing utility and nvs_partition_gen with multipage blob support disabled + // This test will generate a partition using each of generators with blob stored in single page format (without index) + // and then read the data from the partition binaries to check that generators produce the same result. int childpid = fork(); int status; if (childpid == 0) { + // Copy testdata directory to the current working directory + // Create host_test directory for the manufacturing utility + // and copy testdata to it + // Copy nvs_partition_generator testdata to the current working directory exit(execlp("bash", "bash", "-c", "rm -rf " WD_PREFIX "../../../../tools/mass_mfg/host_test && \ @@ -3536,6 +4259,8 @@ TEST_CASE("check and read data from partition generated via manufacturing utilit childpid = fork(); if (childpid == 0) { + // Generate partition binary using mfg_gen (manufacturing utility) with single page blob format + // The partition will contain a single page blob and data as specified in the sample_singlepage_blob.csv file exit(execlp("python", "python", WD_PREFIX "../../../../tools/mass_mfg/mfg_gen.py", "generate", @@ -3555,6 +4280,8 @@ TEST_CASE("check and read data from partition generated via manufacturing utilit childpid = fork(); if (childpid == 0) { + // Generate partition binary using nvs_partition_gen (nvs partition generator utility) + // The partition will contain a single page blob and data as specified in the Test-1.csv file exit(execlp("python", "python", WD_PREFIX "../../nvs_partition_generator/nvs_partition_gen.py", "generate", @@ -3572,23 +4299,28 @@ TEST_CASE("check and read data from partition generated via manufacturing utilit } } - check_nvs_part_gen_args_mfg(WD_PREFIX "../../../../tools/mass_mfg/host_test/bin/Test-1.bin", - "test", - 3, - "mfg_testdata/sample_singlepage_blob.bin"); + // Check the partition binary generated by nvs partition generator utility using nvs flash API + check_nvs_part_gen_args(WD_PREFIX "../../../../tools/mass_mfg/host_test/bin/Test-1.bin", + TEST_3SEC_PARTITION_NAME, + 3, + "mfg_testdata/sample_singlepage_blob.bin", + test_str_short); - check_nvs_part_gen_args_mfg(WD_PREFIX "../../nvs_partition_generator/Test-1-partition.bin", - "test", - 3, - "testdata/sample_singlepage_blob.bin"); + // Check the partition binary generated by manufacturing utility using nvs flash API + check_nvs_part_gen_args(WD_PREFIX "../../nvs_partition_generator/Test-1-partition.bin", + TEST_3SEC_PARTITION_NAME, + 3, + "testdata/sample_singlepage_blob.bin", + test_str_short); childpid = fork(); if (childpid == 0) { + // Clean up testdata directory, generated partition binary and host_test directory exit(execlp("bash", " bash", "-c", "rm -rf " WD_PREFIX "../../../../tools/mass_mfg/host_test | \ rm " WD_PREFIX "../../nvs_partition_generator/Test-1-partition.bin | \ - rm " WD_PREFIX "../../../../tools/mass_mfg/samples/*tmp* | \ + rm " WD_PREFIX "../../../../tools/mass_mfg/samples/" "*tmp* | \ rm -rf mfg_testdata | \ rm -rf testdata", NULL)); } else { @@ -3600,10 +4332,18 @@ TEST_CASE("check and read data from partition generated via manufacturing utilit TEST_CASE("check and read data from partition generated via manufacturing utility with blank lines in csv files and multipage blob support disabled", "[mfg_gen]") { + // TC testing manufacturing utility with blank lines in csv files and multipage blob support disabled + // This test will generate a partition using manufacturing utility with blank lines in csv files + // and blob stored in single page format (without index) and then read the data from the partition binaries + // to check that the partition is generated correctly. int childpid = fork(); int status; if (childpid == 0) { + // Copy testdata directory to the current working directory + // Create host_test directory for the manufacturing utility + // and copy testdata to it + // Copy nvs_partition_generator testdata to the current working directory exit(execlp("bash", "bash", "-c", "rm -rf " WD_PREFIX "../../../../tools/mass_mfg/host_test && \ @@ -3617,6 +4357,10 @@ TEST_CASE("check and read data from partition generated via manufacturing utilit childpid = fork(); if (childpid == 0) { + // Generate partition binary using mfg_gen (manufacturing utility) with single page blob format + // The partition will contain a single page blob and data as specified in the sample_values_singlepage_blob_blank_lines.csv file + // The sample_config_blank_lines.csv file will be used to generate the partition + // The csv files will contain blank lines and the partition should be generated correctly exit(execlp("python", "python", WD_PREFIX "../../../../tools/mass_mfg/mfg_gen.py", "generate", @@ -3636,6 +4380,8 @@ TEST_CASE("check and read data from partition generated via manufacturing utilit childpid = fork(); if (childpid == 0) { + // Generate partition binary using nvs_partition_gen (nvs partition generator utility) + // The partition will contain a single page blob and data as specified in the Test-1.csv file exit(execlp("python", "python", WD_PREFIX "../../nvs_partition_generator/nvs_partition_gen.py", "generate", @@ -3653,17 +4399,29 @@ TEST_CASE("check and read data from partition generated via manufacturing utilit } } - check_nvs_part_gen_args_mfg(WD_PREFIX "../../../../tools/mass_mfg/host_test/bin/Test-1.bin", "test", 3, "mfg_testdata/sample_singlepage_blob.bin"); + // Check the partition binary generated by manufacturing utility using nvs flash API + // The partition should be generated correctly despite the blank lines in the csv files + check_nvs_part_gen_args(WD_PREFIX "../../../../tools/mass_mfg/host_test/bin/Test-1.bin", + TEST_3SEC_PARTITION_NAME, + 3, + "mfg_testdata/sample_singlepage_blob.bin", + test_str_short); - check_nvs_part_gen_args_mfg(WD_PREFIX "../../nvs_partition_generator/Test-1-partition.bin", "test", 3, "testdata/sample_singlepage_blob.bin"); + // Check the partition binary generated by nvs partition generator utility using nvs flash API + check_nvs_part_gen_args(WD_PREFIX "../../nvs_partition_generator/Test-1-partition.bin", + TEST_3SEC_PARTITION_NAME, + 3, + "testdata/sample_singlepage_blob.bin", + test_str_short); childpid = fork(); if (childpid == 0) { + // Clean up testdata directory, generated partition binary and host_test directory exit(execlp("bash", " bash", "-c", "rm -rf " WD_PREFIX "../../../../tools/mass_mfg/host_test | \ rm " WD_PREFIX "../../nvs_partition_generator/Test-1-partition.bin | \ - rm " WD_PREFIX "../../../../tools/mass_mfg/samples/*tmp* | \ + rm " WD_PREFIX "../../../../tools/mass_mfg/samples/" "*tmp* | \ rm -rf mfg_testdata | \ rm -rf testdata", NULL)); } else { @@ -3675,10 +4433,17 @@ TEST_CASE("check and read data from partition generated via manufacturing utilit TEST_CASE("check and read data from partition generated via manufacturing utility with multipage blob support enabled", "[mfg_gen]") { + // TC testing manufacturing utility and nvs_partition_gen with multipage blob support enabled + // This test will generate a partition using each of generators with blob stored in multiple page format (with index) + // and then read the data from the partition binaries to check that generators produce the same result. int childpid = fork(); int status; if (childpid == 0) { + // Copy testdata directory to the current working directory + // Create host_test directory for the manufacturing utility + // and copy testdata to it + // Copy nvs_partition_generator testdata to the current working directory exit(execlp("bash", " bash", "-c", "rm -rf " WD_PREFIX "../../../../tools/mass_mfg/host_test | \ @@ -3692,6 +4457,9 @@ TEST_CASE("check and read data from partition generated via manufacturing utilit childpid = fork(); if (childpid == 0) { + // Generate partition binary using mfg_gen (manufacturing utility) with multipage blob format + // The partition will contain a multipage blob and data as specified in the sample_multipage_blob.csv file + // The partition will be generated with multipage blob support enabled exit(execlp("python", "python", WD_PREFIX "../../../../tools/mass_mfg/mfg_gen.py", "generate", @@ -3711,6 +4479,9 @@ TEST_CASE("check and read data from partition generated via manufacturing utilit childpid = fork(); if (childpid == 0) { + // Generate partition binary using nvs_partition_gen (nvs partition generator utility) + // The partition will contain a multipage blob and data as specified in the Test-1.csv file + // The partition will be generated with multipage blob support enabled exit(execlp("python", "python", WD_PREFIX "../../nvs_partition_generator/nvs_partition_gen.py", "generate", @@ -3728,23 +4499,32 @@ TEST_CASE("check and read data from partition generated via manufacturing utilit } } - check_nvs_part_gen_args_mfg(WD_PREFIX "../../../../tools/mass_mfg/host_test/bin/Test-1.bin", - "test", - 4, - "mfg_testdata/sample_multipage_blob.bin"); + // Check the partition binary generated by manufacturing utility using nvs flash API - check_nvs_part_gen_args_mfg(WD_PREFIX "../../nvs_partition_generator/Test-1-partition.bin", - "test", - 4, - "testdata/sample_multipage_blob.bin"); + check_nvs_part_gen_args(WD_PREFIX "../../../../tools/mass_mfg/host_test/bin/Test-1.bin", + TEST_DEFAULT_PARTITION_NAME, + 4, + "mfg_testdata/sample_multipage_blob.bin", + test_str_short); + + // Check the partition binary generated by nvs partition generator utility using nvs flash API + check_nvs_part_gen_args(WD_PREFIX "../../nvs_partition_generator/Test-1-partition.bin", + TEST_DEFAULT_PARTITION_NAME, + 4, + "testdata/sample_multipage_blob.bin", + test_str_short); childpid = fork(); if (childpid == 0) { + // Clean up testdata directory, generated partition binary and host_test directory + // Remove temporary files created by the manufacturing utility + // and nvs_partition_generator utility + // Remove testdata directory exit(execlp("bash", " bash", "-c", "rm -rf " WD_PREFIX "../../../../tools/mass_mfg/host_test | \ rm " WD_PREFIX "../../nvs_partition_generator/Test-1-partition.bin | \ - rm " WD_PREFIX "../../../../tools/mass_mfg/samples/*tmp* | \ + rm " WD_PREFIX "../../../../tools/mass_mfg/samples/" "*tmp* | \ rm -rf mfg_testdata | \ rm -rf testdata", NULL)); } else { @@ -3756,23 +4536,18 @@ TEST_CASE("check and read data from partition generated via manufacturing utilit TEST_CASE("nvs multiple write with same key but different types", "[nvs]") { - PartitionEmulationFixture f(0, 10); + // TC testing that NVS supports multiple writes with same key but different types + // This test will write multiple values with the same key but different types + // and then read the values to check that the latest value is returned. + // Based on the configuration option CONFIG_NVS_LEGACY_DUP_KEYS_COMPATIBILITY, the behavior will differ. + + // Erase the partition before testing + TEST_ESP_OK(nvs_flash_erase_partition(TEST_3SEC_PARTITION_NAME)); + + TEST_ESP_OK(nvs_flash_init_partition(TEST_3SEC_PARTITION_NAME)); nvs_handle_t handle_1; - const uint32_t NVS_FLASH_SECTOR = 6; - const uint32_t NVS_FLASH_SECTOR_COUNT_MIN = 3; - TEMPORARILY_DISABLED(f.emu.setBounds(NVS_FLASH_SECTOR, NVS_FLASH_SECTOR + NVS_FLASH_SECTOR_COUNT_MIN);) - - for (uint16_t j = NVS_FLASH_SECTOR; j < NVS_FLASH_SECTOR + NVS_FLASH_SECTOR_COUNT_MIN; ++j) { - f.erase(j); - } - TEST_ESP_OK(nvs::NVSPartitionManager::get_instance()->init_custom(f.part(), - NVS_FLASH_SECTOR, - NVS_FLASH_SECTOR_COUNT_MIN)); - - TEST_ESP_OK(nvs_open("namespace1", NVS_READWRITE, &handle_1)); - - nvs_erase_all(handle_1); + TEST_ESP_OK(nvs_open_from_partition(TEST_3SEC_PARTITION_NAME, "namespace1", NVS_READWRITE, &handle_1)); int32_t v32; int8_t v8; @@ -3815,30 +4590,20 @@ TEST_CASE("nvs multiple write with same key but different types", "[nvs]") nvs_close(handle_1); - TEST_ESP_OK(nvs_flash_deinit_partition(NVS_DEFAULT_PART_NAME)); + TEST_ESP_OK(nvs_flash_deinit_partition(TEST_3SEC_PARTITION_NAME)); } #ifndef CONFIG_NVS_LEGACY_DUP_KEYS_COMPATIBILITY // Following test case is not valid for new behavior not leading to multiple active values under the same key TEST_CASE("nvs multiple write with same key blob and string involved", "[nvs]") { - PartitionEmulationFixture f(0, 10); + // Erase the partition before testing + TEST_ESP_OK(nvs_flash_erase_partition(TEST_3SEC_PARTITION_NAME)); + + TEST_ESP_OK(nvs_flash_init_partition(TEST_3SEC_PARTITION_NAME)); nvs_handle_t handle_1; - const uint32_t NVS_FLASH_SECTOR = 6; - const uint32_t NVS_FLASH_SECTOR_COUNT_MIN = 3; - TEMPORARILY_DISABLED(f.emu.setBounds(NVS_FLASH_SECTOR, NVS_FLASH_SECTOR + NVS_FLASH_SECTOR_COUNT_MIN);) - - for (uint16_t j = NVS_FLASH_SECTOR; j < NVS_FLASH_SECTOR + NVS_FLASH_SECTOR_COUNT_MIN; ++j) { - f.erase(j); - } - TEST_ESP_OK(nvs::NVSPartitionManager::get_instance()->init_custom(f.part(), - NVS_FLASH_SECTOR, - NVS_FLASH_SECTOR_COUNT_MIN)); - - TEST_ESP_OK(nvs_open("namespace1", NVS_READWRITE, &handle_1)); - - nvs_erase_all(handle_1); + TEST_ESP_OK(nvs_open_from_partition(TEST_3SEC_PARTITION_NAME, "namespace1", NVS_READWRITE, &handle_1)); const char key_name[] = "foo"; @@ -3893,35 +4658,29 @@ TEST_CASE("nvs multiple write with same key blob and string involved", "[nvs]") nvs_close(handle_1); - TEST_ESP_OK(nvs_flash_deinit_partition(NVS_DEFAULT_PART_NAME)); + TEST_ESP_OK(nvs_flash_deinit_partition(TEST_3SEC_PARTITION_NAME)); } #endif // !CONFIG_NVS_LEGACY_DUP_KEYS_COMPATIBILITY TEST_CASE("nvs find key tests", "[nvs]") { - const size_t buff_len = 4096; + // TC testing nvs_find_key function + // This test will open a writable namespace, write some values, erase them and check that + // nvs_find_key function returns correct results before and after each of the steps. + // It will also test that nvs_find_key respects the namespace and does not find keys in other namespaces. - PartitionEmulationFixture f(0, 20); - f.randomize(100); + // Erase the partition before testing + TEST_ESP_OK(NVSPartitionTestHelper::randomize_partition(TEST_DEFAULT_PARTITION_NAME, 100)); - nvs_handle_t handle_1; - nvs_handle_t handle_2; + TEST_ESP_OK(nvs_flash_init_partition(TEST_DEFAULT_PARTITION_NAME)); - const uint32_t NVS_FLASH_SECTOR = 6; - const uint32_t NVS_FLASH_SECTOR_COUNT_MIN = 13; - - TEST_ESP_ERR(nvs_open("namespace1", NVS_READWRITE, &handle_1), ESP_ERR_NVS_NOT_INITIALIZED); - for (uint16_t i = NVS_FLASH_SECTOR; i < NVS_FLASH_SECTOR + NVS_FLASH_SECTOR_COUNT_MIN; ++i) { - f.erase(i); - } - TEST_ESP_OK(nvs::NVSPartitionManager::get_instance()->init_custom(f.part(), - NVS_FLASH_SECTOR, - NVS_FLASH_SECTOR_COUNT_MIN)); - - nvs_type_t datatype_found; // datatype of entry found + const size_t buff_len = 4096; // length of blob to be used in tests + nvs_handle_t handle_1; // handle for first namespace + nvs_handle_t handle_2; // handle for second namespace + nvs_type_t datatype_found; // datatype of entry found // open writeable namespace - TEST_ESP_OK(nvs_open("namespace1", NVS_READWRITE, &handle_1)); + TEST_ESP_OK(nvs_open_from_partition(TEST_DEFAULT_PARTITION_NAME, "namespace1", NVS_READWRITE, &handle_1)); // set value, erease value, test find before and after each of steps TEST_ESP_ERR(nvs_find_key(handle_1, "foo", &datatype_found), ESP_ERR_NVS_NOT_FOUND); @@ -3966,7 +4725,7 @@ TEST_CASE("nvs find key tests", "[nvs]") // test namespace is respected in nvs_find_key // open second writeable namespace - TEST_ESP_OK(nvs_open("namespace2", NVS_READWRITE, &handle_2)); + TEST_ESP_OK(nvs_open_from_partition(TEST_DEFAULT_PARTITION_NAME, "namespace2", NVS_READWRITE, &handle_2)); TEST_ESP_ERR(nvs_find_key(handle_1, "foon1", &datatype_found), ESP_ERR_NVS_NOT_FOUND); TEST_ESP_ERR(nvs_find_key(handle_2, "foon1", &datatype_found), ESP_ERR_NVS_NOT_FOUND); TEST_ESP_OK(nvs_set_i16(handle_1, "foon1", 0x1234)); @@ -3984,10 +4743,10 @@ TEST_CASE("nvs find key tests", "[nvs]") nvs_close(handle_1); nvs_close(handle_2); - TEST_ESP_OK(nvs_flash_deinit_partition(NVS_DEFAULT_PART_NAME)); + TEST_ESP_OK(nvs_flash_deinit_partition(TEST_DEFAULT_PARTITION_NAME)); } -/* Add new tests above */ -/* This test has to be the final one */ +// Add new tests above +// This test has to be the final one TEST_CASE("dump all performance data", "[nvs]") { diff --git a/components/nvs_flash/host_test/nvs_host_test/main/test_nvs_cxx_api.cpp b/components/nvs_flash/host_test/nvs_host_test/main/test_nvs_cxx_api.cpp index e8a4539698e..87affac1608 100644 --- a/components/nvs_flash/host_test/nvs_host_test/main/test_nvs_cxx_api.cpp +++ b/components/nvs_flash/host_test/nvs_host_test/main/test_nvs_cxx_api.cpp @@ -1,49 +1,51 @@ /* - * SPDX-FileCopyrightText: 2015-2022 Espressif Systems (Shanghai) CO LTD + * SPDX-FileCopyrightText: 2015-2025 Espressif Systems (Shanghai) CO LTD * * SPDX-License-Identifier: Apache-2.0 */ + #include -#include -#include +#include "nvs_flash.h" #include "nvs_handle_simple.hpp" #include "nvs_partition_manager.hpp" -#include "test_fixtures.hpp" -#include + +#include +#include using namespace std; +#define TEST_DEFAULT_PARTITION_NAME "nvs" + TEST_CASE("NVSHandleSimple CXX api open invalid arguments", "[nvs cxx]") { - const uint32_t NVS_FLASH_SECTOR = 6; - const uint32_t NVS_FLASH_SECTOR_COUNT_MIN = 3; - PartitionEmulationFixture f(0, 10, "test"); + // Test case for invalid arguments in open_nvs_handle_from_partition + // Combination of nullptr partition name and namespace name + esp_err_t result; shared_ptr handle; - REQUIRE(nvs::NVSPartitionManager::get_instance()-> - init_custom(f.part(), NVS_FLASH_SECTOR, NVS_FLASH_SECTOR_COUNT_MIN) == ESP_OK); + // Init the default partition + REQUIRE(nvs_flash_init_partition(TEST_DEFAULT_PARTITION_NAME) == ESP_OK); handle = nvs::open_nvs_handle_from_partition(nullptr, "ns_1", NVS_READWRITE, &result); CHECK(result == ESP_ERR_INVALID_ARG); CHECK(!handle); - handle = nvs::open_nvs_handle_from_partition("test", nullptr, NVS_READWRITE, &result); + handle = nvs::open_nvs_handle_from_partition(TEST_DEFAULT_PARTITION_NAME, nullptr, NVS_READWRITE, &result); CHECK(result == ESP_ERR_INVALID_ARG); CHECK(!handle); - nvs::NVSPartitionManager::get_instance()->deinit_partition("test"); + nvs_flash_deinit_partition(TEST_DEFAULT_PARTITION_NAME); } TEST_CASE("NVSHandleSimple CXX api open partition uninitialized", "[nvs cxx]") { - uint8_t *p_part_desc_addr_start; - CHECK(esp_partition_file_mmap((const uint8_t **)&p_part_desc_addr_start) == ESP_OK); + // Negative TC for opening a handle from a partition that has not been initialized esp_err_t result; shared_ptr handle; - handle = nvs::open_nvs_handle_from_partition("test", "ns_1", NVS_READWRITE, &result); + handle = nvs::open_nvs_handle_from_partition(TEST_DEFAULT_PARTITION_NAME, "ns_1", NVS_READWRITE, &result); bool result_expected = result == ESP_ERR_NVS_NOT_INITIALIZED || result == ESP_ERR_NVS_PART_NOT_FOUND; CHECK(result_expected); CHECK(!handle); @@ -51,18 +53,20 @@ TEST_CASE("NVSHandleSimple CXX api open partition uninitialized", "[nvs cxx]") TEST_CASE("NVSHandleSimple CXX api open successful", "[nvs cxx]") { - const uint32_t NVS_FLASH_SECTOR = 6; - const uint32_t NVS_FLASH_SECTOR_COUNT_MIN = 3; - PartitionEmulationFixture f(0, 10, "test"); + // Positive TC for opening a handle using open_nvs_handle_from_partition from an initialized partition + esp_err_t result; shared_ptr handle; - REQUIRE(nvs::NVSPartitionManager::get_instance()->init_custom(f.part(), NVS_FLASH_SECTOR, NVS_FLASH_SECTOR_COUNT_MIN) - == ESP_OK); + // Erase the partition to ensure it is clean + REQUIRE(nvs_flash_erase_partition(TEST_DEFAULT_PARTITION_NAME) == ESP_OK); + + // Init the default partition + REQUIRE(nvs_flash_init_partition(TEST_DEFAULT_PARTITION_NAME) == ESP_OK); CHECK(nvs::NVSPartitionManager::get_instance()->open_handles_size() == 0); - handle = nvs::open_nvs_handle_from_partition("test", "ns_1", NVS_READWRITE, &result); + handle = nvs::open_nvs_handle_from_partition(TEST_DEFAULT_PARTITION_NAME, "ns_1", NVS_READWRITE, &result); CHECK(result == ESP_OK); CHECK(handle); @@ -72,23 +76,27 @@ TEST_CASE("NVSHandleSimple CXX api open successful", "[nvs cxx]") CHECK(nvs::NVSPartitionManager::get_instance()->open_handles_size() == 0); - nvs::NVSPartitionManager::get_instance()->deinit_partition("test"); + nvs_flash_deinit_partition(TEST_DEFAULT_PARTITION_NAME); } TEST_CASE("NVSHandleSimple CXX api open default part successful", "[nvs cxx]") { - const uint32_t NVS_FLASH_SECTOR = 6; - const uint32_t NVS_FLASH_SECTOR_COUNT_MIN = 3; - PartitionEmulationFixture f(0, 10); + // Positive TC for opening a handle using open_nvs_handle from an initialized default partition + esp_err_t result; shared_ptr handle; - REQUIRE(nvs::NVSPartitionManager::get_instance()->init_custom(f.part(), NVS_FLASH_SECTOR, NVS_FLASH_SECTOR_COUNT_MIN) - == ESP_OK); + // Erase the partition to ensure it is clean + REQUIRE(nvs_flash_erase_partition(TEST_DEFAULT_PARTITION_NAME) == ESP_OK); + + // Init the default partition + REQUIRE(nvs_flash_init_partition(TEST_DEFAULT_PARTITION_NAME) == ESP_OK); CHECK(nvs::NVSPartitionManager::get_instance()->open_handles_size() == 0); - handle = nvs::open_nvs_handle("ns_1", NVS_READWRITE, &result); + // Open handle without specifying partition name + // This should open the default partition "nvs" + handle = nvs::open_nvs_handle_from_partition(TEST_DEFAULT_PARTITION_NAME, "ns_1", NVS_READWRITE, &result); CHECK(result == ESP_OK); CHECK(handle); @@ -98,46 +106,50 @@ TEST_CASE("NVSHandleSimple CXX api open default part successful", "[nvs cxx]") CHECK(nvs::NVSPartitionManager::get_instance()->open_handles_size() == 0); - nvs::NVSPartitionManager::get_instance()->deinit_partition("nvs"); + nvs_flash_deinit_partition(TEST_DEFAULT_PARTITION_NAME); } TEST_CASE("NVSHandleSimple CXX api open default part ns NULL", "[nvs cxx]") { - const uint32_t NVS_FLASH_SECTOR = 6; - const uint32_t NVS_FLASH_SECTOR_COUNT_MIN = 3; - PartitionEmulationFixture f(0, 10); + // Negative TC for opening a handle using open_nvs_handle from a partition name specified as nullptr + esp_err_t result; shared_ptr handle; - REQUIRE(nvs::NVSPartitionManager::get_instance()->init_custom(f.part(), NVS_FLASH_SECTOR, NVS_FLASH_SECTOR_COUNT_MIN) - == ESP_OK); + // Erase the partition to ensure it is clean + REQUIRE(nvs_flash_erase_partition(TEST_DEFAULT_PARTITION_NAME) == ESP_OK); + + // Init the default partition + REQUIRE(nvs_flash_init_partition(TEST_DEFAULT_PARTITION_NAME) == ESP_OK); CHECK(nvs::NVSPartitionManager::get_instance()->open_handles_size() == 0); - handle = nvs::open_nvs_handle(nullptr, NVS_READWRITE, &result); + handle = nvs::open_nvs_handle_from_partition(TEST_DEFAULT_PARTITION_NAME, nullptr, NVS_READWRITE, &result); CHECK(result == ESP_ERR_INVALID_ARG); CHECK(!handle); CHECK(nvs::NVSPartitionManager::get_instance()->open_handles_size() == 0); - nvs::NVSPartitionManager::get_instance()->deinit_partition("nvs"); + nvs_flash_deinit_partition(TEST_DEFAULT_PARTITION_NAME); } TEST_CASE("NVSHandleSimple CXX api read/write string", "[nvs cxx]") { - const uint32_t NVS_FLASH_SECTOR = 6; - const uint32_t NVS_FLASH_SECTOR_COUNT_MIN = 3; - PartitionEmulationFixture f(0, 10); + // Positive TC for reading and writing a string using NVSHandleSimple CXX API + char read_buffer [256]; esp_err_t result; shared_ptr handle; - REQUIRE(nvs::NVSPartitionManager::get_instance()->init_custom(f.part(), NVS_FLASH_SECTOR, NVS_FLASH_SECTOR_COUNT_MIN) - == ESP_OK); + // Erase the partition to ensure it is clean + REQUIRE(nvs_flash_erase_partition(TEST_DEFAULT_PARTITION_NAME) == ESP_OK); + + // Init the default partition + REQUIRE(nvs_flash_init_partition(TEST_DEFAULT_PARTITION_NAME) == ESP_OK); CHECK(nvs::NVSPartitionManager::get_instance()->open_handles_size() == 0); - handle = nvs::open_nvs_handle("test_ns", NVS_READWRITE, &result); + handle = nvs::open_nvs_handle_from_partition(TEST_DEFAULT_PARTITION_NAME, "test_ns", NVS_READWRITE, &result); CHECK(result == ESP_OK); REQUIRE(handle); @@ -149,25 +161,27 @@ TEST_CASE("NVSHandleSimple CXX api read/write string", "[nvs cxx]") CHECK(string(read_buffer) == "test string"); - nvs::NVSPartitionManager::get_instance()->deinit_partition("nvs"); + nvs_flash_deinit_partition(TEST_DEFAULT_PARTITION_NAME); } TEST_CASE("NVSHandleSimple CXX api read/write blob", "[nvs cxx]") { - const uint32_t NVS_FLASH_SECTOR = 6; - const uint32_t NVS_FLASH_SECTOR_COUNT_MIN = 3; - PartitionEmulationFixture f(0, 10); + // Positive TC for reading and writing a blob using NVSHandleSimple CXX API + const char blob [6] = {15, 16, 17, 18, 19}; char read_blob[6] = {0}; esp_err_t result; shared_ptr handle; - REQUIRE(nvs::NVSPartitionManager::get_instance()->init_custom(f.part(), NVS_FLASH_SECTOR, NVS_FLASH_SECTOR_COUNT_MIN) - == ESP_OK); + // Erase the partition to ensure it is clean + REQUIRE(nvs_flash_erase_partition(TEST_DEFAULT_PARTITION_NAME) == ESP_OK); + + // Init the default partition + REQUIRE(nvs_flash_init_partition(TEST_DEFAULT_PARTITION_NAME) == ESP_OK); CHECK(nvs::NVSPartitionManager::get_instance()->open_handles_size() == 0); - handle = nvs::open_nvs_handle("test_ns", NVS_READWRITE, &result); + handle = nvs::open_nvs_handle_from_partition(TEST_DEFAULT_PARTITION_NAME, "test_ns", NVS_READWRITE, &result); CHECK(result == ESP_OK); REQUIRE(handle); @@ -179,5 +193,5 @@ TEST_CASE("NVSHandleSimple CXX api read/write blob", "[nvs cxx]") CHECK(vector(blob, blob + sizeof(blob)) == vector(read_blob, read_blob + sizeof(read_blob))); - nvs::NVSPartitionManager::get_instance()->deinit_partition("nvs"); + nvs_flash_deinit_partition(TEST_DEFAULT_PARTITION_NAME); } diff --git a/components/nvs_flash/host_test/nvs_host_test/main/test_nvs_handle.cpp b/components/nvs_flash/host_test/nvs_host_test/main/test_nvs_handle.cpp index be08abe12f4..fcfc7fd856a 100644 --- a/components/nvs_flash/host_test/nvs_host_test/main/test_nvs_handle.cpp +++ b/components/nvs_flash/host_test/nvs_host_test/main/test_nvs_handle.cpp @@ -1,34 +1,34 @@ /* - * SPDX-FileCopyrightText: 2015-2022 Espressif Systems (Shanghai) CO LTD + * SPDX-FileCopyrightText: 2015-2025 Espressif Systems (Shanghai) CO LTD * * SPDX-License-Identifier: Apache-2.0 */ + #include -#include -#include +#include "nvs_flash.h" #include "nvs_handle_simple.hpp" #include "nvs_partition_manager.hpp" -#include "test_fixtures.hpp" -#include -#include using namespace std; -#define TEMPORARILY_DISABLED(x) +#define TEST_DEFAULT_PARTITION_NAME "nvs" // Default partition name used in the tests - 10 sectors +#define TEST_3SEC_PARTITION_NAME "nvs_3sec" // Partition used in the space constrained tests - 3 sectors TEST_CASE("NVSHandleSimple closes its reference in PartitionManager", "[partition_mgr]") { - const uint32_t NVS_FLASH_SECTOR = 6; - const uint32_t NVS_FLASH_SECTOR_COUNT_MIN = 3; - PartitionEmulationFixture f(0, 10, "test"); + // Positive TC verifying that NVSHandleSimple closes its reference in PartitionManager + // when it is deleted. - REQUIRE(nvs::NVSPartitionManager::get_instance()->init_custom(f.part(), NVS_FLASH_SECTOR, NVS_FLASH_SECTOR_COUNT_MIN) - == ESP_OK); + // Erase the partition to ensure it is clean + REQUIRE(nvs_flash_erase_partition(TEST_DEFAULT_PARTITION_NAME) == ESP_OK); + + // Init the default partition + REQUIRE(nvs_flash_init_partition(TEST_DEFAULT_PARTITION_NAME) == ESP_OK); CHECK(nvs::NVSPartitionManager::get_instance()->open_handles_size() == 0); - nvs::NVSHandleSimple *handle; - REQUIRE(nvs::NVSPartitionManager::get_instance()->open_handle("test", "ns_1", NVS_READWRITE, &handle) == ESP_OK); + nvs::NVSHandleSimple *handle = nullptr; + REQUIRE(nvs::NVSPartitionManager::get_instance()->open_handle(TEST_DEFAULT_PARTITION_NAME, "ns_1", NVS_READWRITE, &handle) == ESP_OK); CHECK(nvs::NVSPartitionManager::get_instance()->open_handles_size() == 1); @@ -36,29 +36,29 @@ TEST_CASE("NVSHandleSimple closes its reference in PartitionManager", "[partitio CHECK(nvs::NVSPartitionManager::get_instance()->open_handles_size() == 0); - REQUIRE(nvs::NVSPartitionManager::get_instance()->deinit_partition("test") == ESP_OK); - + REQUIRE(nvs_flash_deinit_partition(TEST_DEFAULT_PARTITION_NAME) == ESP_OK); } TEST_CASE("NVSHandleSimple multiple open and closes with PartitionManager", "[partition_mgr]") { - const uint32_t NVS_FLASH_SECTOR = 6; - const uint32_t NVS_FLASH_SECTOR_COUNT_MIN = 3; - PartitionEmulationFixture f(0, 10, "test"); + // Positive TC for multiple open and close of NVSHandleSimple with PartitionManager - REQUIRE(nvs::NVSPartitionManager::get_instance()->init_custom(f.part(), NVS_FLASH_SECTOR, NVS_FLASH_SECTOR_COUNT_MIN) - == ESP_OK); + // Erase the partition to ensure it is clean + REQUIRE(nvs_flash_erase_partition(TEST_DEFAULT_PARTITION_NAME) == ESP_OK); + + // Init the default partition + REQUIRE(nvs_flash_init_partition(TEST_DEFAULT_PARTITION_NAME) == ESP_OK); CHECK(nvs::NVSPartitionManager::get_instance()->open_handles_size() == 0); - nvs::NVSHandleSimple *handle1; - nvs::NVSHandleSimple *handle2; + nvs::NVSHandleSimple *handle1 = nullptr; + nvs::NVSHandleSimple *handle2 = nullptr; - REQUIRE(nvs::NVSPartitionManager::get_instance()->open_handle("test", "ns_1", NVS_READWRITE, &handle1) == ESP_OK); + REQUIRE(nvs::NVSPartitionManager::get_instance()->open_handle(TEST_DEFAULT_PARTITION_NAME, "ns_1", NVS_READWRITE, &handle1) == ESP_OK); CHECK(nvs::NVSPartitionManager::get_instance()->open_handles_size() == 1); - REQUIRE(nvs::NVSPartitionManager::get_instance()->open_handle("test", "ns_1", NVS_READWRITE, &handle2) == ESP_OK); + REQUIRE(nvs::NVSPartitionManager::get_instance()->open_handle(TEST_DEFAULT_PARTITION_NAME, "ns_1", NVS_READWRITE, &handle2) == ESP_OK); CHECK(nvs::NVSPartitionManager::get_instance()->open_handles_size() == 2); @@ -70,61 +70,60 @@ TEST_CASE("NVSHandleSimple multiple open and closes with PartitionManager", "[pa CHECK(nvs::NVSPartitionManager::get_instance()->open_handles_size() == 0); - REQUIRE(nvs::NVSPartitionManager::get_instance()->deinit_partition("test") == ESP_OK); - + REQUIRE(nvs_flash_deinit_partition(TEST_DEFAULT_PARTITION_NAME) == ESP_OK); } TEST_CASE("NVSHandleSimple readonly fails", "[partition_mgr]") { - PartitionEmulationFixture f(0, 10); + // Negative TC for opening a handle with read-only mode and trying to write to it - nvs::NVSPartitionManager::get_instance()->deinit_partition(NVS_DEFAULT_PART_NAME); - nvs::NVSHandleSimple *handle_1; - nvs::NVSHandleSimple *handle_2; - const uint32_t NVS_FLASH_SECTOR = 6; - const uint32_t NVS_FLASH_SECTOR_COUNT_MIN = 3; - TEMPORARILY_DISABLED(f.emu.setBounds(NVS_FLASH_SECTOR, NVS_FLASH_SECTOR + NVS_FLASH_SECTOR_COUNT_MIN);) + // Erase the partition to ensure it is clean + REQUIRE(nvs_flash_erase_partition(TEST_3SEC_PARTITION_NAME) == ESP_OK); + + // Init the default partition + REQUIRE(nvs_flash_init_partition(TEST_3SEC_PARTITION_NAME) == ESP_OK); + + nvs::NVSHandleSimple *handle_1 = nullptr; + nvs::NVSHandleSimple *handle_2 = nullptr; - CHECK(nvs::NVSPartitionManager::get_instance()->init_custom(f.part(), NVS_FLASH_SECTOR, NVS_FLASH_SECTOR_COUNT_MIN) == ESP_OK); CHECK(nvs::NVSPartitionManager::get_instance()->open_handles_size() == 0); // first, creating namespace... - REQUIRE(nvs::NVSPartitionManager::get_instance()->open_handle(NVS_DEFAULT_PART_NAME, "ns_1", NVS_READWRITE, &handle_1) == ESP_OK); + REQUIRE(nvs::NVSPartitionManager::get_instance()->open_handle(TEST_3SEC_PARTITION_NAME, "ns_1", NVS_READWRITE, &handle_1) == ESP_OK); CHECK(nvs::NVSPartitionManager::get_instance()->open_handles_size() == 1); delete handle_1; CHECK(nvs::NVSPartitionManager::get_instance()->open_handles_size() == 0); - REQUIRE(nvs::NVSPartitionManager::get_instance()->open_handle(NVS_DEFAULT_PART_NAME, "ns_1", NVS_READONLY, &handle_2) == ESP_OK); + REQUIRE(nvs::NVSPartitionManager::get_instance()->open_handle(TEST_3SEC_PARTITION_NAME, "ns_1", NVS_READONLY, &handle_2) == ESP_OK); CHECK(handle_2->set_item("key", 47) == ESP_ERR_NVS_READ_ONLY); CHECK(nvs::NVSPartitionManager::get_instance()->open_handles_size() == 1); delete handle_2; CHECK(nvs::NVSPartitionManager::get_instance()->open_handles_size() == 0); + // without deinit it affects "nvs api tests" - CHECK(nvs_flash_deinit_partition(NVS_DEFAULT_PART_NAME) == ESP_OK); + REQUIRE(nvs_flash_deinit_partition(TEST_3SEC_PARTITION_NAME) == ESP_OK); } TEST_CASE("NVSHandleSimple set/get char", "[partition_mgr]") { + // Positive TC for setting and getting a char using NVSHandleSimple enum class TestEnum : char { FOO = -1, BEER, BAR }; - PartitionEmulationFixture f(0, 10); + // Erase the partition to ensure it is clean + REQUIRE(nvs_flash_erase_partition(TEST_3SEC_PARTITION_NAME) == ESP_OK); - const uint32_t NVS_FLASH_SECTOR = 6; - const uint32_t NVS_FLASH_SECTOR_COUNT_MIN = 3; - TEMPORARILY_DISABLED(f.emu.setBounds(NVS_FLASH_SECTOR, NVS_FLASH_SECTOR + NVS_FLASH_SECTOR_COUNT_MIN);) + // Init the default partition + REQUIRE(nvs_flash_init_partition(TEST_3SEC_PARTITION_NAME) == ESP_OK); - REQUIRE(nvs::NVSPartitionManager::get_instance()->init_custom(f.part(), NVS_FLASH_SECTOR, NVS_FLASH_SECTOR_COUNT_MIN) - == ESP_OK); - - nvs::NVSHandleSimple *handle; - REQUIRE(nvs::NVSPartitionManager::get_instance()->open_handle(NVS_DEFAULT_PART_NAME, "ns_1", NVS_READWRITE, &handle) == ESP_OK); + nvs::NVSHandleSimple *handle = nullptr; + REQUIRE(nvs::NVSPartitionManager::get_instance()->open_handle(TEST_3SEC_PARTITION_NAME, "ns_1", NVS_READWRITE, &handle) == ESP_OK); char test_e = 'a'; char test_e_read = 'z'; @@ -136,27 +135,25 @@ TEST_CASE("NVSHandleSimple set/get char", "[partition_mgr]") delete handle; - REQUIRE(nvs::NVSPartitionManager::get_instance()->deinit_partition(NVS_DEFAULT_PART_NAME) == ESP_OK); + REQUIRE(nvs_flash_deinit_partition(TEST_3SEC_PARTITION_NAME) == ESP_OK); } TEST_CASE("NVSHandleSimple correctly sets/gets int enum", "[partition_mgr]") { + // Positive TC for setting and getting an int enum using NVSHandleSimple enum class TestEnum : int { FOO, BAR }; - PartitionEmulationFixture f(0, 10); + // Erase the partition to ensure it is clean + REQUIRE(nvs_flash_erase_partition(TEST_3SEC_PARTITION_NAME) == ESP_OK); - const uint32_t NVS_FLASH_SECTOR = 6; - const uint32_t NVS_FLASH_SECTOR_COUNT_MIN = 3; - TEMPORARILY_DISABLED(f.emu.setBounds(NVS_FLASH_SECTOR, NVS_FLASH_SECTOR + NVS_FLASH_SECTOR_COUNT_MIN);) + // Init the default partition + REQUIRE(nvs_flash_init_partition(TEST_3SEC_PARTITION_NAME) == ESP_OK); - REQUIRE(nvs::NVSPartitionManager::get_instance()->init_custom(f.part(), NVS_FLASH_SECTOR, NVS_FLASH_SECTOR_COUNT_MIN) - == ESP_OK); - - nvs::NVSHandleSimple *handle; - REQUIRE(nvs::NVSPartitionManager::get_instance()->open_handle(NVS_DEFAULT_PART_NAME, "ns_1", NVS_READWRITE, &handle) == ESP_OK); + nvs::NVSHandleSimple *handle = nullptr; + REQUIRE(nvs::NVSPartitionManager::get_instance()->open_handle(TEST_3SEC_PARTITION_NAME, "ns_1", NVS_READWRITE, &handle) == ESP_OK); TestEnum test_e = TestEnum::BAR; TestEnum test_e_read = TestEnum::FOO; @@ -168,28 +165,26 @@ TEST_CASE("NVSHandleSimple correctly sets/gets int enum", "[partition_mgr]") delete handle; - REQUIRE(nvs::NVSPartitionManager::get_instance()->deinit_partition(NVS_DEFAULT_PART_NAME) == ESP_OK); + REQUIRE(nvs_flash_deinit_partition(TEST_3SEC_PARTITION_NAME) == ESP_OK); } TEST_CASE("NVSHandleSimple correctly sets/gets int enum with negative values", "[partition_mgr]") { + // Positive TC for setting and getting an int enum with negative values using NVSHandleSimple enum class TestEnum : int { FOO = -1, BEER, BAR }; - PartitionEmulationFixture f(0, 10); + // Erase the partition to ensure it is clean + REQUIRE(nvs_flash_erase_partition(TEST_3SEC_PARTITION_NAME) == ESP_OK); - const uint32_t NVS_FLASH_SECTOR = 6; - const uint32_t NVS_FLASH_SECTOR_COUNT_MIN = 3; - TEMPORARILY_DISABLED(f.emu.setBounds(NVS_FLASH_SECTOR, NVS_FLASH_SECTOR + NVS_FLASH_SECTOR_COUNT_MIN);) + // Init the default partition + REQUIRE(nvs_flash_init_partition(TEST_3SEC_PARTITION_NAME) == ESP_OK); - REQUIRE(nvs::NVSPartitionManager::get_instance()->init_custom(f.part(), NVS_FLASH_SECTOR, NVS_FLASH_SECTOR_COUNT_MIN) - == ESP_OK); - - nvs::NVSHandleSimple *handle; - REQUIRE(nvs::NVSPartitionManager::get_instance()->open_handle(NVS_DEFAULT_PART_NAME, "ns_1", NVS_READWRITE, &handle) == ESP_OK); + nvs::NVSHandleSimple *handle = nullptr; + REQUIRE(nvs::NVSPartitionManager::get_instance()->open_handle(TEST_3SEC_PARTITION_NAME, "ns_1", NVS_READWRITE, &handle) == ESP_OK); TestEnum test_e = TestEnum::FOO; TestEnum test_e_read = TestEnum::BEER; @@ -201,27 +196,25 @@ TEST_CASE("NVSHandleSimple correctly sets/gets int enum with negative values", " delete handle; - REQUIRE(nvs::NVSPartitionManager::get_instance()->deinit_partition(NVS_DEFAULT_PART_NAME) == ESP_OK); + REQUIRE(nvs_flash_deinit_partition(TEST_3SEC_PARTITION_NAME) == ESP_OK); } TEST_CASE("NVSHandleSimple correctly sets/gets uint8_t enum", "[partition_mgr]") { + // Positive TC for setting and getting a uint8_t enum using NVSHandleSimple enum class TestEnum : uint8_t { FOO, BAR }; - PartitionEmulationFixture f(0, 10); + // Erase the partition to ensure it is clean + REQUIRE(nvs_flash_erase_partition(TEST_3SEC_PARTITION_NAME) == ESP_OK); - const uint32_t NVS_FLASH_SECTOR = 6; - const uint32_t NVS_FLASH_SECTOR_COUNT_MIN = 3; - TEMPORARILY_DISABLED(f.emu.setBounds(NVS_FLASH_SECTOR, NVS_FLASH_SECTOR + NVS_FLASH_SECTOR_COUNT_MIN);) + // Init the default partition + REQUIRE(nvs_flash_init_partition(TEST_3SEC_PARTITION_NAME) == ESP_OK); - REQUIRE(nvs::NVSPartitionManager::get_instance()->init_custom(f.part(), NVS_FLASH_SECTOR, NVS_FLASH_SECTOR_COUNT_MIN) - == ESP_OK); - - nvs::NVSHandleSimple *handle; - REQUIRE(nvs::NVSPartitionManager::get_instance()->open_handle(NVS_DEFAULT_PART_NAME, "ns_1", NVS_READWRITE, &handle) == ESP_OK); + nvs::NVSHandleSimple *handle = nullptr; + REQUIRE(nvs::NVSPartitionManager::get_instance()->open_handle(TEST_3SEC_PARTITION_NAME, "ns_1", NVS_READWRITE, &handle) == ESP_OK); TestEnum test_e = TestEnum::BAR; TestEnum test_e_read = TestEnum::FOO; @@ -233,28 +226,26 @@ TEST_CASE("NVSHandleSimple correctly sets/gets uint8_t enum", "[partition_mgr]") delete handle; - REQUIRE(nvs::NVSPartitionManager::get_instance()->deinit_partition(NVS_DEFAULT_PART_NAME) == ESP_OK); + REQUIRE(nvs_flash_deinit_partition(TEST_3SEC_PARTITION_NAME) == ESP_OK); } TEST_CASE("NVSHandleSimple correctly sets/gets char enum", "[partition_mgr]") { + // Positive TC for setting and getting a char enum using NVSHandleSimple enum class TestEnum : char { FOO = -1, BEER, BAR }; - PartitionEmulationFixture f(0, 10); + // Erase the partition to ensure it is clean + REQUIRE(nvs_flash_erase_partition(TEST_3SEC_PARTITION_NAME) == ESP_OK); - const uint32_t NVS_FLASH_SECTOR = 6; - const uint32_t NVS_FLASH_SECTOR_COUNT_MIN = 3; - TEMPORARILY_DISABLED(f.emu.setBounds(NVS_FLASH_SECTOR, NVS_FLASH_SECTOR + NVS_FLASH_SECTOR_COUNT_MIN);) + // Init the default partition + REQUIRE(nvs_flash_init_partition(TEST_3SEC_PARTITION_NAME) == ESP_OK); - REQUIRE(nvs::NVSPartitionManager::get_instance()->init_custom(f.part(), NVS_FLASH_SECTOR, NVS_FLASH_SECTOR_COUNT_MIN) - == ESP_OK); - - nvs::NVSHandleSimple *handle; - REQUIRE(nvs::NVSPartitionManager::get_instance()->open_handle(NVS_DEFAULT_PART_NAME, "ns_1", NVS_READWRITE, &handle) == ESP_OK); + nvs::NVSHandleSimple *handle = nullptr; + REQUIRE(nvs::NVSPartitionManager::get_instance()->open_handle(TEST_3SEC_PARTITION_NAME, "ns_1", NVS_READWRITE, &handle) == ESP_OK); TestEnum test_e = TestEnum::BAR; TestEnum test_e_read = TestEnum::FOO; @@ -266,5 +257,5 @@ TEST_CASE("NVSHandleSimple correctly sets/gets char enum", "[partition_mgr]") delete handle; - REQUIRE(nvs::NVSPartitionManager::get_instance()->deinit_partition(NVS_DEFAULT_PART_NAME) == ESP_OK); + REQUIRE(nvs_flash_deinit_partition(TEST_3SEC_PARTITION_NAME) == ESP_OK); } diff --git a/components/nvs_flash/host_test/nvs_host_test/main/test_nvs_initialization.cpp b/components/nvs_flash/host_test/nvs_host_test/main/test_nvs_initialization.cpp index 9e994e46ad2..fa3a3bd4ae6 100644 --- a/components/nvs_flash/host_test/nvs_host_test/main/test_nvs_initialization.cpp +++ b/components/nvs_flash/host_test/nvs_host_test/main/test_nvs_initialization.cpp @@ -1,42 +1,89 @@ /* - * SPDX-FileCopyrightText: 2015-2024 Espressif Systems (Shanghai) CO LTD + * SPDX-FileCopyrightText: 2015-2025 Espressif Systems (Shanghai) CO LTD * * SPDX-License-Identifier: Apache-2.0 */ -#include "sdkconfig.h" + +#include "sdkconfig.h" // for CONFIG_NVS_BDL_STACK #include -#include "nvs.hpp" +#include "nvs_flash.h" #include "nvs_partition_manager.hpp" #include "nvs_partition.hpp" -#include "test_fixtures.hpp" -#include -#include "esp_partition.h" +#include "esp_partition.h" // for esp_partition_find_first +#include "bdl_ramdisk.hpp" // for bdl_ramdisk using namespace std; +#define TEST_DEFAULT_PARTITION_NAME "nvs" + +#ifndef CONFIG_NVS_BDL_STACK TEST_CASE("nvs_flash_init_partition_ptr fails due to nullptr arg", "[nvs_custom_part]") { - uint8_t *p_part_desc_addr_start; - CHECK(esp_partition_file_mmap((const uint8_t **)&p_part_desc_addr_start) == ESP_OK); + // This test checks that nvs_flash_init_partition_ptr fails when passed a nullptr as partition argument. CHECK(nvs_flash_init_partition_ptr(nullptr) == ESP_ERR_INVALID_ARG); } TEST_CASE("nvs_flash_init_partition_ptr inits one partition", "[nvs_custom_part]") { - const uint32_t NVS_FLASH_SECTOR = 6; - const uint32_t NVS_FLASH_SECTOR_COUNT_MIN = 3; - const uint32_t sec_size = esp_partition_get_main_flash_sector_size(); + // This test initializes a partition with a specific name and checks if it is registered in the NVSPartitionManager. + // The test works around the intnernal partition lookup by handing over a partition structure retrieved from esp_partition_find_first. - uint8_t *p_part_desc_addr_start; - CHECK(esp_partition_file_mmap((const uint8_t **)&p_part_desc_addr_start) == ESP_OK); + // Erase the partition to ensure it is clean + REQUIRE(nvs_flash_erase_partition(TEST_DEFAULT_PARTITION_NAME) == ESP_OK); - esp_partition_t partition = {}; - strcpy(partition.label, "test"); - partition.address = NVS_FLASH_SECTOR * sec_size; - partition.size = NVS_FLASH_SECTOR_COUNT_MIN * sec_size; + const esp_partition_t* partition = esp_partition_find_first(ESP_PARTITION_TYPE_DATA, ESP_PARTITION_SUBTYPE_DATA_NVS, TEST_DEFAULT_PARTITION_NAME); + REQUIRE(partition != nullptr); - CHECK(nvs_flash_init_partition_ptr(&partition) == ESP_OK); - CHECK(nvs::NVSPartitionManager::get_instance()->lookup_storage_from_name("test") != nullptr); - CHECK(nvs::NVSPartitionManager::get_instance()->deinit_partition("test") == ESP_OK); + CHECK(nvs_flash_init_partition_ptr(partition) == ESP_OK); + CHECK(nvs::NVSPartitionManager::get_instance()->lookup_storage_from_name(TEST_DEFAULT_PARTITION_NAME) != nullptr); + + REQUIRE(nvs_flash_deinit_partition(TEST_DEFAULT_PARTITION_NAME) == ESP_OK); } +#else +TEST_CASE("nvs_flash_init_partition_bdl fails due to nullptr arg", "[nvs_custom_part]") +{ + // This test checks that nvs_flash_init_partition_bdl fails when nullptr is provided as a block device argument + // or the partition name argument is null. + + // Check nullptr block device + CHECK(nvs_flash_init_partition_bdl("NullBDL", nullptr) == ESP_ERR_INVALID_ARG); + + // Check nullptr partition name + bdl_ramdisk ramdisk(3 * NVS_CONST_PAGE_SIZE, NVS_CONST_PAGE_SIZE); + CHECK(ramdisk.get_handle() != nullptr); + CHECK(nvs_flash_init_partition_bdl(nullptr, ramdisk.get_handle()) == ESP_ERR_INVALID_ARG); +} + +TEST_CASE("nvs_flash_init_partition_bdl fails due to ineligible block device", "[nvs_custom_part]") +{ + // This test checks that nvs_flash_init_partition_bdl fails when passed an ineligible block device. + + const char* part_name = "BadBDL"; + bdl_ramdisk ramdisk(3 * NVS_CONST_PAGE_SIZE, NVS_CONST_PAGE_SIZE); // Ramdisk of 3 NVS pages size + CHECK(ramdisk.get_handle() != nullptr); + + // Cripple the ramdisk to make it ineligible (erase size not matching NVS requirements) + ramdisk.get_handle()->geometry.erase_size = NVS_CONST_PAGE_SIZE - 1; + + CHECK(nvs_flash_init_partition_bdl(part_name, ramdisk.get_handle()) == ESP_ERR_NOT_SUPPORTED); +} + +TEST_CASE("nvs_flash_init_partition_bdl inits externally supplied block device", "[nvs_custom_part]") +{ + // This test initializes NVS on the externally supplied block device with a specific name and checks if + // it is registered in the NVSPartitionManager. + // The test works around the intnernal partition lookup by handing over a partition structure retrieved from esp_partition_find_first. + + // Erase the partition to ensure it is clean + const char* part_name = "GoodBDL"; + bdl_ramdisk ramdisk(3 * NVS_CONST_PAGE_SIZE, NVS_CONST_PAGE_SIZE); // Ramdisk of 3 NVS pages size + CHECK(ramdisk.get_handle() != nullptr); + + CHECK(nvs_flash_init_partition_bdl(part_name, ramdisk.get_handle()) == ESP_OK); + CHECK(nvs::NVSPartitionManager::get_instance()->lookup_storage_from_name(part_name) != nullptr); + + REQUIRE(nvs_flash_deinit_partition(part_name) == ESP_OK); +} + +#endif // CONFIG_NVS_BDL_STACK diff --git a/components/nvs_flash/host_test/nvs_host_test/main/test_nvs_storage.cpp b/components/nvs_flash/host_test/nvs_host_test/main/test_nvs_storage.cpp index 977ba13f0bf..ebfb586c370 100644 --- a/components/nvs_flash/host_test/nvs_host_test/main/test_nvs_storage.cpp +++ b/components/nvs_flash/host_test/nvs_host_test/main/test_nvs_storage.cpp @@ -1,30 +1,34 @@ /* - * SPDX-FileCopyrightText: 2015-2022 Espressif Systems (Shanghai) CO LTD + * SPDX-FileCopyrightText: 2015-2025 Espressif Systems (Shanghai) CO LTD * * SPDX-License-Identifier: Apache-2.0 */ + #include -#include +#include "nvs_flash.h" #include "nvs_storage.hpp" #include "nvs_partition_manager.hpp" -#include "test_fixtures.hpp" -#include using namespace std; +#define TEST_DEFAULT_PARTITION_NAME "nvs" + TEST_CASE("Storage iterator recognizes blob with VerOffset::VER_1_OFFSET", "[nvs_storage]") { - const uint32_t NVS_FLASH_SECTOR = 6; - const uint32_t NVS_FLASH_SECTOR_COUNT_MIN = 3; - PartitionEmulationFixture f(0, 10, "test"); + // Positive TC for Storage iterator recognizing a blob with VerOffset::VER_1_OFFSET - REQUIRE(nvs::NVSPartitionManager::get_instance()->init_custom(f.part(), NVS_FLASH_SECTOR, NVS_FLASH_SECTOR_COUNT_MIN) - == ESP_OK); + // Erase the partition before testing + REQUIRE(nvs_flash_erase_partition(TEST_DEFAULT_PARTITION_NAME) == ESP_OK); + + // Init the default partition + REQUIRE(nvs_flash_init_partition(TEST_DEFAULT_PARTITION_NAME) == ESP_OK); uint8_t blob [] = {0x0, 0x1, 0x2, 0x3}; uint8_t blob_new [] = {0x3, 0x2, 0x1, 0x0}; - nvs::Storage *storage = nvs::NVSPartitionManager::get_instance()->lookup_storage_from_name("test"); + nvs::Storage *storage = nvs::NVSPartitionManager::get_instance()->lookup_storage_from_name(TEST_DEFAULT_PARTITION_NAME); uint8_t ns_index; + + 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); @@ -43,5 +47,5 @@ TEST_CASE("Storage iterator recognizes blob with VerOffset::VER_1_OFFSET", "[nvs CHECK(string(it.entry_info.key) == string("test_blob")); CHECK(it.entry_info.type == NVS_TYPE_BLOB); - REQUIRE(nvs::NVSPartitionManager::get_instance()->deinit_partition("test") == ESP_OK); + REQUIRE(nvs_flash_deinit_partition(TEST_DEFAULT_PARTITION_NAME) == ESP_OK); } diff --git a/components/nvs_flash/host_test/nvs_host_test/main/test_partition_manager.cpp b/components/nvs_flash/host_test/nvs_host_test/main/test_partition_manager.cpp index aa825d71b52..38e27646ba8 100644 --- a/components/nvs_flash/host_test/nvs_host_test/main/test_partition_manager.cpp +++ b/components/nvs_flash/host_test/nvs_host_test/main/test_partition_manager.cpp @@ -1,70 +1,86 @@ /* - * SPDX-FileCopyrightText: 2015-2021 Espressif Systems (Shanghai) CO LTD + * SPDX-FileCopyrightText: 2015-2025 Espressif Systems (Shanghai) CO LTD * * SPDX-License-Identifier: Apache-2.0 */ -#include -#include -#include -#include "nvs_handle_simple.hpp" -#include "nvs_partition_manager.hpp" -#include "nvs_test_api.h" -#include "test_fixtures.hpp" + +#include // for Catch2 test macros +#include "nvs_handle_simple.hpp" // for NVSHandleSimple class +#include "nvs_partition_manager.hpp" // for NVSPartitionManager class +#include "test_fixtures.hpp" // for test fixtures using namespace std; +#define TEST_DEFAULT_PARTITION_NAME "nvs" +#define TEST_SECONDARY_PARTITION_NAME "nvs_sec" + TEST_CASE("Partition manager initializes storage", "[partition_mgr]") { - const uint32_t NVS_FLASH_SECTOR = 6; - const uint32_t NVS_FLASH_SECTOR_COUNT_MIN = 3; - PartitionEmulationFixture f(0, 10, "test"); + // Positive TC for partition manager initializing storage + // This test initializes a partition with a specific name and checks if it is registered in the NVSPartitionManager. - REQUIRE(nvs::NVSPartitionManager::get_instance()->init_custom(f.part(), NVS_FLASH_SECTOR, NVS_FLASH_SECTOR_COUNT_MIN) == ESP_OK); - CHECK(nvs::NVSPartitionManager::get_instance()->lookup_storage_from_name("test") != nullptr); - REQUIRE(nvs::NVSPartitionManager::get_instance()->deinit_partition(f.part()->get_partition_name()) == ESP_OK); + // Erase the partition to ensure it is clean + // We cannot use nvs_flash_erase_partition here because it is actually using the NVSPartitionManager we want to test. + REQUIRE(NVSPartitionTestHelper::erase_partition(TEST_DEFAULT_PARTITION_NAME) == ESP_OK); + + REQUIRE(nvs::NVSPartitionManager::get_instance()->init_partition(TEST_DEFAULT_PARTITION_NAME) == ESP_OK); + CHECK(nvs::NVSPartitionManager::get_instance()->lookup_storage_from_name(TEST_DEFAULT_PARTITION_NAME) != nullptr); + REQUIRE(nvs::NVSPartitionManager::get_instance()->deinit_partition(TEST_DEFAULT_PARTITION_NAME) == ESP_OK); } TEST_CASE("Partition manager de-initializes storage", "[partition_mgr]") { - const uint32_t NVS_FLASH_SECTOR = 6; - const uint32_t NVS_FLASH_SECTOR_COUNT_MIN = 3; - PartitionEmulationFixture f(0, 10, "test"); + // Positive TC for partition manager de-initializing storage + // This test initializes a partition with a specific name and checks if it is registered in the NVSPartitionManager. + // After de-initialization, the storage should no longer be found - REQUIRE(nvs::NVSPartitionManager::get_instance()->init_custom(f.part(), NVS_FLASH_SECTOR, NVS_FLASH_SECTOR_COUNT_MIN) == ESP_OK); - CHECK(nvs::NVSPartitionManager::get_instance()->lookup_storage_from_name("test") != nullptr); - CHECK(nvs::NVSPartitionManager::get_instance()->deinit_partition("test") == ESP_OK); - CHECK(nvs::NVSPartitionManager::get_instance()->lookup_storage_from_name("test") == nullptr); + // Erase the partition to ensure it is clean + // We cannot use nvs_flash_erase_partition here because it is actually using the NVSPartitionManager we want to test. + REQUIRE(NVSPartitionTestHelper::erase_partition(TEST_DEFAULT_PARTITION_NAME) == ESP_OK); + + REQUIRE(nvs::NVSPartitionManager::get_instance()->init_partition(TEST_DEFAULT_PARTITION_NAME) == ESP_OK); + CHECK(nvs::NVSPartitionManager::get_instance()->lookup_storage_from_name(TEST_DEFAULT_PARTITION_NAME) != nullptr); + CHECK(nvs::NVSPartitionManager::get_instance()->deinit_partition(TEST_DEFAULT_PARTITION_NAME) == ESP_OK); + CHECK(nvs::NVSPartitionManager::get_instance()->lookup_storage_from_name(TEST_DEFAULT_PARTITION_NAME) == nullptr); } TEST_CASE("Partition manager open fails on null handle", "[partition_mgr]") { - const uint32_t NVS_FLASH_SECTOR = 6; - const uint32_t NVS_FLASH_SECTOR_COUNT_MIN = 3; - PartitionEmulationFixture f(0, 10, "test"); + // Negative TC for partition manager open_handle failing on null handle pointer + // This test initializes a partition with a specific name. + // The handle shouldn't be null, so the open_handle should return ESP_ERR_INVALID_ARG - REQUIRE(nvs::NVSPartitionManager::get_instance()->init_custom(f.part(), NVS_FLASH_SECTOR, NVS_FLASH_SECTOR_COUNT_MIN) - == ESP_OK); + // Erase the partition to ensure it is clean + // We cannot use nvs_flash_erase_partition here because it is actually using the NVSPartitionManager we want to test. + REQUIRE(NVSPartitionTestHelper::erase_partition(TEST_DEFAULT_PARTITION_NAME) == ESP_OK); - CHECK(nvs::NVSPartitionManager::get_instance()->open_handle("test", "ns_1", NVS_READWRITE, nullptr) + // Init the default partition + REQUIRE(nvs::NVSPartitionManager::get_instance()->init_partition(TEST_DEFAULT_PARTITION_NAME) == ESP_OK); + + CHECK(nvs::NVSPartitionManager::get_instance()->open_handle(TEST_DEFAULT_PARTITION_NAME, "ns_1", NVS_READWRITE, nullptr) == ESP_ERR_INVALID_ARG); - nvs::NVSPartitionManager::get_instance()->deinit_partition("test"); + REQUIRE(nvs::NVSPartitionManager::get_instance()->deinit_partition(TEST_DEFAULT_PARTITION_NAME) == ESP_OK); } TEST_CASE("Partition manager invalidates handle on partition de-init", "[partition_mgr]") { - const uint32_t NVS_FLASH_SECTOR = 6; - const uint32_t NVS_FLASH_SECTOR_COUNT_MIN = 3; - PartitionEmulationFixture f(0, 10, "test"); + // Positive TC for partition manager invalidating handle on partition de-init + // This test initializes a partition with a specific name and checks if it is registered in the NVSPartitionManager. + // After de-initialization, the handle should be invalidated - REQUIRE(nvs::NVSPartitionManager::get_instance()->init_custom(f.part(), NVS_FLASH_SECTOR, NVS_FLASH_SECTOR_COUNT_MIN) - == ESP_OK); + // Erase the partition to ensure it is clean + // We cannot use nvs_flash_erase_partition here because it is actually using the NVSPartitionManager we want to test. + REQUIRE(NVSPartitionTestHelper::erase_partition(TEST_DEFAULT_PARTITION_NAME) == ESP_OK); - nvs::NVSHandleSimple *handle; - REQUIRE(nvs::NVSPartitionManager::get_instance()->open_handle("test", "ns_1", NVS_READWRITE, &handle) == ESP_OK); + // Init the default partition + REQUIRE(nvs::NVSPartitionManager::get_instance()->init_partition(TEST_DEFAULT_PARTITION_NAME) == ESP_OK); + + nvs::NVSHandleSimple *handle = nullptr; + REQUIRE(nvs::NVSPartitionManager::get_instance()->open_handle(TEST_DEFAULT_PARTITION_NAME, "ns_1", NVS_READWRITE, &handle) == ESP_OK); CHECK(handle->erase_all() == ESP_OK); - REQUIRE(nvs::NVSPartitionManager::get_instance()->deinit_partition("test") == ESP_OK); + REQUIRE(nvs::NVSPartitionManager::get_instance()->deinit_partition(TEST_DEFAULT_PARTITION_NAME) == ESP_OK); CHECK(handle->erase_all() == ESP_ERR_NVS_INVALID_HANDLE); @@ -73,37 +89,31 @@ TEST_CASE("Partition manager invalidates handle on partition de-init", "[partiti TEST_CASE("Partition manager initializes multiple partitions", "[partition_mgr]") { - const uint32_t NVS_FLASH_SECTOR_BEGIN1 = 0; - const uint32_t NVS_FLASH_SECTOR_SIZE1 = 3; - const char* NVS_FLASH_PARTITION1 = "test1"; - const uint32_t NVS_FLASH_SECTOR_BEGIN2 = 3; - const uint32_t NVS_FLASH_SECTOR_SIZE2 = 3; - const char* NVS_FLASH_PARTITION2 = "test2"; + // Positive TC for partition manager initializing multiple partitions + // This test initializes two partitions with specific names and checks if they are registered in the NVSPartitionManager. + // After de-initialization, both partitions shouldn't be found - PartitionEmulationFixture2 f(NVS_FLASH_SECTOR_BEGIN1, - NVS_FLASH_SECTOR_SIZE1, - NVS_FLASH_PARTITION1, - NVS_FLASH_SECTOR_BEGIN2, - NVS_FLASH_SECTOR_SIZE2, - NVS_FLASH_PARTITION2 - ); + // Erase the partitions to ensure they are clean + // We cannot use nvs_flash_erase_partition here because it is actually using the NVSPartitionManager we want to test. + REQUIRE(NVSPartitionTestHelper::erase_partition(TEST_DEFAULT_PARTITION_NAME) == ESP_OK); + REQUIRE(NVSPartitionTestHelper::erase_partition(TEST_SECONDARY_PARTITION_NAME) == ESP_OK); - REQUIRE(nvs::NVSPartitionManager::get_instance()->init_custom(f.part(), - NVS_FLASH_SECTOR_BEGIN1, - NVS_FLASH_SECTOR_SIZE1) - == ESP_OK); + // Init the default partition + REQUIRE(nvs::NVSPartitionManager::get_instance()->init_partition(TEST_DEFAULT_PARTITION_NAME) == ESP_OK); - REQUIRE(nvs::NVSPartitionManager::get_instance()->init_custom(f.part2(), - NVS_FLASH_SECTOR_BEGIN2, - NVS_FLASH_SECTOR_SIZE2) - == ESP_OK); + // Init the alternative partition + REQUIRE(nvs::NVSPartitionManager::get_instance()->init_partition(TEST_SECONDARY_PARTITION_NAME) == ESP_OK); - nvs::Storage *storage1 = nvs::NVSPartitionManager::get_instance()->lookup_storage_from_name(NVS_FLASH_PARTITION1); + // Check if both partitions are registered + nvs::Storage *storage1 = nvs::NVSPartitionManager::get_instance()->lookup_storage_from_name(TEST_DEFAULT_PARTITION_NAME); REQUIRE(storage1 != nullptr); - nvs::Storage *storage2 = nvs::NVSPartitionManager::get_instance()->lookup_storage_from_name(NVS_FLASH_PARTITION2); + nvs::Storage *storage2 = nvs::NVSPartitionManager::get_instance()->lookup_storage_from_name(TEST_SECONDARY_PARTITION_NAME); REQUIRE(storage2 != nullptr); + // Check if the two storages are different CHECK(storage1 != storage2); - REQUIRE(nvs::NVSPartitionManager::get_instance()->deinit_partition(NVS_FLASH_PARTITION1) == ESP_OK); - REQUIRE(nvs::NVSPartitionManager::get_instance()->deinit_partition(NVS_FLASH_PARTITION2) == ESP_OK); + + // De-initialize both partitions + REQUIRE(nvs::NVSPartitionManager::get_instance()->deinit_partition(TEST_DEFAULT_PARTITION_NAME) == ESP_OK); + REQUIRE(nvs::NVSPartitionManager::get_instance()->deinit_partition(TEST_SECONDARY_PARTITION_NAME) == ESP_OK); } diff --git a/components/nvs_flash/host_test/nvs_host_test/partitions_nvs_host_test.csv b/components/nvs_flash/host_test/nvs_host_test/partitions_nvs_host_test.csv new file mode 100644 index 00000000000..1ff8abce69d --- /dev/null +++ b/components/nvs_flash/host_test/nvs_host_test/partitions_nvs_host_test.csv @@ -0,0 +1,7 @@ +# Name, Type, SubType, Offset, Size, Flags +# Note: if you have increased the bootloader size, make sure to update the offsets to avoid overlap +nvs, data, nvs, , 0xa000, +nvs_sec, data, nvs, , 0xa000, +nvs_3sec, data, nvs, , 0x3000, +phy_init, data, phy, , 0x1000, +factory, app, factory, , 1M, diff --git a/components/nvs_flash/host_test/nvs_host_test/pytest_nvs_host_linux.py b/components/nvs_flash/host_test/nvs_host_test/pytest_nvs_host_linux.py index 60172550f83..df792a53783 100644 --- a/components/nvs_flash/host_test/nvs_host_test/pytest_nvs_host_linux.py +++ b/components/nvs_flash/host_test/nvs_host_test/pytest_nvs_host_linux.py @@ -11,6 +11,7 @@ from pytest_embedded_idf.utils import idf_parametrize [ 'default_set_key', 'legacy_set_key', + 'esp_blockdev', ], indirect=True, ) diff --git a/components/nvs_flash/host_test/nvs_host_test/sdkconfig.ci.esp_blockdev b/components/nvs_flash/host_test/nvs_host_test/sdkconfig.ci.esp_blockdev new file mode 100644 index 00000000000..0b92dbb3352 --- /dev/null +++ b/components/nvs_flash/host_test/nvs_host_test/sdkconfig.ci.esp_blockdev @@ -0,0 +1,2 @@ +# Configuration enabling internal storage via esp_blockdev instead of direct call to esp_partition nvs_api +CONFIG_NVS_BDL_STACK=y diff --git a/components/nvs_flash/host_test/nvs_host_test/sdkconfig.defaults b/components/nvs_flash/host_test/nvs_host_test/sdkconfig.defaults index 8de4ed6620c..1f830ac7154 100644 --- a/components/nvs_flash/host_test/nvs_host_test/sdkconfig.defaults +++ b/components/nvs_flash/host_test/nvs_host_test/sdkconfig.defaults @@ -2,7 +2,7 @@ CONFIG_IDF_TARGET="linux" CONFIG_COMPILER_CXX_EXCEPTIONS=y CONFIG_UNITY_ENABLE_IDF_TEST_RUNNER=n CONFIG_ESP_PARTITION_ENABLE_STATS=y -CONFIG_PARTITION_TABLE_SINGLE_APP=y -CONFIG_PARTITION_TABLE_CUSTOM_FILENAME="partitions.csv" -CONFIG_PARTITION_TABLE_FILENAME="partitions_singleapp.csv" +CONFIG_PARTITION_TABLE_CUSTOM=y +CONFIG_PARTITION_TABLE_CUSTOM_FILENAME="partitions_nvs_host_test.csv" +CONFIG_PARTITION_TABLE_FILENAME="partitions_nvs_host_test.csv" CONFIG_PARTITION_TABLE_OFFSET=0x8000 diff --git a/components/nvs_flash/host_test/nvs_page_test/main/test_fixtures.hpp b/components/nvs_flash/host_test/nvs_page_test/main/test_fixtures.hpp index 328b11af5d2..d5d1156c28f 100644 --- a/components/nvs_flash/host_test/nvs_page_test/main/test_fixtures.hpp +++ b/components/nvs_flash/host_test/nvs_page_test/main/test_fixtures.hpp @@ -43,7 +43,8 @@ public: esp_partition.erase_size = ESP_PARTITION_EMULATED_SECTOR_SIZE; esp_partition.type = ESP_PARTITION_TYPE_DATA; esp_partition.subtype = ESP_PARTITION_SUBTYPE_DATA_NVS; - strncpy(esp_partition.label, partition_name, PART_NAME_MAX_SIZE); + strncpy(esp_partition.label, partition_name, NVS_PART_NAME_MAX_SIZE); + esp_partition.label[NVS_PART_NAME_MAX_SIZE] = 0; // ensure null termination } ~PartitionEmulationFixture() diff --git a/components/nvs_flash/src/nvs_partition.cpp b/components/nvs_flash/src/nvs_partition.cpp index 372a031cad1..76db297e144 100644 --- a/components/nvs_flash/src/nvs_partition.cpp +++ b/components/nvs_flash/src/nvs_partition.cpp @@ -174,8 +174,8 @@ bool NVSPartition::is_bdl_nvs_compliant(const char* label, const esp_blockdev_ha is_compliant = false; } - if(bdl->geometry.erase_size % NVS_CONST_PAGE_SIZE != 0) { - ESP_LOGV(TAG, "Block device erase size is not a multiple of %d, it is %zu", NVS_CONST_PAGE_SIZE, bdl->geometry.erase_size); + if(NVS_CONST_PAGE_SIZE % bdl->geometry.erase_size != 0) { + ESP_LOGV(TAG, "Block device erase size %zu does not allow erasing pages of size %d.", bdl->geometry.erase_size, NVS_CONST_PAGE_SIZE); is_compliant = false; } diff --git a/components/nvs_flash/test_apps/pytest_nvs_flash.py b/components/nvs_flash/test_apps/pytest_nvs_flash.py index 8ebfcdca90d..4124d503360 100644 --- a/components/nvs_flash/test_apps/pytest_nvs_flash.py +++ b/components/nvs_flash/test_apps/pytest_nvs_flash.py @@ -12,6 +12,13 @@ def test_nvs_flash(dut: IdfDut) -> None: dut.run_all_single_board_cases(group='!nvs_encr_hmac', timeout=120) +@pytest.mark.generic +@pytest.mark.parametrize('config', ['blockdev'], indirect=True) +@idf_parametrize('target', ['esp32', 'esp32c3'], indirect=['target']) +def test_nvs_flash_blockdev(dut: IdfDut) -> None: + dut.run_all_single_board_cases(group='!nvs_encr_hmac', timeout=120) + + @pytest.mark.nvs_encr_hmac @pytest.mark.parametrize('config', ['nvs_encr_hmac_esp32c3'], indirect=True) @idf_parametrize('target', ['esp32c3'], indirect=['target']) diff --git a/components/nvs_flash/test_apps/sdkconfig.ci.blockdev b/components/nvs_flash/test_apps/sdkconfig.ci.blockdev new file mode 100644 index 00000000000..0b92dbb3352 --- /dev/null +++ b/components/nvs_flash/test_apps/sdkconfig.ci.blockdev @@ -0,0 +1,2 @@ +# Configuration enabling internal storage via esp_blockdev instead of direct call to esp_partition nvs_api +CONFIG_NVS_BDL_STACK=y