diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 7a9ef747..789cc33c 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -709,6 +709,7 @@ jobs: -IPlatform/PlusTcp/Interface -IPlatform/LwipRaw/Interface -IPlatform/FatFs/Interface + -IPlatform/PlusFat/Interface --xml --xml-version=2 Core/Source/ Platform/Atomics/Source/ @@ -720,6 +721,7 @@ jobs: Platform/PlusTcp/Source/ Platform/LwipRaw/Source/ Platform/FatFs/Source/ + Platform/PlusFat/Source/ 2> build/cppcheck/cppcheck-report.xml - name: Upload cppcheck report @@ -756,6 +758,7 @@ jobs: -IPlatform/PlusTcp/Interface -IPlatform/LwipRaw/Interface -IPlatform/FatFs/Interface + -IPlatform/PlusFat/Interface Core/Source/ Platform/Atomics/Source/ Platform/Posix/Source/ @@ -766,6 +769,7 @@ jobs: Platform/PlusTcp/Source/ Platform/LwipRaw/Source/ Platform/FatFs/Source/ + Platform/PlusFat/Source/ - name: Generate cppcheck-misra XML report if: success() || failure() @@ -787,6 +791,7 @@ jobs: -IPlatform/PlusTcp/Interface \ -IPlatform/LwipRaw/Interface \ -IPlatform/FatFs/Interface \ + -IPlatform/PlusFat/Interface \ --xml --xml-version=2 \ Core/Source/ \ Platform/Atomics/Source/ \ @@ -798,6 +803,7 @@ jobs: Platform/PlusTcp/Source/ \ Platform/LwipRaw/Source/ \ Platform/FatFs/Source/ \ + Platform/PlusFat/Source/ \ 2> build/cppcheck-misra/cppcheck-misra-report.xml - name: Upload cppcheck-misra report diff --git a/CLAUDE.md b/CLAUDE.md index a705a30e..e7f32a77 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -421,6 +421,8 @@ live under `Core/Interface/`; platform-specific helpers (the `SolidSyslogPosix*` | `SolidSyslogWindowsFileErrors.h` | Any code installing an error handler that wants to react to WindowsFile-specific events (pointer-identity match on `WindowsFileErrorSource`, switch on `enum SolidSyslogWindowsFileErrors`) | `enum SolidSyslogWindowsFileErrors` (`WINDOWSFILE_ERROR_*` codes + `WINDOWSFILE_ERROR_MAX` bookend), `extern const struct SolidSyslogErrorSource WindowsFileErrorSource`. Integrators ignore if not handling errors per source. | | `SolidSyslogFatFsFile.h` | System setup code on targets using ChaN FatFs as the file layer (bare-metal flash / SD / eMMC, FreeRTOS, Zephyr, NuttX, …) | `SolidSyslogFatFsFile_Create(void)`, `_Destroy(file)` (wraps `f_open` / `f_close` / `f_read` / `f_write` with `f_sync` after every successful write so a power loss never loses a record the BlockStore claimed it stored; integrator supplies `diskio.c` and — if `FF_FS_REENTRANT=1` — `ffsystem.c`). Instance struct lives in a library-internal static pool (E11). Pool-exhaustion fallback is the shared `SolidSyslogNullFile`. | | `SolidSyslogFatFsFileErrors.h` | Any code installing an error handler that wants to react to FatFsFile-specific events (pointer-identity match on `FatFsFileErrorSource`, switch on `enum SolidSyslogFatFsFileErrors`) | `enum SolidSyslogFatFsFileErrors` (`FATFSFILE_ERROR_*` codes + `FATFSFILE_ERROR_MAX` bookend), `extern const struct SolidSyslogErrorSource FatFsFileErrorSource`. Integrators ignore if not handling errors per source. | +| `SolidSyslogPlusFatFile.h` | System setup code on FreeRTOS targets using FreeRTOS-Plus-FAT as the file layer | `SolidSyslogPlusFatFile_Create(void)`, `_Destroy(file)` (wraps `ff_fopen` (`"r+"` then `"w+"` fallback — open-or-create without truncation) / `ff_fclose` / `ff_fread` / `ff_fwrite` with `ff_fflush` after every complete write so a power loss never loses a record the BlockStore claimed it stored; `ff_fseek` / `ff_seteof` for truncate-to-empty; `ff_filelength`, `ff_stat`, `ff_remove`). The open-state is the `FF_FILE*` sentinel (no separate flag). Plus-FAT is FreeRTOS-coupled (`ff_headers.h` pulls `FreeRTOS.h` / `task.h` / `semphr.h`); integrator supplies the `FF_Disk_t` media driver and `FreeRTOSFATConfig.h`. Instance struct lives in a library-internal static pool (E11; role tunable `SOLIDSYSLOG_FILE_POOL_SIZE`). Pool-exhaustion fallback is the shared `SolidSyslogNullFile`. | +| `SolidSyslogPlusFatFileErrors.h` | Any code installing an error handler that wants to react to PlusFatFile-specific events (pointer-identity match on `PlusFatFileErrorSource`, switch on `enum SolidSyslogPlusFatFileErrors`) | `enum SolidSyslogPlusFatFileErrors` (`PLUSFATFILE_ERROR_*` codes + `PLUSFATFILE_ERROR_MAX` bookend), `extern const struct SolidSyslogErrorSource PlusFatFileErrorSource`. Integrators ignore if not handling errors per source. | | `SolidSyslogPosixClock.h` | System setup code using POSIX clock | `SolidSyslogPosixClock_GetTimestamp` | | `SolidSyslogPosixHostname.h` | String callback implementor using POSIX hostname | `SolidSyslogPosixHostname_Get` (writes into `SolidSyslogFormatter*`) | | `SolidSyslogPosixProcessId.h` | String callback implementor using POSIX process ID | `SolidSyslogPosixProcessId_Get` (writes into `SolidSyslogFormatter*`) | diff --git a/CMakeLists.txt b/CMakeLists.txt index ada05bcc..02d9992e 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -304,6 +304,13 @@ if(DEFINED ENV{FATFS_PATH}) add_subdirectory(Platform/FatFs) endif() +# FreeRTOS-Plus-FAT adapters ship as sources via an INTERFACE library too — the +# integrator's FreeRTOSFATConfig.h flows into each consumer's compile. Gated on +# the upstream Plus-FAT sources being present in the build environment. +if(DEFINED ENV{FREERTOS_PLUS_FAT_PATH}) + add_subdirectory(Platform/PlusFat) +endif() + # The cross-compiled FreeRTOS target is selected by the arm-none-eabi # toolchain file (see Bdd/Targets/FreeRtos/cmake/arm-none-eabi.cmake); it # only enters the build when a freertos-cross preset is active. The chosen diff --git a/DEVLOG.md b/DEVLOG.md index ed821406..d68c10bd 100644 --- a/DEVLOG.md +++ b/DEVLOG.md @@ -1,5 +1,51 @@ # Dev Log +## 2026-06-03 — S29.04 Platform/PlusFat adapter pack (host TDD) + +The FreeRTOS-Plus-FAT `SolidSyslogFile` adapter, mirroring the FatFs sibling but +adapted to the `ff_stdio` API. No media driver, no QEMU, no target swap — that is +S29.05; this story is the host-tested pack only. + +### Harness spike first + +Plus-FAT is FreeRTOS-coupled (unlike OS-agnostic ChaN FatFs): `ff_stdio.h` → +`ff_headers.h` pulls `FreeRTOS.h` / `task.h` / `semphr.h`. A throwaway spike proved +a host TU compiles `ff_stdio.h` given (1) a test-local `FreeRTOSFATConfig.h` +(byte-order, CWD TLS index, `portINLINE` fallback — matching Plus-FAT's own +DefaultConf), (2) `configNUM_THREAD_LOCAL_STORAGE_POINTERS >= 3` in the shared +`FreeRtosFakes/FreeRTOSConfig.h` (Plus-FAT keeps errno/CWD/ff_error in TLS slots), +and (3) the kernel + Plus-FAT include dirs. We surface that coupling in the test +include path rather than hiding it. + +### Pool experience copied, behaviour TDD'd + +The pool plumbing (`Static.c`) and its 9-test suite are **copied + renamed +verbatim** from FatFs — proven, agreed coverage, not re-derived. Everything +`ff_*` was driven by genuine red→green: Open (`"r+"`→`"w+"` fallback, +open-or-create without truncation), Close (guarded `ff_fclose`), Read +(`ff_fread`, item-count == requested), Write + per-write `ff_fflush` durability, +SeekTo/Size, Truncate (`ff_fseek(0)`+`ff_seteof`), Exists/Delete. 30 tests, 100% +line+branch by construction. + +### Decisions + +- **`FF_FILE*` sentinel is the open-state** — no separate `IsOpen` bool (FatFs + needs one because its `FIL` is embedded; Plus-FAT's handle is a pointer). +- **Adapter calls `ff_fread`/`ff_fwrite` with `xSize == 1`** so the returned item + count is the byte count; a short read/write is the single failure mode. +- **D.002 11.3 suppression** added for the one `SelfFromBase` downcast, mirroring + every other vtable class. + +### Lesson — TDD, not TDD-costume + +First cut wrote the whole pool allocator to satisfy a trivial `CreateSucceeds`, +then dressed it as a slice. Reverted to the literal minimum; the rule for these +pool classes is copy+rename the proven pool block (plumbing **and** its tests) +for instant agreed coverage, then do real per-op TDD for the rest. Also caught two +invalid reds that passed for free through the `NullFile` fallback +(`IsOpen`-false, `Write`-returns-true) — the genuine driver is asserting the call +reached the fake. + ## 2026-06-03 — S29.03 Extract the shared FreeRTOS BDD-target pipeline The DRY pass S29.01 deferred. The two FreeRTOS target `main.c` files diff --git a/Platform/PlusFat/CMakeLists.txt b/Platform/PlusFat/CMakeLists.txt new file mode 100644 index 00000000..cf33bae6 --- /dev/null +++ b/Platform/PlusFat/CMakeLists.txt @@ -0,0 +1,25 @@ +# FreeRTOS-Plus-FAT adapter pack — shipped as sources, not a precompiled library. +# +# Plus-FAT is header-configured: the integrator's FreeRTOSFATConfig.h flows +# through upstream headers and changes the size/layout of the FF_FILE / +# FF_IOManager objects and the visible API surface. Plus-FAT is also FreeRTOS- +# coupled (ff_headers.h pulls FreeRTOS.h / task.h / semphr.h), so compiling our +# adapter once into libSolidSyslog.a would silently break consumers that use a +# different FreeRTOSFATConfig.h or kernel config. +# +# Instead, this is an INTERFACE library — each consumer (Bdd/Targets/FreeRtos, +# Tests/PlusFat/*Test, downstream integrator apps) recompiles the adapter +# sources with its own FreeRTOSFATConfig.h on the include path. Mirrors the +# Platform/FatFs/ and Platform/FreeRtos/ packs — see +# project_header_configured_platforms memory. + +add_library(SolidSyslogPlusFat INTERFACE) + +target_include_directories(SolidSyslogPlusFat INTERFACE + ${CMAKE_CURRENT_SOURCE_DIR}/Interface +) + +install(DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}/Interface/ + DESTINATION ${CMAKE_INSTALL_INCLUDEDIR} + FILES_MATCHING PATTERN "*.h" +) diff --git a/Platform/PlusFat/Interface/SolidSyslogPlusFatFile.h b/Platform/PlusFat/Interface/SolidSyslogPlusFatFile.h new file mode 100644 index 00000000..3251e8bd --- /dev/null +++ b/Platform/PlusFat/Interface/SolidSyslogPlusFatFile.h @@ -0,0 +1,15 @@ +#ifndef SOLIDSYSLOGPLUSFATFILE_H +#define SOLIDSYSLOGPLUSFATFILE_H + +#include "ExternC.h" + +struct SolidSyslogFile; + +EXTERN_C_BEGIN + + struct SolidSyslogFile* SolidSyslogPlusFatFile_Create(void); + void SolidSyslogPlusFatFile_Destroy(struct SolidSyslogFile * base); + +EXTERN_C_END + +#endif /* SOLIDSYSLOGPLUSFATFILE_H */ diff --git a/Platform/PlusFat/Interface/SolidSyslogPlusFatFileErrors.h b/Platform/PlusFat/Interface/SolidSyslogPlusFatFileErrors.h new file mode 100644 index 00000000..47eb9806 --- /dev/null +++ b/Platform/PlusFat/Interface/SolidSyslogPlusFatFileErrors.h @@ -0,0 +1,21 @@ +#ifndef SOLIDSYSLOGPLUSFATFILEERRORS_H +#define SOLIDSYSLOGPLUSFATFILEERRORS_H + +#include "ExternC.h" + +EXTERN_C_BEGIN + + struct SolidSyslogErrorSource; + + enum SolidSyslogPlusFatFileErrors + { + PLUSFATFILE_ERROR_POOL_EXHAUSTED, + PLUSFATFILE_ERROR_UNKNOWN_DESTROY, + PLUSFATFILE_ERROR_MAX + }; + + extern const struct SolidSyslogErrorSource PlusFatFileErrorSource; + +EXTERN_C_END + +#endif /* SOLIDSYSLOGPLUSFATFILEERRORS_H */ diff --git a/Platform/PlusFat/Source/SolidSyslogPlusFatFile.c b/Platform/PlusFat/Source/SolidSyslogPlusFatFile.c new file mode 100644 index 00000000..04b550f1 --- /dev/null +++ b/Platform/PlusFat/Source/SolidSyslogPlusFatFile.c @@ -0,0 +1,133 @@ +#include "SolidSyslogPlusFatFile.h" + +#include +#include + +#include "SolidSyslogFileDefinition.h" +#include "SolidSyslogNullFile.h" +#include "SolidSyslogPlusFatFileErrors.h" +#include "SolidSyslogPlusFatFilePrivate.h" +#include "ff_stdio.h" + +const struct SolidSyslogErrorSource PlusFatFileErrorSource = {"PlusFatFile"}; + +static bool PlusFatFile_Open(struct SolidSyslogFile* base, const char* path); +static void PlusFatFile_Close(struct SolidSyslogFile* base); +static bool PlusFatFile_IsOpen(struct SolidSyslogFile* base); +static bool PlusFatFile_Read(struct SolidSyslogFile* base, void* buf, size_t count); +static bool PlusFatFile_Write(struct SolidSyslogFile* base, const void* buf, size_t count); +static void PlusFatFile_SeekTo(struct SolidSyslogFile* base, size_t offset); +static size_t PlusFatFile_Size(struct SolidSyslogFile* base); +static void PlusFatFile_Truncate(struct SolidSyslogFile* base); +static bool PlusFatFile_Exists(struct SolidSyslogFile* base, const char* path); +static bool PlusFatFile_Delete(struct SolidSyslogFile* base, const char* path); + +static inline struct SolidSyslogPlusFatFile* PlusFatFile_SelfFromBase(struct SolidSyslogFile* base); + +void PlusFatFile_Initialise(struct SolidSyslogFile* base) +{ + struct SolidSyslogPlusFatFile* self = PlusFatFile_SelfFromBase(base); + self->Base.Open = PlusFatFile_Open; + self->Base.Close = PlusFatFile_Close; + self->Base.IsOpen = PlusFatFile_IsOpen; + self->Base.Read = PlusFatFile_Read; + self->Base.Write = PlusFatFile_Write; + self->Base.SeekTo = PlusFatFile_SeekTo; + self->Base.Size = PlusFatFile_Size; + self->Base.Truncate = PlusFatFile_Truncate; + self->Base.Exists = PlusFatFile_Exists; + self->Base.Delete = PlusFatFile_Delete; + self->Fp = NULL; +} + +static inline struct SolidSyslogPlusFatFile* PlusFatFile_SelfFromBase(struct SolidSyslogFile* base) +{ + return (struct SolidSyslogPlusFatFile*) base; +} + +void PlusFatFile_Cleanup(struct SolidSyslogFile* base) +{ + PlusFatFile_Close(base); + /* Overwrite the abstract base with the shared NullFile vtable so + * use-after-destroy is a safe no-op rather than a NULL-fn-pointer crash. */ + *base = *SolidSyslogNullFile_Get(); +} + +static bool PlusFatFile_Open(struct SolidSyslogFile* base, const char* path) +{ + struct SolidSyslogPlusFatFile* self = PlusFatFile_SelfFromBase(base); + /* "r+" opens an existing file without truncating; if it does not exist, + * "w+" creates it. Together this is open-or-create with no data loss — + * Plus-FAT has no single open-always mode. */ + self->Fp = ff_fopen(path, "r+"); + if (self->Fp == NULL) + { + self->Fp = ff_fopen(path, "w+"); + } + return self->Fp != NULL; +} + +static void PlusFatFile_Close(struct SolidSyslogFile* base) +{ + struct SolidSyslogPlusFatFile* self = PlusFatFile_SelfFromBase(base); + if (self->Fp != NULL) + { + ff_fclose(self->Fp); + self->Fp = NULL; + } +} + +static bool PlusFatFile_IsOpen(struct SolidSyslogFile* base) +{ + return PlusFatFile_SelfFromBase(base)->Fp != NULL; +} + +static bool PlusFatFile_Read(struct SolidSyslogFile* base, void* buf, size_t count) +{ + struct SolidSyslogPlusFatFile* self = PlusFatFile_SelfFromBase(base); + /* xSize == 1 so the ff_fread return value is the byte count delivered. */ + return ff_fread(buf, 1, count, self->Fp) == count; +} + +static bool PlusFatFile_Write(struct SolidSyslogFile* base, const void* buf, size_t count) +{ + struct SolidSyslogPlusFatFile* self = PlusFatFile_SelfFromBase(base); + /* Flush after every complete write so a power loss never loses a record + * the BlockStore was told had been stored. */ + bool wroteAll = ff_fwrite(buf, 1, count, self->Fp) == count; + return wroteAll && (ff_fflush(self->Fp) == 0); +} + +static void PlusFatFile_SeekTo(struct SolidSyslogFile* base, size_t offset) +{ + struct SolidSyslogPlusFatFile* self = PlusFatFile_SelfFromBase(base); + ff_fseek(self->Fp, (long) offset, SEEK_SET); +} + +static size_t PlusFatFile_Size(struct SolidSyslogFile* base) +{ + return ff_filelength(PlusFatFile_SelfFromBase(base)->Fp); +} + +static void PlusFatFile_Truncate(struct SolidSyslogFile* base) +{ + struct SolidSyslogPlusFatFile* self = PlusFatFile_SelfFromBase(base); + /* Empty the file: rewind to the start, then set EOF there so the length + * becomes zero. ff_seteof truncates at the current position and Plus-FAT + * has no truncate-to-zero call of its own. */ + ff_fseek(self->Fp, 0, SEEK_SET); + ff_seteof(self->Fp); +} + +static bool PlusFatFile_Exists(struct SolidSyslogFile* base, const char* path) +{ + (void) base; + FF_Stat_t status; + return ff_stat(path, &status) == 0; +} + +static bool PlusFatFile_Delete(struct SolidSyslogFile* base, const char* path) +{ + (void) base; + return ff_remove(path) == 0; +} diff --git a/Platform/PlusFat/Source/SolidSyslogPlusFatFilePrivate.h b/Platform/PlusFat/Source/SolidSyslogPlusFatFilePrivate.h new file mode 100644 index 00000000..d4b339f6 --- /dev/null +++ b/Platform/PlusFat/Source/SolidSyslogPlusFatFilePrivate.h @@ -0,0 +1,30 @@ +#ifndef SOLIDSYSLOGPLUSFATFILEPRIVATE_H +#define SOLIDSYSLOGPLUSFATFILEPRIVATE_H + +#include + +#include "SolidSyslogError.h" +#include "SolidSyslogFileDefinition.h" +#include "SolidSyslogPlusFatFileErrors.h" +#include "SolidSyslogPrival.h" +#include "ff_stdio.h" + +struct SolidSyslogPlusFatFile +{ + struct SolidSyslogFile Base; + FF_FILE* Fp; +}; + +void PlusFatFile_Initialise(struct SolidSyslogFile* base); +void PlusFatFile_Cleanup(struct SolidSyslogFile* base); + +static inline void PlusFatFile_Report( + enum SolidSyslogSeverity severity, + uint16_t category, + enum SolidSyslogPlusFatFileErrors code +) +{ + SolidSyslog_Error(severity, &PlusFatFileErrorSource, category, (int32_t) code); +} + +#endif /* SOLIDSYSLOGPLUSFATFILEPRIVATE_H */ diff --git a/Platform/PlusFat/Source/SolidSyslogPlusFatFileStatic.c b/Platform/PlusFat/Source/SolidSyslogPlusFatFileStatic.c new file mode 100644 index 00000000..a05e95c9 --- /dev/null +++ b/Platform/PlusFat/Source/SolidSyslogPlusFatFileStatic.c @@ -0,0 +1,79 @@ +#include "SolidSyslogPlusFatFile.h" + +#include +#include +#include + +#include "SolidSyslogError.h" +#include "SolidSyslogErrorCategory.h" +#include "SolidSyslogNullFile.h" +#include "SolidSyslogPlusFatFileErrors.h" +#include "SolidSyslogPlusFatFilePrivate.h" +#include "SolidSyslogPoolAllocator.h" +#include "SolidSyslogPrival.h" +#include "SolidSyslogTunables.h" + +struct SolidSyslogFile; + +static inline size_t PlusFatFile_IndexFromHandle(const struct SolidSyslogFile* base); +static inline void PlusFatFile_CleanupAtIndex(size_t index, void* context); + +static bool PlusFatFile_InUse[SOLIDSYSLOG_FILE_POOL_SIZE]; +static struct SolidSyslogPlusFatFile PlusFatFile_Pool[SOLIDSYSLOG_FILE_POOL_SIZE]; +static struct SolidSyslogPoolAllocator PlusFatFile_Allocator = {PlusFatFile_InUse, SOLIDSYSLOG_FILE_POOL_SIZE}; + +struct SolidSyslogFile* SolidSyslogPlusFatFile_Create(void) +{ + size_t index = SolidSyslogPoolAllocator_AcquireFirstFree(&PlusFatFile_Allocator); + struct SolidSyslogFile* handle = SolidSyslogNullFile_Get(); + if (SolidSyslogPoolAllocator_IndexIsValid(&PlusFatFile_Allocator, index) == true) + { + PlusFatFile_Initialise(&PlusFatFile_Pool[index].Base); + handle = &PlusFatFile_Pool[index].Base; + } + else + { + PlusFatFile_Report( + SOLIDSYSLOG_SEVERITY_ERROR, + SOLIDSYSLOG_CAT_POOL_EXHAUSTED, + PLUSFATFILE_ERROR_POOL_EXHAUSTED + ); + } + return handle; +} + +void SolidSyslogPlusFatFile_Destroy(struct SolidSyslogFile* base) +{ + size_t index = PlusFatFile_IndexFromHandle(base); + bool released = + SolidSyslogPoolAllocator_IndexIsValid(&PlusFatFile_Allocator, index) && + SolidSyslogPoolAllocator_FreeIfInUse(&PlusFatFile_Allocator, index, PlusFatFile_CleanupAtIndex, NULL); + if (!released) + { + PlusFatFile_Report( + SOLIDSYSLOG_SEVERITY_WARNING, + SOLIDSYSLOG_CAT_UNKNOWN_DESTROY, + PLUSFATFILE_ERROR_UNKNOWN_DESTROY + ); + } +} + +static inline size_t PlusFatFile_IndexFromHandle(const struct SolidSyslogFile* base) +{ + size_t result = SOLIDSYSLOG_FILE_POOL_SIZE; + for (size_t poolIndex = 0; poolIndex < SOLIDSYSLOG_FILE_POOL_SIZE; poolIndex++) + { + if (base == &PlusFatFile_Pool[poolIndex].Base) + { + result = poolIndex; + break; + } + } + return result; +} + +static inline void PlusFatFile_CleanupAtIndex(size_t index, void* context) +{ + (void) context; + PlusFatFile_Cleanup(&PlusFatFile_Pool[index].Base); +} diff --git a/Tests/CMakeLists.txt b/Tests/CMakeLists.txt index 56d3cc7d..7bcd1a08 100644 --- a/Tests/CMakeLists.txt +++ b/Tests/CMakeLists.txt @@ -16,6 +16,11 @@ if(DEFINED ENV{FATFS_PATH}) add_subdirectory(FatFs) endif() +if(DEFINED ENV{FREERTOS_PLUS_FAT_PATH}) + add_subdirectory(Support/PlusFatFakes) + add_subdirectory(PlusFat) +endif() + if(DEFINED ENV{LWIP_PATH}) add_subdirectory(Lwip) endif() @@ -263,6 +268,14 @@ foreach(fatfs_test_target IN ITEMS endif() endforeach() +foreach(plusfat_test_target IN ITEMS + SolidSyslogPlusFatFileTest +) + if(TARGET ${plusfat_test_target}) + register_junit_test(${plusfat_test_target}) + endif() +endforeach() + foreach(mbedtls_test_target IN ITEMS SolidSyslogMbedTlsStreamPoolTest SolidSyslogMbedTlsStreamTest diff --git a/Tests/PlusFat/CMakeLists.txt b/Tests/PlusFat/CMakeLists.txt new file mode 100644 index 00000000..4222eb07 --- /dev/null +++ b/Tests/PlusFat/CMakeLists.txt @@ -0,0 +1,50 @@ +# Tests/PlusFat — host-side unit tests for FreeRTOS-Plus-FAT adapters under +# Platform/PlusFat/Source/. Each test executable recompiles the adapter source +# under test with the PlusFatFakes config and links against the fakes — same +# shape as Tests/FatFs/ and Tests/FreeRtos/, see +# project_header_configured_platforms memory. +# +# Plus-FAT is FreeRTOS-coupled: ff_stdio.h pulls FreeRTOS.h / task.h / semphr.h, +# so the include path carries FreeRtosFakes/Interface (FreeRTOSConfig.h, +# portmacro.h) and $FREERTOS_KERNEL_PATH/include alongside the test-local +# FreeRTOSFATConfig.h in PlusFatFakes/Interface. We surface that coupling +# rather than hide it. +# +# Gated by $FREERTOS_PLUS_FAT_PATH at the top-level CMakeLists (this CMakeLists +# is only entered when FREERTOS_PLUS_FAT_PATH is set), and additionally needs +# FREERTOS_KERNEL_PATH for the kernel headers Plus-FAT depends on. + +if("$ENV{FREERTOS_KERNEL_PATH}" STREQUAL "") + message(STATUS "Skipping SolidSyslogPlusFatFileTest: FREERTOS_KERNEL_PATH not set.") +else() + add_executable(SolidSyslogPlusFatFileTest + SolidSyslogPlusFatFileTest.cpp + SolidSyslogPlusFatFilePoolTest.cpp + main.cpp + ${CMAKE_SOURCE_DIR}/Platform/PlusFat/Source/SolidSyslogPlusFatFile.c + ${CMAKE_SOURCE_DIR}/Platform/PlusFat/Source/SolidSyslogPlusFatFileStatic.c + ${CMAKE_SOURCE_DIR}/Tests/Support/PlusFatFakes/Source/PlusFatFake.c + ) + + target_link_libraries(SolidSyslogPlusFatFileTest PRIVATE + ${PROJECT_NAME} + ErrorHandlerFake + ConfigLockFake + CppUTest + CppUTestExt + ) + + target_include_directories(SolidSyslogPlusFatFileTest PRIVATE + ${CMAKE_SOURCE_DIR}/Platform/PlusFat/Interface + ${CMAKE_SOURCE_DIR}/Core/Interface + ${CMAKE_SOURCE_DIR}/Core/Source + ${CMAKE_SOURCE_DIR}/Tests/Support + ${CMAKE_SOURCE_DIR}/Tests/Support/PlusFatFakes/Interface + ${CMAKE_SOURCE_DIR}/Tests/Support/PlusFatFakes/Source + ${CMAKE_SOURCE_DIR}/Tests/Support/FreeRtosFakes/Interface + $ENV{FREERTOS_KERNEL_PATH}/include + $ENV{FREERTOS_PLUS_FAT_PATH}/include + ) + + add_test(NAME SolidSyslogPlusFatFileTest COMMAND SolidSyslogPlusFatFileTest) +endif() diff --git a/Tests/PlusFat/SolidSyslogPlusFatFilePoolTest.cpp b/Tests/PlusFat/SolidSyslogPlusFatFilePoolTest.cpp new file mode 100644 index 00000000..d19b2e16 --- /dev/null +++ b/Tests/PlusFat/SolidSyslogPlusFatFilePoolTest.cpp @@ -0,0 +1,173 @@ +#include "CppUTest/TestHarness.h" +#include "TestUtils.h" + +extern "C" +{ +#include "ConfigLockFake.h" +#include "ErrorHandlerFake.h" +#include "PlusFatFake.h" +#include "SolidSyslogErrorCategory.h" +#include "SolidSyslogFile.h" +#include "SolidSyslogFileDefinition.h" +#include "SolidSyslogPlusFatFile.h" +#include "SolidSyslogPlusFatFileErrors.h" +#include "SolidSyslogPrival.h" +#include "SolidSyslogTunables.h" +} + +using namespace CososoTesting; + +// 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) + +// clang-format off +TEST_GROUP(SolidSyslogPlusFatFilePool) +{ + struct SolidSyslogFile* pooled[SOLIDSYSLOG_FILE_POOL_SIZE] = {}; + struct SolidSyslogFile* overflow = nullptr; + + void setup() override + { + PlusFatFake_Reset(); + } + + void teardown() override + { + for (auto* handle : pooled) + { + if (handle != nullptr) + { + SolidSyslogPlusFatFile_Destroy(handle); + } + } + if (overflow != nullptr) + { + SolidSyslogPlusFatFile_Destroy(overflow); + } + ConfigLockFake_Uninstall(); + } + + void FillPool() + { + for (auto*& slot : pooled) + { + slot = SolidSyslogPlusFatFile_Create(); + } + } +}; + +// clang-format on + +TEST(SolidSyslogPlusFatFilePool, FillingPoolThenOverflowReturnsDistinctFallback) +{ + FillPool(); + + overflow = SolidSyslogPlusFatFile_Create(); + + CHECK_IS_FALLBACK(overflow, pooled); +} + +TEST(SolidSyslogPlusFatFilePool, ExhaustedCreateReportsError) +{ + ErrorHandlerFake_Install(nullptr); + FillPool(); + + overflow = SolidSyslogPlusFatFile_Create(); + + CALLED_FAKE(ErrorHandlerFake_Handle, ONCE); + LONGS_EQUAL(SOLIDSYSLOG_SEVERITY_ERROR, ErrorHandlerFake_LastSeverity()); + POINTERS_EQUAL(&PlusFatFileErrorSource, ErrorHandlerFake_LastSource()); + UNSIGNED_LONGS_EQUAL(SOLIDSYSLOG_CAT_POOL_EXHAUSTED, ErrorHandlerFake_LastCategory()); + UNSIGNED_LONGS_EQUAL(PLUSFATFILE_ERROR_POOL_EXHAUSTED, ErrorHandlerFake_LastDetail()); +} + +TEST(SolidSyslogPlusFatFilePool, FallbackOpenReturnsFalse) +{ + FillPool(); + overflow = SolidSyslogPlusFatFile_Create(); + + CHECK_FALSE(SolidSyslogFile_Open(overflow, "anything.log")); +} + +TEST(SolidSyslogPlusFatFilePool, CreateAcquiresAndReleasesConfigLockOnFirstFreeSlot) +{ + ConfigLockFake_Install(); + + pooled[0] = SolidSyslogPlusFatFile_Create(); + + CALLED_FAKE(ConfigLockFake_Lock, ONCE); + CALLED_FAKE(ConfigLockFake_Unlock, ONCE); +} + +TEST(SolidSyslogPlusFatFilePool, CreateLocksOncePerSlotProbedWhenPoolIsFull) +{ + FillPool(); + ConfigLockFake_Install(); + + overflow = SolidSyslogPlusFatFile_Create(); + + LONGS_EQUAL(SOLIDSYSLOG_FILE_POOL_SIZE, ConfigLockFake_LockCallCount()); + LONGS_EQUAL(SOLIDSYSLOG_FILE_POOL_SIZE, ConfigLockFake_UnlockCallCount()); +} + +TEST(SolidSyslogPlusFatFilePool, DestroyOfPooledHandleLocksOnce) +{ + pooled[0] = SolidSyslogPlusFatFile_Create(); + ConfigLockFake_Install(); + + SolidSyslogPlusFatFile_Destroy(pooled[0]); + pooled[0] = nullptr; + + CALLED_FAKE(ConfigLockFake_Lock, ONCE); + CALLED_FAKE(ConfigLockFake_Unlock, ONCE); +} + +TEST(SolidSyslogPlusFatFilePool, DestroyOfUnknownHandleDoesNotLock) +{ + ConfigLockFake_Install(); + struct SolidSyslogFile stranger = {}; + + SolidSyslogPlusFatFile_Destroy(&stranger); + + CALLED_FAKE(ConfigLockFake_Lock, NEVER); + CALLED_FAKE(ConfigLockFake_Unlock, NEVER); +} + +TEST(SolidSyslogPlusFatFilePool, DestroyOfUnknownHandleReportsWarning) +{ + ErrorHandlerFake_Install(nullptr); + struct SolidSyslogFile stranger = {}; + + SolidSyslogPlusFatFile_Destroy(&stranger); + + CALLED_FAKE(ErrorHandlerFake_Handle, ONCE); + LONGS_EQUAL(SOLIDSYSLOG_SEVERITY_WARNING, ErrorHandlerFake_LastSeverity()); + POINTERS_EQUAL(&PlusFatFileErrorSource, ErrorHandlerFake_LastSource()); + UNSIGNED_LONGS_EQUAL(SOLIDSYSLOG_CAT_UNKNOWN_DESTROY, ErrorHandlerFake_LastCategory()); + UNSIGNED_LONGS_EQUAL(PLUSFATFILE_ERROR_UNKNOWN_DESTROY, ErrorHandlerFake_LastDetail()); +} + +TEST(SolidSyslogPlusFatFilePool, DestroyOfStaleHandleReportsWarning) +{ + pooled[0] = SolidSyslogPlusFatFile_Create(); + SolidSyslogPlusFatFile_Destroy(pooled[0]); + ErrorHandlerFake_Install(nullptr); + + SolidSyslogPlusFatFile_Destroy(pooled[0]); + pooled[0] = nullptr; + + CALLED_FAKE(ErrorHandlerFake_Handle, ONCE); + LONGS_EQUAL(SOLIDSYSLOG_SEVERITY_WARNING, ErrorHandlerFake_LastSeverity()); + POINTERS_EQUAL(&PlusFatFileErrorSource, ErrorHandlerFake_LastSource()); + UNSIGNED_LONGS_EQUAL(SOLIDSYSLOG_CAT_UNKNOWN_DESTROY, ErrorHandlerFake_LastCategory()); + UNSIGNED_LONGS_EQUAL(PLUSFATFILE_ERROR_UNKNOWN_DESTROY, ErrorHandlerFake_LastDetail()); +} diff --git a/Tests/PlusFat/SolidSyslogPlusFatFileTest.cpp b/Tests/PlusFat/SolidSyslogPlusFatFileTest.cpp new file mode 100644 index 00000000..a3ac68cc --- /dev/null +++ b/Tests/PlusFat/SolidSyslogPlusFatFileTest.cpp @@ -0,0 +1,223 @@ +#include "CppUTest/TestHarness.h" +#include "TestUtils.h" + +extern "C" +{ +#include "PlusFatFake.h" +#include "SolidSyslogFile.h" +#include "SolidSyslogPlusFatFile.h" +#include "ff_stdio.h" +} + +using namespace CososoTesting; + +static const char* const TEST_PATH = "test.log"; + +// clang-format off +TEST_GROUP(SolidSyslogPlusFatFile) +{ + struct SolidSyslogFile* file = nullptr; + char buffer[5] = {}; + + void setup() override + { + PlusFatFake_Reset(); + file = SolidSyslogPlusFatFile_Create(); + } + + void teardown() override + { + SolidSyslogPlusFatFile_Destroy(file); + } +}; + +// clang-format on + +TEST(SolidSyslogPlusFatFile, CreateSucceeds) +{ + CHECK(file != nullptr); +} + +TEST(SolidSyslogPlusFatFile, OpenSucceeds) +{ + CHECK_TRUE(SolidSyslogFile_Open(file, TEST_PATH)); + CHECK_TRUE(SolidSyslogFile_IsOpen(file)); +} + +TEST(SolidSyslogPlusFatFile, OpenFallsBackToCreateModeWhenReadPlusFails) +{ + PlusFatFake_SetOpenFailsForMode("r+"); + + CHECK_TRUE(SolidSyslogFile_Open(file, TEST_PATH)); + + LONGS_EQUAL(2, PlusFatFake_OpenCallCount()); + STRCMP_EQUAL(TEST_PATH, PlusFatFake_LastOpenPath()); + STRCMP_EQUAL("r+", PlusFatFake_OpenModeAt(0)); + STRCMP_EQUAL("w+", PlusFatFake_OpenModeAt(1)); +} + +TEST(SolidSyslogPlusFatFile, OpenFailsWhenBothModesFail) +{ + PlusFatFake_SetOpenAlwaysFails(); + + CHECK_FALSE(SolidSyslogFile_Open(file, TEST_PATH)); + CHECK_FALSE(SolidSyslogFile_IsOpen(file)); +} + +TEST(SolidSyslogPlusFatFile, CloseCallsFfcloseAndClearsIsOpen) +{ + SolidSyslogFile_Open(file, TEST_PATH); + + SolidSyslogFile_Close(file); + + CALLED_FAKE(PlusFatFake_Close, ONCE); + CHECK_FALSE(SolidSyslogFile_IsOpen(file)); +} + +TEST(SolidSyslogPlusFatFile, CloseIsNoOpWhenAlreadyClosed) +{ + SolidSyslogFile_Close(file); + + CALLED_FAKE(PlusFatFake_Close, NEVER); +} + +TEST(SolidSyslogPlusFatFile, DestroyClosesOpenFile) +{ + SolidSyslogFile_Open(file, TEST_PATH); + + SolidSyslogPlusFatFile_Destroy(file); + file = nullptr; + + CALLED_FAKE(PlusFatFake_Close, ONCE); +} + +TEST(SolidSyslogPlusFatFile, ReadReturnsRequestedBytes) +{ + SolidSyslogFile_Open(file, TEST_PATH); + const char source[5] = {'h', 'e', 'l', 'l', 'o'}; + PlusFatFake_SetReadSource(source, sizeof(source)); + + CHECK_TRUE(SolidSyslogFile_Read(file, buffer, sizeof(buffer))); + + MEMCMP_EQUAL(source, buffer, sizeof(source)); +} + +TEST(SolidSyslogPlusFatFile, ReadCallsFfreadWithUnitSizeAndCount) +{ + SolidSyslogFile_Open(file, TEST_PATH); + const char source[5] = {'h', 'e', 'l', 'l', 'o'}; + PlusFatFake_SetReadSource(source, sizeof(source)); + + SolidSyslogFile_Read(file, buffer, sizeof(buffer)); + + CALLED_FAKE(PlusFatFake_Read, ONCE); + UNSIGNED_LONGS_EQUAL(1, PlusFatFake_LastReadSize()); + UNSIGNED_LONGS_EQUAL(sizeof(buffer), PlusFatFake_LastReadItems()); +} + +TEST(SolidSyslogPlusFatFile, ReadFailsWhenFewerBytesAvailable) +{ + SolidSyslogFile_Open(file, TEST_PATH); + const char source[3] = {'a', 'b', 'c'}; + PlusFatFake_SetReadSource(source, sizeof(source)); + + CHECK_FALSE(SolidSyslogFile_Read(file, buffer, sizeof(buffer))); +} + +TEST(SolidSyslogPlusFatFile, WriteCallsFfwriteWithData) +{ + SolidSyslogFile_Open(file, TEST_PATH); + + CHECK_TRUE(SolidSyslogFile_Write(file, buffer, sizeof(buffer))); + + CALLED_FAKE(PlusFatFake_Write, ONCE); + MEMCMP_EQUAL(buffer, PlusFatFake_LastWriteBytes(), sizeof(buffer)); + UNSIGNED_LONGS_EQUAL(sizeof(buffer), PlusFatFake_LastWriteItems()); +} + +TEST(SolidSyslogPlusFatFile, WriteCommitsToDiskWithFfflush) +{ + SolidSyslogFile_Open(file, TEST_PATH); + + SolidSyslogFile_Write(file, buffer, 1); + + CALLED_FAKE(PlusFatFake_Fflush, ONCE); +} + +TEST(SolidSyslogPlusFatFile, WriteFailsAndSkipsFlushWhenFfwriteIncomplete) +{ + SolidSyslogFile_Open(file, TEST_PATH); + PlusFatFake_SetWriteIncomplete(); + + CHECK_FALSE(SolidSyslogFile_Write(file, buffer, sizeof(buffer))); + + CALLED_FAKE(PlusFatFake_Fflush, NEVER); +} + +TEST(SolidSyslogPlusFatFile, WriteFailsWhenFfflushFails) +{ + SolidSyslogFile_Open(file, TEST_PATH); + PlusFatFake_SetFflushFails(); + + CHECK_FALSE(SolidSyslogFile_Write(file, buffer, sizeof(buffer))); +} + +TEST(SolidSyslogPlusFatFile, SeekToCallsFfseekFromStart) +{ + SolidSyslogFile_Open(file, TEST_PATH); + + SolidSyslogFile_SeekTo(file, 42); + + CALLED_FAKE(PlusFatFake_Seek, ONCE); + LONGS_EQUAL(42, PlusFatFake_LastSeekOffset()); + LONGS_EQUAL(SEEK_SET, PlusFatFake_LastSeekWhence()); +} + +TEST(SolidSyslogPlusFatFile, SizeReturnsFfilelength) +{ + SolidSyslogFile_Open(file, TEST_PATH); + PlusFatFake_SetFileLength(42); + + LONGS_EQUAL(42, SolidSyslogFile_Size(file)); +} + +TEST(SolidSyslogPlusFatFile, TruncateSeeksToZeroAndCallsFseteof) +{ + SolidSyslogFile_Open(file, TEST_PATH); + + SolidSyslogFile_Truncate(file); + + CALLED_FAKE(PlusFatFake_Seek, ONCE); + LONGS_EQUAL(0, PlusFatFake_LastSeekOffset()); + CALLED_FAKE(PlusFatFake_Seteof, ONCE); +} + +TEST(SolidSyslogPlusFatFile, ExistsCallsFstatAndReportsTrue) +{ + CHECK_TRUE(SolidSyslogFile_Exists(file, TEST_PATH)); + + CALLED_FAKE(PlusFatFake_Stat, ONCE); + STRCMP_EQUAL(TEST_PATH, PlusFatFake_LastStatPath()); +} + +TEST(SolidSyslogPlusFatFile, ExistsReportsFalseWhenFstatFails) +{ + PlusFatFake_SetStatFails(); + + CHECK_FALSE(SolidSyslogFile_Exists(file, TEST_PATH)); +} + +TEST(SolidSyslogPlusFatFile, DeleteCallsFremoveAndReportsTrue) +{ + CHECK_TRUE(SolidSyslogFile_Delete(file, TEST_PATH)); + + CALLED_FAKE(PlusFatFake_Remove, ONCE); + STRCMP_EQUAL(TEST_PATH, PlusFatFake_LastRemovePath()); +} + +TEST(SolidSyslogPlusFatFile, DeleteReportsFalseWhenFremoveFails) +{ + PlusFatFake_SetRemoveFails(); + + CHECK_FALSE(SolidSyslogFile_Delete(file, TEST_PATH)); +} diff --git a/Tests/PlusFat/main.cpp b/Tests/PlusFat/main.cpp new file mode 100644 index 00000000..abc587a2 --- /dev/null +++ b/Tests/PlusFat/main.cpp @@ -0,0 +1,6 @@ +#include "CppUTest/CommandLineTestRunner.h" + +int main(int argc, char* argv[]) +{ + return CommandLineTestRunner::RunAllTests(argc, argv); +} diff --git a/Tests/Support/FreeRtosFakes/Interface/FreeRTOSConfig.h b/Tests/Support/FreeRtosFakes/Interface/FreeRTOSConfig.h index c58df8be..85fd2737 100644 --- a/Tests/Support/FreeRtosFakes/Interface/FreeRTOSConfig.h +++ b/Tests/Support/FreeRtosFakes/Interface/FreeRTOSConfig.h @@ -47,6 +47,14 @@ #define configUSE_CO_ROUTINES 0 #define configMAX_CO_ROUTINE_PRIORITIES 1 +/* Thread-local storage pointers. FreeRTOS-Plus-FAT's ff_stdio.h keeps three + * per-task entries (errno, CWD, ff_error) starting at ffconfigCWD_THREAD_LOCAL_INDEX + * and #errors at compile time if fewer than three are available; it also gates + * the v/pvTaskGetThreadLocalStoragePointer declarations on this being > 0. Set + * to 5 to clear that requirement with headroom. Harmless to the other host + * tests, which never touch thread-local storage. */ +#define configNUM_THREAD_LOCAL_STORAGE_POINTERS 5 + /* Optional API inclusions. */ #define INCLUDE_vTaskPrioritySet 0 #define INCLUDE_uxTaskPriorityGet 0 diff --git a/Tests/Support/PlusFatFakes/CMakeLists.txt b/Tests/Support/PlusFatFakes/CMakeLists.txt new file mode 100644 index 00000000..74c37049 --- /dev/null +++ b/Tests/Support/PlusFatFakes/CMakeLists.txt @@ -0,0 +1,15 @@ +# PlusFatFakes — host-side fake implementations of the FreeRTOS-Plus-FAT +# ff_stdio API used by Tests/PlusFat/*Test executables. The real Plus-FAT +# headers come from $FREERTOS_PLUS_FAT_PATH/include/; the test +# FreeRTOSFATConfig.h lives in this directory's Interface/. Drift between the +# fakes and the real Plus-FAT API is caught at compile time when the fake +# source includes ff_stdio.h. +# +# Plus-FAT is FreeRTOS-coupled (ff_headers.h pulls FreeRTOS.h / task.h / +# semphr.h), so each consuming test exe also puts FreeRtosFakes/Interface and +# $FREERTOS_KERNEL_PATH/include on its include path. We do not hide that +# coupling — it is intrinsic to Plus-FAT. +# +# The PlusFatFake.h control surface and PlusFatFake.c bodies grow per-slice as +# new ff_* calls enter the adapter. Each test exe pulls the fake source +# directly via add_executable — see Tests/FatFs/ and Tests/FreeRtos/ pattern. diff --git a/Tests/Support/PlusFatFakes/Interface/FreeRTOSFATConfig.h b/Tests/Support/PlusFatFakes/Interface/FreeRTOSFATConfig.h new file mode 100644 index 00000000..8d35936a --- /dev/null +++ b/Tests/Support/PlusFatFakes/Interface/FreeRTOSFATConfig.h @@ -0,0 +1,22 @@ +/* Host-test FreeRTOS-Plus-FAT configuration for Tests/PlusFat/. + * + * Plus-FAT is header-configured: ff_headers.h includes this file (after + * FreeRTOS.h) and FreeRTOSFATConfigDefaults.h fills in every value not set + * here. The three settings below are the minimum the defaults header demands + * — byte order, a thread-local-storage slot for the CWD, and a portINLINE + * fallback for the host compiler — matching Plus-FAT's own DefaultConf sample. + * + * Note: configNUM_THREAD_LOCAL_STORAGE_POINTERS (which ff_stdio.h requires to + * be >= 3) is a kernel setting and so lives in FreeRtosFakes/FreeRTOSConfig.h, + * which is included earlier via FreeRTOS.h. */ +#ifndef FREERTOSFATCONFIG_H +#define FREERTOSFATCONFIG_H + +#define ffconfigBYTE_ORDER (pdFREERTOS_LITTLE_ENDIAN) +#define ffconfigCWD_THREAD_LOCAL_INDEX (0) + +#if !defined(portINLINE) +#define portINLINE __inline +#endif + +#endif /* FREERTOSFATCONFIG_H */ diff --git a/Tests/Support/PlusFatFakes/Interface/PlusFatFake.h b/Tests/Support/PlusFatFakes/Interface/PlusFatFake.h new file mode 100644 index 00000000..89a49a98 --- /dev/null +++ b/Tests/Support/PlusFatFakes/Interface/PlusFatFake.h @@ -0,0 +1,59 @@ +#ifndef PLUSFATFAKE_H +#define PLUSFATFAKE_H + +#include "ExternC.h" + +EXTERN_C_BEGIN + + void PlusFatFake_Reset(void); + + /* ff_fopen */ + void PlusFatFake_SetOpenFailsForMode(const char* mode); + void PlusFatFake_SetOpenAlwaysFails(void); + int PlusFatFake_OpenCallCount(void); + const char* PlusFatFake_OpenModeAt(int index); + const char* PlusFatFake_LastOpenPath(void); + + /* ff_fclose */ + int PlusFatFake_CloseCallCount(void); + + /* ff_fread */ + void PlusFatFake_SetReadSource(const void* bytes, unsigned long count); + int PlusFatFake_ReadCallCount(void); + unsigned long PlusFatFake_LastReadSize(void); + unsigned long PlusFatFake_LastReadItems(void); + + /* ff_fwrite */ + void PlusFatFake_SetWriteIncomplete(void); + int PlusFatFake_WriteCallCount(void); + const void* PlusFatFake_LastWriteBytes(void); + unsigned long PlusFatFake_LastWriteItems(void); + + /* ff_fflush */ + void PlusFatFake_SetFflushFails(void); + int PlusFatFake_FflushCallCount(void); + + /* ff_fseek */ + int PlusFatFake_SeekCallCount(void); + long PlusFatFake_LastSeekOffset(void); + int PlusFatFake_LastSeekWhence(void); + + /* ff_filelength */ + void PlusFatFake_SetFileLength(unsigned long length); + + /* ff_seteof */ + int PlusFatFake_SeteofCallCount(void); + + /* ff_stat */ + void PlusFatFake_SetStatFails(void); + int PlusFatFake_StatCallCount(void); + const char* PlusFatFake_LastStatPath(void); + + /* ff_remove */ + void PlusFatFake_SetRemoveFails(void); + int PlusFatFake_RemoveCallCount(void); + const char* PlusFatFake_LastRemovePath(void); + +EXTERN_C_END + +#endif /* PLUSFATFAKE_H */ diff --git a/Tests/Support/PlusFatFakes/Source/PlusFatFake.c b/Tests/Support/PlusFatFakes/Source/PlusFatFake.c new file mode 100644 index 00000000..64e6854f --- /dev/null +++ b/Tests/Support/PlusFatFakes/Source/PlusFatFake.c @@ -0,0 +1,334 @@ +#include "PlusFatFake.h" + +#include +#include +#include + +#include "ff_stdio.h" + +enum +{ + OPEN_MODE_CAPACITY = 8, + READ_SOURCE_CAPACITY = 256, + WRITE_CAPTURE_CAPACITY = 256 +}; + +static FF_FILE fakeFile; + +/* ff_fopen state */ +static int openCallCount; +static const char* openModes[OPEN_MODE_CAPACITY]; +static const char* lastOpenPath; +static const char* openFailMode; +static bool openAlwaysFails; + +/* ff_fclose state */ +static int closeCallCount; + +/* ff_fread state */ +static unsigned char readSource[READ_SOURCE_CAPACITY]; +static size_t readSourceCount; +static int readCallCount; +static size_t lastReadSize; +static size_t lastReadItems; + +/* ff_fwrite state */ +static int writeCallCount; +static unsigned char lastWriteBytes[WRITE_CAPTURE_CAPACITY]; +static size_t lastWriteItems; +static bool writeIncomplete; + +/* ff_fflush state */ +static int fflushCallCount; +static int fflushResult; + +/* ff_fseek state */ +static int seekCallCount; +static long lastSeekOffset; +static int lastSeekWhence; + +/* ff_filelength state */ +static size_t fileLength; + +/* ff_seteof state */ +static int seteofCallCount; + +/* ff_stat state */ +static int statCallCount; +static const char* lastStatPath; +static int statResult; + +/* ff_remove state */ +static int removeCallCount; +static const char* lastRemovePath; +static int removeResult; + +void PlusFatFake_Reset(void) +{ + openCallCount = 0; + lastOpenPath = NULL; + openFailMode = NULL; + openAlwaysFails = false; + for (size_t modeIndex = 0; modeIndex < OPEN_MODE_CAPACITY; modeIndex++) + { + openModes[modeIndex] = NULL; + } + closeCallCount = 0; + memset(readSource, 0, sizeof(readSource)); + readSourceCount = 0; + readCallCount = 0; + lastReadSize = 0; + lastReadItems = 0; + writeCallCount = 0; + memset(lastWriteBytes, 0, sizeof(lastWriteBytes)); + lastWriteItems = 0; + writeIncomplete = false; + fflushCallCount = 0; + fflushResult = 0; + seekCallCount = 0; + lastSeekOffset = 0; + lastSeekWhence = 0; + fileLength = 0; + seteofCallCount = 0; + statCallCount = 0; + lastStatPath = NULL; + statResult = 0; + removeCallCount = 0; + lastRemovePath = NULL; + removeResult = 0; +} + +void PlusFatFake_SetOpenFailsForMode(const char* mode) +{ + openFailMode = mode; +} + +void PlusFatFake_SetOpenAlwaysFails(void) +{ + openAlwaysFails = true; +} + +int PlusFatFake_OpenCallCount(void) +{ + return openCallCount; +} + +const char* PlusFatFake_OpenModeAt(int index) +{ + return openModes[index]; +} + +const char* PlusFatFake_LastOpenPath(void) +{ + return lastOpenPath; +} + +FF_FILE* ff_fopen(const char* pcFile, const char* pcMode) +{ + if (openCallCount < OPEN_MODE_CAPACITY) + { + openModes[openCallCount] = pcMode; + } + lastOpenPath = pcFile; + openCallCount++; + + FF_FILE* result = &fakeFile; + if (openAlwaysFails || ((openFailMode != NULL) && (strcmp(pcMode, openFailMode) == 0))) + { + result = NULL; + } + return result; +} + +int PlusFatFake_CloseCallCount(void) +{ + return closeCallCount; +} + +int ff_fclose(FF_FILE* pxStream) +{ + (void) pxStream; + closeCallCount++; + return 0; +} + +void PlusFatFake_SetReadSource(const void* bytes, unsigned long count) +{ + size_t copyCount = (count <= sizeof(readSource)) ? (size_t) count : sizeof(readSource); + memcpy(readSource, bytes, copyCount); + readSourceCount = copyCount; +} + +/* The adapter always calls ff_fread with xSize == 1, so item count == byte + * count. Copies from the programmed source and returns the number of items + * (bytes) delivered, capped at the programmed source length. */ +int PlusFatFake_ReadCallCount(void) +{ + return readCallCount; +} + +unsigned long PlusFatFake_LastReadSize(void) +{ + return (unsigned long) lastReadSize; +} + +unsigned long PlusFatFake_LastReadItems(void) +{ + return (unsigned long) lastReadItems; +} + +size_t ff_fread(void* pvBuffer, size_t xSize, size_t xItems, FF_FILE* pxStream) +{ + (void) pxStream; + readCallCount++; + lastReadSize = xSize; + lastReadItems = xItems; + size_t itemsDelivered = (xItems <= readSourceCount) ? xItems : readSourceCount; + memcpy(pvBuffer, readSource, itemsDelivered); + return itemsDelivered; +} + +void PlusFatFake_SetWriteIncomplete(void) +{ + writeIncomplete = true; +} + +int PlusFatFake_WriteCallCount(void) +{ + return writeCallCount; +} + +const void* PlusFatFake_LastWriteBytes(void) +{ + return lastWriteBytes; +} + +unsigned long PlusFatFake_LastWriteItems(void) +{ + return (unsigned long) lastWriteItems; +} + +size_t ff_fwrite(const void* pvBuffer, size_t xSize, size_t xItems, FF_FILE* pxStream) +{ + (void) xSize; + (void) pxStream; + writeCallCount++; + size_t copyCount = (xItems <= sizeof(lastWriteBytes)) ? xItems : sizeof(lastWriteBytes); + memcpy(lastWriteBytes, pvBuffer, copyCount); + lastWriteItems = xItems; + size_t itemsWritten = xItems; + if (writeIncomplete && (xItems > 0)) + { + itemsWritten = xItems - 1; + } + return itemsWritten; +} + +void PlusFatFake_SetFflushFails(void) +{ + fflushResult = -1; +} + +int PlusFatFake_FflushCallCount(void) +{ + return fflushCallCount; +} + +int ff_fflush(FF_FILE* pxStream) +{ + (void) pxStream; + fflushCallCount++; + return fflushResult; +} + +int PlusFatFake_SeekCallCount(void) +{ + return seekCallCount; +} + +long PlusFatFake_LastSeekOffset(void) +{ + return lastSeekOffset; +} + +int PlusFatFake_LastSeekWhence(void) +{ + return lastSeekWhence; +} + +int ff_fseek(FF_FILE* pxStream, long lOffset, int iWhence) +{ + (void) pxStream; + seekCallCount++; + lastSeekOffset = lOffset; + lastSeekWhence = iWhence; + return 0; +} + +void PlusFatFake_SetFileLength(unsigned long length) +{ + fileLength = (size_t) length; +} + +size_t ff_filelength(FF_FILE* pxFile) +{ + (void) pxFile; + return fileLength; +} + +int PlusFatFake_SeteofCallCount(void) +{ + return seteofCallCount; +} + +int ff_seteof(FF_FILE* pxStream) +{ + (void) pxStream; + seteofCallCount++; + return 0; +} + +void PlusFatFake_SetStatFails(void) +{ + statResult = -1; +} + +int PlusFatFake_StatCallCount(void) +{ + return statCallCount; +} + +const char* PlusFatFake_LastStatPath(void) +{ + return lastStatPath; +} + +int ff_stat(const char* pcFileName, FF_Stat_t* pxStatBuffer) +{ + (void) pxStatBuffer; + statCallCount++; + lastStatPath = pcFileName; + return statResult; +} + +void PlusFatFake_SetRemoveFails(void) +{ + removeResult = -1; +} + +int PlusFatFake_RemoveCallCount(void) +{ + return removeCallCount; +} + +const char* PlusFatFake_LastRemovePath(void) +{ + return lastRemovePath; +} + +int ff_remove(const char* pcPath) +{ + removeCallCount++; + lastRemovePath = pcPath; + return removeResult; +} diff --git a/misra_suppressions.txt b/misra_suppressions.txt index b12ce644..8a00fcd3 100644 --- a/misra_suppressions.txt +++ b/misra_suppressions.txt @@ -37,6 +37,7 @@ misra-c2012-11.3:Core/Source/SolidSyslogTimeQualitySd.c:63 misra-c2012-11.3:Core/Source/SolidSyslogUdpSender.c:100 misra-c2012-11.3:Platform/Atomics/Source/SolidSyslogStdAtomicCounter.c:29 misra-c2012-11.3:Platform/FatFs/Source/SolidSyslogFatFsFile.c:49 +misra-c2012-11.3:Platform/PlusFat/Source/SolidSyslogPlusFatFile.c:45 misra-c2012-11.3:Platform/LwipRaw/Source/SolidSyslogLwipRawAddressPrivate.h:29 misra-c2012-11.3:Platform/LwipRaw/Source/SolidSyslogLwipRawAddressPrivate.h:36 misra-c2012-11.3:Platform/LwipRaw/Source/SolidSyslogLwipRawAddressStatic.c:35 diff --git a/scripts/misra_renumber.py b/scripts/misra_renumber.py index ca3bb23b..c040259b 100755 --- a/scripts/misra_renumber.py +++ b/scripts/misra_renumber.py @@ -78,6 +78,7 @@ "-IPlatform/PlusTcp/Interface", "-IPlatform/LwipRaw/Interface", "-IPlatform/FatFs/Interface", + "-IPlatform/PlusFat/Interface", "--xml", "--xml-version=2", "Core/Source/", @@ -90,6 +91,7 @@ "Platform/PlusTcp/Source/", "Platform/LwipRaw/Source/", "Platform/FatFs/Source/", + "Platform/PlusFat/Source/", ] SUPPRESSION_LINE = re.compile(r"^(misra-c2012-[0-9.]+):([^:]+):(\d+)\s*$")