diff --git a/test-refactor/README.md b/test-refactor/README.md index cd37861f9..8e3fdc0d6 100644 --- a/test-refactor/README.md +++ b/test-refactor/README.md @@ -80,12 +80,13 @@ Translated tests: | `wh_test_dma.c::whTest_Dma` | `misc/wh_test_dma.c::whTest_Dma` | Misc | | | `wh_test_comm.c::whTest_Comm` | `misc/wh_test_comm.c::whTest_Comm` | Misc | Sequential mem variant only; pthread mem/tcp/shmem variants remain in the legacy harness | | `wh_test_keystore_reqsize.c::whTest_KeystoreReqSize` | `misc/wh_test_keystore_reqsize.c::whTest_KeystoreReqSize` | Misc | | -| `wh_test_cert.c::whTest_CertRamSim` | `server/wh_test_cert.c::whTest_CertVerify` | Server | remove ramsim coupling and migrate to server group | -| `wh_test_crypto.c::whTest_Crypto` | `client-server/wh_test_crypto.c::{whTest_CryptoSha256, whTest_CryptoAes, whTest_CryptoEcc256}` | Client | Subset only; remaining cases listed below | +| `wh_test_cert.c::whTest_CertRamSim` | `server/wh_test_cert.c::whTest_CertVerify` | Server | remove ramsim coupling and migrate to server group. Legacy ran FLASH and FLASH_LOG backends; the port runs the plain flash backend only -- FLASH_LOG re-run pending (see Known coverage gaps) | +| `wh_test_crypto.c::whTest_Crypto` | `client-server/wh_test_crypto.c::{whTest_CryptoSha256, whTest_CryptoAes, whTest_CryptoEcc256}` | Client | Subset only; remaining cases listed below. Legacy ran FLASH and FLASH_LOG backends; the port runs the plain flash backend only -- FLASH_LOG re-run pending (see Known coverage gaps) | | `wh_test_clientserver.c` (echo and server-info paths) | `client-server/wh_test_echo.c::whTest_Echo`, `client-server/wh_test_server_info.c::whTest_ServerInfo` | Client | pthread test ported, sequential test dropped | | `wh_test_wolfcrypt_test.c::whTest_WolfCryptTest` | `client-server/wh_test_wolfcrypt.c::whTest_WolfCryptTest` | Client | | | `wh_test_flash_ramsim.c::whTest_Flash_RamSim` | `posix/wh_test_flash_ramsim.c::{whTest_FlashWriteLock, whTest_FlashEraseProgramVerify, whTest_FlashUnitOps}` | POSIX port-specific (`whTestGroup_RunOne`) | remove ramsim coupling and migrate to server group | -| `wh_test_nvm_flash.c::whTest_NvmFlash` | `posix/wh_test_nvm_flash.c::whTest_NvmAddOverwriteDestroy` | POSIX port-specific (`whTestGroup_RunOne`) | remove ramsim coupling and migrate to server group | +| `wh_test_nvm_flash.c::{whTest_NvmFlash, whTest_NvmFlash_Recovery}` | `posix/wh_test_nvm_flash.c::{whTest_NvmAddOverwriteDestroy, whTest_NvmFlashLog, whTest_NvmRecovery}` | POSIX port-specific (`whTestGroup_RunOne`) | remove ramsim coupling and migrate to server group; flash-log backend exercised by `whTest_NvmFlashLog` (skipped unless `WOLFHSM_CFG_SERVER_NVM_FLASH_LOG`) | +| `wh_test_flash_fault_inject.c` | `posix/wh_test_flash_fault_inject.c` | helper (no test) | fault-injection flash wrapper used by the recovery test | | `wh_test_posix_threadsafe_stress.c::whTest_ThreadSafeStress` | called directly from `posix/wh_test_posix_main.c` | POSIX port-specific (direct call) | | Not yet migrated (still live in `wolfHSM/test/`): @@ -105,9 +106,14 @@ Not yet migrated (still live in `wolfHSM/test/`): | `wh_test_auth.c::whTest_AuthMEM`, `whTest_AuthTCP` | | | `wh_test_server_img_mgr.c::whTest_ServerImgMgr` | | | `wh_test_nvmflags.c::whTest_NvmFlags` | | -| `wh_test_flash_fault_inject.c` | | | `wh_test_check_struct_padding.c` | | +### Shared helpers pulled from `test/` +- `wh_test_common.c::whTest_NvmCfgBackend` is compiled into the POSIX port build to select an NVM backend (flash or flash-log) over a ramsim flash. Used by `whTest_NvmFlashLog` today; the not-yet-migrated cert, image-manager, auth, and log tests rely on it too, so it is wired in ahead of those migrations. + +### Known coverage gaps +- FLASH_LOG backend for server/client-group tests. In `test/`, cert (`whTest_CertRamSim`), crypto (`wh_ClientServer_MemThreadTest`), image-manager (`whTest_ServerImgMgr`), and client/server were each run against both the plain flash and flash-log NVM backends. The refactored server/client-group tests consume a single server context (`wh_test_posix_server.c` hard-codes `WH_NVM_FLASH_CB`), so only the plain flash backend is exercised. Restoring parity means selecting the server backend via `whTest_NvmCfgBackend` and running the server + client groups once per backend (flash, then flash-log) from `wh_test_posix_main.c`. Tracked as a follow-up; the port-specific `whTest_NvmFlashLog` already covers the flash-log NVM object lifecycle directly. + ### Other improvements - Add callback from `wh_Server_HandleRequestMessage` to allow sleep and avoid a busy loop - Add client-only harness to feed invalid server inputs from the test bench with the goal of expanding coverage. diff --git a/test-refactor/posix/Makefile b/test-refactor/posix/Makefile index f17a9cf2a..7742b8f83 100644 --- a/test-refactor/posix/Makefile +++ b/test-refactor/posix/Makefile @@ -182,6 +182,11 @@ SRC_C += $(wildcard $(PROJECT_DIR)/*.c) # an empty TU when the gate isn't satisfied. SRC_C += $(TEST_DIR)/wh_test_posix_threadsafe_stress.c +# Shared NVM backend selector pulled from test/; provides +# whTest_NvmCfgBackend for tests that exercise multiple NVM +# backends (flash and flash-log). +SRC_C += $(TEST_DIR)/wh_test_common.c + ## Build rules diff --git a/test-refactor/posix/wh_test_flash_fault_inject.c b/test-refactor/posix/wh_test_flash_fault_inject.c new file mode 100644 index 000000000..47312aa8c --- /dev/null +++ b/test-refactor/posix/wh_test_flash_fault_inject.c @@ -0,0 +1,183 @@ +/* + * Copyright (C) 2026 wolfSSL Inc. + * + * This file is part of wolfHSM. + * + * wolfHSM is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * wolfHSM is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with wolfHSM. If not, see . + */ +/* + * test-refactor/wh_test_flash_fault_inject.c + * + * Flash fault-injection wrapper. Forwards every flash op to a real + * callback but forces an abort on the Nth Program call. Used by the + * NVM recovery test (wh_test_nvm_flash.c) to fail a write mid-object. + */ + +/* Pick up compile-time configuration */ +#include "wolfhsm/wh_settings.h" + +#include +#include /* For NULL */ + +#include +#include + +#include "wolfhsm/wh_error.h" +#include "wh_test_flash_fault_inject.h" + +int whFlashFaultInject_Init(void* context, const void* config) +{ + whFlashFaultInjectCtx* ctx = (whFlashFaultInjectCtx*)context; + const whFlashFaultInjectCfg* cfg = (const whFlashFaultInjectCfg*)config; + + if (ctx == NULL || cfg == NULL || cfg->realCb == NULL) { + return WH_ERROR_BADARGS; + } + + memset(ctx, 0, sizeof(*ctx)); + ctx->realCb = cfg->realCb; + ctx->realCtx = cfg->realCtx; + + if (cfg->realCb->Init != NULL) + return cfg->realCb->Init(cfg->realCtx, cfg->realCfg); + + return WH_ERROR_OK; +} + +int whFlashFaultInject_Cleanup(void* context) +{ + whFlashFaultInjectCtx* ctx = (whFlashFaultInjectCtx*)context; + + if (ctx == NULL || ctx->realCb == NULL) { + return WH_ERROR_BADARGS; + } + if (ctx->realCb->Cleanup != NULL) + return ctx->realCb->Cleanup(ctx->realCtx); + + return WH_ERROR_OK; +} + +int whFlashFaultInject_Program(void* context, uint32_t offset, uint32_t size, + const uint8_t* data) +{ + whFlashFaultInjectCtx* ctx = (whFlashFaultInjectCtx*)context; + + if ((ctx == NULL) || (ctx->realCb == NULL)) + return WH_ERROR_BADARGS; + /* Check if we need to simulate a failure */ + if (ctx->failAfterPrograms > 0) { + ctx->failAfterPrograms--; + if (ctx->failAfterPrograms == 0) + return WH_ERROR_ABORTED; + } + + if (ctx->realCb->Program != NULL) + return ctx->realCb->Program(ctx->realCtx, offset, size, data); + + return WH_ERROR_OK; +} + +int whFlashFaultInject_Read(void* context, uint32_t offset, uint32_t size, + uint8_t* data) +{ + whFlashFaultInjectCtx* ctx = (whFlashFaultInjectCtx*)context; + + if ((ctx == NULL) || (ctx->realCb == NULL)) + return WH_ERROR_BADARGS; + + if (ctx->realCb->Read != NULL) + return ctx->realCb->Read(ctx->realCtx, offset, size, data); + + return WH_ERROR_OK; +} + +int whFlashFaultInject_Erase(void* context, uint32_t offset, uint32_t size) +{ + whFlashFaultInjectCtx* ctx = (whFlashFaultInjectCtx*)context; + + if ((ctx == NULL) || (ctx->realCb == NULL)) + return WH_ERROR_BADARGS; + + if (ctx->realCb->Erase != NULL) + return ctx->realCb->Erase(ctx->realCtx, offset, size); + + return WH_ERROR_OK; +} + +int whFlashFaultInject_Verify(void* context, uint32_t offset, uint32_t size, + const uint8_t* data) +{ + whFlashFaultInjectCtx* ctx = (whFlashFaultInjectCtx*)context; + + if ((ctx == NULL) || (ctx->realCb == NULL)) + return WH_ERROR_BADARGS; + + if (ctx->realCb->Verify != NULL) + return ctx->realCb->Verify(ctx->realCtx, offset, size, data); + + return WH_ERROR_OK; +} + +int whFlashFaultInject_BlankCheck(void* context, uint32_t offset, uint32_t size) +{ + whFlashFaultInjectCtx* ctx = (whFlashFaultInjectCtx*)context; + + if ((ctx == NULL) || (ctx->realCb == NULL)) + return WH_ERROR_BADARGS; + + if (ctx->realCb->BlankCheck != NULL) + return ctx->realCb->BlankCheck(ctx->realCtx, offset, size); + + return WH_ERROR_OK; +} + +uint32_t whFlashFaultInject_PartitionSize(void* context) +{ + whFlashFaultInjectCtx* ctx = (whFlashFaultInjectCtx*)context; + + if ((ctx == NULL) || (ctx->realCb == NULL)) + return WH_ERROR_BADARGS; + + if (ctx->realCb->PartitionSize != NULL) + return ctx->realCb->PartitionSize(ctx->realCtx); + + return WH_ERROR_OK; +} + +int whFlashFaultInject_WriteLock(void* context, uint32_t offset, uint32_t size) +{ + whFlashFaultInjectCtx* ctx = (whFlashFaultInjectCtx*)context; + + if ((ctx == NULL) || (ctx->realCb == NULL)) + return WH_ERROR_BADARGS; + + if (ctx->realCb->WriteLock != NULL) + return ctx->realCb->WriteLock(ctx->realCtx, offset, size); + + return WH_ERROR_OK; +} + +int whFlashFaultInject_WriteUnlock(void* context, uint32_t offset, + uint32_t size) +{ + whFlashFaultInjectCtx* ctx = (whFlashFaultInjectCtx*)context; + + if ((ctx == NULL) || (ctx->realCb == NULL)) + return WH_ERROR_BADARGS; + + if (ctx->realCb->WriteUnlock != NULL) + return ctx->realCb->WriteUnlock(ctx->realCtx, offset, size); + + return WH_ERROR_OK; +} diff --git a/test-refactor/posix/wh_test_flash_fault_inject.h b/test-refactor/posix/wh_test_flash_fault_inject.h new file mode 100644 index 000000000..afbdf22b5 --- /dev/null +++ b/test-refactor/posix/wh_test_flash_fault_inject.h @@ -0,0 +1,80 @@ +/* + * Copyright (C) 2026 wolfSSL Inc. + * + * This file is part of wolfHSM. + * + * wolfHSM is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * wolfHSM is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with wolfHSM. If not, see . + */ +/* + * test-refactor/wh_test_flash_fault_inject.h + * + * Flash fault-injection wrapper. Wraps a real flash callback and + * forces a failure on the Nth Program call, used to drive the NVM + * recovery test on a host-sim flash backend. + */ +#ifndef WH_FLASH_FAULTINJECT_H_ +#define WH_FLASH_FAULTINJECT_H_ + +/* Pick up compile-time configuration */ +#include "wolfhsm/wh_settings.h" + +#include + +#include "wolfhsm/wh_flash.h" + +typedef struct { + const whFlashCb* realCb; + void* realCtx; + int failAfterPrograms; +} whFlashFaultInjectCtx; + +typedef struct { + const whFlashCb* realCb; + void* realCtx; + void* realCfg; +} whFlashFaultInjectCfg; + +int whFlashFaultInject_Init(void* context, const void* config); +int whFlashFaultInject_Cleanup(void* context); +int whFlashFaultInject_Program(void* context, uint32_t offset, uint32_t size, + const uint8_t* data); +int whFlashFaultInject_Read(void* context, uint32_t offset, uint32_t size, + uint8_t* data); +int whFlashFaultInject_Erase(void* context, uint32_t offset, uint32_t size); +int whFlashFaultInject_Verify(void* context, uint32_t offset, uint32_t size, + const uint8_t* data); +int whFlashFaultInject_BlankCheck(void* context, uint32_t offset, + uint32_t size); +uint32_t whFlashFaultInject_PartitionSize(void* context); +int whFlashFaultInject_WriteLock(void* context, uint32_t offset, uint32_t size); +int whFlashFaultInject_WriteUnlock(void* context, uint32_t offset, + uint32_t size); + +/* clang-format off */ +#define WH_FLASH_FAULTINJECT_CB \ + { \ + .Init = whFlashFaultInject_Init, \ + .Cleanup = whFlashFaultInject_Cleanup, \ + .PartitionSize = whFlashFaultInject_PartitionSize, \ + .WriteLock = whFlashFaultInject_WriteLock, \ + .WriteUnlock = whFlashFaultInject_WriteUnlock, \ + .Read = whFlashFaultInject_Read, \ + .Program = whFlashFaultInject_Program, \ + .Erase = whFlashFaultInject_Erase, \ + .Verify = whFlashFaultInject_Verify, \ + .BlankCheck = whFlashFaultInject_BlankCheck, \ + } +/* clang-format on */ + +#endif /* !WH_FLASH_FAULTINJECT_H_ */ diff --git a/test-refactor/posix/wh_test_nvm_flash.c b/test-refactor/posix/wh_test_nvm_flash.c index 957f43e32..5415dad12 100644 --- a/test-refactor/posix/wh_test_nvm_flash.c +++ b/test-refactor/posix/wh_test_nvm_flash.c @@ -38,6 +38,7 @@ #include "wh_test_common.h" #include "wh_test_list.h" +#include "wh_test_flash_fault_inject.h" #define NVM_FLASH_SIZE (1024 * 1024) #define NVM_FLASH_SECTOR_SZ (4096) @@ -55,9 +56,9 @@ typedef struct { whFlashRamsimCfg flashCfg; whFlashCb flashCb; - whNvmFlashContext nvmFlashCtx; - whNvmFlashConfig nvmFlashCfg; - whNvmCb nvmCb; + /* NVM backend selected per-test by whTest_NvmCfgBackend */ + whTestNvmBackendUnion nvmSetup; + whNvmConfig nvmCfg; } whTestNvmFlashCtx; static whTestNvmFlashCtx _ctx; @@ -71,7 +72,6 @@ static void _setup(void) { whTestNvmFlashCtx* c = &_ctx; const whFlashCb initFlashCb[1] = {WH_FLASH_RAMSIM_CB}; - whNvmCb initNvmCb[1] = {WH_NVM_FLASH_CB}; memset(c, 0, sizeof(*c)); @@ -81,12 +81,19 @@ static void _setup(void) c->flashCfg.pageSize = NVM_FLASH_PAGE_SZ; c->flashCfg.erasedByte = 0; c->flashCfg.memory = c->memory; +} - c->nvmFlashCfg.cb = &c->flashCb; - c->nvmFlashCfg.context = &c->flashCtx; - c->nvmFlashCfg.config = &c->flashCfg; - c->nvmCb = initNvmCb[0]; +/* + * Wire the requested NVM backend over the ramsim flash configured + * by _setup, leaving _ctx.nvmCfg ready to Init. Delegates to the + * shared backend selector so flash and flash-log tests stay in + * sync with the rest of the suite. + */ +static int _selectNvm(whTestNvmBackendType type) +{ + return whTest_NvmCfgBackend(type, &_ctx.nvmSetup, &_ctx.nvmCfg, + &_ctx.flashCfg, &_ctx.flashCtx, &_ctx.flashCb); } @@ -221,16 +228,14 @@ static int _addAndCheck(const whNvmCb* cb, void* context, /* - * Add objects, overwrite, reclaim, destroy, verify - * data integrity throughout. + * Backend-agnostic object lifecycle: init the NVM backend from + * cfg, add three objects, overwrite, reclaim, destroy, and verify + * data integrity throughout, then clean up. Shared by the plain + * flash and flash-log backend tests. */ -int whTest_NvmAddOverwriteDestroy(void* ctx) +static int _addOverwriteDestroy(const whNvmCb* cb, void* context, + const void* cfg) { - whTestNvmFlashCtx* c = &_ctx; - const whNvmCb* cb = &c->nvmCb; - - (void)ctx; - _setup(); uint8_t data1[] = "Data1"; uint8_t data2[] = "Data2"; uint8_t data3[] = "Data3"; @@ -246,57 +251,206 @@ int whTest_NvmAddOverwriteDestroy(void* ctx) uint8_t readBuf[256]; size_t i; - WH_TEST_RETURN_ON_FAIL( - cb->Init(&c->nvmFlashCtx, &c->nvmFlashCfg)); + WH_TEST_RETURN_ON_FAIL(cb->Init(context, cfg)); /* Add 3 objects */ WH_TEST_RETURN_ON_FAIL( - _addAndCheck(cb, &c->nvmFlashCtx, - &meta1, sizeof(data1), data1)); + _addAndCheck(cb, context, &meta1, sizeof(data1), data1)); WH_TEST_RETURN_ON_FAIL( - _addAndCheck(cb, &c->nvmFlashCtx, - &meta2, sizeof(data2), data2)); + _addAndCheck(cb, context, &meta2, sizeof(data2), data2)); WH_TEST_RETURN_ON_FAIL( - _addAndCheck(cb, &c->nvmFlashCtx, - &meta3, sizeof(data3), data3)); + _addAndCheck(cb, context, &meta3, sizeof(data3), data3)); /* Overwrite objects */ WH_TEST_RETURN_ON_FAIL( - _addAndCheck(cb, &c->nvmFlashCtx, - &meta1, sizeof(update1), update1)); + _addAndCheck(cb, context, &meta1, sizeof(update1), update1)); WH_TEST_RETURN_ON_FAIL( - _addAndCheck(cb, &c->nvmFlashCtx, - &meta2, sizeof(update2), update2)); + _addAndCheck(cb, context, &meta2, sizeof(update2), update2)); /* Reclaim space */ - WH_TEST_RETURN_ON_FAIL( - cb->DestroyObjects(&c->nvmFlashCtx, 0, NULL)); + WH_TEST_RETURN_ON_FAIL(cb->DestroyObjects(context, 0, NULL)); /* Verify all objects survived reclaim */ for (i = 0; i < sizeof(ids) / sizeof(ids[0]); i++) { memset(&readMeta, 0, sizeof(readMeta)); - WH_TEST_RETURN_ON_FAIL(cb->GetMetadata( - &c->nvmFlashCtx, ids[i], &readMeta)); - WH_TEST_RETURN_ON_FAIL(cb->Read( - &c->nvmFlashCtx, ids[i], 0, - readMeta.len, readBuf)); + WH_TEST_RETURN_ON_FAIL( + cb->GetMetadata(context, ids[i], &readMeta)); + WH_TEST_RETURN_ON_FAIL( + cb->Read(context, ids[i], 0, readMeta.len, readBuf)); } /* Destroy first object, verify it's gone */ - WH_TEST_RETURN_ON_FAIL( - cb->DestroyObjects(&c->nvmFlashCtx, 1, ids)); + WH_TEST_RETURN_ON_FAIL(cb->DestroyObjects(context, 1, ids)); WH_TEST_ASSERT_RETURN( - WH_ERROR_NOTFOUND == cb->Read( - &c->nvmFlashCtx, ids[0], 0, + WH_ERROR_NOTFOUND == cb->Read(context, ids[0], 0, sizeof(readBuf), readBuf)); /* Destroy remaining */ - WH_TEST_RETURN_ON_FAIL( - cb->DestroyObjects(&c->nvmFlashCtx, - sizeof(ids) / sizeof(ids[0]), ids)); + WH_TEST_RETURN_ON_FAIL(cb->DestroyObjects(context, + sizeof(ids) / sizeof(ids[0]), ids)); - WH_TEST_RETURN_ON_FAIL( - cb->Cleanup(&c->nvmFlashCtx)); + WH_TEST_RETURN_ON_FAIL(cb->Cleanup(context)); + + return 0; +} + + +/* + * Object lifecycle against the plain NVM flash backend. + */ +int whTest_NvmAddOverwriteDestroy(void* ctx) +{ + (void)ctx; + _setup(); + WH_TEST_RETURN_ON_FAIL(_selectNvm(WH_NVM_TEST_BACKEND_FLASH)); + return _addOverwriteDestroy(_ctx.nvmCfg.cb, _ctx.nvmCfg.context, + _ctx.nvmCfg.config); +} + + +/* + * Same lifecycle against the NVM flash-log backend, which layers a + * journaled log over the same ramsim flash. Skipped when the log + * backend isn't built. + */ +int whTest_NvmFlashLog(void* ctx) +{ + (void)ctx; +#if defined(WOLFHSM_CFG_SERVER_NVM_FLASH_LOG) + _setup(); + WH_TEST_RETURN_ON_FAIL(_selectNvm(WH_NVM_TEST_BACKEND_FLASH_LOG)); + return _addOverwriteDestroy(_ctx.nvmCfg.cb, _ctx.nvmCfg.context, + _ctx.nvmCfg.config); +#else + return WH_TEST_SKIPPED; +#endif +} + + +/* ---- NVM recovery ---- */ + +/* + * Two flash images for the recovery test: one live backing store + * and one snapshot replayed as init data to model a reboot over a + * dirty flash. File-static to keep 2 MB off the stack. + */ +static uint8_t _recoveryMemory[NVM_FLASH_SIZE]; +static uint8_t _recoveryBackup[NVM_FLASH_SIZE]; + + +/* + * Simulate a failure (eg power loss) during AddObject(), then reinit + * and confirm the half-written object is not found. + */ +static int _simulateFailureAndRecover( + int failAfter, int* dataSize, uint32_t* bytesAvalBefore, + whNvmId* objsAvailBefore, uint32_t* bytesReclBefore, whNvmId* objsReclBefore, + uint32_t* bytesAvalAfter, whNvmId* objsAvailAfter, uint32_t* bytesReclAfter, + whNvmId* objsReclAfter) +{ + unsigned char data[] = "This is test data for recovery test"; + whNvmMetadata meta = {.id = 42, .label = "RecoveryTest"}; + const whFlashCb flashCb[1] = {WH_FLASH_RAMSIM_CB}; + whFlashRamsimCtx flashCtx[1] = {0}; + whFlashRamsimCfg flashCfg[1] = {{ + .size = NVM_FLASH_SIZE, + .sectorSize = NVM_FLASH_SECTOR_SZ, + .pageSize = NVM_FLASH_PAGE_SZ, + .erasedByte = (uint8_t)0, + .memory = _recoveryMemory, + }}; + const whFlashCb flashFaultInjCb[1] = {WH_FLASH_FAULTINJECT_CB}; + whFlashFaultInjectCtx faultInjCtx[1] = {0}; + whFlashFaultInjectCfg faultInjCfg[1] = {{ + .realCb = flashCb, + .realCtx = flashCtx, + .realCfg = flashCfg, + }}; + const whNvmCb cb[1] = {WH_NVM_FLASH_CB}; + whNvmFlashContext context[1] = {0}; + whNvmFlashConfig cfg = { + .cb = flashFaultInjCb, + .context = faultInjCtx, + .config = faultInjCfg, + }; + whNvmMetadata checkMeta = {0}; + int ret = 0; + + WH_TEST_RETURN_ON_FAIL(cb->Init(context, &cfg)); + WH_TEST_RETURN_ON_FAIL(cb->GetAvailable(context, bytesAvalBefore, + objsAvailBefore, bytesReclBefore, + objsReclBefore)); + faultInjCtx->failAfterPrograms = failAfter; + ret = cb->AddObject(context, (whNvmMetadata*)&meta, (whNvmSize)sizeof(data), + data); + WH_TEST_ASSERT_RETURN(ret == WH_ERROR_ABORTED); + + /* Save the memory state for recovery testing */ + memcpy(_recoveryBackup, _recoveryMemory, NVM_FLASH_SIZE); + + WH_TEST_RETURN_ON_FAIL(cb->Cleanup(context)); + /* clean-up the memory */ + memset(_recoveryMemory, 0, NVM_FLASH_SIZE); + + /* Reinit the NVM stack with the backup data from the failure */ + flashCfg->initData = _recoveryBackup; + WH_TEST_RETURN_ON_FAIL(cb->Init(context, &cfg)); + WH_TEST_ASSERT_RETURN(cb->GetMetadata(context, meta.id, &checkMeta) == + WH_ERROR_NOTFOUND); + + /* Return available and reclaimable stats after recovery */ + WH_TEST_RETURN_ON_FAIL(cb->GetAvailable(context, bytesAvalAfter, + objsAvailAfter, bytesReclAfter, + objsReclAfter)); + WH_TEST_RETURN_ON_FAIL(cb->Cleanup(context)); + *dataSize = sizeof(data); + return 0; +} + + +/* + * Recover from a program failure at two points: writing the object + * start (the metadata/start record only) and writing the object + * count (after the data is on flash). Each scenario checks the + * partial object is reclaimed and the live counts are consistent. + */ +int whTest_NvmRecovery(void* ctx) +{ + int test_data_len; + uint32_t bytesBefore, bytesAfter; + whNvmId objsBefore, objsAfter; + uint32_t bytesReclBefore, bytesReclAfter; + whNvmId objsReclBefore, objsReclAfter; + + (void)ctx; + + WH_TEST_PRINT("--simulate failure when writing object start\n"); + WH_TEST_RETURN_ON_FAIL(_simulateFailureAndRecover( + 2 /* program epoch, metadata and fail */, &test_data_len, &bytesBefore, + &objsBefore, &bytesReclBefore, &objsReclBefore, &bytesAfter, &objsAfter, + &bytesReclAfter, &objsReclAfter)); + /* object should be marked as reclaimable */ + WH_TEST_ASSERT_RETURN(objsReclAfter == objsReclBefore + 1); + /* data should not be marked as reclaimable */ + WH_TEST_ASSERT_RETURN(bytesAfter == bytesBefore); + WH_TEST_ASSERT_RETURN(bytesReclAfter == bytesReclBefore); + /* available object should be decremented */ + WH_TEST_ASSERT_RETURN(objsAfter == objsBefore - 1); + + WH_TEST_PRINT("--simulate failure when writing object count\n"); + WH_TEST_RETURN_ON_FAIL(_simulateFailureAndRecover( + 4 /* program epoch, metadata, start, data and fail */, &test_data_len, + &bytesBefore, &objsBefore, &bytesReclBefore, &objsReclBefore, + &bytesAfter, &objsAfter, &bytesReclAfter, &objsReclAfter)); + /* object should be marked as reclaimable */ + WH_TEST_ASSERT_RETURN(objsReclAfter == objsReclBefore + 1); + /* data should be marked as reclaimable by test_data_len rounded up to + * WHFU_BYTES_PER_UNIT */ + WH_TEST_ASSERT_RETURN(bytesAfter <= bytesBefore - test_data_len); + WH_TEST_ASSERT_RETURN(bytesReclAfter >= bytesReclBefore); + WH_TEST_ASSERT_RETURN(bytesReclAfter == bytesBefore - bytesAfter); + /* available object should be decremented */ + WH_TEST_ASSERT_RETURN(objsAfter == objsBefore - 1); return 0; } diff --git a/test-refactor/posix/wh_test_posix_main.c b/test-refactor/posix/wh_test_posix_main.c index 3d8c6083e..38e10eb1b 100644 --- a/test-refactor/posix/wh_test_posix_main.c +++ b/test-refactor/posix/wh_test_posix_main.c @@ -64,13 +64,17 @@ * RAM-based flash simulator, which is a host-sim component; * the nvm_flash test wires the NVM stack to that simulator * with a 1 MB buffer that's not realistic on embedded targets. - * Both run from the POSIX port directly until the NVM test is - * reworked to take a port-supplied flash fixture (then it can + * The recovery test layers a fault-injection wrapper over the + * same simulator to abort a write mid-object and check cleanup. + * All run from the POSIX port directly until the NVM tests are + * reworked to take a port-supplied flash fixture (then they can * lift back into whTestGroup_Server). */ int whTest_FlashWriteLock(void* ctx); int whTest_FlashEraseProgramVerify(void* ctx); int whTest_FlashUnitOps(void* ctx); int whTest_NvmAddOverwriteDestroy(void* ctx); +int whTest_NvmFlashLog(void* ctx); +int whTest_NvmRecovery(void* ctx); /* * Port-owned contexts. The thread functions fill these in and @@ -264,6 +268,16 @@ int main(void) if (rc != 0 && rc != WH_TEST_SKIPPED && miscRc == 0) { miscRc = rc; } + rc = whTestGroup_RunOne("whTest_NvmFlashLog", + whTest_NvmFlashLog, NULL); + if (rc != 0 && rc != WH_TEST_SKIPPED && miscRc == 0) { + miscRc = rc; + } + rc = whTestGroup_RunOne("whTest_NvmRecovery", + whTest_NvmRecovery, NULL); + if (rc != 0 && rc != WH_TEST_SKIPPED && miscRc == 0) { + miscRc = rc; + } } rc = pthread_create(&sthread, NULL, _serverThread, NULL);