diff --git a/Bdd/Targets/Common/BddTargetInteractive.c b/Bdd/Targets/Common/BddTargetInteractive.c index 57233283..bf6b48df 100644 --- a/Bdd/Targets/Common/BddTargetInteractive.c +++ b/Bdd/Targets/Common/BddTargetInteractive.c @@ -62,13 +62,13 @@ static int ParseCount(const char* args) return (count > 0) ? count : 1; } -static void HandleSend(const char* args, const struct SolidSyslogMessage* message) +static void HandleSend(struct SolidSyslog* handle, const char* args, const struct SolidSyslogMessage* message) { int count = ParseCount(args); for (int i = 0; i < count; i++) { - SolidSyslog_Log(message); + SolidSyslog_Log(handle, message); } printf("Sent %d message%s\n", count, (count == 1) ? "" : "s"); @@ -95,6 +95,7 @@ static void HandleSet(const char* args, BddTargetInteractiveSetHandler onSet) } void BddTargetInteractive_Run( + struct SolidSyslog* handle, const struct SolidSyslogMessage* message, FILE* input, BddTargetInteractiveSwitchHandler onSwitch, @@ -121,7 +122,7 @@ void BddTargetInteractive_Run( const char* args = NULL; if (MatchCommand(line, "send", &args)) { - HandleSend(args, message); + HandleSend(handle, args, message); } else if (onSwitch != NULL && MatchCommand(line, "switch", &args)) { diff --git a/Bdd/Targets/Common/BddTargetInteractive.h b/Bdd/Targets/Common/BddTargetInteractive.h index f5c71af8..05be2a96 100644 --- a/Bdd/Targets/Common/BddTargetInteractive.h +++ b/Bdd/Targets/Common/BddTargetInteractive.h @@ -6,6 +6,7 @@ #include "ExternC.h" +struct SolidSyslog; struct SolidSyslogMessage; EXTERN_C_BEGIN @@ -14,6 +15,7 @@ EXTERN_C_BEGIN typedef bool (*BddTargetInteractiveSetHandler)(const char* name, const char* value); void BddTargetInteractive_Run( + struct SolidSyslog * handle, const struct SolidSyslogMessage* message, FILE* input, BddTargetInteractiveSwitchHandler onSwitch, diff --git a/Bdd/Targets/Common/BddTargetServiceThread.c b/Bdd/Targets/Common/BddTargetServiceThread.c index aefc8aba..f6a74f09 100644 --- a/Bdd/Targets/Common/BddTargetServiceThread.c +++ b/Bdd/Targets/Common/BddTargetServiceThread.c @@ -8,11 +8,11 @@ enum // cppcheck-suppress constParameter -- volatile bool written by another thread; const would be incorrect // NOLINTNEXTLINE(readability-non-const-parameter) -- volatile bool written by another thread; const would be incorrect -void BddTargetServiceThread_Run(volatile bool* shutdown, SolidSyslogSleepFunction sleep) +void BddTargetServiceThread_Run(struct SolidSyslog* handle, volatile bool* shutdown, SolidSyslogSleepFunction sleep) { while (!(*shutdown)) { - SolidSyslog_Service(); + SolidSyslog_Service(handle); sleep(IDLE_YIELD_MILLISECONDS); } } diff --git a/Bdd/Targets/Common/BddTargetServiceThread.h b/Bdd/Targets/Common/BddTargetServiceThread.h index 543155cf..132c2207 100644 --- a/Bdd/Targets/Common/BddTargetServiceThread.h +++ b/Bdd/Targets/Common/BddTargetServiceThread.h @@ -8,7 +8,13 @@ EXTERN_C_BEGIN - void BddTargetServiceThread_Run(volatile bool* shutdown, SolidSyslogSleepFunction sleep); + struct SolidSyslog; + + void BddTargetServiceThread_Run( + struct SolidSyslog * handle, + volatile bool* shutdown, + SolidSyslogSleepFunction sleep + ); EXTERN_C_END diff --git a/Bdd/Targets/FreeRtos/main.c b/Bdd/Targets/FreeRtos/main.c index 3c185df4..267ea5e9 100644 --- a/Bdd/Targets/FreeRtos/main.c +++ b/Bdd/Targets/FreeRtos/main.c @@ -172,6 +172,7 @@ static uint8_t bufferRing[SOLIDSYSLOG_CIRCULAR_BUFFER_RING_BYTES(BDD_TARGET_BUFF * for one Service() call per iteration; the rebuild path holds it across * Destroy → BlockStore_Create → Create. */ static struct SolidSyslogMutex* lifecycleMutex = NULL; +static struct SolidSyslog* solidSyslog = NULL; static volatile bool solidSyslogReady; /* Signals Service to self-delete BEFORE Teardown destroys the lifecycle * mutex. Without this, Service races against InteractiveTask: Teardown @@ -572,7 +573,8 @@ static bool RebuildWithFileStore(void) } solidSyslogReady = false; - SolidSyslog_Destroy(); + SolidSyslog_Destroy(solidSyslog); + solidSyslog = NULL; DestroyCurrentStore(); /* Build a fresh FatFs-backed BlockStore. With the volume mounted above, @@ -602,7 +604,7 @@ static bool RebuildWithFileStore(void) * sort order in target_driver.py guarantees `set no-sd` comes before * `set store file` so the value is final by the time we get here. */ solidSyslogConfig.SdCount = pendingNoSd ? 1U : (sizeof(sdList) / sizeof(sdList[0])); - SolidSyslog_Create(&solidSyslogConfig); + solidSyslog = SolidSyslog_Create(&solidSyslogConfig); solidSyslogReady = true; SolidSyslogMutex_Unlock(lifecycleMutex); return true; @@ -637,7 +639,8 @@ static void TeardownAll(void) SolidSyslogMutex_Lock(lifecycleMutex); solidSyslogTeardown = true; solidSyslogReady = false; - SolidSyslog_Destroy(); + SolidSyslog_Destroy(solidSyslog); + solidSyslog = NULL; SolidSyslogOriginSd_Destroy(originSd); SolidSyslogTimeQualitySd_Destroy(timeQualitySd); SolidSyslogMetaSd_Destroy(metaSd); @@ -866,10 +869,10 @@ static void InteractiveTask(void* argument) .SdCount = pendingNoSd ? 1U : (sizeof(sdList) / sizeof(sdList[0])), }; SolidSyslog_SetErrorHandler(ErrorHandler, NULL); - SolidSyslog_Create(&solidSyslogConfig); + solidSyslog = SolidSyslog_Create(&solidSyslogConfig); solidSyslogReady = true; - BddTargetInteractive_Run(&testMessage, stdin, BddTargetSwitchConfig_SetByName, OnSet); + BddTargetInteractive_Run(solidSyslog, &testMessage, stdin, BddTargetSwitchConfig_SetByName, OnSet); /* Peak stack usage report on `quit`. Captured into every BDD run's QEMU * console output so stack regressions surface in bdd-freertos-qemu logs @@ -924,7 +927,7 @@ static void ServiceTask(void* argument) } if (solidSyslogReady) { - SolidSyslog_Service(); + SolidSyslog_Service(solidSyslog); } SolidSyslogMutex_Unlock(lifecycleMutex); vTaskDelay(pdMS_TO_TICKS(1)); diff --git a/Bdd/Targets/Linux/main.c b/Bdd/Targets/Linux/main.c index 657be28a..fbbd8f6a 100644 --- a/Bdd/Targets/Linux/main.c +++ b/Bdd/Targets/Linux/main.c @@ -66,11 +66,12 @@ static void GetTimeQuality(struct SolidSyslogTimeQuality* timeQuality) } static volatile bool shutdown_flag; +static struct SolidSyslog* solidSyslog; static void* ServiceThreadEntry(void* arg) { volatile bool* shutdown = (volatile bool*) arg; - BddTargetServiceThread_Run(shutdown, SolidSyslogPosixSleep); + BddTargetServiceThread_Run(solidSyslog, shutdown, SolidSyslogPosixSleep); return NULL; } @@ -264,7 +265,7 @@ int main(int argc, char* argv[]) .Sd = sdList, .SdCount = sdCount, }; - SolidSyslog_Create(&config); + solidSyslog = SolidSyslog_Create(&config); shutdown_flag = false; haltExit = options.HaltExit; @@ -279,12 +280,12 @@ int main(int argc, char* argv[]) .Msg = options.Msg, }; - BddTargetInteractive_Run(&message, stdin, BddTargetSwitchConfig_SetByName, NULL); + BddTargetInteractive_Run(solidSyslog, &message, stdin, BddTargetSwitchConfig_SetByName, NULL); shutdown_flag = true; pthread_join(serviceThread, NULL); - SolidSyslog_Destroy(); + SolidSyslog_Destroy(solidSyslog); SolidSyslogOriginSd_Destroy(originSd); SolidSyslogTimeQualitySd_Destroy(timeQuality); SolidSyslogMetaSd_Destroy(metaSd); diff --git a/Bdd/Targets/Windows/BddTargetWindows.c b/Bdd/Targets/Windows/BddTargetWindows.c index fc9ef5fb..a9f5e21d 100644 --- a/Bdd/Targets/Windows/BddTargetWindows.c +++ b/Bdd/Targets/Windows/BddTargetWindows.c @@ -66,6 +66,7 @@ static const char* const THRESHOLD_MARKER_PATH = "Bdd/output/solidsyslog_thresho static uint8_t bufferRing[SOLIDSYSLOG_CIRCULAR_BUFFER_RING_BYTES(BDD_TARGET_BUFFER_MESSAGES)]; static volatile bool shutdownFlag; +static struct SolidSyslog* solidSyslog; /* Created in CreateSender, destroyed in DestroySender — held in file scope so teardown can reach them after the SwitchingSender wraps them all. */ @@ -83,7 +84,7 @@ static struct SolidSyslogBlockDevice* storeBlockDevice; // NOLINTNEXTLINE(readability-non-const-parameter) -- _beginthreadex thread-entry signature requires void* static unsigned __stdcall ServiceThreadEntry(void* arg) { - BddTargetServiceThread_Run((volatile bool*) arg, SolidSyslogWindowsSleep); + BddTargetServiceThread_Run(solidSyslog, (volatile bool*) arg, SolidSyslogWindowsSleep); return 0; } @@ -333,7 +334,7 @@ int BddTargetWindows_Run(int argc, char* argv[]) .Sd = sdList, .SdCount = sdCount, }; - SolidSyslog_Create(&config); + solidSyslog = SolidSyslog_Create(&config); shutdownFlag = false; HANDLE serviceThread = (HANDLE) _beginthreadex(NULL, 0, ServiceThreadEntry, (void*) &shutdownFlag, 0, NULL); @@ -345,13 +346,13 @@ int BddTargetWindows_Run(int argc, char* argv[]) .Msg = options.Msg, }; - BddTargetInteractive_Run(&message, stdin, BddTargetSwitchConfig_SetByName, NULL); + BddTargetInteractive_Run(solidSyslog, &message, stdin, BddTargetSwitchConfig_SetByName, NULL); shutdownFlag = true; WaitForSingleObject(serviceThread, INFINITE); CloseHandle(serviceThread); - SolidSyslog_Destroy(); + SolidSyslog_Destroy(solidSyslog); SolidSyslogOriginSd_Destroy(originSd); SolidSyslogTimeQualitySd_Destroy(timeQuality); SolidSyslogMetaSd_Destroy(metaSd); diff --git a/CLAUDE.md b/CLAUDE.md index a2fadea3..bfc753bb 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -310,8 +310,8 @@ live under `Core/Interface/`; platform-specific helpers (the `SolidSyslogPosix*` | Header | Audience | Provides | |---|---|---| -| `SolidSyslog.h` | Application code that logs events | `SolidSyslogMessage`, `SolidSyslog_Log`, `SolidSyslog_Service` | -| `SolidSyslogConfig.h` | System setup code | `SolidSyslogConfig`, `SolidSyslog_Create`, `SolidSyslog_Destroy` | +| `SolidSyslog.h` | Application code that logs events | `SolidSyslogMessage`, `SolidSyslog_Log(handle, message)`, `SolidSyslog_Service(handle)`. `struct SolidSyslog` opaque. | +| `SolidSyslogConfig.h` | System setup code | `SolidSyslogConfig`, `SolidSyslog_Create(config) → handle`, `SolidSyslog_Destroy(handle)`. Instance struct lives in a library-internal static pool (E11; tunable `SOLIDSYSLOG_POOL_SIZE`, default `1U`). Pool-exhaustion fallback is a TU-private `NullInstance` wired to `SolidSyslogNull{Buffer,Sender,Store}_Get()` siblings — `_Log`/`_Service` against it dispatch through Null vtables and silently drop. | | `SolidSyslogError.h` | Any code installing a handler to react to library-internal errors (NULL guards, send failures); also the library-internal call site for emitting them | `SolidSyslogErrorHandler` typedef, `SolidSyslog_SetErrorHandler(handler, context)`, `SolidSyslog_Error(severity, message)`. Default handler is a silent no-op; setting `handler = NULL` restores the default. Single global slot — intended for setup-time configuration, not synchronised with concurrent `Error` calls. | | `SolidSyslogConfigLock.h` | Setup code on systems where library-internal config-time critical sections (E11 pool `_Create`/`_Destroy` slot walks) may race across tasks or cores. Single-task setup needs nothing — defaults are no-ops. | `SolidSyslogConfigLockFunction` typedef (zero-arg `void(void)`), `SolidSyslog_SetConfigLock(lockFn, unlockFn)`, `SolidSyslog_LockConfig()`, `SolidSyslog_UnlockConfig()`. Pair API: both handlers installed together. `NULL` on either side restores that side's no-op default. Single global slot — intended for setup-time configuration, not synchronised with concurrent installs. Integrators wire `taskENTER_CRITICAL`/`taskEXIT_CRITICAL` (FreeRTOS), `pthread_mutex_lock`/`unlock` on a static `pthread_mutex_t` (POSIX), `EnterCriticalSection`/`LeaveCriticalSection` on a static `CRITICAL_SECTION` (Windows), or a spinlock pair. This is the only synchronisation primitive available to the Mutex and AtomicCounter pools for their own pool walks — chicken-and-egg eliminates their own injectables. | | `SolidSyslogStringFunction.h` | Any code needing the string-callback typedef | `SolidSyslogStringFunction` (callback writes into a `SolidSyslogFormatter*`) — used by `SolidSyslogConfig` (hostname/appName/processId), `SolidSyslogMetaSdConfig` (language), `SolidSyslogOriginSdConfig` | diff --git a/Core/Interface/SolidSyslog.h b/Core/Interface/SolidSyslog.h index 50eba0d7..51753079 100644 --- a/Core/Interface/SolidSyslog.h +++ b/Core/Interface/SolidSyslog.h @@ -6,6 +6,8 @@ EXTERN_C_BEGIN + struct SolidSyslog; + struct SolidSyslogMessage { enum SolidSyslogFacility Facility; @@ -14,8 +16,8 @@ EXTERN_C_BEGIN const char* Msg; }; - void SolidSyslog_Log(const struct SolidSyslogMessage* message); - void SolidSyslog_Service(void); + void SolidSyslog_Log(struct SolidSyslog * handle, const struct SolidSyslogMessage* message); + void SolidSyslog_Service(struct SolidSyslog * handle); EXTERN_C_END diff --git a/Core/Interface/SolidSyslogConfig.h b/Core/Interface/SolidSyslogConfig.h index f69b4959..a9cd3ffa 100644 --- a/Core/Interface/SolidSyslogConfig.h +++ b/Core/Interface/SolidSyslogConfig.h @@ -9,6 +9,7 @@ EXTERN_C_BEGIN + struct SolidSyslog; struct SolidSyslogBuffer; struct SolidSyslogFormatter; struct SolidSyslogSender; @@ -28,8 +29,8 @@ EXTERN_C_BEGIN size_t SdCount; }; - void SolidSyslog_Create(const struct SolidSyslogConfig* config); - void SolidSyslog_Destroy(void); + struct SolidSyslog* SolidSyslog_Create(const struct SolidSyslogConfig* config); + void SolidSyslog_Destroy(struct SolidSyslog * handle); EXTERN_C_END diff --git a/Core/Interface/SolidSyslogTunablesDefaults.h b/Core/Interface/SolidSyslogTunablesDefaults.h index 4b6c7f6a..0b807764 100644 --- a/Core/Interface/SolidSyslogTunablesDefaults.h +++ b/Core/Interface/SolidSyslogTunablesDefaults.h @@ -28,6 +28,26 @@ #error "SOLIDSYSLOG_MAX_MESSAGE_SIZE must be >= 64" #endif +/* + * Number of SolidSyslog instances the library's internal static pool + * can simultaneously hold. Each instance is a small bookkeeping struct + * (collaborator pointers + SD array pointer + count). + * + * Most integrators only ever create one SolidSyslog per process; default 1. + * Bump via SOLIDSYSLOG_USER_TUNABLES_FILE if the integrator needs multiple + * concurrent SolidSyslog instances. + * + * Floor: 1. Sub-floor values rejected at compile time. + */ +#ifndef SOLIDSYSLOG_POOL_SIZE +/* NOLINTNEXTLINE(cppcoreguidelines-macro-usage) -- macro form required for preprocessor visibility (floor #if) and C array-size const-expr. */ +#define SOLIDSYSLOG_POOL_SIZE 1U +#endif + +#if SOLIDSYSLOG_POOL_SIZE < 1 +#error "SOLIDSYSLOG_POOL_SIZE must be >= 1" +#endif + /* * Number of SolidSyslogCircularBuffer instances the library's internal * static pool can simultaneously hold. Each instance is a small diff --git a/Core/Source/CMakeLists.txt b/Core/Source/CMakeLists.txt index 485bd7fa..d82ba9d4 100644 --- a/Core/Source/CMakeLists.txt +++ b/Core/Source/CMakeLists.txt @@ -1,5 +1,6 @@ set(SOURCES SolidSyslog.c + SolidSyslogStatic.c SolidSyslogError.c SolidSyslogConfigLock.c SolidSyslogSwitchingSender.c diff --git a/Core/Source/SolidSyslog.c b/Core/Source/SolidSyslog.c index 25654148..ccfeddf0 100644 --- a/Core/Source/SolidSyslog.c +++ b/Core/Source/SolidSyslog.c @@ -5,21 +5,25 @@ #include #include "SolidSyslogBuffer.h" -#include "SolidSyslogBufferDefinition.h" #include "SolidSyslogConfig.h" #include "SolidSyslogError.h" #include "SolidSyslogErrorMessages.h" #include "SolidSyslogFormatter.h" +#include "SolidSyslogNullBuffer.h" +#include "SolidSyslogNullSender.h" +#include "SolidSyslogNullStore.h" +#include "SolidSyslogPrivate.h" #include "SolidSyslogSender.h" -#include "SolidSyslogSenderDefinition.h" #include "SolidSyslogStore.h" -#include "SolidSyslogStoreDefinition.h" #include "SolidSyslogStringFunction.h" #include "SolidSyslogStructuredData.h" #include "SolidSyslogTimestamp.h" #include "SolidSyslogTunables.h" +struct SolidSyslogBuffer; struct SolidSyslogFormatter; +struct SolidSyslogSender; +struct SolidSyslogStore; struct SolidSyslogStructuredData; enum @@ -30,72 +34,21 @@ enum SOLIDSYSLOG_MAX_PROCESS_ID_SIZE = 129 }; -struct SolidSyslog -{ - struct SolidSyslogBuffer* Buffer; - struct SolidSyslogSender* Sender; - SolidSyslogClockFunction Clock; - SolidSyslogStringFunction GetHostname; - SolidSyslogStringFunction GetAppName; - SolidSyslogStringFunction GetProcessId; - struct SolidSyslogStore* Store; - struct SolidSyslogStructuredData** Sd; - size_t SdCount; -}; - -/* Forward declarations for the Nil "class" defined at the bottom of the file. - * The Nil objects act as default collaborators that make the singleton - * crash-safe pre-Create and post-Destroy; the file-static instance below and - * the NilInstance template both reference these by address. The Nil - * implementations live below SolidSyslog so this file reads as two cooperating - * "classes" — SolidSyslog first, then the Nil collaborators it depends on. */ -static void SolidSyslog_NilClock(struct SolidSyslogTimestamp* ts); -static void SolidSyslog_NilStringFunction(struct SolidSyslogFormatter* formatter); -static struct SolidSyslogBuffer NilBuffer; -static struct SolidSyslogSender NilSender; -static struct SolidSyslogStore NilStore; -static bool nilBufferReportArmed; -static bool nilSenderReportArmed; -static bool instanceInitialised; - -static struct SolidSyslog instance = { - .Buffer = &NilBuffer, - .Sender = &NilSender, - .Clock = SolidSyslog_NilClock, - .GetHostname = SolidSyslog_NilStringFunction, - .GetAppName = SolidSyslog_NilStringFunction, - .GetProcessId = SolidSyslog_NilStringFunction, - .Store = &NilStore, -}; - -/* Template used by _Create and _Destroy to reset every slot to its nil. C99 - * forbids initialising one file-static from another, so the same literal - * appears once above and once here; both sites stay in sync trivially because - * the values are nil-object addresses. */ -static const struct SolidSyslog NilInstance = { - .Buffer = &NilBuffer, - .Sender = &NilSender, - .Clock = SolidSyslog_NilClock, - .GetHostname = SolidSyslog_NilStringFunction, - .GetAppName = SolidSyslog_NilStringFunction, - .GetProcessId = SolidSyslog_NilStringFunction, - .Store = &NilStore, -}; - -/* SolidSyslog helpers forward-declared so the public functions and - * _Create/_Destroy can call them; each is defined immediately beneath its - * first caller below. */ static inline int16_t SolidSyslog_AbsoluteInt16(int16_t value); static inline bool SolidSyslog_CaptureTimestamp(struct SolidSyslogTimestamp* ts, SolidSyslogClockFunction clock); static inline uint8_t SolidSyslog_CombineFacilityAndSeverity(uint8_t facility, uint8_t severity); static inline bool SolidSyslog_FacilityIsValid(uint8_t facility); -static inline void SolidSyslog_DrainBufferIntoStore(void); -static inline void SolidSyslog_SendOneFromStore(void); +static inline void SolidSyslog_DrainBufferIntoStore(struct SolidSyslog* self); +static inline void SolidSyslog_SendOneFromStore(struct SolidSyslog* self); static inline void SolidSyslog_FormatCapturedTimestamp( struct SolidSyslogFormatter* f, const struct SolidSyslogTimestamp* ts ); -static inline void SolidSyslog_FormatMessage(struct SolidSyslogFormatter* f, const struct SolidSyslogMessage* message); +static inline void SolidSyslog_FormatMessage( + struct SolidSyslogFormatter* f, + struct SolidSyslog* self, + const struct SolidSyslogMessage* message +); static inline void SolidSyslog_FormatMsg(struct SolidSyslogFormatter* f, const char* msg); static inline void SolidSyslog_FormatMsgId(struct SolidSyslogFormatter* f, const char* messageId); static inline void SolidSyslog_FormatNilvalue(struct SolidSyslogFormatter* f); @@ -113,55 +66,63 @@ static inline void SolidSyslog_FormatStructuredData( ); static inline void SolidSyslog_FormatTimestamp(struct SolidSyslogFormatter* f, SolidSyslogClockFunction clock); static inline void SolidSyslog_FormatUtcOffset(struct SolidSyslogFormatter* f, int16_t offsetMinutes); -static void SolidSyslog_InstallAppName(SolidSyslogStringFunction configured); -static void SolidSyslog_InstallBuffer(struct SolidSyslogBuffer* configured); -static void SolidSyslog_InstallClock(SolidSyslogClockFunction configured); -static void SolidSyslog_InstallConfig(const struct SolidSyslogConfig* config); -static void SolidSyslog_InstallHostname(SolidSyslogStringFunction configured); -static void SolidSyslog_InstallProcessId(SolidSyslogStringFunction configured); -static void SolidSyslog_InstallSender(struct SolidSyslogSender* configured); -static void SolidSyslog_InstallStore(struct SolidSyslogStore* configured); -static void SolidSyslog_InstallStructuredData(struct SolidSyslogStructuredData** configured, size_t count); -static inline bool SolidSyslog_IsServiceEnabled(void); +static void SolidSyslog_InstallAppName(struct SolidSyslog* self, SolidSyslogStringFunction configured); +static void SolidSyslog_InstallBuffer(struct SolidSyslog* self, struct SolidSyslogBuffer* configured); +static void SolidSyslog_InstallClock(struct SolidSyslog* self, SolidSyslogClockFunction configured); +static void SolidSyslog_InstallHostname(struct SolidSyslog* self, SolidSyslogStringFunction configured); +static void SolidSyslog_InstallProcessId(struct SolidSyslog* self, SolidSyslogStringFunction configured); +static void SolidSyslog_InstallSender(struct SolidSyslog* self, struct SolidSyslogSender* configured); +static void SolidSyslog_InstallStore(struct SolidSyslog* self, struct SolidSyslogStore* configured); +static void SolidSyslog_InstallStructuredData( + struct SolidSyslog* self, + struct SolidSyslogStructuredData** configured, + size_t count +); +static inline bool SolidSyslog_IsServiceEnabled(struct SolidSyslog* self); static inline uint8_t SolidSyslog_MakePrival(const struct SolidSyslogMessage* message); static inline bool SolidSyslog_PrivalComponentsAreValid(uint8_t facility, uint8_t severity); -static void SolidSyslog_ProcessMessages(void); +static void SolidSyslog_ProcessMessages(struct SolidSyslog* self); +static void SolidSyslog_ResetToDefaults(struct SolidSyslog* self); static inline bool SolidSyslog_SeverityIsValid(uint8_t severity); static inline const char* SolidSyslog_SkipLeadingBom(const char* msg); static inline bool SolidSyslog_StringIsValid(const char* value); static inline bool SolidSyslog_TimestampIsValid(const struct SolidSyslogTimestamp* ts); -void SolidSyslog_Create(const struct SolidSyslogConfig* config) +void SolidSyslog_Initialise(struct SolidSyslog* self, const struct SolidSyslogConfig* config) { - if (instanceInitialised) - { - SolidSyslog_Error(SOLIDSYSLOG_SEVERITY_ERROR, SOLIDSYSLOG_ERROR_MSG_CREATE_ALREADY_INITIALISED); - } - else if (config == NULL) - { - SolidSyslog_Error(SOLIDSYSLOG_SEVERITY_ERROR, SOLIDSYSLOG_ERROR_MSG_CREATE_NULL_CONFIG); - } - else - { - SolidSyslog_InstallConfig(config); - instanceInitialised = true; - } + SolidSyslog_ResetToDefaults(self); + SolidSyslog_InstallBuffer(self, config->Buffer); + SolidSyslog_InstallSender(self, config->Sender); + SolidSyslog_InstallStore(self, config->Store); + SolidSyslog_InstallClock(self, config->Clock); + SolidSyslog_InstallHostname(self, config->GetHostname); + SolidSyslog_InstallAppName(self, config->GetAppName); + SolidSyslog_InstallProcessId(self, config->GetProcessId); + SolidSyslog_InstallStructuredData(self, config->Sd, config->SdCount); } -static void SolidSyslog_InstallConfig(const struct SolidSyslogConfig* config) +void SolidSyslog_Cleanup(struct SolidSyslog* self) { - instance = NilInstance; - SolidSyslog_InstallBuffer(config->Buffer); - SolidSyslog_InstallSender(config->Sender); - SolidSyslog_InstallStore(config->Store); - SolidSyslog_InstallClock(config->Clock); - SolidSyslog_InstallHostname(config->GetHostname); - SolidSyslog_InstallAppName(config->GetAppName); - SolidSyslog_InstallProcessId(config->GetProcessId); - SolidSyslog_InstallStructuredData(config->Sd, config->SdCount); + /* Reset to safe defaults so a stale-handle Log/Service after Destroy is a + * silent no-op rather than a NULL-fn-pointer crash. The slot is then + * re-claimable by the next _Create. */ + SolidSyslog_ResetToDefaults(self); } -static void SolidSyslog_InstallBuffer(struct SolidSyslogBuffer* configured) +static void SolidSyslog_ResetToDefaults(struct SolidSyslog* self) +{ + self->Buffer = SolidSyslogNullBuffer_Get(); + self->Sender = SolidSyslogNullSender_Get(); + self->Store = SolidSyslogNullStore_Get(); + self->Clock = SolidSyslog_NullClock; + self->GetHostname = SolidSyslog_NullStringFunction; + self->GetAppName = SolidSyslog_NullStringFunction; + self->GetProcessId = SolidSyslog_NullStringFunction; + self->Sd = NULL; + self->SdCount = 0; +} + +static void SolidSyslog_InstallBuffer(struct SolidSyslog* self, struct SolidSyslogBuffer* configured) { if (configured == NULL) { @@ -169,11 +130,11 @@ static void SolidSyslog_InstallBuffer(struct SolidSyslogBuffer* configured) } else { - instance.Buffer = configured; + self->Buffer = configured; } } -static void SolidSyslog_InstallSender(struct SolidSyslogSender* configured) +static void SolidSyslog_InstallSender(struct SolidSyslog* self, struct SolidSyslogSender* configured) { if (configured == NULL) { @@ -181,11 +142,11 @@ static void SolidSyslog_InstallSender(struct SolidSyslogSender* configured) } else { - instance.Sender = configured; + self->Sender = configured; } } -static void SolidSyslog_InstallStore(struct SolidSyslogStore* configured) +static void SolidSyslog_InstallStore(struct SolidSyslog* self, struct SolidSyslogStore* configured) { if (configured == NULL) { @@ -193,73 +154,77 @@ static void SolidSyslog_InstallStore(struct SolidSyslogStore* configured) } else { - instance.Store = configured; + self->Store = configured; } } -static void SolidSyslog_InstallClock(SolidSyslogClockFunction configured) +static void SolidSyslog_InstallClock(struct SolidSyslog* self, SolidSyslogClockFunction configured) { if (configured != NULL) { - instance.Clock = configured; + self->Clock = configured; } } -static void SolidSyslog_InstallHostname(SolidSyslogStringFunction configured) +static void SolidSyslog_InstallHostname(struct SolidSyslog* self, SolidSyslogStringFunction configured) { if (configured != NULL) { - instance.GetHostname = configured; + self->GetHostname = configured; } } -static void SolidSyslog_InstallAppName(SolidSyslogStringFunction configured) +static void SolidSyslog_InstallAppName(struct SolidSyslog* self, SolidSyslogStringFunction configured) { if (configured != NULL) { - instance.GetAppName = configured; + self->GetAppName = configured; } } -static void SolidSyslog_InstallProcessId(SolidSyslogStringFunction configured) +static void SolidSyslog_InstallProcessId(struct SolidSyslog* self, SolidSyslogStringFunction configured) { if (configured != NULL) { - instance.GetProcessId = configured; + self->GetProcessId = configured; } } -static void SolidSyslog_InstallStructuredData(struct SolidSyslogStructuredData** configured, size_t count) -{ - instance.Sd = configured; - instance.SdCount = count; -} - -void SolidSyslog_Destroy(void) +static void SolidSyslog_InstallStructuredData( + struct SolidSyslog* self, + struct SolidSyslogStructuredData** configured, + size_t count +) { - instance = NilInstance; - nilBufferReportArmed = true; - nilSenderReportArmed = true; - instanceInitialised = false; + self->Sd = configured; + self->SdCount = count; } -void SolidSyslog_Service(void) +void SolidSyslog_Service(struct SolidSyslog* handle) { - if (SolidSyslog_IsServiceEnabled()) + if (handle == NULL) + { + SolidSyslog_Error(SOLIDSYSLOG_SEVERITY_ERROR, SOLIDSYSLOG_ERROR_MSG_SERVICE_NULL_HANDLE); + } + else if (SolidSyslog_IsServiceEnabled(handle)) + { + SolidSyslog_ProcessMessages(handle); + } + else { - SolidSyslog_ProcessMessages(); + /* Halted store — skip drain/send. */ } } -static inline bool SolidSyslog_IsServiceEnabled(void) +static inline bool SolidSyslog_IsServiceEnabled(struct SolidSyslog* self) { - return !SolidSyslogStore_IsHalted(instance.Store); + return !SolidSyslogStore_IsHalted(self->Store); } -static void SolidSyslog_ProcessMessages(void) +static void SolidSyslog_ProcessMessages(struct SolidSyslog* self) { - SolidSyslog_DrainBufferIntoStore(); - SolidSyslog_SendOneFromStore(); + SolidSyslog_DrainBufferIntoStore(self); + SolidSyslog_SendOneFromStore(self); } /* Eagerly drain the buffer so the producer-side shock absorber stays small while @@ -271,35 +236,39 @@ static void SolidSyslog_ProcessMessages(void) * discard policy speaking — letting that message escape via direct send would * break the discard-newest contract (a newer message would bypass older stored * ones once the sender recovered). */ -static inline void SolidSyslog_DrainBufferIntoStore(void) +static inline void SolidSyslog_DrainBufferIntoStore(struct SolidSyslog* self) { char buf[SOLIDSYSLOG_MAX_MESSAGE_SIZE]; size_t len = 0; - while (SolidSyslogBuffer_Read(instance.Buffer, buf, sizeof(buf), &len)) + while (SolidSyslogBuffer_Read(self->Buffer, buf, sizeof(buf), &len)) { - if (!SolidSyslogStore_Write(instance.Store, buf, len) && SolidSyslogStore_IsTransient(instance.Store)) + if (!SolidSyslogStore_Write(self->Store, buf, len) && SolidSyslogStore_IsTransient(self->Store)) { - SolidSyslogSender_Send(instance.Sender, buf, len); + SolidSyslogSender_Send(self->Sender, buf, len); } } } -static inline void SolidSyslog_SendOneFromStore(void) +static inline void SolidSyslog_SendOneFromStore(struct SolidSyslog* self) { char buf[SOLIDSYSLOG_MAX_MESSAGE_SIZE]; size_t len = 0; - if (SolidSyslogStore_ReadNextUnsent(instance.Store, buf, sizeof(buf), &len) && - SolidSyslogSender_Send(instance.Sender, buf, len)) + if (SolidSyslogStore_ReadNextUnsent(self->Store, buf, sizeof(buf), &len) && + SolidSyslogSender_Send(self->Sender, buf, len)) { - SolidSyslogStore_MarkSent(instance.Store); + SolidSyslogStore_MarkSent(self->Store); } } -void SolidSyslog_Log(const struct SolidSyslogMessage* message) +void SolidSyslog_Log(struct SolidSyslog* handle, const struct SolidSyslogMessage* message) { - if (message == NULL) + if (handle == NULL) + { + SolidSyslog_Error(SOLIDSYSLOG_SEVERITY_ERROR, SOLIDSYSLOG_ERROR_MSG_LOG_NULL_HANDLE); + } + else if (message == NULL) { SolidSyslog_Error(SOLIDSYSLOG_SEVERITY_ERROR, SOLIDSYSLOG_ERROR_MSG_LOG_NULL_MESSAGE); } @@ -308,31 +277,35 @@ void SolidSyslog_Log(const struct SolidSyslogMessage* message) SolidSyslogFormatterStorage storage[SOLIDSYSLOG_FORMATTER_STORAGE_SIZE(SOLIDSYSLOG_MAX_MESSAGE_SIZE)]; struct SolidSyslogFormatter* f = SolidSyslogFormatter_Create(storage, SOLIDSYSLOG_MAX_MESSAGE_SIZE); - SolidSyslog_FormatMessage(f, message); + SolidSyslog_FormatMessage(f, handle, message); SolidSyslogBuffer_Write( - instance.Buffer, + handle->Buffer, SolidSyslogFormatter_AsFormattedBuffer(f), SolidSyslogFormatter_Length(f) ); } } -static inline void SolidSyslog_FormatMessage(struct SolidSyslogFormatter* f, const struct SolidSyslogMessage* message) +static inline void SolidSyslog_FormatMessage( + struct SolidSyslogFormatter* f, + struct SolidSyslog* self, + const struct SolidSyslogMessage* message +) { SolidSyslog_FormatPrival(f, SolidSyslog_MakePrival(message)); SolidSyslogFormatter_AsciiCharacter(f, '1'); SolidSyslogFormatter_AsciiCharacter(f, ' '); - SolidSyslog_FormatTimestamp(f, instance.Clock); + SolidSyslog_FormatTimestamp(f, self->Clock); SolidSyslogFormatter_AsciiCharacter(f, ' '); - SolidSyslog_FormatStringField(f, instance.GetHostname, SOLIDSYSLOG_MAX_HOSTNAME_SIZE); + SolidSyslog_FormatStringField(f, self->GetHostname, SOLIDSYSLOG_MAX_HOSTNAME_SIZE); SolidSyslogFormatter_AsciiCharacter(f, ' '); - SolidSyslog_FormatStringField(f, instance.GetAppName, SOLIDSYSLOG_MAX_APP_NAME_SIZE); + SolidSyslog_FormatStringField(f, self->GetAppName, SOLIDSYSLOG_MAX_APP_NAME_SIZE); SolidSyslogFormatter_AsciiCharacter(f, ' '); - SolidSyslog_FormatStringField(f, instance.GetProcessId, SOLIDSYSLOG_MAX_PROCESS_ID_SIZE); + SolidSyslog_FormatStringField(f, self->GetProcessId, SOLIDSYSLOG_MAX_PROCESS_ID_SIZE); SolidSyslogFormatter_AsciiCharacter(f, ' '); SolidSyslog_FormatMsgId(f, message->MessageId); SolidSyslogFormatter_AsciiCharacter(f, ' '); - SolidSyslog_FormatStructuredData(f, instance.Sd, instance.SdCount); + SolidSyslog_FormatStructuredData(f, self->Sd, self->SdCount); SolidSyslog_FormatMsg(f, message->Msg); } @@ -557,158 +530,12 @@ static inline void SolidSyslog_FormatNilvalue(struct SolidSyslogFormatter* f) SolidSyslogFormatter_AsciiCharacter(f, '-'); } -/* ============================================================================ - * Nil collaborators - * - * Private no-op vtables that occupy every instance slot at file load, after - * _Destroy, and for any required slot the integrator left NULL in their - * config. Vtable dispatch never NULL-checks — the nils make every collaborator - * pointer safe to call. - * - * NilBuffer and NilSender report one error via SolidSyslog_Error on first - * use, then silently consume; _Destroy re-arms the flags. NilStore is silent - * — its absence is a valid integrator choice (matches SolidSyslogNullStore - * semantics: drain proceeds, fall-through to direct send). - * - * The public SolidSyslogNull* family is for integrator-chosen no-ops; - * these internal nils are crash-safe defaults only. - * ============================================================================ */ - -static bool nilBufferReportArmed = true; -static bool nilSenderReportArmed = true; - -static void SolidSyslog_NilClock(struct SolidSyslogTimestamp* ts) +void SolidSyslog_NullClock(struct SolidSyslogTimestamp* ts) { (void) ts; } -static void SolidSyslog_NilStringFunction(struct SolidSyslogFormatter* formatter) +void SolidSyslog_NullStringFunction(struct SolidSyslogFormatter* formatter) { (void) formatter; } - -/* NilBuffer */ - -static void SolidSyslog_NilBufferWrite(struct SolidSyslogBuffer* base, const void* data, size_t size) -{ - (void) base; - (void) data; - (void) size; - if (nilBufferReportArmed) - { - nilBufferReportArmed = false; - SolidSyslog_Error(SOLIDSYSLOG_SEVERITY_ERROR, SOLIDSYSLOG_ERROR_MSG_NIL_BUFFER_USED); - } -} - -static bool SolidSyslog_NilBufferRead(struct SolidSyslogBuffer* base, void* data, size_t maxSize, size_t* bytesRead) -{ - (void) base; - (void) data; - (void) maxSize; - *bytesRead = 0; - return false; -} - -static struct SolidSyslogBuffer NilBuffer = { - .Write = SolidSyslog_NilBufferWrite, - .Read = SolidSyslog_NilBufferRead, -}; - -/* NilSender */ - -static bool SolidSyslog_NilSenderSend(struct SolidSyslogSender* base, const void* buffer, size_t size) -{ - (void) base; - (void) buffer; - (void) size; - if (nilSenderReportArmed) - { - nilSenderReportArmed = false; - SolidSyslog_Error(SOLIDSYSLOG_SEVERITY_ERROR, SOLIDSYSLOG_ERROR_MSG_NIL_SENDER_USED); - } - return true; -} - -static void SolidSyslog_NilSenderDisconnect(struct SolidSyslogSender* base) -{ - (void) base; -} - -static struct SolidSyslogSender NilSender = { - .Send = SolidSyslog_NilSenderSend, - .Disconnect = SolidSyslog_NilSenderDisconnect, -}; - -/* NilStore */ - -static bool SolidSyslog_NilStoreWrite(struct SolidSyslogStore* base, const void* data, size_t size) -{ - (void) base; - (void) data; - (void) size; - return false; -} - -static bool SolidSyslog_NilStoreReadNextUnsent( - struct SolidSyslogStore* base, - void* data, - size_t maxSize, - size_t* bytesRead -) -{ - (void) base; - (void) data; - (void) maxSize; - *bytesRead = 0; - return false; -} - -static void SolidSyslog_NilStoreMarkSent(struct SolidSyslogStore* base) -{ - (void) base; -} - -static bool SolidSyslog_NilStoreHasUnsent(struct SolidSyslogStore* base) -{ - (void) base; - return false; -} - -static bool SolidSyslog_NilStoreIsHalted(struct SolidSyslogStore* base) -{ - (void) base; - return false; -} - -static size_t SolidSyslog_NilStoreGetTotalBytes(struct SolidSyslogStore* base) -{ - (void) base; - return 0; -} - -static size_t SolidSyslog_NilStoreGetUsedBytes(struct SolidSyslogStore* base) -{ - (void) base; - return 0; -} - -/* NilStore stands in when the integrator passes config.Store = NULL — - * "no store, just try to send." Same transient semantics as NullStore: - * Service falls through to the sender on Write rejection. */ -static bool SolidSyslog_NilStoreIsTransient(struct SolidSyslogStore* base) -{ - (void) base; - return true; -} - -static struct SolidSyslogStore NilStore = { - .Write = SolidSyslog_NilStoreWrite, - .ReadNextUnsent = SolidSyslog_NilStoreReadNextUnsent, - .MarkSent = SolidSyslog_NilStoreMarkSent, - .HasUnsent = SolidSyslog_NilStoreHasUnsent, - .IsHalted = SolidSyslog_NilStoreIsHalted, - .GetTotalBytes = SolidSyslog_NilStoreGetTotalBytes, - .GetUsedBytes = SolidSyslog_NilStoreGetUsedBytes, - .IsTransient = SolidSyslog_NilStoreIsTransient, -}; diff --git a/Core/Source/SolidSyslogErrorMessages.h b/Core/Source/SolidSyslogErrorMessages.h index 913c6d35..d9f9ff03 100644 --- a/Core/Source/SolidSyslogErrorMessages.h +++ b/Core/Source/SolidSyslogErrorMessages.h @@ -5,11 +5,13 @@ #define SOLIDSYSLOG_ERROR_MSG_CREATE_NULL_BUFFER "SolidSyslog_Create config.Buffer is NULL" #define SOLIDSYSLOG_ERROR_MSG_CREATE_NULL_SENDER "SolidSyslog_Create config.Sender is NULL" #define SOLIDSYSLOG_ERROR_MSG_CREATE_NULL_STORE "SolidSyslog_Create config.Store is NULL" -#define SOLIDSYSLOG_ERROR_MSG_CREATE_ALREADY_INITIALISED \ - "SolidSyslog_Create called while already initialised - call _Destroy first" #define SOLIDSYSLOG_ERROR_MSG_LOG_NULL_MESSAGE "SolidSyslog_Log called with NULL message" -#define SOLIDSYSLOG_ERROR_MSG_NIL_BUFFER_USED "SolidSyslog_Log called with no buffer configured" -#define SOLIDSYSLOG_ERROR_MSG_NIL_SENDER_USED "SolidSyslog_Service tried to send with no sender configured" +#define SOLIDSYSLOG_ERROR_MSG_LOG_NULL_HANDLE "SolidSyslog_Log called with NULL handle" +#define SOLIDSYSLOG_ERROR_MSG_SERVICE_NULL_HANDLE "SolidSyslog_Service called with NULL handle" +#define SOLIDSYSLOG_ERROR_MSG_SOLIDSYSLOG_POOL_EXHAUSTED \ + "SolidSyslog_Create pool exhausted; returning fallback instance" +#define SOLIDSYSLOG_ERROR_MSG_SOLIDSYSLOG_UNKNOWN_DESTROY \ + "SolidSyslog_Destroy called with a handle not issued by this pool" #define SOLIDSYSLOG_ERROR_MSG_METASD_CREATE_NULL_CONFIG "SolidSyslogMetaSd_Create called with NULL config" #define SOLIDSYSLOG_ERROR_MSG_METASD_CREATE_NULL_COUNTER "SolidSyslogMetaSd_Create config.Counter is NULL" #define SOLIDSYSLOG_ERROR_MSG_UDPSENDER_CREATE_NULL_CONFIG "SolidSyslogUdpSender_Create called with NULL config" diff --git a/Core/Source/SolidSyslogPrivate.h b/Core/Source/SolidSyslogPrivate.h new file mode 100644 index 00000000..5045efdf --- /dev/null +++ b/Core/Source/SolidSyslogPrivate.h @@ -0,0 +1,39 @@ +#ifndef SOLIDSYSLOGPRIVATE_H +#define SOLIDSYSLOGPRIVATE_H + +#include + +#include "SolidSyslogStringFunction.h" +#include "SolidSyslogTimestamp.h" + +struct SolidSyslogBuffer; +struct SolidSyslogConfig; +struct SolidSyslogFormatter; +struct SolidSyslogSender; +struct SolidSyslogStore; +struct SolidSyslogStructuredData; + +struct SolidSyslog +{ + struct SolidSyslogBuffer* Buffer; + struct SolidSyslogSender* Sender; + SolidSyslogClockFunction Clock; + SolidSyslogStringFunction GetHostname; + SolidSyslogStringFunction GetAppName; + SolidSyslogStringFunction GetProcessId; + struct SolidSyslogStore* Store; + struct SolidSyslogStructuredData** Sd; + size_t SdCount; +}; + +void SolidSyslog_Initialise(struct SolidSyslog* self, const struct SolidSyslogConfig* config); +void SolidSyslog_Cleanup(struct SolidSyslog* self); + +/* No-op function-pointer defaults shared between SolidSyslog.c and + * SolidSyslogStatic.c (the latter wires them into the exhaustion-fallback + * NullInstance). No public Null equivalent exists for the function-pointer + * typedefs, so they stay TU-internal across this class. */ +void SolidSyslog_NullClock(struct SolidSyslogTimestamp* ts); +void SolidSyslog_NullStringFunction(struct SolidSyslogFormatter* formatter); + +#endif /* SOLIDSYSLOGPRIVATE_H */ diff --git a/Core/Source/SolidSyslogStatic.c b/Core/Source/SolidSyslogStatic.c new file mode 100644 index 00000000..c3a86abd --- /dev/null +++ b/Core/Source/SolidSyslogStatic.c @@ -0,0 +1,104 @@ +#include "SolidSyslogConfig.h" + +#include +#include + +#include "SolidSyslogError.h" +#include "SolidSyslogErrorMessages.h" +#include "SolidSyslogPrival.h" +#include "SolidSyslogNullBuffer.h" +#include "SolidSyslogNullSender.h" +#include "SolidSyslogNullStore.h" +#include "SolidSyslogPoolAllocator.h" +#include "SolidSyslogPrivate.h" +#include "SolidSyslogTunables.h" + +static inline size_t SolidSyslog_IndexFromHandle(const struct SolidSyslog* handle); +static inline void SolidSyslog_CleanupAtIndex(size_t index, void* context); +static void SolidSyslog_EnsureNullInstancePopulated(void); + +static bool SolidSyslog_InUse[SOLIDSYSLOG_POOL_SIZE]; +static struct SolidSyslog SolidSyslog_Pool[SOLIDSYSLOG_POOL_SIZE]; +static struct SolidSyslogPoolAllocator SolidSyslog_Allocator = {SolidSyslog_InUse, SOLIDSYSLOG_POOL_SIZE}; + +/* Exhaustion-fallback handle. Populated lazily on first reach because + * SolidSyslogNull*_Get() returns runtime addresses (no file-scope designated + * initialiser will accept them). Sits outside SolidSyslog_Pool[] so + * IndexFromHandle naturally returns invalid for it — _Destroy(&NullInstance) + * fires WARNING + ignore, while _Log/_Service against it route through the + * public Null* siblings and silently drop. */ +static struct SolidSyslog SolidSyslog_NullInstance; +static bool SolidSyslog_NullInstancePopulated; + +struct SolidSyslog* SolidSyslog_Create(const struct SolidSyslogConfig* config) +{ + SolidSyslog_EnsureNullInstancePopulated(); + struct SolidSyslog* result = &SolidSyslog_NullInstance; + if (config == NULL) + { + SolidSyslog_Error(SOLIDSYSLOG_SEVERITY_ERROR, SOLIDSYSLOG_ERROR_MSG_CREATE_NULL_CONFIG); + } + else + { + size_t index = SolidSyslogPoolAllocator_AcquireFirstFree(&SolidSyslog_Allocator); + if (SolidSyslogPoolAllocator_IndexIsValid(&SolidSyslog_Allocator, index)) + { + SolidSyslog_Initialise(&SolidSyslog_Pool[index], config); + result = &SolidSyslog_Pool[index]; + } + else + { + SolidSyslog_Error(SOLIDSYSLOG_SEVERITY_ERROR, SOLIDSYSLOG_ERROR_MSG_SOLIDSYSLOG_POOL_EXHAUSTED); + } + } + return result; +} + +void SolidSyslog_Destroy(struct SolidSyslog* handle) +{ + size_t index = SolidSyslog_IndexFromHandle(handle); + bool released = + SolidSyslogPoolAllocator_IndexIsValid(&SolidSyslog_Allocator, index) && + SolidSyslogPoolAllocator_FreeIfInUse(&SolidSyslog_Allocator, index, SolidSyslog_CleanupAtIndex, NULL); + if (!released) + { + SolidSyslog_Error(SOLIDSYSLOG_SEVERITY_WARNING, SOLIDSYSLOG_ERROR_MSG_SOLIDSYSLOG_UNKNOWN_DESTROY); + } +} + +static inline size_t SolidSyslog_IndexFromHandle(const struct SolidSyslog* handle) +{ + size_t result = SOLIDSYSLOG_POOL_SIZE; + for (size_t poolIndex = 0; poolIndex < SOLIDSYSLOG_POOL_SIZE; poolIndex++) + { + if (handle == &SolidSyslog_Pool[poolIndex]) + { + result = poolIndex; + break; + } + } + return result; +} + +static inline void SolidSyslog_CleanupAtIndex(size_t index, void* context) +{ + (void) context; + SolidSyslog_Cleanup(&SolidSyslog_Pool[index]); +} + +static void SolidSyslog_EnsureNullInstancePopulated(void) +{ + if (!SolidSyslog_NullInstancePopulated) + { + SolidSyslog_NullInstance.Buffer = SolidSyslogNullBuffer_Get(); + SolidSyslog_NullInstance.Sender = SolidSyslogNullSender_Get(); + SolidSyslog_NullInstance.Store = SolidSyslogNullStore_Get(); + SolidSyslog_NullInstance.Clock = SolidSyslog_NullClock; + SolidSyslog_NullInstance.GetHostname = SolidSyslog_NullStringFunction; + SolidSyslog_NullInstance.GetAppName = SolidSyslog_NullStringFunction; + SolidSyslog_NullInstance.GetProcessId = SolidSyslog_NullStringFunction; + SolidSyslog_NullInstance.Sd = NULL; + SolidSyslog_NullInstance.SdCount = 0; + SolidSyslog_NullInstancePopulated = true; + } +} diff --git a/DEVLOG.md b/DEVLOG.md index 6f6ac68b..26d94ad0 100644 --- a/DEVLOG.md +++ b/DEVLOG.md @@ -1,5 +1,193 @@ # Dev Log +## 2026-05-20 — S11.10: Retrofit SolidSyslog core onto PoolAllocator (handle API) + +Closes S11.10 (#413). The penultimate E11 story — retrofits the +`SolidSyslog` core itself onto the canonical 3-TU split + handle-taking +public API. The class-by-class sweep closed in S11.09; only S11.11 +(MISRA `D.002` suppression cleanup) remains in E11. + +Two commits on the branch, then DEVLOG. + +- `795df8e` — **`refactor: S11.10 collapse Nil collaborators onto public + Null siblings`**. Strip `NilBuffer` / `NilSender` / `NilStore` vtables + (~120 lines), point the file-static `instance` defaults at the public + `SolidSyslogNull{Buffer,Sender,Store}_Get()` siblings, drop the + loud-once `NIL_BUFFER_USED` / `NIL_SENDER_USED` reporters and their 5 + asserting tests. The authoritative bad-config signal is the + Create-time `NULL_BUFFER` / `NULL_SENDER` error fired by + `_InstallBuffer` / `_InstallSender`. Net deletion: 273 lines. + `NilClock` / `NilStringFunction` (the two function-pointer no-ops with + no public Null equivalent) renamed to `NullClock` / `NullStringFunction` + and kept TU-local. + +- `00fa7d7` — **`feat!: S11.10 migrate SolidSyslog core onto + PoolAllocator with handle API`**. The architectural change. New + `SolidSyslogPrivate.h` lifts `struct SolidSyslog` + `_Initialise` / + `_Cleanup` signatures + the two TU-shared `NullClock` / + `NullStringFunction` declarations into a private header. New + `SolidSyslogStatic.c` carries `SolidSyslog_InUse[]`, + `SolidSyslog_Pool[]`, `SolidSyslog_Allocator`, `_Create` / `_Destroy` / + `IndexFromHandle` / `CleanupAtIndex`, and the `NullInstance` + exhaustion-fallback template. `SolidSyslog.c` keeps the algorithm: + every `instance.X` reference becomes `self->X`, and `_Log` / `_Service` + / `_FormatMessage` / `_DrainBufferIntoStore` / `_SendOneFromStore` / + `_IsServiceEnabled` take a `self` parameter. `_Initialise` absorbs the + per-slot `Install*` helpers; `_Cleanup` resets the slot to safe + defaults so a stale-handle Log/Service after Destroy is a silent + no-op. + + The collapsed commit also folds in commit 3 from the story body — the + consumer-side BDD-target updates. Splitting them per the ticket + recipe would have left the debug build broken between commits 2 and + 3, and CMake doesn't offer a granular "compile but don't link" + knob. All four BDD targets (Linux, Windows, FreeRtos, Common) + + every test fixture (`SolidSyslog`, `SolidSyslogLifecycle`, + `SolidSyslogServiceEagerDrain`, `BddTargetServiceThread`, + `ServiceDrainInterleave`, the PosixMessageQueueBuffer service test) + thread a `struct SolidSyslog*` through. + +### Decisions + +- **`SOLIDSYSLOG_POOL_SIZE` tunable, default 1U.** Mirrors every other + E11 class. The default preserves single-instance integrators + exactly; multi-instance is opt-in via + `SOLIDSYSLOG_USER_TUNABLES_FILE`. + `Tests/Fixtures/SmallMessageSizeTunables.h` bumps to 3U so + `tunable-override-debug` exercises pool walks across three slots. + +- **NullInstance, not NilInstance — and lazy-populated.** Decided + pre-coding (memory: AskUserQuestion answers). Once the collaborator + slots route through public Null siblings, "Nil" is a historical + name. The lazy-populate inside `SolidSyslog_EnsureNullInstancePopulated` + is forced by C99: `SolidSyslogNull*_Get()` returns runtime addresses, + so a file-scope designated initialiser cannot use them. Populated on + first reach (`_Create` always touches it). + +- **Class-specific error-message wording** + (`SOLIDSYSLOG_ERROR_MSG_SOLIDSYSLOG_POOL_EXHAUSTED` / + `_UNKNOWN_DESTROY`). Decided pre-coding. Matches every other E11 + class's `MSG__POOL_EXHAUSTED` / `_UNKNOWN_DESTROY` shape; the + uniform-format alternative would have forced a wider sweep across + every previous E11 class. + +- **`MSG_CREATE_ALREADY_INITIALISED` deleted.** Pool semantics replace + the singleton's double-Create-into-overwrite contract: a second Create + either gets a fresh slot or fires `POOL_EXHAUSTED`. The legacy + error message has no caller. + +- **NULL-handle defensive paths on the runtime entry points** — + `_Log(NULL, ...)` and `_Service(NULL)` fire ERROR + return. + `_Destroy(NULL)` and `_Destroy(unknown)` fire WARNING + return via + the existing `IndexFromHandle` invalid-pointer path; no separate + NULL check needed. + +- **Test fixtures absorb the handle as a member.** Five fixtures + needed `struct SolidSyslog* solidSyslog` added. The + `SolidSyslogLifecycle` fixture's old `setup()` called + `SolidSyslog_Destroy()` to clear singleton state — that's gone; the + new setup just initialises `solidSyslog = nullptr`, and teardown + destroys only if non-null. `LogBeforeCreateDoesNotCrash` / + `ServiceBeforeCreateDoesNotCrash` were rewritten as + `LogWithNullHandleReportsError` / `ServiceWithNullHandleReportsError` + — same defensive property, different shape. + +- **The two pool-exhaustion tests live in the new + `SolidSyslogPool` TEST_GROUP only.** Initial drafts put parallel + tests in `SolidSyslogLifecycle`; those broke under + `tunable-override-debug` (POOL_SIZE=3) because they assumed a single + Create exhausts the pool. The Pool TEST_GROUP's `FillPool` is + pool-size-agnostic and the right home. + +- **`BddTargetInteractive_Run` and `BddTargetServiceThread_Run` + take the handle as a leading parameter.** Most natural place to + thread the handle; alternative was a global hook, which would have + been worse. `BddTargetInteractiveTest` doesn't exercise the `send` + command so it passes `nullptr` for the handle — documented in a + test-file comment. + +- **Forged "unknown handle" via `reinterpret_cast(&stackByte)`** in the unknown-handle Destroy tests. + `struct SolidSyslog` is opaque in the public headers (private + shape lives in `SolidSyslogPrivate.h`, internal-only), so a + declared local won't compile. NOLINT-suppressed + `cppcoreguidelines-pro-type-reinterpret-cast` at each site. + +### Per-class delta + +- `SolidSyslog.c`: 568 → 567 lines (the format helpers dominate; + collapsed Install*-on-instance into Install*-on-self, dropped + `_Create` / `_Destroy` / lazy-init helpers / Nil collaborators). +- `SolidSyslogStatic.c` new: 99 lines (Pool, Allocator, Create, + Destroy, IndexFromHandle, CleanupAtIndex, NullInstance). +- `SolidSyslogPrivate.h` new: 41 lines (struct, _Initialise, _Cleanup, + NullClock / NullStringFunction extern decls). +- `SolidSyslogPoolTest.cpp` new: 178 lines (9 tests mirroring + `SolidSyslogStdAtomicCounterPool`). +- `Core/Interface/SolidSyslog.h` / `SolidSyslogConfig.h`: handle API. +- `SolidSyslogErrorMessages.h`: -2 (NIL_*_USED) +4 (POOL_EXHAUSTED, + UNKNOWN_DESTROY, LOG_NULL_HANDLE, SERVICE_NULL_HANDLE) -1 + (CREATE_ALREADY_INITIALISED) = +1 net. + +### Local validation + +All host gates green from the gcc devcontainer: + +- debug: 1282 tests pass at default `SOLIDSYSLOG_POOL_SIZE=1` and at + `SOLIDSYSLOG_POOL_SIZE=3` via the `tunable-override-debug` preset. +- sanitize: 1282 tests pass; pre-existing UBSan finding on + `Tests/FileFake.c:424` from S10.04 unchanged. +- coverage: aggregated 99.9% line; `SolidSyslog.c` 100% line on its + 225 lines, `SolidSyslogStatic.c` 100% line on its 41 lines. +- tidy: clean (NOLINTNEXTLINE needed for the + multi-line-signature shutdown-pointer-const-parameter complaint + after I added the handle param; folded back into single-line + signature). +- cppcheck: clean (two new `unreadVariable` suppressions on + fixture-assigned `solidSyslog` per the existing + `// cppcheck-suppress unreadVariable -- ... cppcheck does not model + CppUTest macros` precedent). +- format: clean. clang-format prefers `struct SolidSyslog * handle` + with spaces in some opaque-forward-decl contexts and `struct + SolidSyslog* handle` in others — the project's `.clang-format` + accepts both, both forms appear in formatted output. +- IWYU clean via cpputest-clang (caught one missing + `SolidSyslogPrival.h` include in `SolidSyslogStatic.c` and missing + forward decls in `SolidSyslog.c`, both folded back into the + architectural commit). +- OpenSSL integration suite green. + +### Handoff for CI + +- **`build-windows-msvc`** — Windows BDD target now stores the handle + + passes it through `BddTargetServiceThread_Run` and + `BddTargetInteractive_Run`. Expected pass. +- **`bdd-windows-otel`** — exercises the handle threading via + Windows main + Interactive run. Expected pass. +- **`bdd-linux-syslog-ng`** — exercises Linux main's handle storage + and threading. Expected pass. +- **`bdd-freertos-qemu`** — exercises FreeRtos main's lifecycle-mutex + + handle pattern across `RebuildWithFileStore` and `TeardownAll`. + Expected pass; the lifecycle-mutex still guards Service against + Destroy/Create transitions, only now both ends thread the handle. + +### Known stale findings (pre-existing) + +- `Tests/FileFake.c:424` UBSan finding — pre-existing, S10.04. +- `Tests/FreeRtos/CmsdkUartFake.c` tidy errors — pre-existing, S11.08. + +### Deferred + +- **S11.11 — wholesale MISRA `D.002` storage-cast deviation cleanup.** + Final E11 story. SolidSyslog itself had no storage-cast deviation to + begin with (it was file-static, not caller-supplied-storage), so + nothing in this story to inherit; S11.11 sweeps the per-class + deviations that landed across S11.02–S11.09. + +### Open questions + +- None. + ## 2026-05-20 — S11.09: AtomicCounter family + TLS + FatFs onto PoolAllocator Closes S11.09 (#412). Closes out E11's class-by-class migration with the diff --git a/Tests/Bdd/Targets/BddTargetInteractiveTest.cpp b/Tests/Bdd/Targets/BddTargetInteractiveTest.cpp index f7abac97..baa195d4 100644 --- a/Tests/Bdd/Targets/BddTargetInteractiveTest.cpp +++ b/Tests/Bdd/Targets/BddTargetInteractiveTest.cpp @@ -72,7 +72,9 @@ void RunWithInput(const char* input, BddTargetInteractiveSetHandler onSet) // NOLINTNEXTLINE(cppcoreguidelines-pro-type-const-cast) -- see comment above; fmemopen mode "r" never writes FILE* in = fmemopen(const_cast(input), strlen(input), "r"); struct SolidSyslogMessage message = {}; - BddTargetInteractive_Run(&message, in, nullptr, onSet); + /* These tests only exercise the set/switch/quit input paths, never `send`, + so the SolidSyslog handle is never dereferenced — nullptr is safe. */ + BddTargetInteractive_Run(nullptr, &message, in, nullptr, onSet); // NOLINTNEXTLINE(cppcoreguidelines-owning-memory) -- fclose is POSIX C; no owning memory concern fclose(in); } diff --git a/Tests/Bdd/Targets/BddTargetServiceThreadTest.cpp b/Tests/Bdd/Targets/BddTargetServiceThreadTest.cpp index 46459094..5d353826 100644 --- a/Tests/Bdd/Targets/BddTargetServiceThreadTest.cpp +++ b/Tests/Bdd/Targets/BddTargetServiceThreadTest.cpp @@ -49,6 +49,7 @@ static uint32_t BddTargetEndpointVersion() // NOLINT(modernize-use-trailing-retu // clang-format off TEST_GROUP(BddTargetServiceThread) { + struct SolidSyslog* solidSyslog = nullptr; struct SolidSyslogSender* sender = nullptr; struct SolidSyslogBuffer* buffer = nullptr; struct SolidSyslogStore* store = nullptr; @@ -75,22 +76,23 @@ TEST_GROUP(BddTargetServiceThread) store = SolidSyslogNullStore_Get(); SolidSyslogConfig config = {buffer, sender, nullptr, nullptr, nullptr, nullptr, store, nullptr, 0}; - SolidSyslog_Create(&config); + // cppcheck-suppress unreadVariable -- read via Run() in tests; cppcheck does not model CppUTest macros + solidSyslog = SolidSyslog_Create(&config); } void teardown() override { - SolidSyslog_Destroy(); + SolidSyslog_Destroy(solidSyslog); SolidSyslogPosixMessageQueueBuffer_Destroy(buffer); SolidSyslogUdpSender_Destroy(sender); SolidSyslogPosixDatagram_Destroy(datagram); SolidSyslogGetAddrInfoResolver_Destroy(resolver); } - static void Log() + void Log() const { SolidSyslogMessage message = {SOLIDSYSLOG_FACILITY_LOCAL0, SOLIDSYSLOG_SEVERITY_INFORMATIONAL, nullptr, nullptr}; - SolidSyslog_Log(&message); + SolidSyslog_Log(solidSyslog, &message); } }; @@ -98,7 +100,7 @@ TEST_GROUP(BddTargetServiceThread) TEST(BddTargetServiceThread, DoesNotSendWhenBufferEmpty) { - BddTargetServiceThread_Run(&shutdown, SleepFake); + BddTargetServiceThread_Run(solidSyslog, &shutdown, SleepFake); CALLED_FAKE(SocketFake_Sendto, NEVER); } @@ -107,7 +109,7 @@ TEST(BddTargetServiceThread, YieldsOneMillisecondAfterEachServiceTick) shutdown = false; sleepShutdownFlag = &shutdown; - BddTargetServiceThread_Run(&shutdown, SleepFake); + BddTargetServiceThread_Run(solidSyslog, &shutdown, SleepFake); CALLED_FUNCTION(SleepFake, ONCE); LONGS_EQUAL(1, lastSleepMs); diff --git a/Tests/CMakeLists.txt b/Tests/CMakeLists.txt index 821d903f..c5a6356e 100644 --- a/Tests/CMakeLists.txt +++ b/Tests/CMakeLists.txt @@ -18,6 +18,7 @@ endif() set(TEST_SOURCES SolidSyslogTest.cpp + SolidSyslogPoolTest.cpp SolidSyslogTunablesTest.cpp SolidSyslogSwitchingSenderTest.cpp SolidSyslogMetaSdTest.cpp diff --git a/Tests/Fixtures/SmallMessageSizeTunables.h b/Tests/Fixtures/SmallMessageSizeTunables.h index f7acbaab..3b2b60c4 100644 --- a/Tests/Fixtures/SmallMessageSizeTunables.h +++ b/Tests/Fixtures/SmallMessageSizeTunables.h @@ -11,8 +11,11 @@ * feedback on PR #407). * - SOLIDSYSLOG_STD_ATOMIC_COUNTER_POOL_SIZE bumped to 2 enables * `TwoCountersFromPoolAreIndependent` in SolidSyslogAtomicCounterContractTest - * to exercise two concurrent counters drawn from the same pool. */ + * to exercise two concurrent counters drawn from the same pool. + * - SOLIDSYSLOG_POOL_SIZE bumped to 3 lets the SolidSyslogPool fixture's + * FillPool walk three slots and exercise multi-instance Create/Destroy. */ #define SOLIDSYSLOG_MAX_MESSAGE_SIZE 512 #define SOLIDSYSLOG_POSIX_MESSAGE_QUEUE_BUFFER_POOL_SIZE 2U #define SOLIDSYSLOG_STD_ATOMIC_COUNTER_POOL_SIZE 2U #define SOLIDSYSLOG_WINDOWS_ATOMIC_COUNTER_POOL_SIZE 2U +#define SOLIDSYSLOG_POOL_SIZE 3U diff --git a/Tests/SolidSyslogBlockStoreDrainOrderingTest.cpp b/Tests/SolidSyslogBlockStoreDrainOrderingTest.cpp index 79b67a09..fa20d23e 100644 --- a/Tests/SolidSyslogBlockStoreDrainOrderingTest.cpp +++ b/Tests/SolidSyslogBlockStoreDrainOrderingTest.cpp @@ -221,6 +221,7 @@ TEST_GROUP_BASE(ServiceDrainInterleave, DrainTestFixtureBase) * until Service drains them (unlike BufferFake which only keeps * the last one). */ uint8_t bufferRing[SOLIDSYSLOG_CIRCULAR_BUFFER_RING_BYTES(16)] = {}; + struct SolidSyslog* solidSyslog = nullptr; struct SolidSyslogStore* store = nullptr; struct SolidSyslogMutex* mutex = nullptr; struct SolidSyslogBuffer* buffer = nullptr; @@ -236,7 +237,8 @@ TEST_GROUP_BASE(ServiceDrainInterleave, DrainTestFixtureBase) void teardown() override { - SolidSyslog_Destroy(); + SolidSyslog_Destroy(solidSyslog); + solidSyslog = nullptr; if (store != nullptr) { SolidSyslogBlockStore_Destroy(store); @@ -261,7 +263,7 @@ TEST_GROUP_BASE(ServiceDrainInterleave, DrainTestFixtureBase) sysCfg.Buffer = buffer; sysCfg.Sender = &spy.Base; sysCfg.Store = store; - SolidSyslog_Create(&sysCfg); + solidSyslog = SolidSyslog_Create(&sysCfg); } /* Push one record into the buffer with sequenceId encoded in the first @@ -278,11 +280,11 @@ TEST_GROUP_BASE(ServiceDrainInterleave, DrainTestFixtureBase) SolidSyslogBuffer_Write(buffer, buf.data(), buf.size()); } - static void ServiceTickUntilQuiet(size_t cap) + void ServiceTickUntilQuiet(size_t cap) const { for (size_t i = 0; i < cap; ++i) { - SolidSyslog_Service(); + SolidSyslog_Service(solidSyslog); } } }; @@ -307,7 +309,7 @@ TEST(ServiceDrainInterleave, DiscardNewestDoesNotLetNewestBypassOldestOnRecovery /* Pre-outage send: msg 1 flows buffer -> store -> sender successfully. */ Enqueue(1, cfg.PayloadSize); - SolidSyslog_Service(); + SolidSyslog_Service(solidSyslog); LONGS_EQUAL(1U, spy.successfulSends.size()); /* Outage begins. */ @@ -316,7 +318,7 @@ TEST(ServiceDrainInterleave, DiscardNewestDoesNotLetNewestBypassOldestOnRecovery /* Two messages fit into the 2-block store. */ Enqueue(2, cfg.PayloadSize); Enqueue(3, cfg.PayloadSize); - SolidSyslog_Service(); + SolidSyslog_Service(solidSyslog); /* Message 11 arrives still in outage — it lands in the buffer but * hasn't been pulled by Service yet at the moment the oracle resumes. */ diff --git a/Tests/SolidSyslogPoolTest.cpp b/Tests/SolidSyslogPoolTest.cpp new file mode 100644 index 00000000..5a13c5e7 --- /dev/null +++ b/Tests/SolidSyslogPoolTest.cpp @@ -0,0 +1,188 @@ +#include "CppUTest/TestHarness.h" + +#include "ConfigLockFake.h" +#include "ErrorHandlerFake.h" +#include "SenderFake.h" +#include "SolidSyslog.h" +#include "SolidSyslogConfig.h" +#include "SolidSyslogErrorMessages.h" +#include "SolidSyslogNullStore.h" +#include "SolidSyslogPassthroughBuffer.h" +#include "SolidSyslogPrival.h" +#include "SolidSyslogTunables.h" +#include "TestUtils.h" + +using namespace CososoTesting; // NOLINT(google-build-using-namespace) -- test-file scope only; brings ONCE/NEVER into scope for CALLED_FAKE + +// NOLINTBEGIN(cppcoreguidelines-macro-usage,cppcoreguidelines-avoid-do-while) -- macros preserve __FILE__/__LINE__ at the call site + +// Asserts handle is non-null and not one of the slots in pool. +#define CHECK_IS_FALLBACK(handle, pool) \ + do \ + { \ + CHECK_TEXT((handle) != nullptr, "Fallback handle was nullptr"); \ + for (auto* slot : (pool)) \ + { \ + CHECK_TEXT(slot != nullptr, "pool slot was nullptr (FillPool failed?)"); \ + CHECK_TEXT((handle) != slot, "Fallback handle collided with a pool slot"); \ + } \ + } while (0) + +// NOLINTEND(cppcoreguidelines-macro-usage,cppcoreguidelines-avoid-do-while) + +// clang-format off +TEST_GROUP(SolidSyslogPool) +{ + struct SolidSyslogSender* fakeSender = nullptr; + struct SolidSyslogBuffer* buffer = nullptr; + SolidSyslogConfig config{}; + // cppcheck-suppress constVariable -- assigned in test bodies; cppcheck does not model CppUTest lifecycle + struct SolidSyslog* pooled[SOLIDSYSLOG_POOL_SIZE] = {}; + struct SolidSyslog* overflow = nullptr; + + void setup() override + { + fakeSender = SenderFake_Create(); + buffer = SolidSyslogPassthroughBuffer_Create(fakeSender); + config = {buffer, fakeSender, nullptr, nullptr, nullptr, nullptr, + SolidSyslogNullStore_Get(), nullptr, 0}; + } + + void teardown() override + { + for (auto* handle : pooled) + { + if (handle != nullptr) + { + SolidSyslog_Destroy(handle); + } + } + // cppcheck-suppress knownConditionTrueFalse -- assigned in test bodies; cppcheck does not model CppUTest lifecycle + if (overflow != nullptr) + { + SolidSyslog_Destroy(overflow); + } + SolidSyslogPassthroughBuffer_Destroy(buffer); + SenderFake_Destroy(fakeSender); + ConfigLockFake_Uninstall(); + ErrorHandlerFake_Uninstall(); + } + + void FillPool() + { + for (auto*& slot : pooled) + { + slot = SolidSyslog_Create(&config); + } + } +}; + +// clang-format on + +TEST(SolidSyslogPool, FillingPoolThenOverflowReturnsDistinctFallback) +{ + FillPool(); + + overflow = SolidSyslog_Create(&config); + + CHECK_IS_FALLBACK(overflow, pooled); +} + +TEST(SolidSyslogPool, ExhaustedCreateReportsError) +{ + ErrorHandlerFake_Install(nullptr); + FillPool(); + + overflow = SolidSyslog_Create(&config); + + CALLED_FAKE(ErrorHandlerFake_Handle, ONCE); + LONGS_EQUAL(SOLIDSYSLOG_SEVERITY_ERROR, ErrorHandlerFake_LastSeverity()); + STRCMP_EQUAL(SOLIDSYSLOG_ERROR_MSG_SOLIDSYSLOG_POOL_EXHAUSTED, ErrorHandlerFake_LastMessage()); +} + +TEST(SolidSyslogPool, FallbackLogSilentlyDropsTheMessage) +{ + FillPool(); + overflow = SolidSyslog_Create(&config); + + SolidSyslogMessage message = {SOLIDSYSLOG_FACILITY_LOCAL0, SOLIDSYSLOG_SEVERITY_INFORMATIONAL, nullptr, nullptr}; + SolidSyslog_Log(overflow, &message); + SolidSyslog_Service(overflow); + + CALLED_FAKE_ON(SenderFake_Send, fakeSender, NEVER); +} + +TEST(SolidSyslogPool, CreateAcquiresAndReleasesConfigLockOnFirstFreeSlot) +{ + ConfigLockFake_Install(); + + pooled[0] = SolidSyslog_Create(&config); + + CALLED_FAKE(ConfigLockFake_Lock, ONCE); + CALLED_FAKE(ConfigLockFake_Unlock, ONCE); +} + +TEST(SolidSyslogPool, CreateLocksOncePerSlotProbedWhenPoolIsFull) +{ + FillPool(); + ConfigLockFake_Install(); + + overflow = SolidSyslog_Create(&config); + + LONGS_EQUAL(SOLIDSYSLOG_POOL_SIZE, ConfigLockFake_LockCallCount()); + LONGS_EQUAL(SOLIDSYSLOG_POOL_SIZE, ConfigLockFake_UnlockCallCount()); +} + +TEST(SolidSyslogPool, DestroyOfPooledHandleLocksOnce) +{ + pooled[0] = SolidSyslog_Create(&config); + ConfigLockFake_Install(); + + SolidSyslog_Destroy(pooled[0]); + pooled[0] = nullptr; + + CALLED_FAKE(ConfigLockFake_Lock, ONCE); + CALLED_FAKE(ConfigLockFake_Unlock, ONCE); +} + +TEST(SolidSyslogPool, DestroyOfUnknownHandleDoesNotLock) +{ + ConfigLockFake_Install(); + /* Any non-pool address — cast a stack byte, value never dereferenced. */ + char stackByte = 0; + // NOLINTNEXTLINE(cppcoreguidelines-pro-type-reinterpret-cast) -- forging an "unknown" handle to drive the bad-setup path + auto* stranger = reinterpret_cast(&stackByte); + + SolidSyslog_Destroy(stranger); + + CALLED_FAKE(ConfigLockFake_Lock, NEVER); + CALLED_FAKE(ConfigLockFake_Unlock, NEVER); +} + +TEST(SolidSyslogPool, DestroyOfUnknownHandleReportsWarning) +{ + ErrorHandlerFake_Install(nullptr); + char stackByte = 0; + // NOLINTNEXTLINE(cppcoreguidelines-pro-type-reinterpret-cast) -- forging an "unknown" handle to drive the bad-setup path + auto* stranger = reinterpret_cast(&stackByte); + + SolidSyslog_Destroy(stranger); + + CALLED_FAKE(ErrorHandlerFake_Handle, ONCE); + LONGS_EQUAL(SOLIDSYSLOG_SEVERITY_WARNING, ErrorHandlerFake_LastSeverity()); + STRCMP_EQUAL(SOLIDSYSLOG_ERROR_MSG_SOLIDSYSLOG_UNKNOWN_DESTROY, ErrorHandlerFake_LastMessage()); +} + +TEST(SolidSyslogPool, DestroyOfStaleHandleReportsWarning) +{ + pooled[0] = SolidSyslog_Create(&config); + SolidSyslog_Destroy(pooled[0]); + ErrorHandlerFake_Install(nullptr); + + SolidSyslog_Destroy(pooled[0]); + pooled[0] = nullptr; + + CALLED_FAKE(ErrorHandlerFake_Handle, ONCE); + LONGS_EQUAL(SOLIDSYSLOG_SEVERITY_WARNING, ErrorHandlerFake_LastSeverity()); + STRCMP_EQUAL(SOLIDSYSLOG_ERROR_MSG_SOLIDSYSLOG_UNKNOWN_DESTROY, ErrorHandlerFake_LastMessage()); +} diff --git a/Tests/SolidSyslogPosixMessageQueueBufferTest.cpp b/Tests/SolidSyslogPosixMessageQueueBufferTest.cpp index 71de18ee..26463090 100644 --- a/Tests/SolidSyslogPosixMessageQueueBufferTest.cpp +++ b/Tests/SolidSyslogPosixMessageQueueBufferTest.cpp @@ -107,14 +107,14 @@ TEST(SolidSyslogPosixMessageQueueBuffer, ServiceSendsMessageWrittenViaLog) struct SolidSyslogSender* fakeSender = SenderFake_Create(); SolidSyslogStore* nullStore = SolidSyslogNullStore_Get(); SolidSyslogConfig config = {buffer, fakeSender, nullptr, nullptr, nullptr, nullptr, nullStore, nullptr, 0}; - SolidSyslog_Create(&config); + struct SolidSyslog* solidSyslog = SolidSyslog_Create(&config); SolidSyslogMessage message = {SOLIDSYSLOG_FACILITY_LOCAL0, SOLIDSYSLOG_SEVERITY_INFORMATIONAL, nullptr, nullptr}; - SolidSyslog_Log(&message); - SolidSyslog_Service(); + SolidSyslog_Log(solidSyslog, &message); + SolidSyslog_Service(solidSyslog); CALLED_FAKE_ON(SenderFake_Send, fakeSender, ONCE); - SolidSyslog_Destroy(); + SolidSyslog_Destroy(solidSyslog); SenderFake_Destroy(fakeSender); } diff --git a/Tests/SolidSyslogTest.cpp b/Tests/SolidSyslogTest.cpp index c1b5ea1a..45546ca1 100644 --- a/Tests/SolidSyslogTest.cpp +++ b/Tests/SolidSyslogTest.cpp @@ -356,6 +356,8 @@ TEST_GROUP(SolidSyslog) SolidSyslogConfig config; SolidSyslogMessage message; // cppcheck-suppress variableScope -- member of TEST_GROUP; scope managed by CppUTest macro + struct SolidSyslog *solidSyslog; + // cppcheck-suppress variableScope -- member of TEST_GROUP; scope managed by CppUTest macro SolidSyslogBuffer *buffer; // cppcheck-suppress variableScope -- member of TEST_GROUP; scope managed by CppUTest macro SolidSyslogStore *store; @@ -382,14 +384,14 @@ TEST_GROUP(SolidSyslog) metaSd = nullptr; timeQualitySd = nullptr; config = {buffer, nullptr, nullptr, StringFake_GetHostname, StringFake_GetAppName, StringFake_GetProcessId, store, nullptr, 0}; - SolidSyslog_Create(&config); + solidSyslog = SolidSyslog_Create(&config); // cppcheck-suppress unreadVariable -- read via Log() through &message; cppcheck does not model CppUTest macros message = {SOLIDSYSLOG_FACILITY_LOCAL0, SOLIDSYSLOG_SEVERITY_INFORMATIONAL, nullptr, nullptr}; } void teardown() override { - SolidSyslog_Destroy(); + SolidSyslog_Destroy(solidSyslog); if (timeQualitySd != nullptr) { SolidSyslogTimeQualitySd_Destroy(timeQualitySd); @@ -406,9 +408,9 @@ TEST_GROUP(SolidSyslog) SenderFake_Destroy(fakeSender); } - void Log() const + void Log() { - SolidSyslog_Log(&message); + SolidSyslog_Log(solidSyslog, &message); } [[nodiscard]] const char *lastMessage() const @@ -511,8 +513,8 @@ TEST(SolidSyslog, VersionIs1) TEST(SolidSyslog, NullGetHostnameProducesNilvalue) { config.GetHostname = nullptr; - SolidSyslog_Destroy(); - SolidSyslog_Create(&config); + SolidSyslog_Destroy(solidSyslog); + solidSyslog = SolidSyslog_Create(&config); Log(); CHECK_HOSTNAME("-"); } @@ -537,8 +539,8 @@ TEST(SolidSyslog, HostnameCallbackIsInvokedPerLogCall) TEST(SolidSyslog, NullGetAppNameProducesNilvalue) { config.GetAppName = nullptr; - SolidSyslog_Destroy(); - SolidSyslog_Create(&config); + SolidSyslog_Destroy(solidSyslog); + solidSyslog = SolidSyslog_Create(&config); Log(); CHECK_APP_NAME("-"); } @@ -563,8 +565,8 @@ TEST(SolidSyslog, AppNameCallbackIsInvokedPerLogCall) TEST(SolidSyslog, NullGetProcessIdProducesNilvalue) { config.GetProcessId = nullptr; - SolidSyslog_Destroy(); - SolidSyslog_Create(&config); + SolidSyslog_Destroy(solidSyslog); + solidSyslog = SolidSyslog_Create(&config); Log(); CHECK_PROCID("-"); } @@ -648,8 +650,8 @@ TEST(SolidSyslog, InjectedSdObjectFormatIsCalledDuringLog) SolidSyslogStructuredData* sdList[] = {&sdSpy}; config.Sd = sdList; config.SdCount = 1; - SolidSyslog_Destroy(); - SolidSyslog_Create(&config); + SolidSyslog_Destroy(solidSyslog); + solidSyslog = SolidSyslog_Create(&config); Log(); STRCMP_EQUAL("[spy]", SyslogField(lastMessage(), SYSLOG_FIELD_SDATA).c_str()); } @@ -663,8 +665,8 @@ TEST(SolidSyslog, MetaSdProducesSequenceIdInStructuredData) SolidSyslogStructuredData* sdList[] = {metaSd}; config.Sd = sdList; config.SdCount = 1; - SolidSyslog_Destroy(); - SolidSyslog_Create(&config); + SolidSyslog_Destroy(solidSyslog); + solidSyslog = SolidSyslog_Create(&config); Log(); STRCMP_EQUAL("[meta sequenceId=\"1\"]", SyslogField(lastMessage(), SYSLOG_FIELD_SDATA).c_str()); } @@ -678,8 +680,8 @@ TEST(SolidSyslog, MetaSdSequenceIdIncrementsAcrossLogCalls) SolidSyslogStructuredData* sdList[] = {metaSd}; config.Sd = sdList; config.SdCount = 1; - SolidSyslog_Destroy(); - SolidSyslog_Create(&config); + SolidSyslog_Destroy(solidSyslog); + solidSyslog = SolidSyslog_Create(&config); Log(); Log(); STRCMP_EQUAL("[meta sequenceId=\"2\"]", SyslogField(lastMessage(), SYSLOG_FIELD_SDATA).c_str()); @@ -694,8 +696,8 @@ TEST(SolidSyslog, MsgFieldPreservedWithMetaSd) SolidSyslogStructuredData* sdList[] = {metaSd}; config.Sd = sdList; config.SdCount = 1; - SolidSyslog_Destroy(); - SolidSyslog_Create(&config); + SolidSyslog_Destroy(solidSyslog); + solidSyslog = SolidSyslog_Create(&config); message.Msg = "hello world"; Log(); STRCMP_EQUAL("hello world", SyslogMsg(lastMessage()).c_str()); @@ -706,8 +708,8 @@ TEST(SolidSyslog, MultipleSdItemsAreConcatenated) SolidSyslogStructuredData* sdList[] = {&sdSpy, &sdSpy2}; config.Sd = sdList; config.SdCount = 2; - SolidSyslog_Destroy(); - SolidSyslog_Create(&config); + SolidSyslog_Destroy(solidSyslog); + solidSyslog = SolidSyslog_Create(&config); Log(); STRCMP_EQUAL("[spy][spy2]", SyslogField(lastMessage(), SYSLOG_FIELD_SDATA).c_str()); } @@ -717,8 +719,8 @@ TEST(SolidSyslog, SingleSdReturningZeroBytesProducesNilvalue) SolidSyslogStructuredData* sdList[] = {&sdFail}; config.Sd = sdList; config.SdCount = 1; - SolidSyslog_Destroy(); - SolidSyslog_Create(&config); + SolidSyslog_Destroy(solidSyslog); + solidSyslog = SolidSyslog_Create(&config); Log(); STRCMP_EQUAL("-", SyslogField(lastMessage(), SYSLOG_FIELD_SDATA).c_str()); } @@ -728,8 +730,8 @@ TEST(SolidSyslog, FailingSdIsSkippedWhenOtherSdSucceeds) SolidSyslogStructuredData* sdList[] = {&sdFail, &sdSpy}; config.Sd = sdList; config.SdCount = 2; - SolidSyslog_Destroy(); - SolidSyslog_Create(&config); + SolidSyslog_Destroy(solidSyslog); + solidSyslog = SolidSyslog_Create(&config); Log(); STRCMP_EQUAL("[spy]", SyslogField(lastMessage(), SYSLOG_FIELD_SDATA).c_str()); } @@ -739,8 +741,8 @@ TEST(SolidSyslog, AllSdFailingProducesNilvalue) SolidSyslogStructuredData* sdList[] = {&sdFail, &sdFail}; config.Sd = sdList; config.SdCount = 2; - SolidSyslog_Destroy(); - SolidSyslog_Create(&config); + SolidSyslog_Destroy(solidSyslog); + solidSyslog = SolidSyslog_Create(&config); Log(); STRCMP_EQUAL("-", SyslogField(lastMessage(), SYSLOG_FIELD_SDATA).c_str()); } @@ -755,8 +757,8 @@ TEST(SolidSyslog, MetaSdAndTimeQualitySdCoexistInSdArray) SolidSyslogStructuredData* sdList[] = {metaSd, timeQualitySd}; config.Sd = sdList; config.SdCount = 2; - SolidSyslog_Destroy(); - SolidSyslog_Create(&config); + SolidSyslog_Destroy(solidSyslog); + solidSyslog = SolidSyslog_Create(&config); Log(); STRCMP_EQUAL( "[meta sequenceId=\"1\"][timeQuality tzKnown=\"1\" isSynced=\"1\"]", @@ -881,8 +883,8 @@ TEST_GROUP_BASE(SolidSyslogTimestamp, TEST_GROUP_CppUTestGroupSolidSyslog) TEST_GROUP_CppUTestGroupSolidSyslog::setup(); stubTimestamp = {TEST_YEAR, TEST_MONTH, TEST_DAY, TEST_HOUR, TEST_MINUTE, TEST_SECOND, TEST_MICROSECOND, TEST_UTC_OFFSET}; config.Clock = StubClock; - SolidSyslog_Destroy(); - SolidSyslog_Create(&config); + SolidSyslog_Destroy(solidSyslog); + solidSyslog = SolidSyslog_Create(&config); } }; @@ -891,8 +893,8 @@ TEST_GROUP_BASE(SolidSyslogTimestamp, TEST_GROUP_CppUTestGroupSolidSyslog) TEST(SolidSyslogTimestamp, NullClockProducesNilvalue) { config.Clock = nullptr; - SolidSyslog_Destroy(); - SolidSyslog_Create(&config); + SolidSyslog_Destroy(solidSyslog); + solidSyslog = SolidSyslog_Create(&config); Log(); CHECK_TIMESTAMP_IS_NILVALUE(); } @@ -1270,8 +1272,8 @@ TEST(SolidSyslog, AllFieldsAtMaxLengthProducesValidMessage) StringFake_SetProcessId(maxProcessId.c_str()); stubTimestamp = {9999, 12, 31, 23, 59, 59, 999999, 840}; config.Clock = StubClock; - SolidSyslog_Destroy(); - SolidSyslog_Create(&config); + SolidSyslog_Destroy(solidSyslog); + solidSyslog = SolidSyslog_Create(&config); message.Facility = SOLIDSYSLOG_FACILITY_LOCAL7; message.Severity = SOLIDSYSLOG_SEVERITY_DEBUG; Log(); @@ -1308,18 +1310,18 @@ TEST(SolidSyslog, ServiceSendsMessageReadFromBuffer) SolidSyslogBuffer* fakeBuffer = BufferFake_Create(); SolidSyslogConfig serviceConfig = {fakeBuffer, fakeSender, nullptr, nullptr, nullptr, nullptr, store, nullptr, 0}; - SolidSyslog_Destroy(); - SolidSyslog_Create(&serviceConfig); + SolidSyslog_Destroy(solidSyslog); + solidSyslog = SolidSyslog_Create(&serviceConfig); SolidSyslogBuffer_Write(fakeBuffer, "test", 4); SenderFake_Reset(fakeSender); - SolidSyslog_Service(); + SolidSyslog_Service(solidSyslog); CALLED_FAKE_ON(SenderFake_Send, fakeSender, ONCE); STRCMP_EQUAL("test", SenderFake_LastBufferAsString(fakeSender)); - SolidSyslog_Destroy(); - SolidSyslog_Create(&config); + SolidSyslog_Destroy(solidSyslog); + solidSyslog = SolidSyslog_Create(&config); BufferFake_Destroy(); } @@ -1330,18 +1332,18 @@ TEST(SolidSyslog, ServiceSendsBufferedMessageWithNullStore) SolidSyslogConfig serviceConfig = {fakeBuffer, fakeSender, nullptr, nullptr, nullptr, nullptr, nullStore, nullptr, 0}; - SolidSyslog_Destroy(); - SolidSyslog_Create(&serviceConfig); + SolidSyslog_Destroy(solidSyslog); + solidSyslog = SolidSyslog_Create(&serviceConfig); SolidSyslogBuffer_Write(fakeBuffer, "test", 4); SenderFake_Reset(fakeSender); - SolidSyslog_Service(); + SolidSyslog_Service(solidSyslog); CALLED_FAKE_ON(SenderFake_Send, fakeSender, ONCE); STRCMP_EQUAL("test", SenderFake_LastBufferAsString(fakeSender)); - SolidSyslog_Destroy(); - SolidSyslog_Create(&config); + SolidSyslog_Destroy(solidSyslog); + solidSyslog = SolidSyslog_Create(&config); BufferFake_Destroy(); } @@ -1352,18 +1354,18 @@ TEST(SolidSyslog, ServiceSendsFromStoreWhenHasUnsent) SolidSyslogConfig serviceConfig = {fakeBuffer, fakeSender, nullptr, nullptr, nullptr, nullptr, fakeStore, nullptr, 0}; - SolidSyslog_Destroy(); - SolidSyslog_Create(&serviceConfig); + SolidSyslog_Destroy(solidSyslog); + solidSyslog = SolidSyslog_Create(&serviceConfig); SolidSyslogStore_Write(fakeStore, "stored", 6); SenderFake_Reset(fakeSender); - SolidSyslog_Service(); + SolidSyslog_Service(solidSyslog); CALLED_FAKE_ON(SenderFake_Send, fakeSender, ONCE); STRCMP_EQUAL("stored", SenderFake_LastBufferAsString(fakeSender)); - SolidSyslog_Destroy(); - SolidSyslog_Create(&config); + SolidSyslog_Destroy(solidSyslog); + solidSyslog = SolidSyslog_Create(&config); StoreFake_Destroy(); BufferFake_Destroy(); } @@ -1375,16 +1377,16 @@ TEST(SolidSyslog, ServiceMarksSentAfterSuccessfulSend) SolidSyslogConfig serviceConfig = {fakeBuffer, fakeSender, nullptr, nullptr, nullptr, nullptr, fakeStore, nullptr, 0}; - SolidSyslog_Destroy(); - SolidSyslog_Create(&serviceConfig); + SolidSyslog_Destroy(solidSyslog); + solidSyslog = SolidSyslog_Create(&serviceConfig); SolidSyslogStore_Write(fakeStore, "stored", 6); - SolidSyslog_Service(); + SolidSyslog_Service(solidSyslog); CHECK_FALSE(SolidSyslogStore_HasUnsent(fakeStore)); - SolidSyslog_Destroy(); - SolidSyslog_Create(&config); + SolidSyslog_Destroy(solidSyslog); + solidSyslog = SolidSyslog_Create(&config); StoreFake_Destroy(); BufferFake_Destroy(); } @@ -1396,17 +1398,17 @@ TEST(SolidSyslog, ServiceDoesNotMarkSentOnSendFailure) SolidSyslogConfig serviceConfig = {fakeBuffer, fakeSender, nullptr, nullptr, nullptr, nullptr, fakeStore, nullptr, 0}; - SolidSyslog_Destroy(); - SolidSyslog_Create(&serviceConfig); + SolidSyslog_Destroy(solidSyslog); + solidSyslog = SolidSyslog_Create(&serviceConfig); SolidSyslogStore_Write(fakeStore, "stored", 6); SenderFake_FailNextSend(fakeSender); - SolidSyslog_Service(); + SolidSyslog_Service(solidSyslog); CHECK_TRUE(SolidSyslogStore_HasUnsent(fakeStore)); - SolidSyslog_Destroy(); - SolidSyslog_Create(&config); + SolidSyslog_Destroy(solidSyslog); + solidSyslog = SolidSyslog_Create(&config); StoreFake_Destroy(); BufferFake_Destroy(); } @@ -1418,12 +1420,12 @@ TEST(SolidSyslog, ServiceWritesBufferMessageToStore) SolidSyslogConfig serviceConfig = {fakeBuffer, fakeSender, nullptr, nullptr, nullptr, nullptr, fakeStore, nullptr, 0}; - SolidSyslog_Destroy(); - SolidSyslog_Create(&serviceConfig); + SolidSyslog_Destroy(solidSyslog); + solidSyslog = SolidSyslog_Create(&serviceConfig); SolidSyslogBuffer_Write(fakeBuffer, "buffered", 8); SenderFake_FailNextSend(fakeSender); - SolidSyslog_Service(); + SolidSyslog_Service(solidSyslog); char readData[512]; size_t readSize = 0; @@ -1431,8 +1433,8 @@ TEST(SolidSyslog, ServiceWritesBufferMessageToStore) LONGS_EQUAL(8, readSize); MEMCMP_EQUAL("buffered", readData, 8); - SolidSyslog_Destroy(); - SolidSyslog_Create(&config); + SolidSyslog_Destroy(solidSyslog); + solidSyslog = SolidSyslog_Create(&config); StoreFake_Destroy(); BufferFake_Destroy(); } @@ -1444,18 +1446,18 @@ TEST(SolidSyslog, ServiceSendsStoreMessageNotBufferMessage) SolidSyslogConfig serviceConfig = {fakeBuffer, fakeSender, nullptr, nullptr, nullptr, nullptr, fakeStore, nullptr, 0}; - SolidSyslog_Destroy(); - SolidSyslog_Create(&serviceConfig); + SolidSyslog_Destroy(solidSyslog); + solidSyslog = SolidSyslog_Create(&serviceConfig); SolidSyslogStore_Write(fakeStore, "old", 3); SolidSyslogBuffer_Write(fakeBuffer, "new", 3); SenderFake_Reset(fakeSender); - SolidSyslog_Service(); + SolidSyslog_Service(solidSyslog); STRCMP_EQUAL("old", SenderFake_LastBufferAsString(fakeSender)); - SolidSyslog_Destroy(); - SolidSyslog_Create(&config); + SolidSyslog_Destroy(solidSyslog); + solidSyslog = SolidSyslog_Create(&config); StoreFake_Destroy(); BufferFake_Destroy(); } @@ -1471,18 +1473,18 @@ TEST(SolidSyslog, ServiceDoesNotBypassToSenderWhenNonTransientStoreRejectsWrite) SolidSyslogConfig serviceConfig = {fakeBuffer, fakeSender, nullptr, nullptr, nullptr, nullptr, fakeStore, nullptr, 0}; - SolidSyslog_Destroy(); - SolidSyslog_Create(&serviceConfig); + SolidSyslog_Destroy(solidSyslog); + solidSyslog = SolidSyslog_Create(&serviceConfig); SolidSyslogBuffer_Write(fakeBuffer, "direct", 6); StoreFake_FailNextWrite(); SenderFake_Reset(fakeSender); - SolidSyslog_Service(); + SolidSyslog_Service(solidSyslog); CALLED_FAKE_ON(SenderFake_Send, fakeSender, NEVER); - SolidSyslog_Destroy(); - SolidSyslog_Create(&config); + SolidSyslog_Destroy(solidSyslog); + solidSyslog = SolidSyslog_Create(&config); StoreFake_Destroy(); BufferFake_Destroy(); } @@ -1494,18 +1496,18 @@ TEST(SolidSyslog, ServiceDoesNotSendWhenStoreReadFails) SolidSyslogConfig serviceConfig = {fakeBuffer, fakeSender, nullptr, nullptr, nullptr, nullptr, fakeStore, nullptr, 0}; - SolidSyslog_Destroy(); - SolidSyslog_Create(&serviceConfig); + SolidSyslog_Destroy(solidSyslog); + solidSyslog = SolidSyslog_Create(&serviceConfig); SolidSyslogStore_Write(fakeStore, "stored", 6); StoreFake_FailNextRead(); SenderFake_Reset(fakeSender); - SolidSyslog_Service(); + SolidSyslog_Service(solidSyslog); CALLED_FAKE_ON(SenderFake_Send, fakeSender, NEVER); - SolidSyslog_Destroy(); - SolidSyslog_Create(&config); + SolidSyslog_Destroy(solidSyslog); + solidSyslog = SolidSyslog_Create(&config); StoreFake_Destroy(); BufferFake_Destroy(); } @@ -1517,20 +1519,20 @@ TEST(SolidSyslog, ServiceDoesNotMarkSentWhenSendingFromBuffer) SolidSyslogConfig serviceConfig = {fakeBuffer, fakeSender, nullptr, nullptr, nullptr, nullptr, fakeStore, nullptr, 0}; - SolidSyslog_Destroy(); - SolidSyslog_Create(&serviceConfig); + SolidSyslog_Destroy(solidSyslog); + solidSyslog = SolidSyslog_Create(&serviceConfig); SolidSyslogStore_Write(fakeStore, "in-store", 8); SolidSyslogStore_MarkSent(fakeStore); SolidSyslogBuffer_Write(fakeBuffer, "from-buffer", 11); SenderFake_Reset(fakeSender); - SolidSyslog_Service(); + SolidSyslog_Service(solidSyslog); CALLED_FAKE_ON(SenderFake_Send, fakeSender, ONCE); STRCMP_EQUAL("from-buffer", SenderFake_LastBufferAsString(fakeSender)); - SolidSyslog_Destroy(); - SolidSyslog_Create(&config); + SolidSyslog_Destroy(solidSyslog); + solidSyslog = SolidSyslog_Create(&config); StoreFake_Destroy(); BufferFake_Destroy(); } @@ -1545,6 +1547,7 @@ TEST_GROUP(SolidSyslogServiceEagerDrain) { static constexpr size_t BUFFER_BYTES = 256; + struct SolidSyslog* solidSyslog = nullptr; struct SolidSyslogSender* fakeSender = nullptr; struct SolidSyslogBuffer* circularBuffer = nullptr; struct SolidSyslogStore* fakeStore = nullptr; @@ -1562,12 +1565,13 @@ TEST_GROUP(SolidSyslogServiceEagerDrain) serviceConfig.Buffer = circularBuffer; serviceConfig.Sender = fakeSender; serviceConfig.Store = fakeStore; - SolidSyslog_Create(&serviceConfig); + // cppcheck-suppress unreadVariable -- read via Service(solidSyslog) in tests; cppcheck does not model CppUTest macros + solidSyslog = SolidSyslog_Create(&serviceConfig); } void teardown() override { - SolidSyslog_Destroy(); + SolidSyslog_Destroy(solidSyslog); StoreFake_Destroy(); SolidSyslogCircularBuffer_Destroy(circularBuffer); SenderFake_Destroy(fakeSender); @@ -1582,7 +1586,7 @@ TEST(SolidSyslogServiceEagerDrain, AllBufferedMessagesReachStoreInOneTickWhenSen SolidSyslogBuffer_Write(circularBuffer, "msg2", 4); SolidSyslogBuffer_Write(circularBuffer, "msg3", 4); SenderFake_FailNextSend(fakeSender); - SolidSyslog_Service(); + SolidSyslog_Service(solidSyslog); CALLED_FAKE_ON(StoreFake_Write, fakeStore, THRICE); } @@ -1593,11 +1597,11 @@ TEST(SolidSyslogServiceEagerDrain, StoredMessagesDrainInFifoOrderAcrossTicks) SolidSyslogBuffer_Write(circularBuffer, "m2", 2); SolidSyslogBuffer_Write(circularBuffer, "m3", 2); - SolidSyslog_Service(); + SolidSyslog_Service(solidSyslog); STRCMP_EQUAL("m1", SenderFake_LastBufferAsString(fakeSender)); - SolidSyslog_Service(); + SolidSyslog_Service(solidSyslog); STRCMP_EQUAL("m2", SenderFake_LastBufferAsString(fakeSender)); - SolidSyslog_Service(); + SolidSyslog_Service(solidSyslog); STRCMP_EQUAL("m3", SenderFake_LastBufferAsString(fakeSender)); CALLED_FAKE_ON(SenderFake_Send, fakeSender, THRICE); } @@ -1609,27 +1613,27 @@ TEST(SolidSyslog, ServiceDoesNothingWhenStoreIsHalted) SolidSyslogConfig serviceConfig = {fakeBuffer, fakeSender, nullptr, nullptr, nullptr, nullptr, fakeStore, nullptr, 0}; - SolidSyslog_Destroy(); - SolidSyslog_Create(&serviceConfig); + SolidSyslog_Destroy(solidSyslog); + solidSyslog = SolidSyslog_Create(&serviceConfig); SolidSyslogBuffer_Write(fakeBuffer, "test", 4); StoreFake_SetHalted(); SenderFake_Reset(fakeSender); - SolidSyslog_Service(); + SolidSyslog_Service(solidSyslog); CALLED_FAKE_ON(SenderFake_Send, fakeSender, NEVER); - SolidSyslog_Destroy(); - SolidSyslog_Create(&config); + SolidSyslog_Destroy(solidSyslog); + solidSyslog = SolidSyslog_Create(&config); StoreFake_Destroy(); BufferFake_Destroy(); } TEST(SolidSyslog, LogAfterDestroyAndRecreateWithNullFunctionsProducesNilvalues) { - SolidSyslog_Destroy(); + SolidSyslog_Destroy(solidSyslog); SolidSyslogConfig nilConfig = {buffer, nullptr, nullptr, nullptr, nullptr, nullptr, store, nullptr, 0}; - SolidSyslog_Create(&nilConfig); + solidSyslog = SolidSyslog_Create(&nilConfig); Log(); CHECK_TIMESTAMP_IS_NILVALUE(); CHECK_HOSTNAME("-"); @@ -1644,10 +1648,11 @@ TEST_GROUP(SolidSyslogLifecycle) SolidSyslogBuffer* buffer = nullptr; SolidSyslogSender* sender = nullptr; SolidSyslogStore* store = nullptr; + struct SolidSyslog* solidSyslog = nullptr; void setup() override { - SolidSyslog_Destroy(); + solidSyslog = nullptr; // cppcheck-suppress unreadVariable -- read via Log() in tests; cppcheck does not model CppUTest macros message = {SOLIDSYSLOG_FACILITY_LOCAL0, SOLIDSYSLOG_SEVERITY_INFORMATIONAL, nullptr, nullptr}; // cppcheck-suppress unreadVariable -- read via validConfig() in tests; cppcheck does not model CppUTest macros @@ -1660,8 +1665,11 @@ TEST_GROUP(SolidSyslogLifecycle) void teardown() override { - SolidSyslog_Destroy(); ErrorHandlerFake_Uninstall(); + if (solidSyslog != nullptr) + { + SolidSyslog_Destroy(solidSyslog); + } BufferFake_Destroy(); SenderFake_Destroy(sender); } @@ -1674,52 +1682,31 @@ TEST_GROUP(SolidSyslogLifecycle) // clang-format on -TEST(SolidSyslogLifecycle, ServiceBeforeCreateDoesNotCrash) -{ - SolidSyslog_Service(); -} - -TEST(SolidSyslogLifecycle, LogBeforeCreateDoesNotCrash) -{ - SolidSyslog_Log(&message); -} - -TEST(SolidSyslogLifecycle, LogBeforeCreateReportsNilBufferUsedOnce) -{ - ErrorHandlerFake_Install(nullptr); - - SolidSyslog_Log(&message); - - CHECK_REPORTED_ERROR(SOLIDSYSLOG_ERROR_MSG_NIL_BUFFER_USED); -} - -TEST(SolidSyslogLifecycle, RepeatedLogBeforeCreateReportsNilBufferUsedOnlyOnce) +TEST(SolidSyslogLifecycle, ServiceWithNullHandleReportsError) { ErrorHandlerFake_Install(nullptr); - SolidSyslog_Log(&message); - SolidSyslog_Log(&message); - SolidSyslog_Log(&message); + SolidSyslog_Service(nullptr); - CHECK_REPORTED_ERROR(SOLIDSYSLOG_ERROR_MSG_NIL_BUFFER_USED); + CHECK_REPORTED_ERROR(SOLIDSYSLOG_ERROR_MSG_SERVICE_NULL_HANDLE); } -TEST(SolidSyslogLifecycle, DestroyReArmsNilBufferReporter) +TEST(SolidSyslogLifecycle, LogWithNullHandleReportsError) { - SolidSyslog_Log(&message); ErrorHandlerFake_Install(nullptr); - SolidSyslog_Destroy(); - SolidSyslog_Log(&message); + SolidSyslog_Log(nullptr, &message); - CHECK_REPORTED_ERROR(SOLIDSYSLOG_ERROR_MSG_NIL_BUFFER_USED); + CHECK_REPORTED_ERROR(SOLIDSYSLOG_ERROR_MSG_LOG_NULL_HANDLE); } TEST(SolidSyslogLifecycle, LogWithNullMessageReportsError) { + SolidSyslogConfig config = validConfig(); + solidSyslog = SolidSyslog_Create(&config); ErrorHandlerFake_Install(nullptr); - SolidSyslog_Log(nullptr); + SolidSyslog_Log(solidSyslog, nullptr); CHECK_REPORTED_ERROR(SOLIDSYSLOG_ERROR_MSG_LOG_NULL_MESSAGE); } @@ -1739,7 +1726,7 @@ TEST(SolidSyslogLifecycle, CreateWithNullBufferReportsError) SolidSyslogConfig config = validConfig(); config.Buffer = nullptr; - SolidSyslog_Create(&config); + solidSyslog = SolidSyslog_Create(&config); CHECK_REPORTED_ERROR(SOLIDSYSLOG_ERROR_MSG_CREATE_NULL_BUFFER); } @@ -1750,7 +1737,7 @@ TEST(SolidSyslogLifecycle, CreateWithNullSenderReportsError) SolidSyslogConfig config = validConfig(); config.Sender = nullptr; - SolidSyslog_Create(&config); + solidSyslog = SolidSyslog_Create(&config); CHECK_REPORTED_ERROR(SOLIDSYSLOG_ERROR_MSG_CREATE_NULL_SENDER); } @@ -1761,80 +1748,48 @@ TEST(SolidSyslogLifecycle, CreateWithNullStoreReportsError) SolidSyslogConfig config = validConfig(); config.Store = nullptr; - SolidSyslog_Create(&config); + solidSyslog = SolidSyslog_Create(&config); CHECK_REPORTED_ERROR(SOLIDSYSLOG_ERROR_MSG_CREATE_NULL_STORE); } -TEST(SolidSyslogLifecycle, ServiceWithNilStoreDrainsThroughToRealSender) +TEST(SolidSyslogLifecycle, ServiceWithDefaultStoreDrainsThroughToRealSender) { SolidSyslogConfig config = validConfig(); config.Store = nullptr; - SolidSyslog_Create(&config); - SolidSyslog_Log(&message); + solidSyslog = SolidSyslog_Create(&config); + SolidSyslog_Log(solidSyslog, &message); - SolidSyslog_Service(); + SolidSyslog_Service(solidSyslog); CALLED_FAKE_ON(SenderFake_Send, sender, ONCE); } -TEST(SolidSyslogLifecycle, ServiceWithNilSenderReportsNilSenderUsed) +TEST(SolidSyslogLifecycle, DestroyWithUnknownHandleReportsWarning) { - SolidSyslogConfig config = validConfig(); - config.Sender = nullptr; - SolidSyslog_Create(&config); - SolidSyslog_Log(&message); + /* Any non-pool address is "unknown" to IndexFromHandle. Cast a stack + byte's address — its value never gets dereferenced, only compared. */ + char stackByte = 0; + // NOLINTNEXTLINE(cppcoreguidelines-pro-type-reinterpret-cast) -- forging an "unknown" handle to drive the bad-setup path + auto* notAHandle = reinterpret_cast(&stackByte); ErrorHandlerFake_Install(nullptr); - SolidSyslog_Service(); + SolidSyslog_Destroy(notAHandle); - CHECK_REPORTED_ERROR(SOLIDSYSLOG_ERROR_MSG_NIL_SENDER_USED); + CALLED_FAKE(ErrorHandlerFake_Handle, ONCE); + LONGS_EQUAL(SOLIDSYSLOG_SEVERITY_WARNING, ErrorHandlerFake_LastSeverity()); + STRCMP_EQUAL(SOLIDSYSLOG_ERROR_MSG_SOLIDSYSLOG_UNKNOWN_DESTROY, ErrorHandlerFake_LastMessage()); } -TEST(SolidSyslogLifecycle, RepeatedServiceWithNilSenderReportsNilSenderUsedOnlyOnce) +TEST(SolidSyslogLifecycle, DestroyWithNullHandleReportsWarning) { - SolidSyslogConfig config = validConfig(); - config.Sender = nullptr; - SolidSyslog_Create(&config); - SolidSyslog_Log(&message); - SolidSyslog_Service(); ErrorHandlerFake_Install(nullptr); - SolidSyslog_Log(&message); - SolidSyslog_Service(); - SolidSyslog_Log(&message); - SolidSyslog_Service(); + SolidSyslog_Destroy(nullptr); - CHECK_NOTHING_REPORTED(); -} - -TEST(SolidSyslogLifecycle, SecondCreateWithoutDestroyReportsAlreadyInitialised) -{ - SolidSyslogConfig config = validConfig(); - SolidSyslog_Create(&config); - ErrorHandlerFake_Install(nullptr); - - SolidSyslog_Create(&config); - - CHECK_REPORTED_ERROR(SOLIDSYSLOG_ERROR_MSG_CREATE_ALREADY_INITIALISED); -} - -TEST(SolidSyslogLifecycle, SecondCreateLeavesFirstConfigInstalled) -{ - SolidSyslogConfig firstConfig = validConfig(); - SolidSyslog_Create(&firstConfig); - SolidSyslogSender* otherSender = SenderFake_Create(); - SolidSyslogConfig secondConfig = validConfig(); - secondConfig.Sender = otherSender; - - SolidSyslog_Create(&secondConfig); - SolidSyslog_Log(&message); - SolidSyslog_Service(); - - CALLED_FAKE_ON(SenderFake_Send, sender, ONCE); - CALLED_FAKE_ON(SenderFake_Send, otherSender, NEVER); - - SenderFake_Destroy(otherSender); + CALLED_FAKE(ErrorHandlerFake_Handle, ONCE); + LONGS_EQUAL(SOLIDSYSLOG_SEVERITY_WARNING, ErrorHandlerFake_LastSeverity()); + STRCMP_EQUAL(SOLIDSYSLOG_ERROR_MSG_SOLIDSYSLOG_UNKNOWN_DESTROY, ErrorHandlerFake_LastMessage()); } TEST(SolidSyslogLifecycle, CreateWithNullConfigDoesNotBlockSubsequentCreate) @@ -1842,9 +1797,9 @@ TEST(SolidSyslogLifecycle, CreateWithNullConfigDoesNotBlockSubsequentCreate) SolidSyslog_Create(nullptr); SolidSyslogConfig config = validConfig(); - SolidSyslog_Create(&config); - SolidSyslog_Log(&message); - SolidSyslog_Service(); + solidSyslog = SolidSyslog_Create(&config); + SolidSyslog_Log(solidSyslog, &message); + SolidSyslog_Service(solidSyslog); CALLED_FAKE_ON(SenderFake_Send, sender, ONCE); } @@ -1852,31 +1807,14 @@ TEST(SolidSyslogLifecycle, CreateWithNullConfigDoesNotBlockSubsequentCreate) TEST(SolidSyslogLifecycle, DestroyClearsInitialisedFlagSoCreateSucceedsAgain) { SolidSyslogConfig config = validConfig(); - SolidSyslog_Create(&config); - SolidSyslog_Destroy(); + solidSyslog = SolidSyslog_Create(&config); + SolidSyslog_Destroy(solidSyslog); ErrorHandlerFake_Install(nullptr); - SolidSyslog_Create(&config); - SolidSyslog_Log(&message); - SolidSyslog_Service(); + solidSyslog = SolidSyslog_Create(&config); + SolidSyslog_Log(solidSyslog, &message); + SolidSyslog_Service(solidSyslog); CHECK_NOTHING_REPORTED(); CALLED_FAKE_ON(SenderFake_Send, sender, ONCE); } - -TEST(SolidSyslogLifecycle, DestroyReArmsNilSenderReporter) -{ - SolidSyslogConfig config = validConfig(); - config.Sender = nullptr; - SolidSyslog_Create(&config); - SolidSyslog_Log(&message); - SolidSyslog_Service(); - - SolidSyslog_Destroy(); - SolidSyslog_Create(&config); - SolidSyslog_Log(&message); - ErrorHandlerFake_Install(nullptr); - SolidSyslog_Service(); - - CHECK_REPORTED_ERROR(SOLIDSYSLOG_ERROR_MSG_NIL_SENDER_USED); -}