diff --git a/test-refactor/README.md b/test-refactor/README.md
index 54fac5cb6..8037ae079 100644
--- a/test-refactor/README.md
+++ b/test-refactor/README.md
@@ -99,6 +99,8 @@ Translated tests:
| `wh_test_auth.c` (`whTest_AuthMEM` / `whTest_AuthTest` sub-tests) | `client-server/wh_test_auth.c::{whTest_AuthBadArgs, whTest_AuthLogin, whTest_AuthLogout, whTest_AuthAddUser, whTest_AuthDeleteUser, whTest_AuthSetPermissions, whTest_AuthSetCredentials, whTest_AuthRequestAuthorization}` | Client | Under `WOLFHSM_CFG_ENABLE_AUTHENTICATION` the POSIX server installs an auth context + admin user and the client logs in as admin at connect, so the ordinary client tests run authorized; each auth test brackets its own session (logout to start clean, restore admin on exit). Uses the blocking client API; the legacy own-server setup and single-thread manual-pump are dropped. Build with `make AUTH=1`. The TCP/client-only variant (`whTest_AuthTCP`) is not ported |
| `wh_test_she.c` (`whTest_SheMasterEcuKeyFallback`, `whTest_SheReqSizeChecking`) | `server/wh_test_she_server.c::{whTest_SheMasterEcuKeyFallback, whTest_SheReqSizeChecking}` | Server | server-internal checks reworked to use the shared server context; the POSIX server config gains a `whServerSheContext` under `WOLFHSM_CFG_SHE_EXTENSION` |
| `wh_test_she.c::whTest_She` (client flows) | `client-server/wh_test_she.c::whTest_She` | Client | SHE UID/secure-boot state is one-shot per server lifetime, so the three legacy client flows are folded into one test that does `SetUid` plus a single comm-boundary-sized secure boot, then the load-key vectors, UID handling, RND, ECB/CBC/MAC, and write-protect rejection -- all of which only need UID set and secure boot complete. Build with `make SHE=1` |
+| `wh_test_log.c::whTest_Log` (frontend, macros, ring buffer, mock/ringbuf generic harness) | `misc/wh_test_log.c::whTest_Log` | Misc | Portable subset: frontend API, log macros, formatted macros, ring buffer backend, and the backend-agnostic harness against the mock and ring buffer backends. The harness itself lives in `misc/wh_test_log_backend.h` so the POSIX port reuses it |
+| `wh_test_log.c` (POSIX file backend, concurrent, client/server) | `posix/wh_test_log_posix.c::{whTest_LogPosixFile_Generic, whTest_LogPosixFile, whTest_LogPosixFileConcurrent, whTest_LogClientServerMemTransport}` | POSIX port-specific (`whTestGroup_RunOne`) | POSIX file backend (via the shared harness and directly), multi-threaded concurrent writers, and a client/server log smoke test over the mem transport. Need pthreads and the host file system, so they run from the port main, not the portable registry |
Not yet migrated (still live in `wolfHSM/test/`):
@@ -111,7 +113,6 @@ Not yet migrated (still live in `wolfHSM/test/`):
| `wh_test_crypto_affinity.c::whTest_CryptoAffinity` | |
| `wh_test_keywrap.c::whTest_KeyWrapClientConfig` | |
| `wh_test_lock.c::whTest_LockConfig`, `whTest_LockPosix` | `whTest_LockConfig` to be reworked to fit the Misc group, likely with a context param. |
-| `wh_test_log.c::whTest_Log`, `whTest_LogBackend_RunAll` | `whTest_LogBackend_RunAll` to be reworked to fit the Misc group, likely with a context param. |
| `wh_test_timeout.c::whTest_TimeoutPosix` | |
| `wh_test_server_img_mgr.c::whTest_ServerImgMgr` | |
| `wh_test_nvmflags.c::whTest_NvmFlags` | |
diff --git a/test-refactor/misc/wh_test_log.c b/test-refactor/misc/wh_test_log.c
new file mode 100644
index 000000000..f3ab5d9ff
--- /dev/null
+++ b/test-refactor/misc/wh_test_log.c
@@ -0,0 +1,1141 @@
+/*
+ * 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/misc/wh_test_log.c
+ *
+ * Portable logging tests from legacy test/wh_test_log.c: frontend API,
+ * log macros, the ring buffer backend, and the backend-agnostic harness
+ * run against the mock and ring buffer backends. The POSIX file backend,
+ * concurrent, and client/server log tests live in the POSIX port.
+ */
+
+#include "wolfhsm/wh_settings.h"
+
+#ifdef WOLFHSM_CFG_LOGGING
+
+#include
+#include
+#include
+
+#include "wolfhsm/wh_error.h"
+#include "wolfhsm/wh_log.h"
+#include "wolfhsm/wh_log_ringbuf.h"
+#include "wolfhsm/wh_log_printf.h"
+
+#include "wh_test_common.h"
+#include "wh_test_list.h"
+#include "wh_test_log_backend.h"
+
+#define ITERATE_STOP_MAGIC 99
+#define ITERATE_STOP_COUNT 3
+
+/* Mock log backend definitions */
+
+#define MOCK_LOG_MAX_ENTRIES 16
+
+typedef struct {
+ whLogEntry entries[MOCK_LOG_MAX_ENTRIES];
+ int count;
+ int init_called;
+ int cleanup_called;
+} mockLogContext;
+
+static int mockLog_Init(void* context, const void* config)
+{
+ mockLogContext* ctx = (mockLogContext*)context;
+ (void)config;
+
+ if (ctx == NULL) {
+ return WH_ERROR_BADARGS;
+ }
+
+ memset(ctx, 0, sizeof(*ctx));
+ ctx->init_called = 1;
+ return 0;
+}
+
+static int mockLog_Cleanup(void* context)
+{
+ mockLogContext* ctx = (mockLogContext*)context;
+
+ if (ctx == NULL) {
+ return WH_ERROR_BADARGS;
+ }
+
+ ctx->cleanup_called = 1;
+ return 0;
+}
+
+static int mockLog_AddEntry(void* context, const whLogEntry* entry)
+{
+ mockLogContext* ctx = (mockLogContext*)context;
+
+ if (ctx == NULL || entry == NULL) {
+ return WH_ERROR_BADARGS;
+ }
+
+ if (ctx->count >= MOCK_LOG_MAX_ENTRIES) {
+ return WH_ERROR_NOSPACE;
+ }
+
+ memcpy(&ctx->entries[ctx->count], entry, sizeof(whLogEntry));
+ ctx->count++;
+ return 0;
+}
+
+/* Test-specific export structure for mock backend */
+typedef struct {
+ int (*callback)(void* arg, const whLogEntry* entry);
+ void* callback_arg;
+} mockLogExportArg;
+
+static int mockLog_Export(void* context, void* export_arg)
+{
+ mockLogContext* ctx = (mockLogContext*)context;
+ mockLogExportArg* args = (mockLogExportArg*)export_arg;
+ int i;
+ int ret;
+
+ if (ctx == NULL) {
+ return WH_ERROR_BADARGS;
+ }
+
+ /* If no export args or callback, just succeed */
+ if (args == NULL || args->callback == NULL) {
+ return 0;
+ }
+
+ /* Iterate and call user's callback */
+ for (i = 0; i < ctx->count; i++) {
+ ret = args->callback(args->callback_arg, &ctx->entries[i]);
+ if (ret != 0) {
+ return ret;
+ }
+ }
+
+ return 0;
+}
+
+static int mockLog_Iterate(void* context, whLogIterateCb iterate_cb,
+ void* iterate_arg)
+{
+ mockLogContext* ctx = (mockLogContext*)context;
+ int i;
+ int ret;
+
+ if (ctx == NULL || iterate_cb == NULL) {
+ return WH_ERROR_BADARGS;
+ }
+
+ /* Iterate and call user's callback */
+ for (i = 0; i < ctx->count; i++) {
+ ret = iterate_cb(iterate_arg, &ctx->entries[i]);
+ if (ret != 0) {
+ return ret;
+ }
+ }
+
+ return 0;
+}
+
+static int mockLog_Clear(void* context)
+{
+ mockLogContext* ctx = (mockLogContext*)context;
+
+ if (ctx == NULL) {
+ return WH_ERROR_BADARGS;
+ }
+
+ ctx->count = 0;
+ memset(ctx->entries, 0, sizeof(ctx->entries));
+ return 0;
+}
+
+static whLogCb mockLogCb = {
+ .Init = mockLog_Init,
+ .Cleanup = mockLog_Cleanup,
+ .AddEntry = mockLog_AddEntry,
+ .Export = mockLog_Export,
+ .Iterate = mockLog_Iterate,
+ .Clear = mockLog_Clear,
+};
+
+
+/* Helper for iterate callback - counts entries */
+static int iterateCallbackCount(void* arg, const whLogEntry* entry)
+{
+ int* count = (int*)arg;
+ (void)entry;
+ (*count)++;
+ return 0;
+}
+
+/* Helper callback - stops iteration after a fixed number of entries */
+static int iterateCallbackStopAt2(void* arg, const whLogEntry* entry)
+{
+ int* count = (int*)arg;
+ (void)entry;
+ (*count)++;
+ if (*count >= ITERATE_STOP_COUNT) {
+ /* Custom return code to test propagation */
+ return ITERATE_STOP_MAGIC;
+ }
+ return WH_ERROR_OK;
+}
+
+/* Helper for iterate callback - validates specific entries */
+typedef struct {
+ int count;
+ int valid; /* Set to 0 if entry doesn't match expected pattern */
+} iterateValidationArgs;
+
+static int iterateCallbackValidator(void* arg, const whLogEntry* entry)
+{
+ iterateValidationArgs* args = (iterateValidationArgs*)arg;
+ char expected[32];
+
+ /* Expect messages like "Entry 0", "Entry 1", etc. */
+ snprintf(expected, sizeof(expected), "Entry %d", args->count);
+
+ if (strncmp(entry->msg, expected, WOLFHSM_CFG_LOG_MSG_MAX) != 0) {
+ args->valid = 0;
+ }
+ if (entry->level != WH_LOG_LEVEL_INFO) {
+ args->valid = 0;
+ }
+
+ args->count++;
+ return 0;
+}
+
+/* Frontend API test using mock backend */
+static int whTest_LogFrontend(void)
+{
+ whLogContext logCtx;
+ mockLogContext mockCtx;
+ whLogConfig logConfig;
+ int iterate_count = 0;
+ int i;
+ mockLogExportArg exportArgs;
+ iterateValidationArgs valArgs;
+ whLogEntry entry = {0};
+
+ /* Setup */
+ memset(&logCtx, 0, sizeof(logCtx));
+ memset(&mockCtx, 0, sizeof(mockCtx));
+ logConfig.cb = &mockLogCb;
+ logConfig.context = &mockCtx;
+ logConfig.config = NULL;
+
+ /* Test: NULL input rejections */
+ WH_TEST_ASSERT_RETURN(wh_Log_Init(NULL, &logConfig) == WH_ERROR_BADARGS);
+ WH_TEST_ASSERT_RETURN(wh_Log_Init(&logCtx, NULL) == WH_ERROR_BADARGS);
+ WH_TEST_ASSERT_RETURN(wh_Log_AddEntry(NULL, &entry) == WH_ERROR_BADARGS);
+ WH_TEST_ASSERT_RETURN(wh_Log_AddEntry(&logCtx, NULL) == WH_ERROR_BADARGS);
+ WH_TEST_ASSERT_RETURN(wh_Log_Cleanup(NULL) == WH_ERROR_BADARGS);
+ WH_TEST_ASSERT_RETURN(wh_Log_Clear(NULL) == WH_ERROR_BADARGS);
+ WH_TEST_ASSERT_RETURN(wh_Log_Export(NULL, &exportArgs) == WH_ERROR_BADARGS);
+ WH_TEST_ASSERT_RETURN(wh_Log_Iterate(NULL, iterateCallbackCount,
+ &iterate_count) == WH_ERROR_BADARGS);
+ WH_TEST_ASSERT_RETURN(wh_Log_Iterate(&logCtx, NULL, &iterate_count) ==
+ WH_ERROR_BADARGS);
+
+ /* Initialize the log context */
+ WH_TEST_RETURN_ON_FAIL(wh_Log_Init(&logCtx, &logConfig));
+ WH_TEST_ASSERT_RETURN(mockCtx.init_called == 1);
+
+ /* Test: Fill buffer completely and verify all entries */
+ for (i = 0; i < MOCK_LOG_MAX_ENTRIES; i++) {
+ WH_LOG_F(&logCtx, WH_LOG_LEVEL_INFO, "Entry %d", i);
+ }
+ WH_TEST_ASSERT_RETURN(mockCtx.count == MOCK_LOG_MAX_ENTRIES);
+
+ /* Verify each entry has correct content */
+ for (i = 0; i < MOCK_LOG_MAX_ENTRIES; i++) {
+ char expected[32];
+ snprintf(expected, sizeof(expected), "Entry %d", i);
+ WH_TEST_ASSERT_RETURN(strncmp(mockCtx.entries[i].msg, expected,
+ WOLFHSM_CFG_LOG_MSG_MAX) == 0);
+ WH_TEST_ASSERT_RETURN(mockCtx.entries[i].level == WH_LOG_LEVEL_INFO);
+ WH_TEST_ASSERT_RETURN(strncmp(mockCtx.entries[i].file, __FILE__,
+ WOLFHSM_CFG_LOG_MSG_MAX) == 0);
+ WH_TEST_ASSERT_RETURN(strncmp(mockCtx.entries[i].function, __func__,
+ WOLFHSM_CFG_LOG_MSG_MAX) == 0);
+ WH_TEST_ASSERT_RETURN(mockCtx.entries[i].line > 0);
+ WH_TEST_ASSERT_RETURN(mockCtx.entries[i].timestamp > 0);
+ }
+
+ /* Test: Export works */
+ iterate_count = 0;
+ exportArgs.callback = iterateCallbackCount;
+ exportArgs.callback_arg = &iterate_count;
+ WH_TEST_RETURN_ON_FAIL(wh_Log_Export(&logCtx, &exportArgs));
+ WH_TEST_ASSERT_RETURN(iterate_count == MOCK_LOG_MAX_ENTRIES);
+
+ /* Test: Iterate works and iterates over expected elements */
+ valArgs.count = 0;
+ valArgs.valid = 1;
+ WH_TEST_RETURN_ON_FAIL(
+ wh_Log_Iterate(&logCtx, iterateCallbackValidator, &valArgs));
+ WH_TEST_ASSERT_RETURN(valArgs.count == MOCK_LOG_MAX_ENTRIES);
+ WH_TEST_ASSERT_RETURN(valArgs.valid == 1);
+
+ /* Test: Clear works */
+ WH_TEST_RETURN_ON_FAIL(wh_Log_Clear(&logCtx));
+ WH_TEST_ASSERT_RETURN(mockCtx.count == 0);
+
+ /* Verify buffer is actually empty via iterate */
+ iterate_count = 0;
+ WH_TEST_RETURN_ON_FAIL(
+ wh_Log_Iterate(&logCtx, iterateCallbackCount, &iterate_count));
+ WH_TEST_ASSERT_RETURN(iterate_count == 0);
+
+ /* Test: Can write after clear */
+ WH_LOG(&logCtx, WH_LOG_LEVEL_INFO, "Entry 0");
+ WH_TEST_ASSERT_RETURN(mockCtx.count == 1);
+ WH_TEST_ASSERT_RETURN(strncmp(mockCtx.entries[0].msg, "Entry 0",
+ WOLFHSM_CFG_LOG_MSG_MAX) == 0);
+
+ /* Cleanup */
+ WH_TEST_RETURN_ON_FAIL(wh_Log_Cleanup(&logCtx));
+ WH_TEST_ASSERT_RETURN(mockCtx.cleanup_called == 1);
+
+ return 0;
+}
+
+/* Test helper macros using mock backend */
+static int whTest_LogMacros(void)
+{
+ whLogContext logCtx;
+ mockLogContext mockCtx;
+ whLogConfig logConfig;
+
+ /* Setup */
+ memset(&logCtx, 0, sizeof(logCtx));
+ memset(&mockCtx, 0, sizeof(mockCtx));
+ logConfig.cb = &mockLogCb;
+ logConfig.context = &mockCtx;
+ logConfig.config = NULL;
+
+ WH_TEST_RETURN_ON_FAIL(wh_Log_Init(&logCtx, &logConfig));
+
+ /* Test: WH_LOG_INFO creates proper entry with __FILE__/__LINE__ */
+ WH_LOG(&logCtx, WH_LOG_LEVEL_INFO, "Info message");
+ WH_TEST_ASSERT_RETURN(mockCtx.count == 1);
+ WH_TEST_ASSERT_RETURN(mockCtx.entries[0].level == WH_LOG_LEVEL_INFO);
+ WH_TEST_ASSERT_RETURN(strncmp(mockCtx.entries[0].msg, "Info message",
+ WOLFHSM_CFG_LOG_MSG_MAX) == 0);
+ WH_TEST_ASSERT_RETURN(strncmp(mockCtx.entries[0].file, __FILE__,
+ WOLFHSM_CFG_LOG_MSG_MAX) == 0);
+ WH_TEST_ASSERT_RETURN(strncmp(mockCtx.entries[0].function, __func__,
+ WOLFHSM_CFG_LOG_MSG_MAX) == 0);
+ WH_TEST_ASSERT_RETURN(mockCtx.entries[0].line > 0);
+
+ /* Test: WH_LOG_ERROR creates proper entry */
+ WH_LOG(&logCtx, WH_LOG_LEVEL_ERROR, "Error message");
+ WH_TEST_ASSERT_RETURN(mockCtx.count == 2);
+ WH_TEST_ASSERT_RETURN(mockCtx.entries[1].level == WH_LOG_LEVEL_ERROR);
+ WH_TEST_ASSERT_RETURN(strncmp(mockCtx.entries[1].msg, "Error message",
+ WOLFHSM_CFG_LOG_MSG_MAX) == 0);
+
+ /* Test: WH_LOG_SECEVENT creates proper entry */
+ WH_LOG(&logCtx, WH_LOG_LEVEL_SECEVENT, "Security event");
+ WH_TEST_ASSERT_RETURN(mockCtx.count == 3);
+ WH_TEST_ASSERT_RETURN(mockCtx.entries[2].level == WH_LOG_LEVEL_SECEVENT);
+ WH_TEST_ASSERT_RETURN(strncmp(mockCtx.entries[2].msg, "Security event",
+ WOLFHSM_CFG_LOG_MSG_MAX) == 0);
+
+ /* Test: WH_LOG_F creates proper entry with runtime string */
+ {
+ const char* runtime_info = "Runtime info message";
+ WH_LOG_F(&logCtx, WH_LOG_LEVEL_INFO, "%s", runtime_info);
+ WH_TEST_ASSERT_RETURN(mockCtx.count == 4);
+ WH_TEST_ASSERT_RETURN(mockCtx.entries[3].level == WH_LOG_LEVEL_INFO);
+ WH_TEST_ASSERT_RETURN(strncmp(mockCtx.entries[3].msg, runtime_info,
+ WOLFHSM_CFG_LOG_MSG_MAX) == 0);
+ WH_TEST_ASSERT_RETURN(strncmp(mockCtx.entries[3].file, __FILE__,
+ WOLFHSM_CFG_LOG_MSG_MAX) == 0);
+ WH_TEST_ASSERT_RETURN(strncmp(mockCtx.entries[3].function, __func__,
+ WOLFHSM_CFG_LOG_MSG_MAX) == 0);
+ WH_TEST_ASSERT_RETURN(mockCtx.entries[3].line > 0);
+ }
+
+ /* Test: WH_LOG_F with empty string */
+ {
+ const char* empty_str = "";
+ WH_LOG_F(&logCtx, WH_LOG_LEVEL_INFO, "%s", empty_str);
+ WH_TEST_ASSERT_RETURN(mockCtx.count == 5);
+ WH_TEST_ASSERT_RETURN(mockCtx.entries[4].level == WH_LOG_LEVEL_INFO);
+ WH_TEST_ASSERT_RETURN(
+ strncmp(mockCtx.entries[4].msg, "", WOLFHSM_CFG_LOG_MSG_MAX) == 0);
+ WH_TEST_ASSERT_RETURN(mockCtx.entries[4].msg_len == 0);
+ }
+
+ /* Test: WH_LOG_F with string exactly at max length */
+ {
+ char exact_msg[WOLFHSM_CFG_LOG_MSG_MAX];
+ int i;
+ /* Fill with 'B' characters, leaving room for null terminator */
+ for (i = 0; i < WOLFHSM_CFG_LOG_MSG_MAX - 1; i++) {
+ exact_msg[i] = 'B';
+ }
+ exact_msg[WOLFHSM_CFG_LOG_MSG_MAX - 1] = '\0';
+
+ WH_LOG_F(&logCtx, WH_LOG_LEVEL_INFO, "%s", exact_msg);
+ WH_TEST_ASSERT_RETURN(mockCtx.count == 6);
+ WH_TEST_ASSERT_RETURN(mockCtx.entries[5].level == WH_LOG_LEVEL_INFO);
+ /* Message should be exactly max length - 1 */
+ WH_TEST_ASSERT_RETURN(mockCtx.entries[5].msg_len ==
+ WOLFHSM_CFG_LOG_MSG_MAX - 1);
+ WH_TEST_ASSERT_RETURN(strncmp(mockCtx.entries[5].msg, exact_msg,
+ WOLFHSM_CFG_LOG_MSG_MAX) == 0);
+ /* Should be null-terminated */
+ WH_TEST_ASSERT_RETURN(
+ mockCtx.entries[5].msg[mockCtx.entries[5].msg_len] == '\0');
+ }
+
+ /* Test: Log assert with true condition doesn't add log entry */
+ WH_LOG_ASSERT(&logCtx, WH_LOG_LEVEL_ERROR, 1, "Assert Message");
+ WH_TEST_ASSERT_RETURN(mockCtx.count == 6);
+
+ /* Test: Log assert with false condition adds log entry */
+ WH_LOG_ASSERT(&logCtx, WH_LOG_LEVEL_ERROR, 0, "Assert Message");
+ WH_TEST_ASSERT_RETURN(mockCtx.count == 7);
+ WH_TEST_ASSERT_RETURN(mockCtx.entries[6].level == WH_LOG_LEVEL_ERROR);
+ WH_TEST_ASSERT_RETURN(strncmp(mockCtx.entries[6].msg, "Assert Message",
+ WOLFHSM_CFG_LOG_MSG_MAX) == 0);
+
+ /* Test: Timestamp is populated */
+ WH_TEST_ASSERT_RETURN(mockCtx.entries[0].timestamp > 0);
+ WH_TEST_ASSERT_RETURN(mockCtx.entries[1].timestamp >=
+ mockCtx.entries[0].timestamp);
+
+ /* Cleanup */
+ WH_TEST_RETURN_ON_FAIL(wh_Log_Cleanup(&logCtx));
+
+ return 0;
+}
+
+
+static int whTest_LogFormattedMacros(void)
+{
+ whLogContext logCtx;
+ mockLogContext mockCtx;
+ whLogConfig logConfig;
+
+ /* Setup */
+ memset(&logCtx, 0, sizeof(logCtx));
+ memset(&mockCtx, 0, sizeof(mockCtx));
+ logConfig.cb = &mockLogCb;
+ logConfig.context = &mockCtx;
+ logConfig.config = NULL;
+
+ WH_TEST_RETURN_ON_FAIL(wh_Log_Init(&logCtx, &logConfig));
+
+ /* Test: WH_LOG_INFO_F with single integer */
+ WH_LOG_F(&logCtx, WH_LOG_LEVEL_INFO, "Value: %d", 42);
+ WH_TEST_ASSERT_RETURN(mockCtx.count == 1);
+ WH_TEST_ASSERT_RETURN(mockCtx.entries[0].level == WH_LOG_LEVEL_INFO);
+ WH_TEST_ASSERT_RETURN(strncmp(mockCtx.entries[0].msg, "Value: 42",
+ WOLFHSM_CFG_LOG_MSG_MAX) == 0);
+ WH_TEST_ASSERT_RETURN(mockCtx.entries[0].msg_len == 9);
+ WH_TEST_ASSERT_RETURN(strncmp(mockCtx.entries[0].file, __FILE__,
+ WOLFHSM_CFG_LOG_MSG_MAX) == 0);
+ WH_TEST_ASSERT_RETURN(strncmp(mockCtx.entries[0].function, __func__,
+ WOLFHSM_CFG_LOG_MSG_MAX) == 0);
+ WH_TEST_ASSERT_RETURN(mockCtx.entries[0].line > 0);
+
+ /* Test: WH_LOG_ERROR_F with multiple integers */
+ WH_LOG_F(&logCtx, WH_LOG_LEVEL_ERROR, "x=%d, y=%d", 10, 20);
+ WH_TEST_ASSERT_RETURN(mockCtx.count == 2);
+ WH_TEST_ASSERT_RETURN(mockCtx.entries[1].level == WH_LOG_LEVEL_ERROR);
+ WH_TEST_ASSERT_RETURN(strncmp(mockCtx.entries[1].msg, "x=10, y=20",
+ WOLFHSM_CFG_LOG_MSG_MAX) == 0);
+ WH_TEST_ASSERT_RETURN(mockCtx.entries[1].msg_len == 10);
+
+ /* Test: WH_LOG_SECEVENT_F with string formatting */
+ WH_LOG_F(&logCtx, WH_LOG_LEVEL_SECEVENT, "User: %s", "admin");
+ WH_TEST_ASSERT_RETURN(mockCtx.count == 3);
+ WH_TEST_ASSERT_RETURN(mockCtx.entries[2].level == WH_LOG_LEVEL_SECEVENT);
+ WH_TEST_ASSERT_RETURN(strncmp(mockCtx.entries[2].msg, "User: admin",
+ WOLFHSM_CFG_LOG_MSG_MAX) == 0);
+ WH_TEST_ASSERT_RETURN(mockCtx.entries[2].msg_len == 11);
+
+ /* Test: WH_LOG_INFO_F with mixed types */
+ WH_LOG_F(&logCtx, WH_LOG_LEVEL_INFO, "Status %d: %s", 404, "Not Found");
+ WH_TEST_ASSERT_RETURN(mockCtx.count == 4);
+ WH_TEST_ASSERT_RETURN(mockCtx.entries[3].level == WH_LOG_LEVEL_INFO);
+ WH_TEST_ASSERT_RETURN(strncmp(mockCtx.entries[3].msg,
+ "Status 404: Not Found",
+ WOLFHSM_CFG_LOG_MSG_MAX) == 0);
+ WH_TEST_ASSERT_RETURN(mockCtx.entries[3].msg_len == 21);
+
+ /* Test: WH_LOG_ERROR_F with hex formatting */
+ WH_LOG_F(&logCtx, WH_LOG_LEVEL_ERROR, "Addr: 0x%08x", 0xDEADBEEF);
+ WH_TEST_ASSERT_RETURN(mockCtx.count == 5);
+ WH_TEST_ASSERT_RETURN(mockCtx.entries[4].level == WH_LOG_LEVEL_ERROR);
+ WH_TEST_ASSERT_RETURN(strncmp(mockCtx.entries[4].msg, "Addr: 0xdeadbeef",
+ WOLFHSM_CFG_LOG_MSG_MAX) == 0);
+ WH_TEST_ASSERT_RETURN(mockCtx.entries[4].msg_len == 16);
+
+ /* Test: WH_LOG_F generic macro with level */
+ WH_LOG_F(&logCtx, WH_LOG_LEVEL_ERROR, "val=%d", 123);
+ WH_TEST_ASSERT_RETURN(mockCtx.count == 6);
+ WH_TEST_ASSERT_RETURN(mockCtx.entries[5].level == WH_LOG_LEVEL_ERROR);
+ WH_TEST_ASSERT_RETURN(strncmp(mockCtx.entries[5].msg, "val=123",
+ WOLFHSM_CFG_LOG_MSG_MAX) == 0);
+ WH_TEST_ASSERT_RETURN(mockCtx.entries[5].msg_len == 7);
+
+ /* Test: Long formatted output (truncation) */
+ {
+ char expected[WOLFHSM_CFG_LOG_MSG_MAX];
+ char longStr[WOLFHSM_CFG_LOG_MSG_MAX + 100];
+
+ /* Create a string longer than the log buffer limit and pass via %s */
+ memset(longStr, 'X', sizeof(longStr) - 1);
+ longStr[sizeof(longStr) - 1] = '\0';
+ WH_LOG_F(&logCtx, WH_LOG_LEVEL_INFO, "%s", longStr);
+
+ WH_TEST_ASSERT_RETURN(mockCtx.count == 7);
+ WH_TEST_ASSERT_RETURN(mockCtx.entries[6].level == WH_LOG_LEVEL_INFO);
+ /* Message should be truncated to WOLFHSM_CFG_LOG_MSG_MAX - 1 */
+ WH_TEST_ASSERT_RETURN(mockCtx.entries[6].msg_len ==
+ WOLFHSM_CFG_LOG_MSG_MAX - 1);
+ /* Should be null-terminated */
+ WH_TEST_ASSERT_RETURN(
+ mockCtx.entries[6].msg[mockCtx.entries[6].msg_len] == '\0');
+
+ /* Verify the beginning of the truncated message */
+ memset(expected, 'X', WOLFHSM_CFG_LOG_MSG_MAX - 1);
+ expected[WOLFHSM_CFG_LOG_MSG_MAX - 1] = '\0';
+ /* Compare up to the truncated length */
+ WH_TEST_ASSERT_RETURN(strncmp(mockCtx.entries[6].msg, expected,
+ WOLFHSM_CFG_LOG_MSG_MAX - 1) == 0);
+ }
+
+ /* Test: Format with multiple argument types */
+ WH_LOG_F(&logCtx, WH_LOG_LEVEL_SECEVENT, "ID=%u, Name=%s, Code=0x%04x", 100,
+ "test", 0xABCD);
+ WH_TEST_ASSERT_RETURN(mockCtx.count == 8);
+ WH_TEST_ASSERT_RETURN(mockCtx.entries[7].level == WH_LOG_LEVEL_SECEVENT);
+ WH_TEST_ASSERT_RETURN(strncmp(mockCtx.entries[7].msg,
+ "ID=100, Name=test, Code=0xabcd",
+ WOLFHSM_CFG_LOG_MSG_MAX) == 0);
+ WH_TEST_ASSERT_RETURN(mockCtx.entries[7].msg_len == 30);
+
+ /* Test: Pointer formatting */
+ {
+ void* ptr = (void*)0x1234;
+ WH_LOG_F(&logCtx, WH_LOG_LEVEL_INFO, "Pointer: %p", ptr);
+ WH_TEST_ASSERT_RETURN(mockCtx.count == 9);
+ WH_TEST_ASSERT_RETURN(mockCtx.entries[8].level == WH_LOG_LEVEL_INFO);
+ /* Just verify message contains "Pointer:" and is non-empty */
+ WH_TEST_ASSERT_RETURN(mockCtx.entries[8].msg_len > 9);
+ WH_TEST_ASSERT_RETURN(strncmp(mockCtx.entries[8].msg, "Pointer: ", 9) ==
+ 0);
+ }
+
+ /* Test: Character formatting */
+ WH_LOG_F(&logCtx, WH_LOG_LEVEL_ERROR, "Char: %c", 'X');
+ WH_TEST_ASSERT_RETURN(mockCtx.count == 10);
+ WH_TEST_ASSERT_RETURN(strncmp(mockCtx.entries[9].msg, "Char: X",
+ WOLFHSM_CFG_LOG_MSG_MAX) == 0);
+ WH_TEST_ASSERT_RETURN(mockCtx.entries[9].msg_len == 7);
+
+
+ /* Test assert formatting with simple int args */
+ WH_LOG_ASSERT_F(&logCtx, WH_LOG_LEVEL_ERROR, 0, "Assert Message: %d", 42);
+ WH_TEST_ASSERT_RETURN(mockCtx.count == 11);
+ WH_TEST_ASSERT_RETURN(mockCtx.entries[10].level == WH_LOG_LEVEL_ERROR);
+ WH_TEST_ASSERT_RETURN(strncmp(mockCtx.entries[10].msg, "Assert Message: 42",
+ WOLFHSM_CFG_LOG_MSG_MAX) == 0);
+
+ /* Test: Timestamp is populated */
+ WH_TEST_ASSERT_RETURN(mockCtx.entries[0].timestamp > 0);
+ WH_TEST_ASSERT_RETURN(mockCtx.entries[1].timestamp >=
+ mockCtx.entries[0].timestamp);
+
+ /* Cleanup */
+ WH_TEST_RETURN_ON_FAIL(wh_Log_Cleanup(&logCtx));
+
+ return 0;
+}
+
+
+/*
+ * Generic backend test - smoke test for basic operations
+ */
+static int whTest_LogBackend_BasicOperations(whTestLogBackendTestConfig* cfg)
+{
+ whLogContext logCtx;
+ whLogConfig logConfig;
+ void* backend_context;
+ int iterate_count;
+
+ /* Use driver-provided backend context */
+ backend_context = cfg->backend_context;
+ WH_TEST_ASSERT_RETURN(backend_context != NULL);
+ memset(backend_context, 0, cfg->config_size);
+
+ /* Setup log configuration */
+ memset(&logCtx, 0, sizeof(logCtx));
+ logConfig.cb = cfg->cb;
+ logConfig.context = backend_context;
+ logConfig.config = cfg->config;
+
+ /* Test: Init with valid config */
+ WH_TEST_RETURN_ON_FAIL(wh_Log_Init(&logCtx, &logConfig));
+
+ /* Test: Add single entry */
+ WH_LOG(&logCtx, WH_LOG_LEVEL_INFO, "Single entry");
+ iterate_count = 0;
+ WH_TEST_RETURN_ON_FAIL(
+ wh_Log_Iterate(&logCtx, iterateCallbackCount, &iterate_count));
+ WH_TEST_ASSERT_RETURN(iterate_count == 1);
+
+ /* Test: Add multiple entries (3) */
+ WH_LOG(&logCtx, WH_LOG_LEVEL_INFO, "Entry 0");
+ WH_LOG(&logCtx, WH_LOG_LEVEL_INFO, "Entry 1");
+ WH_LOG(&logCtx, WH_LOG_LEVEL_INFO, "Entry 2");
+
+ /* Verify count via Iterate */
+ iterate_count = 0;
+ WH_TEST_RETURN_ON_FAIL(
+ wh_Log_Iterate(&logCtx, iterateCallbackCount, &iterate_count));
+ WH_TEST_ASSERT_RETURN(iterate_count == 4); /* 1 + 3 */
+
+ /* Test: Clear and verify empty */
+ WH_TEST_RETURN_ON_FAIL(wh_Log_Clear(&logCtx));
+ iterate_count = 0;
+ WH_TEST_RETURN_ON_FAIL(
+ wh_Log_Iterate(&logCtx, iterateCallbackCount, &iterate_count));
+ WH_TEST_ASSERT_RETURN(iterate_count == 0);
+
+ /* Test: Cleanup */
+ WH_TEST_RETURN_ON_FAIL(wh_Log_Cleanup(&logCtx));
+
+ return WH_ERROR_OK;
+}
+
+/*
+ * Generic backend test - Capacity Handling
+ * Tests behavior when buffer reaches capacity
+ */
+static int whTest_LogBackend_CapacityHandling(whTestLogBackendTestConfig* cfg)
+{
+ whLogContext logCtx;
+ whLogConfig logConfig;
+ void* backend_context;
+ int iterate_count;
+ int i;
+
+ /* Skip if capacity is unlimited */
+ if (cfg->expected_capacity < 0) {
+ WH_TEST_PRINT(" Skipped (unlimited capacity)\n");
+ return WH_ERROR_OK;
+ }
+
+ /* Use driver-provided backend context */
+ backend_context = cfg->backend_context;
+ WH_TEST_ASSERT_RETURN(backend_context != NULL);
+ memset(backend_context, 0, cfg->config_size);
+
+ /* Setup and init */
+ memset(&logCtx, 0, sizeof(logCtx));
+ logConfig.cb = cfg->cb;
+ logConfig.context = backend_context;
+ logConfig.config = cfg->config;
+ WH_TEST_RETURN_ON_FAIL(wh_Log_Init(&logCtx, &logConfig));
+
+ /* Test: Fill to capacity */
+ for (i = 0; i < cfg->expected_capacity; i++) {
+ WH_LOG_F(&logCtx, WH_LOG_LEVEL_INFO, "Entry %d", i);
+ }
+
+ /* Verify count == capacity */
+ iterate_count = 0;
+ WH_TEST_RETURN_ON_FAIL(
+ wh_Log_Iterate(&logCtx, iterateCallbackCount, &iterate_count));
+ WH_TEST_ASSERT_RETURN(iterate_count == cfg->expected_capacity);
+
+ /* Test: Add 10 more entries (overflow) */
+ for (i = 0; i < 10; i++) {
+ WH_LOG_F(&logCtx, WH_LOG_LEVEL_INFO, "Overflow %d", i);
+ }
+
+ /* Verify count still == capacity (overflow behavior) */
+ iterate_count = 0;
+ WH_TEST_RETURN_ON_FAIL(
+ wh_Log_Iterate(&logCtx, iterateCallbackCount, &iterate_count));
+ WH_TEST_ASSERT_RETURN(iterate_count == cfg->expected_capacity);
+
+ WH_TEST_RETURN_ON_FAIL(wh_Log_Cleanup(&logCtx));
+ return WH_ERROR_OK;
+}
+
+/*
+ * Generic backend test - Message Handling
+ * Tests various message sizes and special characters
+ */
+static int whTest_LogBackend_MessageHandling(whTestLogBackendTestConfig* cfg)
+{
+ whLogContext logCtx;
+ whLogConfig logConfig;
+ void* backend_context;
+ int iterate_count;
+ char maxMsg[WOLFHSM_CFG_LOG_MSG_MAX];
+
+ /* Use driver-provided backend context */
+ backend_context = cfg->backend_context;
+ WH_TEST_ASSERT_RETURN(backend_context != NULL);
+ memset(backend_context, 0, cfg->config_size);
+
+ /* Setup and init */
+ memset(&logCtx, 0, sizeof(logCtx));
+ logConfig.cb = cfg->cb;
+ logConfig.context = backend_context;
+ logConfig.config = cfg->config;
+ WH_TEST_RETURN_ON_FAIL(wh_Log_Init(&logCtx, &logConfig));
+
+ /* Test: Empty message */
+ WH_LOG(&logCtx, WH_LOG_LEVEL_INFO, "");
+
+ /* Test: Short message */
+ WH_LOG(&logCtx, WH_LOG_LEVEL_INFO, "Hi");
+
+ /* Test: Max size message (255 chars + null) */
+ memset(maxMsg, 'A', sizeof(maxMsg) - 1);
+ maxMsg[sizeof(maxMsg) - 1] = '\0';
+ WH_LOG_F(&logCtx, WH_LOG_LEVEL_INFO, "%s", maxMsg);
+
+
+ /* Verify all entries were added */
+ iterate_count = 0;
+ WH_TEST_RETURN_ON_FAIL(
+ wh_Log_Iterate(&logCtx, iterateCallbackCount, &iterate_count));
+ WH_TEST_ASSERT_RETURN(iterate_count == 3);
+
+ WH_TEST_RETURN_ON_FAIL(wh_Log_Cleanup(&logCtx));
+ return WH_ERROR_OK;
+}
+
+/*
+ * Generic backend test - Iteration
+ * Tests iteration behavior in various scenarios
+ */
+static int whTest_LogBackend_Iteration(whTestLogBackendTestConfig* cfg)
+{
+ whLogContext logCtx;
+ whLogConfig logConfig;
+ void* backend_context;
+ int iterate_count;
+ int ret;
+
+ /* Use driver-provided backend context */
+ backend_context = cfg->backend_context;
+ WH_TEST_ASSERT_RETURN(backend_context != NULL);
+ memset(backend_context, 0, cfg->config_size);
+
+ /* Setup and init */
+ memset(&logCtx, 0, sizeof(logCtx));
+ logConfig.cb = cfg->cb;
+ logConfig.context = backend_context;
+ logConfig.config = cfg->config;
+ WH_TEST_RETURN_ON_FAIL(wh_Log_Init(&logCtx, &logConfig));
+
+ /* Clear first to ensure clean state for persistent backends */
+ WH_TEST_RETURN_ON_FAIL(wh_Log_Clear(&logCtx));
+
+ /* Test: Iterate empty log */
+ iterate_count = 0;
+ WH_TEST_RETURN_ON_FAIL(
+ wh_Log_Iterate(&logCtx, iterateCallbackCount, &iterate_count));
+ WH_TEST_ASSERT_RETURN(iterate_count == 0);
+
+ /* Test: Iterate single entry */
+ WH_LOG(&logCtx, WH_LOG_LEVEL_INFO, "Single");
+ iterate_count = 0;
+ WH_TEST_RETURN_ON_FAIL(
+ wh_Log_Iterate(&logCtx, iterateCallbackCount, &iterate_count));
+ WH_TEST_ASSERT_RETURN(iterate_count == 1);
+
+ /* Test: Iterate 3 entries */
+ WH_TEST_RETURN_ON_FAIL(wh_Log_Clear(&logCtx));
+ WH_LOG(&logCtx, WH_LOG_LEVEL_INFO, "Entry 1");
+ WH_LOG(&logCtx, WH_LOG_LEVEL_INFO, "Entry 2");
+ WH_LOG(&logCtx, WH_LOG_LEVEL_INFO, "Entry 3");
+ iterate_count = 0;
+ WH_TEST_RETURN_ON_FAIL(
+ wh_Log_Iterate(&logCtx, iterateCallbackCount, &iterate_count));
+ WH_TEST_ASSERT_RETURN(iterate_count == 3);
+
+ /* Test: Early termination (callback returns magic number after
+ * fixed number of entries) */
+ iterate_count = 0;
+ ret = wh_Log_Iterate(&logCtx, iterateCallbackStopAt2, &iterate_count);
+ WH_TEST_ASSERT_RETURN(ret == ITERATE_STOP_MAGIC);
+ WH_TEST_ASSERT_RETURN(iterate_count == ITERATE_STOP_COUNT);
+
+ /* Test: Iterate after clear */
+ WH_TEST_RETURN_ON_FAIL(wh_Log_Clear(&logCtx));
+ iterate_count = 0;
+ WH_TEST_RETURN_ON_FAIL(
+ wh_Log_Iterate(&logCtx, iterateCallbackCount, &iterate_count));
+ WH_TEST_ASSERT_RETURN(iterate_count == 0);
+
+ WH_TEST_RETURN_ON_FAIL(wh_Log_Cleanup(&logCtx));
+ return WH_ERROR_OK;
+}
+
+/*
+ * Generic backend tests - Main runner
+ * Executes all generic backend tests on a given backend test config
+ */
+int whTest_LogBackend_RunAll(whTestLogBackendTestConfig* cfg)
+{
+ int ret = 0;
+
+ /* Call setup hook if provided */
+ if (cfg->setup != NULL) {
+ if (cfg->setup(&cfg->test_context) != 0) {
+ WH_ERROR_PRINT("Setup hook failed\n");
+ return WH_TEST_FAIL;
+ }
+ }
+
+ /* Run all test suites */
+ ret = whTest_LogBackend_BasicOperations(cfg);
+ if (ret != WH_ERROR_OK) {
+ WH_ERROR_PRINT("whTest_LogBackend_BasicOperations returned %d\n", ret);
+ }
+
+ if (ret == WH_ERROR_OK) {
+ ret = whTest_LogBackend_CapacityHandling(cfg);
+ if (ret != WH_ERROR_OK) {
+ WH_ERROR_PRINT("whTest_LogBackend_CapacityHandling returned %d\n",
+ ret);
+ }
+ }
+
+ if (ret == WH_ERROR_OK) {
+ ret = whTest_LogBackend_MessageHandling(cfg);
+ if (ret != WH_ERROR_OK) {
+ WH_ERROR_PRINT("whTest_LogBackend_MessageHandling returned %d\n",
+ ret);
+ }
+ }
+
+ if (ret == WH_ERROR_OK) {
+ ret = whTest_LogBackend_Iteration(cfg);
+ if (ret != WH_ERROR_OK) {
+ WH_ERROR_PRINT("whTest_LogBackend_Iteration returned %d\n", ret);
+ }
+ }
+
+ /* Call teardown hook if provided */
+ if (cfg->teardown != NULL) {
+ if (cfg->teardown(cfg->test_context) != 0) {
+ WH_ERROR_PRINT("Teardown hook failed\n");
+ return WH_TEST_FAIL;
+ }
+ }
+
+ return ret;
+}
+
+
+/* Ring buffer backend tests */
+static int whTest_LogRingbuf(void)
+{
+ whLogContext logCtx;
+ whLogRingbufContext ringbufCtx;
+ whLogRingbufConfig ringbufConfig;
+ whLogConfig logConfig;
+ int i;
+ int iterate_count;
+ uint32_t capacity;
+ /* Backend storage for ring buffer */
+ const size_t numLogEntries = 32;
+ whLogEntry ringbuf_buffer[32];
+ whLogCb ringbufCb = WH_LOG_RINGBUF_CB;
+
+ /* Setup ring buffer backend */
+ memset(&logCtx, 0, sizeof(logCtx));
+ memset(&ringbufCtx, 0, sizeof(ringbufCtx));
+ memset(&ringbuf_buffer, 0, sizeof(ringbuf_buffer));
+
+ /* Configure ring buffer with user-supplied buffer */
+ ringbufConfig.buffer = ringbuf_buffer;
+ ringbufConfig.buffer_size = sizeof(ringbuf_buffer);
+
+ logConfig.cb = &ringbufCb;
+ logConfig.context = &ringbufCtx;
+ logConfig.config = &ringbufConfig;
+
+ /* Test: Init with valid config */
+ WH_TEST_RETURN_ON_FAIL(wh_Log_Init(&logCtx, &logConfig));
+ WH_TEST_ASSERT_RETURN(ringbufCtx.initialized == 1);
+ WH_TEST_ASSERT_RETURN(ringbufCtx.count == 0);
+
+ /* Get capacity from initialized context */
+ capacity = ringbufCtx.capacity;
+ WH_TEST_ASSERT_RETURN(capacity == numLogEntries);
+
+ /* Test: Add a few entries */
+ for (i = 0; i < 5; i++) {
+ WH_LOG_F(&logCtx, WH_LOG_LEVEL_INFO, "Message %d", i);
+ }
+
+ WH_TEST_ASSERT_RETURN(ringbufCtx.count == 5);
+
+ /* Verify the entries are correct */
+ WH_TEST_ASSERT_RETURN(ringbufCtx.count == 5);
+ for (i = 0; i < 5; i++) {
+ char expected[32];
+ snprintf(expected, sizeof(expected), "Message %d", i);
+ WH_TEST_ASSERT_RETURN(strncmp(ringbufCtx.entries[i].msg, expected,
+ sizeof(expected)) == 0);
+ }
+
+ /* Test: Clear buffer */
+ WH_TEST_RETURN_ON_FAIL(wh_Log_Clear(&logCtx));
+ WH_TEST_ASSERT_RETURN(ringbufCtx.count == 0);
+
+ /* Test: Fill buffer to capacity */
+ for (i = 0; i < (int)capacity; i++) {
+ WH_LOG_F(&logCtx, WH_LOG_LEVEL_INFO, "Entry %d", i);
+ }
+
+ WH_TEST_ASSERT_RETURN(ringbufCtx.count == capacity);
+
+ /* Test: Wraparound - add more entries to overwrite oldest */
+ for (i = 0; i < 5; i++) {
+ WH_LOG_F(&logCtx, WH_LOG_LEVEL_INFO, "Wrapped %d", i);
+ }
+
+ /* Count increments freely to track total messages ever written */
+ WH_TEST_ASSERT_RETURN(ringbufCtx.count == capacity + 5);
+
+ /* Verify oldest entries were overwritten */
+ WH_TEST_ASSERT_RETURN(strncmp(ringbufCtx.entries[0].msg, "Wrapped 0",
+ WOLFHSM_CFG_LOG_MSG_MAX) == 0);
+ WH_TEST_ASSERT_RETURN(strncmp(ringbufCtx.entries[4].msg, "Wrapped 4",
+ WOLFHSM_CFG_LOG_MSG_MAX) == 0);
+
+ /* Verify some non-overwritten entries still exist */
+ WH_TEST_ASSERT_RETURN(strncmp(ringbufCtx.entries[5].msg, "Entry 5",
+ WOLFHSM_CFG_LOG_MSG_MAX) == 0);
+
+ /* Test: Iterate through ring buffer */
+ /* Clear and add known entries for iteration test */
+ WH_TEST_RETURN_ON_FAIL(wh_Log_Clear(&logCtx));
+ for (i = 0; i < 3; i++) {
+ WH_LOG_F(&logCtx, WH_LOG_LEVEL_INFO, "Iterate test %d", i);
+ }
+
+ /* Count entries via iteration */
+ iterate_count = 0;
+ WH_TEST_RETURN_ON_FAIL(
+ wh_Log_Iterate(&logCtx, iterateCallbackCount, &iterate_count));
+ WH_TEST_ASSERT_RETURN(iterate_count == 3);
+
+ /* Test: Iterate when buffer is full and wrapped */
+ WH_TEST_RETURN_ON_FAIL(wh_Log_Clear(&logCtx));
+ for (i = 0; i < (int)capacity + 5; i++) {
+ WH_LOG_F(&logCtx, WH_LOG_LEVEL_INFO, "Wrap %d", i);
+ }
+
+ /* Should iterate exactly capacity entries */
+ iterate_count = 0;
+ WH_TEST_RETURN_ON_FAIL(
+ wh_Log_Iterate(&logCtx, iterateCallbackCount, &iterate_count));
+ WH_TEST_ASSERT_RETURN(iterate_count == (int)capacity);
+
+ /* Cleanup */
+ WH_TEST_RETURN_ON_FAIL(wh_Log_Cleanup(&logCtx));
+ WH_TEST_ASSERT_RETURN(ringbufCtx.initialized == 0);
+
+ return 0;
+}
+
+/* Mock backend generic tests */
+static int whTest_LogMock_Generic(void)
+{
+ mockLogContext mockCtx;
+ whTestLogBackendTestConfig testCfg;
+
+ memset(&mockCtx, 0, sizeof(mockCtx));
+
+ testCfg.backend_name = "Mock";
+ testCfg.cb = &mockLogCb;
+ testCfg.config = NULL;
+ testCfg.config_size = sizeof(mockLogContext);
+ testCfg.backend_context = &mockCtx;
+ testCfg.expected_capacity = MOCK_LOG_MAX_ENTRIES;
+ testCfg.supports_concurrent = 0;
+ testCfg.setup = NULL;
+ testCfg.teardown = NULL;
+ testCfg.test_context = NULL;
+
+ return whTest_LogBackend_RunAll(&testCfg);
+}
+
+/* Ring buffer backend generic tests */
+static int whTest_LogRingbuf_Generic(void)
+{
+ whLogRingbufContext ringbufCtx;
+ whLogRingbufConfig ringbufConfig;
+ whTestLogBackendTestConfig testCfg;
+ whLogCb ringbufCb;
+ const size_t numLogEntries = 32;
+ static whLogEntry ringbuf_buffer[32];
+
+ /* Setup ring buffer configuration with user-supplied buffer */
+ memset(&ringbuf_buffer, 0, sizeof(ringbuf_buffer));
+ memset(&ringbufCtx, 0, sizeof(ringbufCtx));
+ ringbufConfig.buffer = ringbuf_buffer;
+ ringbufConfig.buffer_size = sizeof(ringbuf_buffer);
+
+ /* Initialize callback table (C90 compatible) */
+ memset(&ringbufCb, 0, sizeof(ringbufCb));
+ ringbufCb.Init = whLogRingbuf_Init;
+ ringbufCb.Cleanup = whLogRingbuf_Cleanup;
+ ringbufCb.AddEntry = whLogRingbuf_AddEntry;
+ ringbufCb.Export = whLogRingbuf_Export;
+ ringbufCb.Iterate = whLogRingbuf_Iterate;
+ ringbufCb.Clear = whLogRingbuf_Clear;
+
+ testCfg.backend_name = "RingBuffer";
+ testCfg.cb = &ringbufCb;
+ testCfg.config = &ringbufConfig;
+ testCfg.config_size = sizeof(whLogRingbufContext);
+ testCfg.backend_context = &ringbufCtx;
+ testCfg.expected_capacity = numLogEntries;
+ testCfg.supports_concurrent = 0;
+ testCfg.setup = NULL;
+ testCfg.teardown = NULL;
+ testCfg.test_context = NULL;
+
+ return whTest_LogBackend_RunAll(&testCfg);
+}
+
+/* Printf backend tests. The printf backend is a write-only sink: it
+ * implements only Init and AddEntry, so the remaining frontend ops
+ * report NOTIMPL. Exercises both the "log always" and "drop unless
+ * debug" config paths and the bad-args/uninitialized rejections. */
+static int whTest_LogPrintf(void)
+{
+ whLogContext logCtx;
+ whLogPrintfContext printfCtx;
+ whLogPrintfConfig printfCfg;
+ whLogConfig logConfig;
+ whLogCb printfCb = WH_LOG_PRINTF_CB;
+ whLogEntry entry = {0};
+ int iterate_count = 0;
+
+ memset(&printfCtx, 0, sizeof(printfCtx));
+
+ /* Direct backend bad-args rejections */
+ WH_TEST_ASSERT_RETURN(whLogPrintf_Init(NULL, NULL) == WH_ERROR_BADARGS);
+ WH_TEST_ASSERT_RETURN(whLogPrintf_AddEntry(NULL, &entry) ==
+ WH_ERROR_BADARGS);
+ WH_TEST_ASSERT_RETURN(whLogPrintf_AddEntry(&printfCtx, NULL) ==
+ WH_ERROR_BADARGS);
+
+ /* Adding to an uninitialized backend is rejected */
+ WH_TEST_ASSERT_RETURN(whLogPrintf_AddEntry(&printfCtx, &entry) ==
+ WH_ERROR_ABORTED);
+
+ /* Init with NULL config uses defaults (logIfNotDebug = 0) */
+ memset(&logCtx, 0, sizeof(logCtx));
+ memset(&printfCtx, 0, sizeof(printfCtx));
+ logConfig.cb = &printfCb;
+ logConfig.context = &printfCtx;
+ logConfig.config = NULL;
+ WH_TEST_RETURN_ON_FAIL(wh_Log_Init(&logCtx, &logConfig));
+ WH_TEST_ASSERT_RETURN(printfCtx.initialized == 1);
+ WH_TEST_ASSERT_RETURN(printfCtx.logIfNotDebug == 0);
+
+ /* With logIfNotDebug = 0 and a non-debug build, entries are dropped */
+ WH_LOG(&logCtx, WH_LOG_LEVEL_INFO, "Dropped unless debug build");
+
+ /* Re-init with logIfNotDebug = 1 so entries are always printed */
+ memset(&logCtx, 0, sizeof(logCtx));
+ memset(&printfCtx, 0, sizeof(printfCtx));
+ printfCfg.logIfNotDebug = 1;
+ logConfig.config = &printfCfg;
+ WH_TEST_RETURN_ON_FAIL(wh_Log_Init(&logCtx, &logConfig));
+ WH_TEST_ASSERT_RETURN(printfCtx.logIfNotDebug == 1);
+
+ /* Exercise the print path for each known level */
+ WH_LOG(&logCtx, WH_LOG_LEVEL_INFO, "Printf info");
+ WH_LOG(&logCtx, WH_LOG_LEVEL_ERROR, "Printf error");
+ WH_LOG(&logCtx, WH_LOG_LEVEL_SECEVENT, "Printf secevent");
+
+ /* An out-of-range level exercises the level-to-string default */
+ entry.timestamp = 1;
+ entry.level = (whLogLevel)999;
+ entry.file = __FILE__;
+ entry.function = __func__;
+ entry.line = __LINE__;
+ entry.msg_len = 0;
+ WH_TEST_RETURN_ON_FAIL(wh_Log_AddEntry(&logCtx, &entry));
+
+ /* The printf backend implements no store, so the remaining frontend
+ * operations report NOTIMPL */
+ WH_TEST_ASSERT_RETURN(wh_Log_Cleanup(&logCtx) == WH_ERROR_NOTIMPL);
+ WH_TEST_ASSERT_RETURN(wh_Log_Export(&logCtx, NULL) == WH_ERROR_NOTIMPL);
+ WH_TEST_ASSERT_RETURN(
+ wh_Log_Iterate(&logCtx, iterateCallbackCount, &iterate_count) ==
+ WH_ERROR_NOTIMPL);
+ WH_TEST_ASSERT_RETURN(wh_Log_Clear(&logCtx) == WH_ERROR_NOTIMPL);
+
+ return WH_ERROR_OK;
+}
+
+/* Portable log test entry point (Misc group) */
+int whTest_Log(void* ctx)
+{
+ (void)ctx;
+
+ WH_TEST_PRINT("Testing log frontend API...\n");
+ WH_TEST_RETURN_ON_FAIL(whTest_LogFrontend());
+
+ WH_TEST_PRINT("Testing log macros...\n");
+ WH_TEST_RETURN_ON_FAIL(whTest_LogMacros());
+
+ WH_TEST_PRINT("Testing formatted log macros...\n");
+ WH_TEST_RETURN_ON_FAIL(whTest_LogFormattedMacros());
+
+ WH_TEST_PRINT("Testing mock log backend in generic harness...\n");
+ WH_TEST_RETURN_ON_FAIL(whTest_LogMock_Generic());
+
+ WH_TEST_PRINT("Testing ringbuf backend in generic harness...\n");
+ WH_TEST_RETURN_ON_FAIL(whTest_LogRingbuf_Generic());
+
+ WH_TEST_PRINT("Testing ring buffer backend...\n");
+ WH_TEST_RETURN_ON_FAIL(whTest_LogRingbuf());
+
+ WH_TEST_PRINT("Testing printf backend...\n");
+ WH_TEST_RETURN_ON_FAIL(whTest_LogPrintf());
+
+ return WH_ERROR_OK;
+}
+
+#endif /* WOLFHSM_CFG_LOGGING */
diff --git a/test-refactor/misc/wh_test_log_backend.h b/test-refactor/misc/wh_test_log_backend.h
new file mode 100644
index 000000000..9732face0
--- /dev/null
+++ b/test-refactor/misc/wh_test_log_backend.h
@@ -0,0 +1,59 @@
+/*
+ * 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/misc/wh_test_log_backend.h
+ *
+ * Backend-agnostic log harness shared by the portable Misc log tests
+ * (mock, ring buffer) and the POSIX port log tests (file backend). The
+ * caller supplies a backend callback table and context; the harness runs
+ * the same suite against it.
+ */
+
+#ifndef WH_TEST_LOG_BACKEND_H_
+#define WH_TEST_LOG_BACKEND_H_
+
+#include
+
+#include "wolfhsm/wh_log.h"
+
+/* Configuration describing a backend under test. */
+typedef struct {
+ const char* backend_name; /* Backend name for test output */
+ whLogCb* cb; /* Backend callback table */
+ void* config; /* Backend-specific config */
+ size_t config_size; /* Size of context structure */
+ void* backend_context; /* Pre-allocated backend context */
+
+ /* Capabilities */
+ int expected_capacity; /* Max entries (-1 = unlimited) */
+ int supports_concurrent; /* supports multithreaded use */
+
+ /* Optional hooks */
+ int (*setup)(void** context); /* Setup hook (optional) */
+ int (*teardown)(void* context); /* Teardown hook (optional) */
+ void* test_context; /* Context for setup/teardown */
+} whTestLogBackendTestConfig;
+
+/*
+ * Runs all generic test suites against the supplied backend.
+ * Returns WH_ERROR_OK on success, non-zero on failure.
+ */
+int whTest_LogBackend_RunAll(whTestLogBackendTestConfig* cfg);
+
+#endif /* WH_TEST_LOG_BACKEND_H_ */
diff --git a/test-refactor/posix/wh_test_log_posix.c b/test-refactor/posix/wh_test_log_posix.c
new file mode 100644
index 000000000..8812d76fe
--- /dev/null
+++ b/test-refactor/posix/wh_test_log_posix.c
@@ -0,0 +1,608 @@
+/*
+ * 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/posix/wh_test_log_posix.c
+ *
+ * POSIX-specific logging tests from legacy test/wh_test_log.c: the
+ * POSIX file backend (via the shared generic harness and directly),
+ * concurrent access from multiple threads, and a client/server log
+ * smoke test over the mem transport. These are invoked from the POSIX
+ * port main via whTestGroup_RunOne, not from the portable registry.
+ */
+
+#include "wolfhsm/wh_settings.h"
+
+#include
+#include
+#include
+
+#include "wh_test_common.h"
+#include "wh_test_list.h"
+
+#if defined(WOLFHSM_CFG_LOGGING)
+
+#include
+#include
+
+#include "wolfhsm/wh_error.h"
+#include "wolfhsm/wh_log.h"
+
+#include "port/posix/posix_log_file.h"
+
+#include "wh_test_log_backend.h"
+
+#if defined(WOLFHSM_CFG_ENABLE_CLIENT) && defined(WOLFHSM_CFG_ENABLE_SERVER)
+#include "wolfhsm/wh_client.h"
+#include "wolfhsm/wh_server.h"
+#include "wolfhsm/wh_comm.h"
+#include "wolfhsm/wh_transport_mem.h"
+#include "wolfhsm/wh_nvm.h"
+#include "wolfhsm/wh_nvm_flash.h"
+#include "wolfhsm/wh_flash_ramsim.h"
+#ifndef WOLFHSM_CFG_NO_CRYPTO
+#include "wolfhsm/wh_server_crypto.h"
+#endif
+#endif /* WOLFHSM_CFG_ENABLE_CLIENT && WOLFHSM_CFG_ENABLE_SERVER */
+
+
+/* Helper for iterate callback - counts entries */
+static int _logIterateCount(void* arg, const whLogEntry* entry)
+{
+ int* count = (int*)arg;
+ (void)entry;
+ (*count)++;
+ return 0;
+}
+
+
+/* POSIX file backend in the generic harness */
+int whTest_LogPosixFile_Generic(void* ctx)
+{
+ posixLogFileContext posixCtx;
+ posixLogFileConfig posixCfg;
+ whTestLogBackendTestConfig testCfg;
+ whLogCb posixCb;
+ const char* test_log_file = "/tmp/wolfhsm_test_generic.log";
+
+ (void)ctx;
+
+ /* Initialize callback table (C90 compatible) */
+ memset(&posixCtx, 0, sizeof(posixCtx));
+ memset(&posixCb, 0, sizeof(posixCb));
+ posixCb.Init = posixLogFile_Init;
+ posixCb.Cleanup = posixLogFile_Cleanup;
+ posixCb.AddEntry = posixLogFile_AddEntry;
+ posixCb.Export = posixLogFile_Export;
+ posixCb.Iterate = posixLogFile_Iterate;
+ posixCb.Clear = posixLogFile_Clear;
+
+ /* Remove any existing test log file */
+ unlink(test_log_file);
+
+ posixCfg.filename = test_log_file;
+
+ testCfg.backend_name = "PosixFile";
+ testCfg.cb = &posixCb;
+ testCfg.config = &posixCfg;
+ testCfg.config_size = sizeof(posixLogFileContext);
+ testCfg.backend_context = &posixCtx;
+ testCfg.expected_capacity = -1; /* Unlimited */
+ testCfg.supports_concurrent = 1;
+ testCfg.setup = NULL;
+ testCfg.teardown = NULL;
+ testCfg.test_context = NULL;
+
+ return whTest_LogBackend_RunAll(&testCfg);
+}
+
+
+/* POSIX file backend tests */
+int whTest_LogPosixFile(void* ctx)
+{
+ whLogContext logCtx;
+ posixLogFileContext posixCtx;
+ posixLogFileConfig posixCfg;
+ whLogConfig logConfig;
+ whLogCb posixCb = POSIX_LOG_FILE_CB;
+ const char* test_log_file = "/tmp/wolfhsm_test_log.txt";
+ int export_count;
+ int iterate_count;
+ FILE* export_fp;
+ char line[2048];
+
+ (void)ctx;
+
+ /* Remove any existing test log file */
+ unlink(test_log_file);
+
+ /* Test: Create log file, add entries, verify file exists */
+ memset(&logCtx, 0, sizeof(logCtx));
+ memset(&posixCtx, 0, sizeof(posixCtx));
+ posixCfg.filename = test_log_file;
+
+ logConfig.cb = &posixCb;
+ logConfig.context = &posixCtx;
+ logConfig.config = &posixCfg;
+
+ WH_TEST_RETURN_ON_FAIL(wh_Log_Init(&logCtx, &logConfig));
+ WH_TEST_ASSERT_RETURN(posixCtx.initialized == 1);
+ WH_TEST_ASSERT_RETURN(posixCtx.fd >= 0);
+
+ /* Add some log entries */
+ WH_LOG(&logCtx, WH_LOG_LEVEL_INFO, "First info message");
+ WH_LOG(&logCtx, WH_LOG_LEVEL_ERROR, "First error message");
+ WH_LOG(&logCtx, WH_LOG_LEVEL_SECEVENT, "First security event");
+
+ /* Test: Export reads back all entries correctly */
+ /* For POSIX backend, export to a temp file and count lines */
+ export_fp = tmpfile();
+ WH_TEST_ASSERT_RETURN(export_fp != NULL);
+ WH_TEST_RETURN_ON_FAIL(wh_Log_Export(&logCtx, export_fp));
+ fflush(export_fp);
+ rewind(export_fp);
+
+ export_count = 0;
+ while (fgets(line, sizeof(line), export_fp) != NULL) {
+ export_count++;
+ }
+ fclose(export_fp);
+ WH_TEST_ASSERT_RETURN(export_count == 3);
+
+ /* Test: Append preserves existing entries */
+ WH_LOG(&logCtx, WH_LOG_LEVEL_INFO, "Second info message");
+ export_fp = tmpfile();
+ WH_TEST_ASSERT_RETURN(export_fp != NULL);
+ WH_TEST_RETURN_ON_FAIL(wh_Log_Export(&logCtx, export_fp));
+ fflush(export_fp);
+ rewind(export_fp);
+
+ export_count = 0;
+ while (fgets(line, sizeof(line), export_fp) != NULL) {
+ export_count++;
+ }
+ fclose(export_fp);
+ WH_TEST_ASSERT_RETURN(export_count == 4);
+
+ /* Test: Clear truncates file */
+ WH_TEST_RETURN_ON_FAIL(wh_Log_Clear(&logCtx));
+ export_fp = tmpfile();
+ WH_TEST_ASSERT_RETURN(export_fp != NULL);
+ WH_TEST_RETURN_ON_FAIL(wh_Log_Export(&logCtx, export_fp));
+ fflush(export_fp);
+ rewind(export_fp);
+
+ export_count = 0;
+ while (fgets(line, sizeof(line), export_fp) != NULL) {
+ export_count++;
+ }
+ fclose(export_fp);
+ WH_TEST_ASSERT_RETURN(export_count == 0);
+
+ /* Test: Iterate functionality with parsing */
+ /* Add entries for iterate test */
+ WH_LOG(&logCtx, WH_LOG_LEVEL_INFO, "Iterate message 1");
+ WH_LOG(&logCtx, WH_LOG_LEVEL_ERROR, "Iterate message 2");
+ WH_LOG(&logCtx, WH_LOG_LEVEL_SECEVENT, "Iterate message 3");
+
+ /* Count entries via iteration */
+ iterate_count = 0;
+ WH_TEST_RETURN_ON_FAIL(
+ wh_Log_Iterate(&logCtx, _logIterateCount, &iterate_count));
+ WH_TEST_ASSERT_RETURN(iterate_count == 3);
+
+ /* Cleanup */
+ WH_TEST_RETURN_ON_FAIL(wh_Log_Cleanup(&logCtx));
+
+ /* Remove test log file */
+ unlink(test_log_file);
+
+ return 0;
+}
+
+
+/* Thread function for concurrent access test */
+typedef struct {
+ whLogContext* ctx;
+ int thread_id;
+ int iterations;
+} thread_test_args;
+
+static void* threadTestFunc(void* arg)
+{
+ thread_test_args* args = (thread_test_args*)arg;
+ int i;
+
+ for (i = 0; i < args->iterations; i++) {
+ WH_LOG_F(args->ctx, WH_LOG_LEVEL_INFO, "Thread %d iteration %d",
+ args->thread_id, i);
+ }
+
+ return (void*)0;
+}
+
+int whTest_LogPosixFileConcurrent(void* ctx)
+{
+ whLogContext logCtx;
+ posixLogFileContext posixCtx;
+ posixLogFileConfig posixCfg;
+ whLogConfig logConfig;
+ whLogCb posixCb = POSIX_LOG_FILE_CB;
+ const char* test_log_file = "/tmp/wolfhsm_test_log_concurrent.txt";
+ int export_count;
+ const int NUM_THREADS = 4;
+ const int ITERATIONS_PER_THREAD = 10;
+ pthread_t threads[4];
+ thread_test_args args[4];
+ int i;
+ FILE* verify_fp;
+ char line[2048];
+
+ (void)ctx;
+
+ /* Remove any existing test log file */
+ unlink(test_log_file);
+
+ /* Setup */
+ memset(&logCtx, 0, sizeof(logCtx));
+ memset(&posixCtx, 0, sizeof(posixCtx));
+ posixCfg.filename = test_log_file;
+
+ logConfig.cb = &posixCb;
+ logConfig.context = &posixCtx;
+ logConfig.config = &posixCfg;
+
+ WH_TEST_RETURN_ON_FAIL(wh_Log_Init(&logCtx, &logConfig));
+
+ /* Test: Concurrent access from multiple threads */
+ for (i = 0; i < NUM_THREADS; i++) {
+ args[i].ctx = &logCtx;
+ args[i].thread_id = i;
+ args[i].iterations = ITERATIONS_PER_THREAD;
+
+ if (pthread_create(&threads[i], NULL, threadTestFunc, &args[i]) != 0) {
+ WH_ERROR_PRINT("Failed to create thread %d\n", i);
+ return WH_TEST_FAIL;
+ }
+ }
+
+ /* Wait for all threads */
+ for (i = 0; i < NUM_THREADS; i++) {
+ void* result;
+ pthread_join(threads[i], &result);
+ if (result != (void*)0) {
+ WH_ERROR_PRINT("Thread %d failed\n", i);
+ return WH_TEST_FAIL;
+ }
+ }
+
+ /* Verify all entries were written */
+ /* For POSIX backend, export to a temp file and count lines */
+ verify_fp = tmpfile();
+ WH_TEST_ASSERT_RETURN(verify_fp != NULL);
+ WH_TEST_RETURN_ON_FAIL(wh_Log_Export(&logCtx, verify_fp));
+ fflush(verify_fp);
+ rewind(verify_fp);
+
+ /* Count lines in exported file */
+ export_count = 0;
+ while (fgets(line, sizeof(line), verify_fp) != NULL) {
+ export_count++;
+ }
+ fclose(verify_fp);
+ WH_TEST_ASSERT_RETURN(export_count == NUM_THREADS * ITERATIONS_PER_THREAD);
+
+ /* Cleanup */
+ WH_TEST_RETURN_ON_FAIL(wh_Log_Cleanup(&logCtx));
+
+ /* Remove test log file */
+ unlink(test_log_file);
+
+ return 0;
+}
+
+
+#if defined(WOLFHSM_CFG_ENABLE_CLIENT) && defined(WOLFHSM_CFG_ENABLE_SERVER)
+
+#define WH_LOG_TEST_FLASH_RAM_SIZE (1024 * 1024)
+#define WH_LOG_TEST_FLASH_SECTOR_SIZE (128 * 1024)
+#define WH_LOG_TEST_FLASH_PAGE_SIZE (8)
+#define WH_LOG_TEST_SERVER_LOG_FILE "/tmp/wh_log_clientserver_posix.txt"
+
+enum {
+ WH_LOG_TEST_BUFFER_SIZE = sizeof(whTransportMemCsr) + sizeof(whCommHeader) +
+ WOLFHSM_CFG_COMM_DATA_LEN,
+};
+
+static int _clientServerLogSmokeTest(whClientContext* client)
+{
+ FILE* log_file = NULL;
+ size_t entry_count = 0;
+ char line[1024];
+ size_t expected_entries = 3;
+
+#ifdef WOLFHSM_CFG_ENABLE_AUTHENTICATION
+ /* When authentication is compiled in but no auth context is configured,
+ * server init emits an extra SECEVENT warning after the startup INFO log */
+ expected_entries++;
+#endif
+
+ /* Connect to the server, which should trigger an info log entry */
+ WH_TEST_ASSERT(WH_ERROR_OK == wh_Client_CommInit(client, NULL, NULL));
+
+ /* Disconnect the server, which should trigger an info log entry */
+ WH_TEST_RETURN_ON_FAIL(wh_Client_CommClose(client));
+
+ /* Now read the log file and verify that the log entries are present */
+ log_file = fopen(WH_LOG_TEST_SERVER_LOG_FILE, "r");
+ WH_TEST_ASSERT(log_file != NULL);
+
+ while (fgets(line, sizeof(line), log_file) != NULL) {
+ entry_count++;
+ WH_TEST_PRINT("Log entry: %s", line);
+
+ /* First log entry should be startup INFO log */
+ if (entry_count == 1) {
+ WH_TEST_ASSERT(strstr(line, "INFO") != NULL);
+ }
+#ifdef WOLFHSM_CFG_ENABLE_AUTHENTICATION
+ else if (entry_count == 2) {
+ /* Auth enabled with no auth context triggers a SECEVENT */
+ WH_TEST_ASSERT(strstr(line, "SECEVENT") != NULL);
+ }
+ else if (entry_count == 3) {
+ WH_TEST_ASSERT(strstr(line, "INFO") != NULL);
+ }
+ else if (entry_count == 4) {
+ WH_TEST_ASSERT(strstr(line, "INFO") != NULL);
+ }
+#else
+ else if (entry_count == 2) {
+ /* Second log entry should the connect INFO log */
+ WH_TEST_ASSERT(strstr(line, "INFO") != NULL);
+ }
+ else if (entry_count == 3) {
+ /* Third log entry should be another INFO from comm close */
+ WH_TEST_ASSERT(strstr(line, "INFO") != NULL);
+ }
+#endif
+ else {
+ break;
+ }
+ }
+ fclose(log_file);
+
+ /* Ensure we have at least the number of expected log entries */
+ WH_TEST_ASSERT(entry_count >= expected_entries);
+
+ return WH_ERROR_OK;
+}
+
+static void* _whLogClientTask(void* cfg)
+{
+ whClientConfig* client_cfg = (whClientConfig*)cfg;
+ whClientContext client[1] = {{0}};
+
+ if (client_cfg == NULL) {
+ return NULL;
+ }
+
+ WH_TEST_ASSERT(WH_ERROR_OK == wh_Client_Init(client, client_cfg));
+
+ WH_TEST_ASSERT(WH_ERROR_OK == _clientServerLogSmokeTest(client));
+
+ WH_TEST_ASSERT(WH_ERROR_OK == wh_Client_Cleanup(client));
+ return NULL;
+}
+
+static void* _whLogServerTask(void* cfg)
+{
+ whServerConfig* server_cfg = (whServerConfig*)cfg;
+ whServerContext server[1] = {{0}};
+ whCommConnected connected = WH_COMM_CONNECTED;
+ int ret;
+
+ if (server_cfg == NULL) {
+ return NULL;
+ }
+
+ WH_TEST_ASSERT(WH_ERROR_OK == wh_Server_Init(server, server_cfg));
+ WH_TEST_ASSERT(WH_ERROR_OK == wh_Server_SetConnected(server, connected));
+
+ while (connected == WH_COMM_CONNECTED) {
+ ret = wh_Server_HandleRequestMessage(server);
+ if ((ret != WH_ERROR_NOTREADY) && (ret != WH_ERROR_OK)) {
+ WH_ERROR_PRINT(
+ "[server] Failed to wh_Server_HandleRequestMessage ret=%d\n",
+ ret);
+ break;
+ }
+ wh_Server_GetConnected(server, &connected);
+ }
+
+ WH_TEST_ASSERT(0 == wh_Server_Cleanup(server));
+ return NULL;
+}
+
+static void _whLogClientServerThreadTest(whClientConfig* c_conf,
+ whServerConfig* s_conf)
+{
+ pthread_t client_thread;
+ pthread_t server_thread;
+ void* retval = NULL;
+ int rc;
+
+ rc = pthread_create(&server_thread, NULL, _whLogServerTask, s_conf);
+ WH_TEST_ASSERT(rc == 0);
+
+ rc = pthread_create(&client_thread, NULL, _whLogClientTask, c_conf);
+ if (rc != 0) {
+ pthread_cancel(server_thread);
+ pthread_join(server_thread, &retval);
+ WH_TEST_ASSERT(rc == 0);
+ return;
+ }
+
+ pthread_join(client_thread, &retval);
+ pthread_join(server_thread, &retval);
+}
+
+int whTest_LogClientServerMemTransport(void* ctx)
+{
+ uint8_t req[WH_LOG_TEST_BUFFER_SIZE] = {0};
+ uint8_t resp[WH_LOG_TEST_BUFFER_SIZE] = {0};
+ whTransportMemConfig tmcf[1] = {{
+ .req = (whTransportMemCsr*)req,
+ .req_size = sizeof(req),
+ .resp = (whTransportMemCsr*)resp,
+ .resp_size = sizeof(resp),
+ }};
+
+ /* Client configuration */
+ whTransportClientCb tccb[1] = {WH_TRANSPORT_MEM_CLIENT_CB};
+ whTransportMemClientContext tmcc[1] = {{0}};
+ whCommClientConfig cc_conf[1] = {{
+ .transport_cb = tccb,
+ .transport_context = (void*)tmcc,
+ .transport_config = (void*)tmcf,
+ .client_id = WH_TEST_DEFAULT_CLIENT_ID,
+ }};
+
+#ifdef WOLFHSM_CFG_DMA
+ whClientDmaConfig clientDmaConfig = {0};
+#endif
+
+ whClientConfig c_conf[1] = {{
+ .comm = cc_conf,
+#ifdef WOLFHSM_CFG_DMA
+ .dmaConfig = &clientDmaConfig,
+#endif
+ }};
+
+ /* Server configuration */
+ whTransportServerCb tscb[1] = {WH_TRANSPORT_MEM_SERVER_CB};
+ whTransportMemServerContext tmsc[1] = {{0}};
+ whCommServerConfig cs_conf[1] = {{
+ .transport_cb = tscb,
+ .transport_context = (void*)tmsc,
+ .transport_config = (void*)tmcf,
+ .server_id = 124,
+ }};
+
+ uint8_t memory[WH_LOG_TEST_FLASH_RAM_SIZE] = {0};
+ whFlashRamsimCtx fc[1] = {{0}};
+ whFlashRamsimCfg fc_conf[1] = {{
+ .size = WH_LOG_TEST_FLASH_RAM_SIZE,
+ .sectorSize = WH_LOG_TEST_FLASH_SECTOR_SIZE,
+ .pageSize = WH_LOG_TEST_FLASH_PAGE_SIZE,
+ .erasedByte = ~(uint8_t)0,
+ .memory = memory,
+ }};
+ const whFlashCb fcb[1] = {WH_FLASH_RAMSIM_CB};
+
+ whTestNvmBackendUnion nvm_setup;
+ whNvmConfig n_conf[1] = {0};
+ whNvmContext nvm[1] = {{0}};
+
+#ifndef WOLFHSM_CFG_NO_CRYPTO
+ whServerCryptoContext crypto[1] = {0};
+#endif
+
+ posixLogFileContext posixCtx[1] = {0};
+ posixLogFileConfig posixCfg[1] = {{
+ .filename = WH_LOG_TEST_SERVER_LOG_FILE,
+ }};
+ whLogCb posixCb = POSIX_LOG_FILE_CB;
+ whLogConfig logConfig[1] = {{
+ .cb = &posixCb,
+ .context = posixCtx,
+ .config = posixCfg,
+ }};
+
+ whServerConfig s_conf[1] = {{
+ .comm_config = cs_conf,
+ .nvm = nvm,
+#ifndef WOLFHSM_CFG_NO_CRYPTO
+ .crypto = crypto,
+ .devId = INVALID_DEVID,
+#endif
+ .logConfig = logConfig,
+ }};
+
+ (void)ctx;
+
+ WH_TEST_RETURN_ON_FAIL(whTest_NvmCfgBackend(
+ WH_NVM_TEST_BACKEND_FLASH, &nvm_setup, n_conf, fc_conf, fc, fcb));
+
+ unlink(WH_LOG_TEST_SERVER_LOG_FILE);
+
+ WH_TEST_RETURN_ON_FAIL(wh_Nvm_Init(nvm, n_conf));
+
+#ifndef WOLFHSM_CFG_NO_CRYPTO
+ WH_TEST_RETURN_ON_FAIL(wolfCrypt_Init());
+ WH_TEST_RETURN_ON_FAIL(wc_InitRng_ex(crypto->rng, NULL, INVALID_DEVID));
+#endif
+
+ _whLogClientServerThreadTest(c_conf, s_conf);
+
+#ifndef WOLFHSM_CFG_NO_CRYPTO
+ wc_FreeRng(crypto->rng);
+ wolfCrypt_Cleanup();
+#endif
+ wh_Nvm_Cleanup(nvm);
+
+ return WH_ERROR_OK;
+}
+
+#else /* WOLFHSM_CFG_ENABLE_CLIENT && WOLFHSM_CFG_ENABLE_SERVER */
+
+int whTest_LogClientServerMemTransport(void* ctx)
+{
+ (void)ctx;
+ return WH_TEST_SKIPPED;
+}
+
+#endif /* WOLFHSM_CFG_ENABLE_CLIENT && WOLFHSM_CFG_ENABLE_SERVER */
+
+#else /* WOLFHSM_CFG_LOGGING */
+
+int whTest_LogPosixFile_Generic(void* ctx)
+{
+ (void)ctx;
+ return WH_TEST_SKIPPED;
+}
+
+int whTest_LogPosixFile(void* ctx)
+{
+ (void)ctx;
+ return WH_TEST_SKIPPED;
+}
+
+int whTest_LogPosixFileConcurrent(void* ctx)
+{
+ (void)ctx;
+ return WH_TEST_SKIPPED;
+}
+
+int whTest_LogClientServerMemTransport(void* ctx)
+{
+ (void)ctx;
+ return WH_TEST_SKIPPED;
+}
+
+#endif /* WOLFHSM_CFG_LOGGING */
diff --git a/test-refactor/posix/wh_test_posix_main.c b/test-refactor/posix/wh_test_posix_main.c
index 756ed6041..d36f6fcd2 100644
--- a/test-refactor/posix/wh_test_posix_main.c
+++ b/test-refactor/posix/wh_test_posix_main.c
@@ -76,6 +76,16 @@ int whTest_NvmAddOverwriteDestroy(void* ctx);
int whTest_NvmFlashLog(void* ctx);
int whTest_NvmRecovery(void* ctx);
+/* POSIX-specific logging tests. The portable log suite (frontend,
+ * macros, ring buffer, mock/ringbuf harness) runs in the Misc group;
+ * these exercise the POSIX file backend, concurrent writers, and a
+ * client/server log smoke test that need pthreads and the host file
+ * system, so they run from the port directly. */
+int whTest_LogPosixFile_Generic(void* ctx);
+int whTest_LogPosixFile(void* ctx);
+int whTest_LogPosixFileConcurrent(void* ctx);
+int whTest_LogClientServerMemTransport(void* ctx);
+
/*
* Port-owned contexts. The thread functions fill these in and
* hand them to the group functions, paralleling the firmware
@@ -284,6 +294,29 @@ int main(void)
if (rc != 0 && rc != WH_TEST_SKIPPED && miscRc == 0) {
miscRc = rc;
}
+ /* POSIX-specific log backend tests. Self-contained (each owns
+ * its own contexts and threads), so run inline before the port
+ * spins up its own server thread. */
+ rc = whTestGroup_RunOne("whTest_LogPosixFile_Generic",
+ whTest_LogPosixFile_Generic, NULL);
+ if (rc != 0 && rc != WH_TEST_SKIPPED && miscRc == 0) {
+ miscRc = rc;
+ }
+ rc = whTestGroup_RunOne("whTest_LogPosixFile",
+ whTest_LogPosixFile, NULL);
+ if (rc != 0 && rc != WH_TEST_SKIPPED && miscRc == 0) {
+ miscRc = rc;
+ }
+ rc = whTestGroup_RunOne("whTest_LogPosixFileConcurrent",
+ whTest_LogPosixFileConcurrent, NULL);
+ if (rc != 0 && rc != WH_TEST_SKIPPED && miscRc == 0) {
+ miscRc = rc;
+ }
+ rc = whTestGroup_RunOne("whTest_LogClientServerMemTransport",
+ whTest_LogClientServerMemTransport, NULL);
+ if (rc != 0 && rc != WH_TEST_SKIPPED && miscRc == 0) {
+ miscRc = rc;
+ }
}
rc = pthread_create(&sthread, NULL, _serverThread, NULL);
diff --git a/test-refactor/wh_test_list.c b/test-refactor/wh_test_list.c
index 683555463..fdb70aee5 100644
--- a/test-refactor/wh_test_list.c
+++ b/test-refactor/wh_test_list.c
@@ -44,7 +44,11 @@ WH_TEST_DECL(whTest_ClientDevId);
WH_TEST_DECL(whTest_Comm);
WH_TEST_DECL(whTest_Dma);
WH_TEST_DECL(whTest_KeystoreReqSize);
+<<<<<<< HEAD
WH_TEST_DECL(whTest_MultiClient);
+=======
+WH_TEST_DECL(whTest_Log);
+>>>>>>> 8b3eb9b (Add refactored log tests, coverage identical)
WH_TEST_DECL(whTest_CertVerify);
WH_TEST_DECL(whTest_NvmOptional);
WH_TEST_DECL(whTest_ClientCerts);
@@ -86,11 +90,12 @@ WH_TEST_DECL(whTest_AuthSetCredentials);
WH_TEST_DECL(whTest_AuthRequestAuthorization);
const whTestCase whTestsMisc[] = {
- { "whTest_ClientDevId", whTest_ClientDevId },
- { "whTest_Comm", whTest_Comm },
- { "whTest_Dma", whTest_Dma },
- { "whTest_KeystoreReqSize", whTest_KeystoreReqSize },
- { "whTest_MultiClient", whTest_MultiClient },
+ { "whTest_ClientDevId", whTest_ClientDevId},
+ { "whTest_Comm", whTest_Comm},
+ { "whTest_Dma", whTest_Dma},
+ { "whTest_KeystoreReqSize", whTest_KeystoreReqSize},
+ { "whTest_MultiClient", whTest_MultiClient },
+ { "whTest_Log", whTest_Log},
};
const size_t whTestsMiscCount = ARRAY_SIZE(whTestsMisc);