mirror of
https://github.com/espressif/esp-idf.git
synced 2024-10-05 20:47:46 -04:00
Merge branch 'feature/nvs_tests' into 'master'
Power off recovery tests for non-volatile storage library, bug fixes, test coverage This merge request: - fixes several issues with internal state handling in nvs::Page - fixes possible infinite loop when initializing namespace list - fixes nvs_get_{str,blob} APIs to behave according to header file doc (they didn't return *length) - adds extra consistency checks in nvs::PageManger initialization. - adds test coverage via gcov and lcov - adds host side tests during CI builds See merge request !9
This commit is contained in:
commit
0f35ff8a98
@ -1,9 +1,8 @@
|
||||
stages:
|
||||
- build
|
||||
# - test
|
||||
- test
|
||||
- deploy
|
||||
|
||||
|
||||
build_template_app:
|
||||
stage: build
|
||||
image: espressif/esp32-ci-env
|
||||
@ -18,6 +17,13 @@ build_template_app:
|
||||
- cd esp-idf-template
|
||||
- make all
|
||||
|
||||
test_nvs_on_host:
|
||||
stage: test
|
||||
image: espressif/esp32-ci-env
|
||||
script:
|
||||
- cd components/nvs_flash/test
|
||||
- make test
|
||||
|
||||
push_master_to_github:
|
||||
stage: deploy
|
||||
only:
|
||||
|
6
components/nvs_flash/.gitignore
vendored
6
components/nvs_flash/.gitignore
vendored
@ -1 +1,7 @@
|
||||
test/test_nvs
|
||||
test/coverage
|
||||
test/coverage.info
|
||||
*.gcno
|
||||
*.gcda
|
||||
*.gcov
|
||||
*.o
|
||||
|
@ -61,7 +61,7 @@ Internals
|
||||
Log of key-value pairs
|
||||
~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
NVS stores key-value pairs sequentially, with new key-value pairs being added at the end. When a value of any given key has to be updated, old key-value pair is marked as erased and new key-value pair is added at the end of the log.
|
||||
NVS stores key-value pairs sequentially, with new key-value pairs being added at the end. When a value of any given key has to be updated, new key-value pair is added at the end of the log and old key-value pair is marked as erased.
|
||||
|
||||
Pages and entries
|
||||
~~~~~~~~~~~~~~~~~
|
||||
@ -69,10 +69,10 @@ Pages and entries
|
||||
NVS library uses two main entities in its operation: pages and entries. Page is a logical structure which stores a portion of the overall log. Logical page corresponds to one physical sector of flash memory. Pages which are in use have a *sequence number* associated with them. Sequence numbers impose an ordering on pages. Higher sequence numbers correspond to pages which were created later. Each page can be in one of the following states:
|
||||
|
||||
Empty/uninitialized
|
||||
Flash storage for the page is empty (all bytes are ``0xff``). Page isn't used to store any data at this point and doesn’t have
|
||||
Flash storage for the page is empty (all bytes are ``0xff``). Page isn't used to store any data at this point and doesn’t have a sequence number.
|
||||
|
||||
Active
|
||||
Flash storage is initialized, page header has been written to flash, page has a valid sequence number. Page has some empty entries and data can be written there. Normally only one page can be in this state.
|
||||
Flash storage is initialized, page header has been written to flash, page has a valid sequence number. Page has some empty entries and data can be written there. At most one page can be in this state at any given moment.
|
||||
|
||||
Full
|
||||
Flash storage is in a consistent state and is filled with key-value pairs.
|
||||
@ -213,7 +213,7 @@ As mentioned above, each key-value pair belongs to one of the namespaces. Namesp
|
||||
+-------------------------------------------+
|
||||
| NS=0 Type=uint8_t Key="pwm" Value=2 | Entry describing namespace "pwm"
|
||||
+-------------------------------------------+
|
||||
| NS=0 Type=uint16_t Key="channel" Value=20 | Key "channel" in namespace "pwm"
|
||||
| NS=2 Type=uint16_t Key="channel" Value=20 | Key "channel" in namespace "pwm"
|
||||
+-------------------------------------------+
|
||||
|
||||
|
||||
|
@ -36,6 +36,7 @@ typedef uint32_t nvs_handle;
|
||||
#define ESP_ERR_NVS_NOT_ENOUGH_SPACE (ESP_ERR_NVS_BASE + 0x05)
|
||||
#define ESP_ERR_NVS_INVALID_NAME (ESP_ERR_NVS_BASE + 0x06)
|
||||
#define ESP_ERR_NVS_INVALID_HANDLE (ESP_ERR_NVS_BASE + 0x07)
|
||||
#define ESP_ERR_NVS_REMOVE_FAILED (ESP_ERR_NVS_BASE + 0x08)
|
||||
#define ESP_ERR_NVS_KEY_TOO_LONG (ESP_ERR_NVS_BASE + 0x09)
|
||||
#define ESP_ERR_NVS_PAGE_FULL (ESP_ERR_NVS_BASE + 0x0a)
|
||||
#define ESP_ERR_NVS_INVALID_STATE (ESP_ERR_NVS_BASE + 0x0b)
|
||||
@ -92,6 +93,10 @@ esp_err_t nvs_open(const char* name, nvs_open_mode open_mode, nvs_handle *out_ha
|
||||
* - ESP_ERR_NVS_INVALID_NAME if key name doesn't satisfy constraints
|
||||
* - ESP_ERR_NVS_NOT_ENOUGH_SPACE if there is not enough space in the
|
||||
* underlying storage to save the value
|
||||
* - ESP_ERR_NVS_REMOVE_FAILED if the value wasn't updated because flash
|
||||
* write operation has failed. The value was written however, and
|
||||
* update will be finished after re-initialization of nvs, provided that
|
||||
* flash operation doesn't fail again.
|
||||
*/
|
||||
esp_err_t nvs_set_i8 (nvs_handle handle, const char* key, int8_t value);
|
||||
esp_err_t nvs_set_u8 (nvs_handle handle, const char* key, uint8_t value);
|
||||
|
@ -33,7 +33,7 @@ public:
|
||||
return mData;
|
||||
}
|
||||
|
||||
Tenum get(size_t index)
|
||||
Tenum get(size_t index) const
|
||||
{
|
||||
assert(index >= 0 && index < Nitems);
|
||||
size_t wordIndex = index / ITEMS_PER_WORD;
|
||||
|
@ -45,11 +45,18 @@ static intrusive_list<HandleEntry> s_nvs_handles;
|
||||
static uint32_t s_nvs_next_handle = 1;
|
||||
static nvs::Storage s_nvs_storage;
|
||||
|
||||
extern "C" void nvs_dump()
|
||||
{
|
||||
Lock lock;
|
||||
s_nvs_storage.debugDump();
|
||||
}
|
||||
|
||||
extern "C" esp_err_t nvs_flash_init(uint32_t baseSector, uint32_t sectorCount)
|
||||
{
|
||||
Lock::init();
|
||||
Lock lock;
|
||||
NVS_DEBUGV("%s %d %d\r\n", __func__, baseSector, sectorCount);
|
||||
s_nvs_handles.clear();
|
||||
return s_nvs_storage.init(baseSector, sectorCount);
|
||||
}
|
||||
|
||||
@ -254,12 +261,15 @@ static esp_err_t nvs_get_str_or_blob(nvs_handle handle, nvs::ItemType type, cons
|
||||
return err;
|
||||
}
|
||||
|
||||
if (length != nullptr && out_value == nullptr) {
|
||||
if (length == nullptr) {
|
||||
return ESP_ERR_NVS_INVALID_LENGTH;
|
||||
}
|
||||
else if (out_value == nullptr) {
|
||||
*length = dataSize;
|
||||
return ESP_OK;
|
||||
}
|
||||
|
||||
if (length == nullptr || *length < dataSize) {
|
||||
else if (*length < dataSize) {
|
||||
*length = dataSize;
|
||||
return ESP_ERR_NVS_INVALID_LENGTH;
|
||||
}
|
||||
|
||||
|
@ -17,7 +17,8 @@
|
||||
#else
|
||||
#include "crc.h"
|
||||
#endif
|
||||
|
||||
#include <cstdio>
|
||||
#include <cstring>
|
||||
|
||||
namespace nvs
|
||||
{
|
||||
@ -98,15 +99,12 @@ esp_err_t Page::writeEntry(const Item& item)
|
||||
return err;
|
||||
}
|
||||
|
||||
if (mNextFreeEntry == 0) {
|
||||
mFirstUsedEntry = 0;
|
||||
if (mFirstUsedEntry == INVALID_ENTRY) {
|
||||
mFirstUsedEntry = mNextFreeEntry;
|
||||
}
|
||||
|
||||
++mUsedEntryCount;
|
||||
|
||||
if (++mNextFreeEntry == ENTRY_COUNT) {
|
||||
alterPageState(PageState::FULL);
|
||||
}
|
||||
++mNextFreeEntry;
|
||||
|
||||
return ESP_OK;
|
||||
}
|
||||
@ -143,7 +141,7 @@ esp_err_t Page::writeItem(uint8_t nsIndex, ItemType datatype, const char* key, c
|
||||
// primitive types should fit into one entry
|
||||
assert(totalSize == ENTRY_SIZE || datatype == ItemType::BLOB || datatype == ItemType::SZ);
|
||||
|
||||
if (mNextFreeEntry + entriesCount > ENTRY_COUNT) {
|
||||
if (mNextFreeEntry == INVALID_ENTRY || mNextFreeEntry + entriesCount > ENTRY_COUNT) {
|
||||
// page will not fit this amount of data
|
||||
return ESP_ERR_NVS_PAGE_FULL;
|
||||
}
|
||||
@ -158,8 +156,8 @@ esp_err_t Page::writeItem(uint8_t nsIndex, ItemType datatype, const char* key, c
|
||||
std::fill_n(reinterpret_cast<uint32_t*>(item.key), sizeof(item.key) / 4, 0xffffffff);
|
||||
std::fill_n(reinterpret_cast<uint32_t*>(item.data), sizeof(item.data) / 4, 0xffffffff);
|
||||
|
||||
strlcpy(item.key, key, Item::MAX_KEY_LENGTH + 1);
|
||||
|
||||
strncpy(item.key, key, sizeof(item.key) - 1);
|
||||
item.key[sizeof(item.key) - 1] = 0;
|
||||
|
||||
if (datatype != ItemType::SZ && datatype != ItemType::BLOB) {
|
||||
memcpy(item.data, data, dataSize);
|
||||
@ -295,10 +293,14 @@ esp_err_t Page::eraseEntryAndSpan(size_t index)
|
||||
} else {
|
||||
span = item.span;
|
||||
for (ptrdiff_t i = index + span - 1; i >= static_cast<ptrdiff_t>(index); --i) {
|
||||
if (mEntryTable.get(i) == EntryState::WRITTEN) {
|
||||
--mUsedEntryCount;
|
||||
}
|
||||
rc = alterEntryState(i, EntryState::ERASED);
|
||||
if (rc != ESP_OK) {
|
||||
return rc;
|
||||
}
|
||||
++mErasedEntryCount;
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -312,9 +314,10 @@ esp_err_t Page::eraseEntryAndSpan(size_t index)
|
||||
if (index == mFirstUsedEntry) {
|
||||
updateFirstUsedEntry(index, span);
|
||||
}
|
||||
|
||||
mErasedEntryCount += span;
|
||||
mUsedEntryCount -= span;
|
||||
|
||||
if (index + span > mNextFreeEntry) {
|
||||
mNextFreeEntry = index + span;
|
||||
}
|
||||
|
||||
return ESP_OK;
|
||||
}
|
||||
@ -323,7 +326,11 @@ void Page::updateFirstUsedEntry(size_t index, size_t span)
|
||||
{
|
||||
assert(index == mFirstUsedEntry);
|
||||
mFirstUsedEntry = INVALID_ENTRY;
|
||||
for (size_t i = index + span; i < mNextFreeEntry; ++i) {
|
||||
size_t end = mNextFreeEntry;
|
||||
if (end > ENTRY_COUNT) {
|
||||
end = ENTRY_COUNT;
|
||||
}
|
||||
for (size_t i = index + span; i < end; ++i) {
|
||||
if (mEntryTable.get(i) == EntryState::WRITTEN) {
|
||||
mFirstUsedEntry = i;
|
||||
break;
|
||||
@ -357,10 +364,6 @@ esp_err_t Page::moveItem(Page& other)
|
||||
if (err != ESP_OK) {
|
||||
return err;
|
||||
}
|
||||
err = eraseEntry(mFirstUsedEntry);
|
||||
if (err != ESP_OK) {
|
||||
return err;
|
||||
}
|
||||
|
||||
size_t span = entry.span;
|
||||
size_t end = mFirstUsedEntry + span;
|
||||
@ -373,6 +376,8 @@ esp_err_t Page::moveItem(Page& other)
|
||||
if (err != ESP_OK) {
|
||||
return err;
|
||||
}
|
||||
}
|
||||
for (size_t i = mFirstUsedEntry; i < end; ++i) {
|
||||
err = eraseEntry(i);
|
||||
if (err != ESP_OK) {
|
||||
return err;
|
||||
@ -427,30 +432,39 @@ esp_err_t Page::mLoadEntryTable()
|
||||
// but before the entry state table was altered, the entry locacted via
|
||||
// entry state table may actually be half-written.
|
||||
// this is easy to check by reading EntryHeader (i.e. first word)
|
||||
uint32_t entryAddress = mBaseAddress + ENTRY_DATA_OFFSET +
|
||||
static_cast<uint32_t>(mNextFreeEntry) * ENTRY_SIZE;
|
||||
uint32_t header;
|
||||
auto rc = spi_flash_read(entryAddress, &header, sizeof(header));
|
||||
if (rc != ESP_OK) {
|
||||
mState = PageState::INVALID;
|
||||
return rc;
|
||||
}
|
||||
if (header != 0xffffffff) {
|
||||
auto err = alterEntryState(mNextFreeEntry, EntryState::ERASED);
|
||||
if (err != ESP_OK) {
|
||||
if (mNextFreeEntry != INVALID_ENTRY) {
|
||||
uint32_t entryAddress = getEntryAddress(mNextFreeEntry);
|
||||
uint32_t header;
|
||||
auto rc = spi_flash_read(entryAddress, &header, sizeof(header));
|
||||
if (rc != ESP_OK) {
|
||||
mState = PageState::INVALID;
|
||||
return err;
|
||||
return rc;
|
||||
}
|
||||
if (header != 0xffffffff) {
|
||||
auto err = alterEntryState(mNextFreeEntry, EntryState::ERASED);
|
||||
if (err != ESP_OK) {
|
||||
mState = PageState::INVALID;
|
||||
return err;
|
||||
}
|
||||
++mNextFreeEntry;
|
||||
}
|
||||
++mNextFreeEntry;
|
||||
}
|
||||
|
||||
// check that all variable-length items are written or erased fully
|
||||
for (size_t i = 0; i < mNextFreeEntry; ++i) {
|
||||
Item item;
|
||||
size_t lastItemIndex = INVALID_ENTRY;
|
||||
size_t end = mNextFreeEntry;
|
||||
if (end > ENTRY_COUNT) {
|
||||
end = ENTRY_COUNT;
|
||||
}
|
||||
for (size_t i = 0; i < end; ++i) {
|
||||
if (mEntryTable.get(i) == EntryState::ERASED) {
|
||||
lastItemIndex = INVALID_ENTRY;
|
||||
continue;
|
||||
}
|
||||
|
||||
lastItemIndex = i;
|
||||
|
||||
Item item;
|
||||
auto err = readEntry(i, item);
|
||||
if (err != ESP_OK) {
|
||||
mState = PageState::INVALID;
|
||||
@ -475,6 +489,7 @@ esp_err_t Page::mLoadEntryTable()
|
||||
for (size_t j = i; j < i + span; ++j) {
|
||||
if (mEntryTable.get(j) != EntryState::WRITTEN) {
|
||||
needErase = true;
|
||||
lastItemIndex = INVALID_ENTRY;
|
||||
break;
|
||||
}
|
||||
}
|
||||
@ -483,7 +498,21 @@ esp_err_t Page::mLoadEntryTable()
|
||||
}
|
||||
i += span - 1;
|
||||
}
|
||||
|
||||
|
||||
// check that last item is not duplicate
|
||||
if (lastItemIndex != INVALID_ENTRY) {
|
||||
size_t findItemIndex = 0;
|
||||
Item dupItem;
|
||||
if (findItem(item.nsIndex, item.datatype, item.key, findItemIndex, dupItem) == ESP_OK) {
|
||||
if (findItemIndex < lastItemIndex) {
|
||||
auto err = eraseEntryAndSpan(findItemIndex);
|
||||
if (err != ESP_OK) {
|
||||
mState = PageState::INVALID;
|
||||
return err;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return ESP_OK;
|
||||
@ -536,7 +565,7 @@ esp_err_t Page::alterPageState(PageState state)
|
||||
return ESP_OK;
|
||||
}
|
||||
|
||||
esp_err_t Page::readEntry(size_t index, Item& dst)
|
||||
esp_err_t Page::readEntry(size_t index, Item& dst) const
|
||||
{
|
||||
auto rc = spi_flash_read(getEntryAddress(index), reinterpret_cast<uint32_t*>(&dst), sizeof(dst));
|
||||
if (rc != ESP_OK) {
|
||||
@ -550,6 +579,10 @@ esp_err_t Page::findItem(uint8_t nsIndex, ItemType datatype, const char* key, si
|
||||
if (mState == PageState::CORRUPT || mState == PageState::INVALID || mState == PageState::UNINITIALIZED) {
|
||||
return ESP_ERR_NVS_NOT_FOUND;
|
||||
}
|
||||
|
||||
if (itemIndex >= ENTRY_COUNT) {
|
||||
return ESP_ERR_NVS_NOT_FOUND;
|
||||
}
|
||||
|
||||
CachedFindInfo findInfo(nsIndex, datatype, key);
|
||||
if (mFindInfo == findInfo) {
|
||||
@ -560,9 +593,14 @@ esp_err_t Page::findItem(uint8_t nsIndex, ItemType datatype, const char* key, si
|
||||
if (itemIndex > mFirstUsedEntry && itemIndex < ENTRY_COUNT) {
|
||||
start = itemIndex;
|
||||
}
|
||||
|
||||
size_t end = mNextFreeEntry;
|
||||
if (end > ENTRY_COUNT) {
|
||||
end = ENTRY_COUNT;
|
||||
}
|
||||
|
||||
size_t next;
|
||||
for (size_t i = start; i < mNextFreeEntry; i = next) {
|
||||
for (size_t i = start; i < end; i = next) {
|
||||
next = i + 1;
|
||||
if (mEntryTable.get(i) != EntryState::WRITTEN) {
|
||||
continue;
|
||||
@ -658,7 +696,7 @@ void Page::invalidateCache()
|
||||
mFindInfo = CachedFindInfo();
|
||||
}
|
||||
|
||||
void Page::debugDump()
|
||||
void Page::debugDump() const
|
||||
{
|
||||
printf("state=%x addr=%x seq=%d\nfirstUsed=%d nextFree=%d used=%d erased=%d\n", mState, mBaseAddress, mSeqNumber, static_cast<int>(mFirstUsedEntry), static_cast<int>(mNextFreeEntry), mUsedEntryCount, mErasedEntryCount);
|
||||
size_t skip = 0;
|
||||
|
@ -40,7 +40,7 @@ public:
|
||||
|
||||
bool operator==(const CachedFindInfo& other) const
|
||||
{
|
||||
return mKeyPtr == other.mKeyPtr && mType == other.mType && mNsIndex == other.mNsIndex;
|
||||
return mKeyPtr != nullptr && mKeyPtr == other.mKeyPtr && mType == other.mType && mNsIndex == other.mNsIndex;
|
||||
}
|
||||
|
||||
void setItemIndex(uint32_t index)
|
||||
@ -162,7 +162,7 @@ public:
|
||||
|
||||
void invalidateCache();
|
||||
|
||||
void debugDump();
|
||||
void debugDump() const;
|
||||
|
||||
protected:
|
||||
|
||||
@ -195,7 +195,7 @@ protected:
|
||||
|
||||
esp_err_t alterPageState(PageState state);
|
||||
|
||||
esp_err_t readEntry(size_t index, Item& dst);
|
||||
esp_err_t readEntry(size_t index, Item& dst) const;
|
||||
|
||||
esp_err_t writeEntry(const Item& item);
|
||||
|
||||
@ -210,7 +210,7 @@ protected:
|
||||
return static_cast<uint8_t>(type) & 0x0f;
|
||||
}
|
||||
|
||||
uint32_t getEntryAddress(size_t entry)
|
||||
uint32_t getEntryAddress(size_t entry) const
|
||||
{
|
||||
assert(entry < ENTRY_COUNT);
|
||||
return mBaseAddress + ENTRY_DATA_OFFSET + static_cast<uint32_t>(entry) * ENTRY_SIZE;
|
||||
|
@ -53,6 +53,58 @@ esp_err_t PageManager::load(uint32_t baseSector, uint32_t sectorCount)
|
||||
assert(mPageList.back().getSeqNumber(lastSeqNo) == ESP_OK);
|
||||
mSeqNumber = lastSeqNo + 1;
|
||||
}
|
||||
|
||||
// if power went out after a new item for the given key was written,
|
||||
// but before the old one was erased, we end up with a duplicate item
|
||||
Page& lastPage = back();
|
||||
size_t lastItemIndex = SIZE_MAX;
|
||||
Item item;
|
||||
size_t itemIndex = 0;
|
||||
while (lastPage.findItem(Page::NS_ANY, ItemType::ANY, nullptr, itemIndex, item) == ESP_OK) {
|
||||
itemIndex += item.span;
|
||||
lastItemIndex = itemIndex;
|
||||
}
|
||||
|
||||
if (lastItemIndex != SIZE_MAX) {
|
||||
auto last = PageManager::TPageListIterator(&lastPage);
|
||||
for (auto it = begin(); it != last; ++it) {
|
||||
if (it->eraseItem(item.nsIndex, item.datatype, item.key) == ESP_OK) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// check if power went out while page was being freed
|
||||
for (auto it = begin(); it!= end(); ++it) {
|
||||
if (it->state() == Page::PageState::FREEING) {
|
||||
Page* newPage = &mPageList.back();
|
||||
if(newPage->state() != Page::PageState::ACTIVE) {
|
||||
auto err = activatePage();
|
||||
if (err != ESP_OK) {
|
||||
return err;
|
||||
}
|
||||
newPage = &mPageList.back();
|
||||
}
|
||||
while (true) {
|
||||
auto err = it->moveItem(*newPage);
|
||||
if (err == ESP_ERR_NVS_NOT_FOUND) {
|
||||
break;
|
||||
} else if (err != ESP_OK) {
|
||||
return err;
|
||||
}
|
||||
}
|
||||
|
||||
auto err = it->erase();
|
||||
if (err != ESP_OK) {
|
||||
return err;
|
||||
}
|
||||
|
||||
Page* p = static_cast<Page*>(it);
|
||||
mPageList.erase(it);
|
||||
mFreePageList.push_back(p);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return ESP_OK;
|
||||
}
|
||||
@ -91,6 +143,7 @@ esp_err_t PageManager::requestNewPage()
|
||||
Page* newPage = &mPageList.back();
|
||||
|
||||
Page* erasedPage = maxErasedItemsPageIt;
|
||||
size_t usedEntries = erasedPage->getUsedEntryCount();
|
||||
err = erasedPage->markFreeing();
|
||||
if (err != ESP_OK) {
|
||||
return err;
|
||||
@ -108,6 +161,8 @@ esp_err_t PageManager::requestNewPage()
|
||||
if (err != ESP_OK) {
|
||||
return err;
|
||||
}
|
||||
|
||||
assert(usedEntries == newPage->getUsedEntryCount());
|
||||
|
||||
mPageList.erase(maxErasedItemsPageIt);
|
||||
mFreePageList.push_back(erasedPage);
|
||||
|
@ -13,6 +13,11 @@
|
||||
// limitations under the License.
|
||||
#include "nvs_storage.hpp"
|
||||
|
||||
#ifndef ESP_PLATFORM
|
||||
#include <map>
|
||||
#include <sstream>
|
||||
#endif
|
||||
|
||||
namespace nvs
|
||||
{
|
||||
|
||||
@ -39,6 +44,7 @@ esp_err_t Storage::init(uint32_t baseSector, uint32_t sectorCount)
|
||||
return err;
|
||||
}
|
||||
|
||||
// load namespaces list
|
||||
clearNamespaces();
|
||||
std::fill_n(mNamespaceUsage.data(), mNamespaceUsage.byteSize() / 4, 0);
|
||||
for (auto it = mPageManager.begin(); it != mPageManager.end(); ++it) {
|
||||
@ -51,11 +57,15 @@ esp_err_t Storage::init(uint32_t baseSector, uint32_t sectorCount)
|
||||
item.getValue(entry->mIndex);
|
||||
mNamespaces.push_back(entry);
|
||||
mNamespaceUsage.set(entry->mIndex, true);
|
||||
itemIndex += item.span;
|
||||
}
|
||||
}
|
||||
mNamespaceUsage.set(0, true);
|
||||
mNamespaceUsage.set(255, true);
|
||||
mState = StorageState::ACTIVE;
|
||||
#ifndef ESP_PLATFORM
|
||||
debugCheck();
|
||||
#endif
|
||||
return ESP_OK;
|
||||
}
|
||||
|
||||
@ -75,29 +85,46 @@ esp_err_t Storage::writeItem(uint8_t nsIndex, ItemType datatype, const char* key
|
||||
Page& page = getCurrentPage();
|
||||
err = page.writeItem(nsIndex, datatype, key, data, dataSize);
|
||||
if (err == ESP_ERR_NVS_PAGE_FULL) {
|
||||
page.markFull();
|
||||
if (page.state() != Page::PageState::FULL) {
|
||||
err = page.markFull();
|
||||
if (err != ESP_OK) {
|
||||
return err;
|
||||
}
|
||||
}
|
||||
err = mPageManager.requestNewPage();
|
||||
if (err != ESP_OK) {
|
||||
return err;
|
||||
}
|
||||
|
||||
err = getCurrentPage().writeItem(nsIndex, datatype, key, data, dataSize);
|
||||
if (err == ESP_ERR_NVS_PAGE_FULL) {
|
||||
return ESP_ERR_NVS_NOT_ENOUGH_SPACE;
|
||||
}
|
||||
if (err != ESP_OK) {
|
||||
return err;
|
||||
}
|
||||
}
|
||||
else if (err != ESP_OK) {
|
||||
return err;
|
||||
}
|
||||
|
||||
if (findPage) {
|
||||
if (findPage->state() == Page::PageState::UNINITIALIZED) {
|
||||
if (findPage->state() == Page::PageState::UNINITIALIZED ||
|
||||
findPage->state() == Page::PageState::INVALID) {
|
||||
auto err = findItem(nsIndex, datatype, key, findPage, item);
|
||||
assert(err == ESP_OK);
|
||||
}
|
||||
err = findPage->eraseItem(nsIndex, datatype, key);
|
||||
if (err == ESP_ERR_FLASH_OP_FAIL) {
|
||||
return ESP_ERR_NVS_REMOVE_FAILED;
|
||||
}
|
||||
if (err != ESP_OK) {
|
||||
return err;
|
||||
}
|
||||
}
|
||||
|
||||
#ifndef ESP_PLATFORM
|
||||
debugCheck();
|
||||
#endif
|
||||
return ESP_OK;
|
||||
}
|
||||
|
||||
@ -134,7 +161,8 @@ esp_err_t Storage::createOrOpenNamespace(const char* nsName, bool canCreate, uin
|
||||
|
||||
NamespaceEntry* entry = new NamespaceEntry;
|
||||
entry->mIndex = ns;
|
||||
strlcpy(entry->mName, nsName, sizeof(entry->mName));
|
||||
strncpy(entry->mName, nsName, sizeof(entry->mName) - 1);
|
||||
entry->mName[sizeof(entry->mName) - 1] = 0;
|
||||
mNamespaces.push_back(entry);
|
||||
|
||||
} else {
|
||||
@ -192,4 +220,35 @@ esp_err_t Storage::getItemDataSize(uint8_t nsIndex, ItemType datatype, const cha
|
||||
return ESP_OK;
|
||||
}
|
||||
|
||||
}
|
||||
void Storage::debugDump()
|
||||
{
|
||||
for (auto p = mPageManager.begin(); p != mPageManager.end(); ++p) {
|
||||
p->debugDump();
|
||||
}
|
||||
}
|
||||
|
||||
#ifndef ESP_PLATFORM
|
||||
void Storage::debugCheck()
|
||||
{
|
||||
std::map<std::string, Page*> keys;
|
||||
|
||||
for (auto p = mPageManager.begin(); p != mPageManager.end(); ++p) {
|
||||
size_t itemIndex = 0;
|
||||
Item item;
|
||||
while (p->findItem(Page::NS_ANY, ItemType::ANY, nullptr, itemIndex, item) == ESP_OK) {
|
||||
std::stringstream keyrepr;
|
||||
keyrepr << static_cast<unsigned>(item.nsIndex) << "_" << static_cast<unsigned>(item.datatype) << "_" << item.key;
|
||||
std::string keystr = keyrepr.str();
|
||||
if (keys.find(keystr) != std::end(keys)) {
|
||||
printf("Duplicate key: %s\n", keystr.c_str());
|
||||
debugDump();
|
||||
assert(0);
|
||||
}
|
||||
keys.insert(std::make_pair(keystr, static_cast<Page*>(p)));
|
||||
itemIndex += item.span;
|
||||
}
|
||||
}
|
||||
}
|
||||
#endif //ESP_PLATFORM
|
||||
|
||||
}
|
||||
|
@ -74,6 +74,9 @@ public:
|
||||
{
|
||||
return eraseItem(nsIndex, itemTypeOf<T>(), key);
|
||||
}
|
||||
|
||||
void debugDump();
|
||||
void debugCheck();
|
||||
|
||||
|
||||
protected:
|
||||
|
@ -1,5 +1,5 @@
|
||||
TEST_PROGRAM=test_nvs
|
||||
all: test
|
||||
all: $(TEST_PROGRAM)
|
||||
|
||||
SOURCE_FILES = \
|
||||
$(addprefix ../src/, \
|
||||
@ -14,19 +14,22 @@ SOURCE_FILES = \
|
||||
test_spi_flash_emulation.cpp \
|
||||
test_intrusive_list.cpp \
|
||||
test_nvs.cpp \
|
||||
crc.cpp \
|
||||
crc.cpp \
|
||||
main.cpp
|
||||
|
||||
CPPFLAGS += -I../include -I../src -I./ -I../../esp32/include -I ../../spi_flash/include
|
||||
CPPFLAGS += -I../include -I../src -I./ -I../../esp32/include -I ../../spi_flash/include -fprofile-arcs -ftest-coverage
|
||||
CFLAGS += -fprofile-arcs -ftest-coverage
|
||||
CXXFLAGS += -std=c++11 -Wall -Werror
|
||||
LDFLAGS += -lstdc++
|
||||
LDFLAGS += -lstdc++ -Wall -fprofile-arcs -ftest-coverage
|
||||
|
||||
OBJ_FILES = $(SOURCE_FILES:.cpp=.o)
|
||||
|
||||
COVERAGE_FILES = $(OBJ_FILES:.o=.gc*)
|
||||
|
||||
$(OBJ_FILES): %.o: %.cpp
|
||||
|
||||
$(TEST_PROGRAM): $(OBJ_FILES)
|
||||
gcc $(LDFLAGS) -o $(TEST_PROGRAM) $(OBJ_FILES)
|
||||
g++ $(LDFLAGS) -o $(TEST_PROGRAM) $(OBJ_FILES)
|
||||
|
||||
$(OUTPUT_DIR):
|
||||
mkdir -p $(OUTPUT_DIR)
|
||||
@ -34,7 +37,23 @@ $(OUTPUT_DIR):
|
||||
test: $(TEST_PROGRAM)
|
||||
./$(TEST_PROGRAM)
|
||||
|
||||
long-test: $(TEST_PROGRAM)
|
||||
./$(TEST_PROGRAM) [list],[enumtable],[spi_flash_emu],[nvs],[long]
|
||||
|
||||
$(COVERAGE_FILES): $(TEST_PROGRAM) long-test
|
||||
|
||||
coverage.info: $(COVERAGE_FILES)
|
||||
find ../src/ -name "*.gcno" -exec gcov -r -pb {} +
|
||||
lcov --capture --directory ../src --no-external --output-file coverage.info
|
||||
|
||||
coverage_report: coverage.info
|
||||
genhtml coverage.info --output-directory coverage_report
|
||||
@echo "Coverage report is in coverage_report/index.html"
|
||||
|
||||
clean:
|
||||
rm -f $(OBJ_FILES) $(TEST_PROGRAM)
|
||||
rm -f $(COVERAGE_FILES) *.gcov
|
||||
rm -rf coverage_report/
|
||||
rm -f coverage.info
|
||||
|
||||
.PHONY: clean all test
|
||||
|
@ -73,6 +73,10 @@ public:
|
||||
dstAddr + size > mData.size() * 4) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (mFailCountdown != SIZE_MAX && mFailCountdown-- == 0) {
|
||||
return false;
|
||||
}
|
||||
|
||||
for (size_t i = 0; i < size / 4; ++i) {
|
||||
uint32_t sv = src[i];
|
||||
@ -103,6 +107,10 @@ public:
|
||||
WARN("invalid flash operation detected: erase sector=" << sectorNumber);
|
||||
return false;
|
||||
}
|
||||
|
||||
if (mFailCountdown != SIZE_MAX && mFailCountdown-- == 0) {
|
||||
return false;
|
||||
}
|
||||
|
||||
std::fill_n(begin(mData) + offset, SPI_FLASH_SEC_SIZE / 4, 0xffffffff);
|
||||
|
||||
@ -173,6 +181,10 @@ public:
|
||||
mLowerSectorBound = lowerSector;
|
||||
mUpperSectorBound = upperSector;
|
||||
}
|
||||
|
||||
void failAfter(uint32_t count) {
|
||||
mFailCountdown = count;
|
||||
}
|
||||
|
||||
protected:
|
||||
static size_t getReadOpTime(uint32_t bytes);
|
||||
@ -190,6 +202,8 @@ protected:
|
||||
mutable size_t mTotalTime = 0;
|
||||
size_t mLowerSectorBound = 0;
|
||||
size_t mUpperSectorBound = 0;
|
||||
|
||||
size_t mFailCountdown = SIZE_MAX;
|
||||
|
||||
};
|
||||
|
||||
|
@ -19,7 +19,8 @@
|
||||
struct TestNode : public intrusive_list_node<TestNode> {
|
||||
TestNode(const char* name_ = "", int num_ = 0) : num(num_)
|
||||
{
|
||||
strlcpy(name, name_, sizeof(name));
|
||||
strncpy(name, name_, sizeof(name) - 1);
|
||||
name[sizeof(name) - 1] = 0;
|
||||
}
|
||||
char name[32];
|
||||
int num;
|
||||
|
@ -62,7 +62,7 @@ TEST_CASE("crc32 behaves as expected", "[nvs]")
|
||||
CHECK(crc32_1 != item2.calculateCrc32());
|
||||
|
||||
item2 = item1;
|
||||
strlcpy(item2.key, "foo", Item::MAX_KEY_LENGTH);
|
||||
strncpy(item2.key, "foo", Item::MAX_KEY_LENGTH);
|
||||
CHECK(crc32_1 != item2.calculateCrc32());
|
||||
}
|
||||
|
||||
@ -153,9 +153,7 @@ TEST_CASE("when page is full, adding an element fails", "[nvs]")
|
||||
snprintf(name, sizeof(name), "i%ld", i);
|
||||
CHECK(page.writeItem(1, name, i) == ESP_OK);
|
||||
}
|
||||
CHECK(page.state() == Page::PageState::FULL);
|
||||
CHECK(page.writeItem(1, "foo", 64UL) == ESP_ERR_NVS_PAGE_FULL);
|
||||
CHECK(page.state() == Page::PageState::FULL);
|
||||
}
|
||||
|
||||
TEST_CASE("page maintains its seq number")
|
||||
@ -527,162 +525,189 @@ TEST_CASE("nvs api tests, starting with random data in flash", "[nvs][.][long]")
|
||||
}
|
||||
}
|
||||
|
||||
template<typename TGen>
|
||||
esp_err_t doRandomThings(nvs_handle handle, TGen gen, size_t count) {
|
||||
|
||||
const char* keys[] = {"foo", "bar", "longkey_0123456", "another key", "param1", "param2", "param3", "param4", "param5"};
|
||||
const ItemType types[] = {ItemType::I32, ItemType::I32, ItemType::U64, ItemType::U64, ItemType::SZ, ItemType::SZ, ItemType::SZ, ItemType::SZ, ItemType::SZ};
|
||||
extern "C" void nvs_dump();
|
||||
|
||||
class RandomTest {
|
||||
|
||||
static const size_t nKeys = 9;
|
||||
int32_t v1 = 0, v2 = 0;
|
||||
uint64_t v3 = 0, v4 = 0;
|
||||
const size_t strBufLen = 1024;
|
||||
static const size_t strBufLen = 1024;
|
||||
char v5[strBufLen], v6[strBufLen], v7[strBufLen], v8[strBufLen], v9[strBufLen];
|
||||
|
||||
void* values[] = {&v1, &v2, &v3, &v4, &v5, &v6, &v7, &v8, &v9};
|
||||
|
||||
const size_t nKeys = sizeof(keys) / sizeof(keys[0]);
|
||||
static_assert(nKeys == sizeof(types) / sizeof(types[0]), "");
|
||||
static_assert(nKeys == sizeof(values) / sizeof(values[0]), "");
|
||||
|
||||
bool written[nKeys];
|
||||
std::fill_n(written, nKeys, false);
|
||||
|
||||
auto generateRandomString = [](char* dst, size_t size) {
|
||||
size_t len = 0;
|
||||
};
|
||||
|
||||
auto randomRead = [&](size_t index) -> esp_err_t {
|
||||
switch (types[index]) {
|
||||
case ItemType::I32:
|
||||
{
|
||||
int32_t val;
|
||||
auto err = nvs_get_i32(handle, keys[index], &val);
|
||||
if (err == ESP_ERR_FLASH_OP_FAIL) {
|
||||
return err;
|
||||
}
|
||||
if (!written[index]) {
|
||||
REQUIRE(err == ESP_ERR_NVS_NOT_FOUND);
|
||||
}
|
||||
else {
|
||||
REQUIRE(val == *reinterpret_cast<int32_t*>(values[index]));
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
case ItemType::U64:
|
||||
{
|
||||
uint64_t val;
|
||||
auto err = nvs_get_u64(handle, keys[index], &val);
|
||||
if (err == ESP_ERR_FLASH_OP_FAIL) {
|
||||
return err;
|
||||
}
|
||||
if (!written[index]) {
|
||||
REQUIRE(err == ESP_ERR_NVS_NOT_FOUND);
|
||||
}
|
||||
else {
|
||||
REQUIRE(val == *reinterpret_cast<uint64_t*>(values[index]));
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
case ItemType::SZ:
|
||||
{
|
||||
char buf[strBufLen];
|
||||
size_t len = strBufLen;
|
||||
auto err = nvs_get_str(handle, keys[index], buf, &len);
|
||||
if (err == ESP_ERR_FLASH_OP_FAIL) {
|
||||
return err;
|
||||
}
|
||||
if (!written[index]) {
|
||||
REQUIRE(err == ESP_ERR_NVS_NOT_FOUND);
|
||||
}
|
||||
else {
|
||||
REQUIRE(strncmp(buf, reinterpret_cast<const char*>(values[index]), strBufLen));
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
default:
|
||||
assert(0);
|
||||
}
|
||||
return ESP_OK;
|
||||
};
|
||||
|
||||
auto randomWrite = [&](size_t index) -> esp_err_t {
|
||||
switch (types[index]) {
|
||||
case ItemType::I32:
|
||||
{
|
||||
int32_t val = static_cast<int32_t>(gen());
|
||||
*reinterpret_cast<int32_t*>(values[index]) = val;
|
||||
|
||||
auto err = nvs_set_i32(handle, keys[index], val);
|
||||
if (err == ESP_ERR_FLASH_OP_FAIL) {
|
||||
return err;
|
||||
}
|
||||
REQUIRE(err == ESP_OK);
|
||||
written[index] = true;
|
||||
break;
|
||||
}
|
||||
|
||||
case ItemType::U64:
|
||||
{
|
||||
uint64_t val = static_cast<uint64_t>(gen());
|
||||
*reinterpret_cast<uint64_t*>(values[index]) = val;
|
||||
|
||||
auto err = nvs_set_u64(handle, keys[index], val);
|
||||
if (err == ESP_ERR_FLASH_OP_FAIL) {
|
||||
return err;
|
||||
}
|
||||
REQUIRE(err == ESP_OK);
|
||||
written[index] = true;
|
||||
break;
|
||||
}
|
||||
|
||||
case ItemType::SZ:
|
||||
{
|
||||
char buf[strBufLen];
|
||||
size_t len = strBufLen;
|
||||
|
||||
size_t strLen = gen() % (strBufLen - 1);
|
||||
std::generate_n(buf, strLen, [&]() -> char {
|
||||
const char c = static_cast<char>(gen() % 127);
|
||||
return (c < 32) ? 32 : c;
|
||||
});
|
||||
|
||||
auto err = nvs_set_str(handle, keys[index], buf);
|
||||
if (err == ESP_ERR_FLASH_OP_FAIL) {
|
||||
return err;
|
||||
}
|
||||
REQUIRE(err == ESP_OK);
|
||||
written[index] = true;
|
||||
break;
|
||||
}
|
||||
|
||||
default:
|
||||
assert(0);
|
||||
}
|
||||
return ESP_OK;
|
||||
};
|
||||
|
||||
|
||||
for (size_t i = 0; i < count; ++i) {
|
||||
size_t index = gen() % nKeys;
|
||||
switch (gen() % 3) {
|
||||
case 0: // read, 1/3
|
||||
if (randomRead(index) == ESP_ERR_FLASH_OP_FAIL) {
|
||||
return ESP_ERR_FLASH_OP_FAIL;
|
||||
}
|
||||
break;
|
||||
|
||||
default: // write, 2/3
|
||||
if (randomWrite(index) == ESP_ERR_FLASH_OP_FAIL) {
|
||||
return ESP_ERR_FLASH_OP_FAIL;
|
||||
}
|
||||
break;
|
||||
}
|
||||
public:
|
||||
RandomTest()
|
||||
{
|
||||
std::fill_n(written, nKeys, false);
|
||||
}
|
||||
return ESP_OK;
|
||||
}
|
||||
|
||||
template<typename TGen>
|
||||
esp_err_t doRandomThings(nvs_handle handle, TGen gen, size_t& count) {
|
||||
|
||||
const char* keys[] = {"foo", "bar", "longkey_0123456", "another key", "param1", "param2", "param3", "param4", "param5"};
|
||||
const ItemType types[] = {ItemType::I32, ItemType::I32, ItemType::U64, ItemType::U64, ItemType::SZ, ItemType::SZ, ItemType::SZ, ItemType::SZ, ItemType::SZ};
|
||||
|
||||
void* values[] = {&v1, &v2, &v3, &v4, &v5, &v6, &v7, &v8, &v9};
|
||||
|
||||
const size_t nKeys = sizeof(keys) / sizeof(keys[0]);
|
||||
static_assert(nKeys == sizeof(types) / sizeof(types[0]), "");
|
||||
static_assert(nKeys == sizeof(values) / sizeof(values[0]), "");
|
||||
|
||||
auto randomRead = [&](size_t index) -> esp_err_t {
|
||||
switch (types[index]) {
|
||||
case ItemType::I32:
|
||||
{
|
||||
int32_t val;
|
||||
auto err = nvs_get_i32(handle, keys[index], &val);
|
||||
if (err == ESP_ERR_FLASH_OP_FAIL) {
|
||||
return err;
|
||||
}
|
||||
if (!written[index]) {
|
||||
REQUIRE(err == ESP_ERR_NVS_NOT_FOUND);
|
||||
}
|
||||
else {
|
||||
REQUIRE(err == ESP_OK);
|
||||
REQUIRE(val == *reinterpret_cast<int32_t*>(values[index]));
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
case ItemType::U64:
|
||||
{
|
||||
uint64_t val;
|
||||
auto err = nvs_get_u64(handle, keys[index], &val);
|
||||
if (err == ESP_ERR_FLASH_OP_FAIL) {
|
||||
return err;
|
||||
}
|
||||
if (!written[index]) {
|
||||
REQUIRE(err == ESP_ERR_NVS_NOT_FOUND);
|
||||
}
|
||||
else {
|
||||
REQUIRE(err == ESP_OK);
|
||||
REQUIRE(val == *reinterpret_cast<uint64_t*>(values[index]));
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
case ItemType::SZ:
|
||||
{
|
||||
char buf[strBufLen];
|
||||
size_t len = strBufLen;
|
||||
auto err = nvs_get_str(handle, keys[index], buf, &len);
|
||||
if (err == ESP_ERR_FLASH_OP_FAIL) {
|
||||
return err;
|
||||
}
|
||||
if (!written[index]) {
|
||||
REQUIRE(err == ESP_ERR_NVS_NOT_FOUND);
|
||||
}
|
||||
else {
|
||||
REQUIRE(err == ESP_OK);
|
||||
REQUIRE(strncmp(buf, reinterpret_cast<const char*>(values[index]), strBufLen) == 0);
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
default:
|
||||
assert(0);
|
||||
}
|
||||
return ESP_OK;
|
||||
};
|
||||
|
||||
auto randomWrite = [&](size_t index) -> esp_err_t {
|
||||
switch (types[index]) {
|
||||
case ItemType::I32:
|
||||
{
|
||||
int32_t val = static_cast<int32_t>(gen());
|
||||
|
||||
auto err = nvs_set_i32(handle, keys[index], val);
|
||||
if (err == ESP_ERR_FLASH_OP_FAIL) {
|
||||
return err;
|
||||
}
|
||||
if (err == ESP_ERR_NVS_REMOVE_FAILED) {
|
||||
written[index] = true;
|
||||
*reinterpret_cast<int32_t*>(values[index]) = val;
|
||||
return ESP_ERR_FLASH_OP_FAIL;
|
||||
}
|
||||
REQUIRE(err == ESP_OK);
|
||||
written[index] = true;
|
||||
*reinterpret_cast<int32_t*>(values[index]) = val;
|
||||
break;
|
||||
}
|
||||
|
||||
case ItemType::U64:
|
||||
{
|
||||
uint64_t val = static_cast<uint64_t>(gen());
|
||||
|
||||
auto err = nvs_set_u64(handle, keys[index], val);
|
||||
if (err == ESP_ERR_FLASH_OP_FAIL) {
|
||||
return err;
|
||||
}
|
||||
if (err == ESP_ERR_NVS_REMOVE_FAILED) {
|
||||
written[index] = true;
|
||||
*reinterpret_cast<uint64_t*>(values[index]) = val;
|
||||
return ESP_ERR_FLASH_OP_FAIL;
|
||||
}
|
||||
REQUIRE(err == ESP_OK);
|
||||
written[index] = true;
|
||||
*reinterpret_cast<uint64_t*>(values[index]) = val;
|
||||
break;
|
||||
}
|
||||
|
||||
case ItemType::SZ:
|
||||
{
|
||||
char buf[strBufLen];
|
||||
size_t len = strBufLen;
|
||||
|
||||
size_t strLen = gen() % (strBufLen - 1);
|
||||
std::generate_n(buf, strLen, [&]() -> char {
|
||||
const char c = static_cast<char>(gen() % 127);
|
||||
return (c < 32) ? 32 : c;
|
||||
});
|
||||
buf[strLen] = 0;
|
||||
|
||||
auto err = nvs_set_str(handle, keys[index], buf);
|
||||
if (err == ESP_ERR_FLASH_OP_FAIL) {
|
||||
return err;
|
||||
}
|
||||
if (err == ESP_ERR_NVS_REMOVE_FAILED) {
|
||||
written[index] = true;
|
||||
strncpy(reinterpret_cast<char*>(values[index]), buf, strBufLen);
|
||||
return ESP_ERR_FLASH_OP_FAIL;
|
||||
}
|
||||
REQUIRE(err == ESP_OK);
|
||||
written[index] = true;
|
||||
strncpy(reinterpret_cast<char*>(values[index]), buf, strBufLen);
|
||||
break;
|
||||
}
|
||||
|
||||
default:
|
||||
assert(0);
|
||||
}
|
||||
return ESP_OK;
|
||||
};
|
||||
|
||||
|
||||
for (; count != 0; --count) {
|
||||
size_t index = gen() % nKeys;
|
||||
switch (gen() % 3) {
|
||||
case 0: // read, 1/3
|
||||
if (randomRead(index) == ESP_ERR_FLASH_OP_FAIL) {
|
||||
return ESP_ERR_FLASH_OP_FAIL;
|
||||
}
|
||||
break;
|
||||
|
||||
default: // write, 2/3
|
||||
if (randomWrite(index) == ESP_ERR_FLASH_OP_FAIL) {
|
||||
return ESP_ERR_FLASH_OP_FAIL;
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
return ESP_OK;
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
TEST_CASE("monkey test", "[nvs][monkey]")
|
||||
{
|
||||
@ -693,6 +718,7 @@ TEST_CASE("monkey test", "[nvs][monkey]")
|
||||
|
||||
SpiFlashEmulator emu(10);
|
||||
emu.randomize(seed);
|
||||
emu.clearStats();
|
||||
|
||||
const uint32_t NVS_FLASH_SECTOR = 6;
|
||||
const uint32_t NVS_FLASH_SECTOR_COUNT_MIN = 3;
|
||||
@ -702,9 +728,66 @@ TEST_CASE("monkey test", "[nvs][monkey]")
|
||||
|
||||
nvs_handle handle;
|
||||
TEST_ESP_OK(nvs_open("namespace1", NVS_READWRITE, &handle));
|
||||
RandomTest test;
|
||||
size_t count = 1000;
|
||||
CHECK(test.doRandomThings(handle, gen, count) == ESP_OK);
|
||||
|
||||
s_perf << "Monkey test: nErase=" << emu.getEraseOps() << " nWrite=" << emu.getWriteOps() << std::endl;
|
||||
}
|
||||
|
||||
CHECK(doRandomThings(handle, gen, 10000) == ESP_OK);
|
||||
TEST_CASE("test recovery from sudden poweroff", "[.][long][nvs][recovery][monkey]")
|
||||
{
|
||||
std::random_device rd;
|
||||
std::mt19937 gen(rd());
|
||||
uint32_t seed = 3;
|
||||
gen.seed(seed);
|
||||
const size_t iter_count = 2000;
|
||||
|
||||
SpiFlashEmulator emu(10);
|
||||
|
||||
const uint32_t NVS_FLASH_SECTOR = 6;
|
||||
const uint32_t NVS_FLASH_SECTOR_COUNT_MIN = 3;
|
||||
emu.setBounds(NVS_FLASH_SECTOR, NVS_FLASH_SECTOR + NVS_FLASH_SECTOR_COUNT_MIN);
|
||||
|
||||
size_t totalOps = 0;
|
||||
int lastPercent = -1;
|
||||
for (uint32_t errDelay = 4; ; ++errDelay) {
|
||||
INFO(errDelay);
|
||||
emu.randomize(seed);
|
||||
emu.clearStats();
|
||||
emu.failAfter(errDelay);
|
||||
RandomTest test;
|
||||
|
||||
if (totalOps != 0) {
|
||||
int percent = errDelay * 100 / totalOps;
|
||||
if (percent != lastPercent) {
|
||||
printf("%d/%d (%d%%)\r\n", errDelay, static_cast<int>(totalOps), percent);
|
||||
lastPercent = percent;
|
||||
}
|
||||
}
|
||||
|
||||
TEST_ESP_OK(nvs_flash_init(NVS_FLASH_SECTOR, NVS_FLASH_SECTOR_COUNT_MIN));
|
||||
|
||||
nvs_handle handle;
|
||||
TEST_ESP_OK(nvs_open("namespace1", NVS_READWRITE, &handle));
|
||||
|
||||
size_t count = iter_count;
|
||||
if(test.doRandomThings(handle, gen, count) != ESP_ERR_FLASH_OP_FAIL) {
|
||||
nvs_close(handle);
|
||||
break;
|
||||
}
|
||||
nvs_close(handle);
|
||||
|
||||
TEST_ESP_OK(nvs_flash_init(NVS_FLASH_SECTOR, NVS_FLASH_SECTOR_COUNT_MIN));
|
||||
TEST_ESP_OK(nvs_open("namespace1", NVS_READWRITE, &handle));
|
||||
auto res = test.doRandomThings(handle, gen, count);
|
||||
if (res != ESP_OK) {
|
||||
nvs_dump();
|
||||
CHECK(0);
|
||||
}
|
||||
nvs_close(handle);
|
||||
totalOps = emu.getEraseOps() + emu.getWriteOps();
|
||||
}
|
||||
}
|
||||
|
||||
TEST_CASE("dump all performance data", "[nvs]")
|
||||
|
Loading…
x
Reference in New Issue
Block a user