From a4d368aab0b0c6f6e156853a6816f66a4df25e0a Mon Sep 17 00:00:00 2001 From: Kody Stribrny Date: Tue, 23 Jun 2026 12:26:10 -0700 Subject: [PATCH 1/2] fix: Break when configured max MBR/EBR partitions are found Previously the code would break from the inner loop but reenter due to the outer while loop in `FF_ParseExtended`. This could result in writing outside of the configured storage area. --- ff_ioman.c | 21 ++++++++++++++++----- 1 file changed, 16 insertions(+), 5 deletions(-) diff --git a/ff_ioman.c b/ff_ioman.c index b0662f7..c300fb7 100644 --- a/ff_ioman.c +++ b/ff_ioman.c @@ -944,6 +944,14 @@ static FF_Error_t FF_ParseExtended( FF_IOManager_t * pxIOManager, while( xTryCount-- ) { + /* Stop following the extended-partition (EBR) chain once the + * caller's partition array is full, otherwise the loop below + * could write past pPartsFound->pxPartitions[ ffconfigMAX_PARTITIONS ]. */ + if( pPartsFound->iCount >= ffconfigMAX_PARTITIONS ) + { + break; + } + if( ( pxBuffer == NULL ) || ( pxBuffer->ulSector != ulThisSector ) ) { /* Moving to a different sector. Release the @@ -1027,6 +1035,14 @@ static FF_Error_t FF_ParseExtended( FF_IOManager_t * pxIOManager, continue; } + /* Make sure there is room to store the partition before + * writing it. The increment below must never index past the + * end of pPartsFound->pxPartitions[ ffconfigMAX_PARTITIONS ]. */ + if( pPartsFound->iCount >= ffconfigMAX_PARTITIONS ) + { + break; + } + { /* Store this partition for the caller */ FF_Part_t * p = &pPartsFound->pxPartitions[ pPartsFound->iCount++ ]; @@ -1039,11 +1055,6 @@ static FF_Error_t FF_ParseExtended( FF_IOManager_t * pxIOManager, p->bIsExtended = pdTRUE; } - if( pPartsFound->iCount >= ffconfigMAX_PARTITIONS ) - { - break; - } - xTryCount = 100; } /* for( xPartNr = 0; xPartNr < 4; xPartNr++ ) */ From acf19a25b8c07f9266cbe7dc507fd4d022189d9f Mon Sep 17 00:00:00 2001 From: Kody Stribrny Date: Tue, 23 Jun 2026 14:02:25 -0700 Subject: [PATCH 2/2] Introduce unit tests, verify FF_ParseExtended changes --- .github/.cSpellWords.txt | 14 + .github/workflows/ci.yml | 20 +- .gitmodules | 3 + README.md | 34 +++ test/unit-test/.gitignore | 2 + test/unit-test/CMakeLists.txt | 120 ++++++++ test/unit-test/CMock | 1 + test/unit-test/README.md | 79 +++++ test/unit-test/cmock_build.cmake | 60 ++++ test/unit-test/config/FreeRTOSFATConfig.h | 44 +++ test/unit-test/ff_ioman_utest.c | 350 ++++++++++++++++++++++ test/unit-test/include/FreeRTOS.h | 69 +++++ test/unit-test/include/FreeRTOSConfig.h | 14 + test/unit-test/include/event_groups.h | 28 ++ test/unit-test/include/semphr.h | 20 ++ test/unit-test/include/task.h | 17 ++ tools/cmock/coverage.cmake | 59 ++++ tools/cmock/create_test.cmake | 135 +++++++++ tools/cmock/project.yml | 31 ++ 19 files changed, 1099 insertions(+), 1 deletion(-) create mode 100644 .gitmodules create mode 100644 test/unit-test/.gitignore create mode 100644 test/unit-test/CMakeLists.txt create mode 160000 test/unit-test/CMock create mode 100644 test/unit-test/README.md create mode 100644 test/unit-test/cmock_build.cmake create mode 100644 test/unit-test/config/FreeRTOSFATConfig.h create mode 100644 test/unit-test/ff_ioman_utest.c create mode 100644 test/unit-test/include/FreeRTOS.h create mode 100644 test/unit-test/include/FreeRTOSConfig.h create mode 100644 test/unit-test/include/event_groups.h create mode 100644 test/unit-test/include/semphr.h create mode 100644 test/unit-test/include/task.h create mode 100644 tools/cmock/coverage.cmake create mode 100644 tools/cmock/create_test.cmake create mode 100644 tools/cmock/project.yml diff --git a/.github/.cSpellWords.txt b/.github/.cSpellWords.txt index 2ddd8aa..0845b2e 100644 --- a/.github/.cSpellWords.txt +++ b/.github/.cSpellWords.txt @@ -214,6 +214,20 @@ pxhandle pylint pytest pyyaml +reentrant +gcno +gcov +gcda +ebr +dylib +rpath +ggdb +fprofile +ftest +genhtml +tracefile +tracefiles +zerocounters sddisk sdtype sinclude diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 92fdd4c..8f77797 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -8,7 +8,25 @@ on: workflow_dispatch: jobs: - # Currently no unit tests + unit-tests: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v3 + with: + submodules: recursive + - name: Install Dependencies + run: sudo apt-get install -y cmake build-essential lcov ruby + - name: Build unit tests + run: | + cmake -S test/unit-test -B test/unit-test/build/ + make -C test/unit-test/build/ all + - name: Run unit tests + run: ctest --test-dir test/unit-test/build/ -E system --output-on-failure + - name: Coverage + run: | + make -C test/unit-test/build/ coverage + lcov --list --rc branch_coverage=1 test/unit-test/build/coverage.info + formatting: runs-on: ubuntu-latest steps: diff --git a/.gitmodules b/.gitmodules new file mode 100644 index 0000000..629e4fa --- /dev/null +++ b/.gitmodules @@ -0,0 +1,3 @@ +[submodule "test/unit-test/CMock"] + path = test/unit-test/CMock + url = https://github.com/ThrowTheSwitch/CMock.git diff --git a/README.md b/README.md index d9702f0..9de8194 100644 --- a/README.md +++ b/README.md @@ -59,6 +59,40 @@ If you already have FreeRTOS in your project, you may skip the fetch content by It is recommended to use this repository as a submodule. Please refer to [Git Tools — Submodules](https://git-scm.com/book/en/v2/Git-Tools-Submodules). +## Running the unit tests + +Host-based unit tests live in [`test/unit-test`](test/unit-test) and are built +with [Unity](https://github.com/ThrowTheSwitch/Unity) and +[CMock](https://github.com/ThrowTheSwitch/CMock). + +Prerequisites: CMake, a C compiler, `make`, Ruby (used by CMock and the Unity +test-runner generator), and `lcov` for coverage. + +The tests rely on the CMock submodule. Initialise submodules first (the build +will also attempt to clone it automatically if missing): + +```sh +git submodule update --init --recursive +``` + +Configure, build, and run the tests: + +```sh +cmake -S test/unit-test -B test/unit-test/build/ +make -C test/unit-test/build/ all +ctest --test-dir test/unit-test/build/ -E system --output-on-failure +``` + +Optionally generate a coverage report (writes `coverage.info`): + +```sh +make -C test/unit-test/build/ coverage +lcov --list --rc branch_coverage=1 test/unit-test/build/coverage.info +``` + +See [`test/unit-test/README.md`](test/unit-test/README.md) for details on the +test layout and how to add new tests. + ## Notes This project is undergoing optimizations or refactoring to improve memory usage, diff --git a/test/unit-test/.gitignore b/test/unit-test/.gitignore new file mode 100644 index 0000000..ea55c83 --- /dev/null +++ b/test/unit-test/.gitignore @@ -0,0 +1,2 @@ +# CMake build output for the unit tests. +build/ diff --git a/test/unit-test/CMakeLists.txt b/test/unit-test/CMakeLists.txt new file mode 100644 index 0000000..46eb7e8 --- /dev/null +++ b/test/unit-test/CMakeLists.txt @@ -0,0 +1,120 @@ +# ------------------------------------------------------------------------------ +# FreeRTOS+FAT unit tests (CMock / Unity) +# +# Host-based unit tests for the library's algorithmic code. The module under +# test (ff_ioman.c) is compiled as a "real" library and linked against CMock +# generated mocks of its dependencies (locking, FAT and CRC layers). Lightweight +# kernel type/macro stubs (under include/) stand in for the FreeRTOS kernel. +# +# Usage (matches .github/workflows/release.yml): +# cmake -S test/unit-test -B test/unit-test/build/ +# make -C test/unit-test/build/ all +# ctest --test-dir test/unit-test/build/ -E system --output-on-failure +# make -C test/unit-test/build/ coverage +# ------------------------------------------------------------------------------ +cmake_minimum_required( VERSION 3.13 ) + +project( freertos_plus_fat_utest + VERSION 1.0.0 + LANGUAGES C ) + +# Repository root: test/unit-test/ -> test/ -> +get_filename_component( MODULE_ROOT_DIR "${CMAKE_CURRENT_LIST_DIR}/../.." ABSOLUTE ) +set( UNIT_TEST_DIR "${CMAKE_CURRENT_LIST_DIR}" ) +set( CMOCK_DIR "${UNIT_TEST_DIR}/CMock" ) + +# C90 is the library's baseline; the tests/mocks need a couple of C99 niceties, +# so build everything as C99 without GNU extensions disabled. +set( CMAKE_C_STANDARD 99 ) +set( CMAKE_C_STANDARD_REQUIRED ON ) + +enable_testing() + +# ------------------------------------------------------------------------------ +# CMock / Unity +# ------------------------------------------------------------------------------ +include( ${MODULE_ROOT_DIR}/tools/cmock/create_test.cmake ) +include( ${UNIT_TEST_DIR}/cmock_build.cmake ) + +if( NOT EXISTS ${CMOCK_DIR}/src/cmock.c ) + clone_cmock() +endif() + +add_cmock_targets() + +# ------------------------------------------------------------------------------ +# Shared include directories. +# - config/ : test FreeRTOSFATConfig.h +# - include/ : minimal FreeRTOS kernel type/macro stubs (FreeRTOS.h, task.h ...) +# - /include : the real FreeRTOS+FAT public headers +# The kernel stubs MUST precede the real include dir so they shadow the absent +# kernel headers. +# ------------------------------------------------------------------------------ +set( FAT_TEST_INCLUDE_DIRS + ${UNIT_TEST_DIR}/include + ${UNIT_TEST_DIR}/config + ${MODULE_ROOT_DIR}/include ) + +set( project_name "ff_ioman" ) + +# ===================== Mocks ================================================ +# Dependencies of ff_ioman.c that are satisfied by mocks at link time. +list( APPEND mock_list + ${MODULE_ROOT_DIR}/include/ff_locking.h + ${MODULE_ROOT_DIR}/include/ff_fat.h + ${MODULE_ROOT_DIR}/include/ff_crc.h ) + +list( APPEND mock_include_list ${FAT_TEST_INCLUDE_DIRS} ) +list( APPEND mock_define_list "" ) + +# ================== Module under test ======================================= +# ff_ioman.c is the unit under test; ff_memory.c supplies the (non-inlined) byte +# accessors it calls and is compiled for real rather than mocked. +list( APPEND real_source_files + ${MODULE_ROOT_DIR}/ff_ioman.c + ${MODULE_ROOT_DIR}/ff_memory.c ) + +list( APPEND real_include_directories ${FAT_TEST_INCLUDE_DIRS} ) + +# ===================== Test code ============================================ +list( APPEND test_include_directories ${FAT_TEST_INCLUDE_DIRS} ) + +# ============================================================================== +set( mock_name "${project_name}_mock" ) +set( real_name "${project_name}_real" ) + +create_mock_list( ${mock_name} + "${mock_list}" + "${MODULE_ROOT_DIR}/tools/cmock/project.yml" + "${mock_include_list}" + "${mock_define_list}" ) + +create_real_library( ${real_name} + "${real_source_files}" + "${real_include_directories}" + "${mock_name}" ) + +list( APPEND utest_dep_list ${real_name} ) + +list( APPEND utest_link_list + lib${real_name}.a + -l${mock_name} ) + +set( utest_name "${project_name}_utest" ) +set( utest_source "${UNIT_TEST_DIR}/ff_ioman_utest.c" ) + +create_test( ${utest_name} + ${utest_source} + "${utest_link_list}" + "${utest_dep_list}" + "${test_include_directories}" ) + +# ------------------------------------------------------------------------------ +# `coverage` target: run the tests and collect lcov data into coverage.info. +# ------------------------------------------------------------------------------ +add_custom_target( coverage + COMMAND ${CMAKE_COMMAND} -DCMAKE_BINARY_DIR=${CMAKE_BINARY_DIR} + -P ${MODULE_ROOT_DIR}/tools/cmock/coverage.cmake + DEPENDS ${utest_name} + WORKING_DIRECTORY ${CMAKE_BINARY_DIR} + COMMENT "Running unit tests and collecting coverage" ) diff --git a/test/unit-test/CMock b/test/unit-test/CMock new file mode 160000 index 0000000..d482f56 --- /dev/null +++ b/test/unit-test/CMock @@ -0,0 +1 @@ +Subproject commit d482f560666f7438aa78486af6dad7998592e1e5 diff --git a/test/unit-test/README.md b/test/unit-test/README.md new file mode 100644 index 0000000..539ebb9 --- /dev/null +++ b/test/unit-test/README.md @@ -0,0 +1,79 @@ +# FreeRTOS+FAT unit tests + +Host-based unit tests built with [Unity] and [CMock], following the same +pattern as the FreeRTOS core libraries (for example [coreMQTT]). + +The module under test (`ff_ioman.c`) is compiled as a "real" library and linked +against CMock-generated mocks of its dependencies (the locking, FAT and CRC +layers). Lightweight kernel type/macro stubs (under `include/`) stand in for the +FreeRTOS kernel, and the FreeRTOS+FAT byte accessors and buffer cache run for +real, fed by an in-memory block device. + +[Unity]: https://github.com/ThrowTheSwitch/Unity +[CMock]: https://github.com/ThrowTheSwitch/CMock +[coreMQTT]: https://github.com/FreeRTOS/coreMQTT/tree/main/test/unit-test + +## Layout + +| Path | Purpose | +| --- | --- | +| `CMock/` | CMock submodule (vendors Unity + CException). | +| `CMakeLists.txt` | Top-level test build: sets up CMock/Unity, declares the mocks, the module under test, and the `coverage` target. | +| `cmock_build.cmake` | Clones CMock and builds the `unity` / `cmock` libraries. | +| `config/FreeRTOSFATConfig.h` | Test configuration. `ffconfigMAX_PARTITIONS` is 4 so the partition-enumeration bounds checks are reachable with a compact disk image. | +| `include/` | Minimal `FreeRTOS.h`, `task.h`, `semphr.h`, `event_groups.h` stubs (types/macros only), shadowing the absent kernel headers. | +| `ff_ioman_utest.c` | Unity tests for partition-table parsing in `ff_ioman.c`. | + +Shared CMake helpers live at the repository root under +[`tools/cmock/`](../../tools/cmock): `create_test.cmake` (the +`create_mock_list` / `create_real_library` / `create_test` functions), +`project.yml` (CMock configuration) and `coverage.cmake`. + +## Prerequisites + +- CMake, a C compiler, and `make` +- Ruby (CMock and the Unity test-runner generator are Ruby scripts) +- `lcov` for the coverage target +- The `CMock` submodule (`git submodule update --init --recursive`). The build + will attempt to clone it automatically if missing. + +## Running + +```sh +cmake -S test/unit-test -B test/unit-test/build/ +make -C test/unit-test/build/ all +ctest --test-dir test/unit-test/build/ -E system --output-on-failure +make -C test/unit-test/build/ coverage # writes build/coverage.info +``` + +These are the same steps the release workflow (`.github/workflows/release.yml`) +runs. + +## What `ff_ioman_utest` covers + +The suite drives the public `FF_PartitionSearch()`, which walks the extended / +logical partition (EBR) chain via the internal `FF_ParseExtended()`. It focuses +on the bounds checks that keep `FF_SPartFound_t::pxPartitions[]` from being +written past `ffconfigMAX_PARTITIONS`: + +- **Overflowing chain is clamped** — a disk advertising more logical partitions + than configured stops at the limit (and does not overrun the array). +- **Chain at the limit is recorded** — exactly `ffconfigMAX_PARTITIONS` logical + partitions are all enumerated. +- **Self-referential chain terminates** — an EBR that links to itself is capped + rather than looping forever and overrunning the array. + +Each test wraps `FF_SPartFound_t` in a guard structure and asserts the guard +bytes are untouched, so an out-of-bounds write is detected as a test failure. + +The locking layer is mocked and ignored (`FF_PendSemaphore_Ignore()` etc.); +`FF_CreateEvents_IgnoreAndReturn( pdTRUE )` lets the I/O manager be created. + +## Adding more tests + +1. Add the test source and declare it in `CMakeLists.txt` via `create_test`. +2. If the unit under test pulls in new dependencies, add their headers to + `mock_list` so CMock generates mocks for them, and add any extra real + sources to `real_source_files`. +3. Write `test_*` functions using Unity assertions plus `setUp`/`tearDown`; the + test runner is generated automatically. diff --git a/test/unit-test/cmock_build.cmake b/test/unit-test/cmock_build.cmake new file mode 100644 index 0000000..aa433c3 --- /dev/null +++ b/test/unit-test/cmock_build.cmake @@ -0,0 +1,60 @@ +# Macro utilities to build the Unity and CMock libraries used by the unit tests. +# Adapted from the FreeRTOS core library repositories. + +# Clone the CMock submodule (including its Unity / CException sub-submodules) if +# it has not been checked out yet. +macro(clone_cmock) + find_package(Git REQUIRED) + message("Cloning submodule CMock.") + execute_process(COMMAND ${GIT_EXECUTABLE} submodule update --checkout --init --recursive ${CMOCK_DIR} + WORKING_DIRECTORY ${MODULE_ROOT_DIR} + RESULT_VARIABLE CMOCK_CLONE_RESULT) + + if(NOT ${CMOCK_CLONE_RESULT} STREQUAL "0") + message(FATAL_ERROR "Failed to clone CMock submodule.") + endif() +endmacro() + +# Add the Unity and CMock library targets to the build. +macro(add_cmock_targets) + list(APPEND CMOCK_INCLUDE_DIRS + "${CMOCK_DIR}/vendor/unity/src/" + "${CMOCK_DIR}/vendor/unity/extras/fixture/src" + "${CMOCK_DIR}/vendor/unity/extras/memory/src" + "${CMOCK_DIR}/src" + ) + + add_library(cmock STATIC + "${CMOCK_DIR}/src/cmock.c" + ) + + set_target_properties(cmock PROPERTIES + ARCHIVE_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/lib + POSITION_INDEPENDENT_CODE ON + COMPILE_FLAGS "-Og" + ) + + target_include_directories(cmock PUBLIC + ${CMOCK_DIR}/src + ${CMOCK_DIR}/vendor/unity/src/ + ${CMOCK_DIR}/examples + ${CMOCK_INCLUDE_DIRS} + ) + + add_library(unity STATIC + "${CMOCK_DIR}/vendor/unity/src/unity.c" + "${CMOCK_DIR}/vendor/unity/extras/fixture/src/unity_fixture.c" + "${CMOCK_DIR}/vendor/unity/extras/memory/src/unity_memory.c" + ) + + set_target_properties(unity PROPERTIES + ARCHIVE_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/lib + POSITION_INDEPENDENT_CODE ON + ) + + target_include_directories(unity PUBLIC + ${CMOCK_INCLUDE_DIRS} + ) + + target_link_libraries(cmock unity) +endmacro() diff --git a/test/unit-test/config/FreeRTOSFATConfig.h b/test/unit-test/config/FreeRTOSFATConfig.h new file mode 100644 index 0000000..5000614 --- /dev/null +++ b/test/unit-test/config/FreeRTOSFATConfig.h @@ -0,0 +1,44 @@ +/* + * FreeRTOS+FAT unit-test configuration. + * + * SPDX-License-Identifier: MIT + * + * Minimal configuration used to host-compile the FreeRTOS+FAT sources for + * unit testing. ffconfigMAX_PARTITIONS is deliberately kept small so the + * partition-enumeration bounds checks in ff_ioman.c can be exercised with a + * modest, easy to read on-disk layout. + */ + +#ifndef FREERTOS_FAT_CONFIG_H +#define FREERTOS_FAT_CONFIG_H + +#include + +/* The host (and CI runners) are little endian. */ +#define ffconfigBYTE_ORDER ( pdFREERTOS_LITTLE_ENDIAN ) + +#define ffconfigCWD_THREAD_LOCAL_INDEX ( 0 ) + +#if !defined( portINLINE ) + #define portINLINE __inline +#endif + +/* Keep the maximum partition count small so the EBR-chain bounds checks in + * FF_ParseExtended()/FF_PartitionSearch() are reachable with a compact disk + * image. Must remain within the 1..8 range enforced by the defaults header. */ +#define ffconfigMAX_PARTITIONS ( 4 ) + +/* Use the standard C allocator on the host so the I/O manager can be created + * without a running FreeRTOS heap. */ +#define ffconfigMALLOC( size ) malloc( size ) +#define ffconfigFREE( ptr ) free( ptr ) + +/* CMock does not evaluate preprocessor conditionals, so for the dual-prototype + * helpers (FF_GetFreeSize / FF_GetVolumeSize) it generates the 64-bit variant. + * Select the matching 64-bit configuration here so the real declarations agree + * with the generated mocks. */ +#define ffconfig64_NUM_SUPPORT ( 1 ) + +/* All other ffconfig values fall back to FreeRTOSFATConfigDefaults.h. */ + +#endif /* FREERTOS_FAT_CONFIG_H */ diff --git a/test/unit-test/ff_ioman_utest.c b/test/unit-test/ff_ioman_utest.c new file mode 100644 index 0000000..330c998 --- /dev/null +++ b/test/unit-test/ff_ioman_utest.c @@ -0,0 +1,350 @@ +/* + * Unit tests for partition enumeration in ff_ioman.c. + * + * SPDX-License-Identifier: MIT + * + * These tests focus on the extended/logical partition (EBR chain) walk in + * FF_ParseExtended(), reached through the public FF_PartitionSearch() API. + * They specifically cover the bounds checks that prevent writing past + * FF_SPartFound_t::pxPartitions[ ffconfigMAX_PARTITIONS ] when a disk presents + * more logical partitions than the build is configured to hold, including a + * self-referential EBR chain that would otherwise loop forever. + * + * The module under test is linked against CMock generated mocks of its + * locking / FAT / CRC dependencies; the FreeRTOS+FAT byte accessors and buffer + * management run for real, fed by an in-memory block device. + */ + +#include +#include + +#include "unity.h" + +/* CMock generated mocks for the dependencies of ff_ioman.c. */ +#include "mock_ff_locking.h" +#include "mock_ff_fat.h" +#include "mock_ff_crc.h" + +/* The module under test (public API) and its types. */ +#include "ff_headers.h" + +/*-----------------------------------------------------------*/ +/* Virtual disk + block device callback. */ +/*-----------------------------------------------------------*/ + +#define TEST_SECTOR_SIZE ( 512U ) +#define TEST_DISK_SECTORS ( 256U ) +#define TEST_CACHE_SECTORS ( 8U ) + +/* Partition-table byte offsets within an MBR/EBR entry. */ +#define PTBL_BASE ( 0x1BEU ) +#define PTBL_ENTRY_SIZE ( 16U ) + +static uint8_t ucVirtualDisk[ TEST_DISK_SECTORS * TEST_SECTOR_SIZE ]; + +static int32_t prvReadBlocks( uint8_t * pucBuffer, + uint32_t ulSectorAddress, + uint32_t ulCount, + FF_Disk_t * pxDisk ) +{ + ( void ) pxDisk; + + if( ( ulSectorAddress + ulCount ) > TEST_DISK_SECTORS ) + { + return -1; + } + + memcpy( pucBuffer, + &ucVirtualDisk[ ulSectorAddress * TEST_SECTOR_SIZE ], + ulCount * TEST_SECTOR_SIZE ); + + return ( int32_t ) ulCount; +} + +static int32_t prvWriteBlocks( uint8_t * pucBuffer, + uint32_t ulSectorAddress, + uint32_t ulCount, + FF_Disk_t * pxDisk ) +{ + ( void ) pucBuffer; + ( void ) ulSectorAddress; + ( void ) pxDisk; + + /* The partition-search path is read-only. */ + return ( int32_t ) ulCount; +} + +/*-----------------------------------------------------------*/ +/* Helpers to lay out MBR/EBR sectors. */ +/*-----------------------------------------------------------*/ + +static void prvPutLong( uint8_t * pucSector, + uint32_t ulOffset, + uint32_t ulValue ) +{ + pucSector[ ulOffset + 0 ] = ( uint8_t ) ( ulValue & 0xFFU ); + pucSector[ ulOffset + 1 ] = ( uint8_t ) ( ( ulValue >> 8 ) & 0xFFU ); + pucSector[ ulOffset + 2 ] = ( uint8_t ) ( ( ulValue >> 16 ) & 0xFFU ); + pucSector[ ulOffset + 3 ] = ( uint8_t ) ( ( ulValue >> 24 ) & 0xFFU ); +} + +static void prvSetSignature( uint8_t * pucSector ) +{ + pucSector[ 0x1FE ] = 0x55; + pucSector[ 0x1FF ] = 0xAA; +} + +/* Write one partition-table entry into a 512-byte sector. */ +static void prvSetPartEntry( uint8_t * pucSector, + uint32_t ulEntryIdx, + uint8_t ucActive, + uint8_t ucPartitionID, + uint32_t ulStartLBA, + uint32_t ulSectorCount ) +{ + uint32_t ulOffset = PTBL_BASE + ( ulEntryIdx * PTBL_ENTRY_SIZE ); + + pucSector[ ulOffset + FF_FAT_PTBL_ACTIVE ] = ucActive; + pucSector[ ulOffset + FF_FAT_PTBL_ID ] = ucPartitionID; + prvPutLong( pucSector, ulOffset + FF_FAT_PTBL_LBA, ulStartLBA ); + prvPutLong( pucSector, ulOffset + FF_FAT_PTBL_SECT_COUNT, ulSectorCount ); +} + +static uint8_t * prvSector( uint32_t ulSectorNr ) +{ + return &ucVirtualDisk[ ulSectorNr * TEST_SECTOR_SIZE ]; +} + +/*-----------------------------------------------------------*/ +/* I/O manager lifecycle for a single test. */ +/*-----------------------------------------------------------*/ + +static FF_IOManager_t * prvCreateTestIOManager( void ) +{ + FF_CreationParameters_t xParameters; + FF_Error_t xError = FF_ERR_NONE; + + memset( &xParameters, 0, sizeof( xParameters ) ); + xParameters.ulMemorySize = TEST_CACHE_SECTORS * TEST_SECTOR_SIZE; + xParameters.ulSectorSize = TEST_SECTOR_SIZE; + xParameters.fnReadBlocks = prvReadBlocks; + xParameters.fnWriteBlocks = prvWriteBlocks; + xParameters.pxDisk = NULL; + xParameters.pvSemaphore = NULL; + xParameters.xBlockDeviceIsReentrant = pdTRUE; + + return FF_CreateIOManager( &xParameters, &xError ); +} + +/* + * Build an MBR (sector 0) whose first entry is an extended partition, and a + * forward-linked EBR chain. Each EBR defines one logical (data) partition and, + * unless it is the last link, points to the next EBR. + * + * The extended link is placed in entry 0 (before the data partition in entry 1) + * on purpose: this is the adversarial ordering in which the next-extended + * pointer is discovered before the data partition is stored, so the count limit + * must be enforced before the store rather than after it. + */ +static void prvBuildExtendedChain( uint32_t ulFirstSector, + uint32_t ulEbrCount, + uint32_t ulEbrSpacing ) +{ + uint32_t i; + const uint32_t ulExtendedSize = 4096U; /* generous, passes sanity checks */ + + memset( ucVirtualDisk, 0, sizeof( ucVirtualDisk ) ); + + /* MBR: a single extended partition that contains the whole chain. */ + prvSetPartEntry( prvSector( 0 ), 0, 0x00, FF_DOS_EXT_PART, + ulFirstSector, ulExtendedSize ); + prvSetSignature( prvSector( 0 ) ); + + for( i = 0; i < ulEbrCount; i++ ) + { + uint32_t ulThisEbr = ulFirstSector + ( i * ulEbrSpacing ); + uint8_t * pucEbr = prvSector( ulThisEbr ); + + /* Entry 0: link to the next EBR (relative to the first EBR), unless + * this is the final link in the chain. */ + if( i < ( ulEbrCount - 1 ) ) + { + uint32_t ulNextRel = ( i + 1 ) * ulEbrSpacing; + prvSetPartEntry( pucEbr, 0, 0x00, FF_DOS_EXT_PART, + ulNextRel, ulExtendedSize ); + } + + /* Entry 1: a logical data partition, located 1 sector past the EBR. */ + prvSetPartEntry( pucEbr, 1, 0x00, FF_T_FAT32 /* 0x0C */, 1U, 8U ); + + prvSetSignature( pucEbr ); + } +} + +/* + * Build an MBR plus a single EBR that links to itself - a malformed, + * self-referential chain. Each visit yields one logical partition, so without + * a count cap the parser would loop forever and overflow the array. + */ +static void prvBuildSelfReferentialChain( uint32_t ulFirstSector ) +{ + uint8_t * pucEbr; + const uint32_t ulExtendedSize = 4096U; + + memset( ucVirtualDisk, 0, sizeof( ucVirtualDisk ) ); + + prvSetPartEntry( prvSector( 0 ), 0, 0x00, FF_DOS_EXT_PART, + ulFirstSector, ulExtendedSize ); + prvSetSignature( prvSector( 0 ) ); + + pucEbr = prvSector( ulFirstSector ); + + /* Entry 0: extended link with relative LBA 0 -> resolves back to + * ulFirstSector, i.e. the EBR points at itself. */ + prvSetPartEntry( pucEbr, 0, 0x00, FF_DOS_EXT_PART, 0U, ulExtendedSize ); + /* Entry 1: logical data partition, yielded on every visit. */ + prvSetPartEntry( pucEbr, 1, 0x00, FF_T_FAT32, 1U, 8U ); + prvSetSignature( pucEbr ); +} + +/*-----------------------------------------------------------*/ +/* A guarded FF_SPartFound_t to detect writes past the array. */ +/*-----------------------------------------------------------*/ + +#define GUARD_BYTES ( 64U ) +#define GUARD_PATTERN ( 0xA5U ) + +typedef struct +{ + FF_SPartFound_t xFound; + uint8_t ucGuard[ GUARD_BYTES ]; +} GuardedPartsFound_t; + +static int prvGuardIntact( const GuardedPartsFound_t * pxGuarded ) +{ + uint32_t i; + + for( i = 0; i < GUARD_BYTES; i++ ) + { + if( pxGuarded->ucGuard[ i ] != GUARD_PATTERN ) + { + return 0; + } + } + + return 1; +} + +/*-----------------------------------------------------------*/ +/* Unity fixtures. */ +/*-----------------------------------------------------------*/ + +void setUp( void ) +{ + memset( ucVirtualDisk, 0, sizeof( ucVirtualDisk ) ); + + /* The locking layer is irrelevant to partition parsing: let every call + * pass through. FF_CreateEvents must report success so the I/O manager is + * created. */ + FF_CreateEvents_IgnoreAndReturn( pdTRUE ); + FF_DeleteEvents_Ignore(); + FF_PendSemaphore_Ignore(); + FF_ReleaseSemaphore_Ignore(); + FF_BufferWait_IgnoreAndReturn( pdTRUE ); + FF_BufferProceed_Ignore(); + FF_Sleep_Ignore(); +} + +void tearDown( void ) +{ +} + +/*-----------------------------------------------------------*/ +/* Tests. */ +/*-----------------------------------------------------------*/ + +/* + * More logical partitions on disk than ffconfigMAX_PARTITIONS: the result must + * be clamped and the array must not be overrun. + */ +void test_PartitionSearch_overflowing_chain_is_clamped( void ) +{ + FF_IOManager_t * pxIOManager; + GuardedPartsFound_t xGuarded; + FF_Error_t xResult; + + memset( &xGuarded, 0, sizeof( xGuarded ) ); + memset( xGuarded.ucGuard, GUARD_PATTERN, GUARD_BYTES ); + + /* 6 logical partitions, but ffconfigMAX_PARTITIONS is 4. */ + prvBuildExtendedChain( 100U, 6U, 10U ); + + pxIOManager = prvCreateTestIOManager(); + TEST_ASSERT_NOT_NULL( pxIOManager ); + + xResult = FF_PartitionSearch( pxIOManager, &xGuarded.xFound ); + + TEST_ASSERT_FALSE( FF_isERR( xResult ) ); + TEST_ASSERT_EQUAL_INT( ffconfigMAX_PARTITIONS, xGuarded.xFound.iCount ); + TEST_ASSERT_TRUE_MESSAGE( prvGuardIntact( &xGuarded ), + "partition array was written out of bounds" ); + + ( void ) FF_DeleteIOManager( pxIOManager ); +} + +/* + * Exactly ffconfigMAX_PARTITIONS logical partitions: all are recorded and the + * guard remains intact. + */ +void test_PartitionSearch_chain_at_limit_is_recorded( void ) +{ + FF_IOManager_t * pxIOManager; + GuardedPartsFound_t xGuarded; + FF_Error_t xResult; + + memset( &xGuarded, 0, sizeof( xGuarded ) ); + memset( xGuarded.ucGuard, GUARD_PATTERN, GUARD_BYTES ); + + prvBuildExtendedChain( 100U, ( uint32_t ) ffconfigMAX_PARTITIONS, 10U ); + + pxIOManager = prvCreateTestIOManager(); + TEST_ASSERT_NOT_NULL( pxIOManager ); + + xResult = FF_PartitionSearch( pxIOManager, &xGuarded.xFound ); + + TEST_ASSERT_FALSE( FF_isERR( xResult ) ); + TEST_ASSERT_EQUAL_INT( ffconfigMAX_PARTITIONS, xGuarded.xFound.iCount ); + TEST_ASSERT_TRUE_MESSAGE( prvGuardIntact( &xGuarded ), + "partition array was written out of bounds" ); + + ( void ) FF_DeleteIOManager( pxIOManager ); +} + +/* + * A self-referential EBR chain would loop forever and overrun the array if the + * count were not capped. The search must terminate, clamp the count, and leave + * the guard intact. + */ +void test_PartitionSearch_self_referential_chain_terminates( void ) +{ + FF_IOManager_t * pxIOManager; + GuardedPartsFound_t xGuarded; + FF_Error_t xResult; + + memset( &xGuarded, 0, sizeof( xGuarded ) ); + memset( xGuarded.ucGuard, GUARD_PATTERN, GUARD_BYTES ); + + prvBuildSelfReferentialChain( 200U ); + + pxIOManager = prvCreateTestIOManager(); + TEST_ASSERT_NOT_NULL( pxIOManager ); + + xResult = FF_PartitionSearch( pxIOManager, &xGuarded.xFound ); + + TEST_ASSERT_FALSE( FF_isERR( xResult ) ); + TEST_ASSERT_EQUAL_INT( ffconfigMAX_PARTITIONS, xGuarded.xFound.iCount ); + TEST_ASSERT_TRUE_MESSAGE( prvGuardIntact( &xGuarded ), + "partition array was written out of bounds" ); + + ( void ) FF_DeleteIOManager( pxIOManager ); +} diff --git a/test/unit-test/include/FreeRTOS.h b/test/unit-test/include/FreeRTOS.h new file mode 100644 index 0000000..518471f --- /dev/null +++ b/test/unit-test/include/FreeRTOS.h @@ -0,0 +1,69 @@ +/* + * Minimal FreeRTOS kernel stub for host-based unit testing of FreeRTOS+FAT. + * + * SPDX-License-Identifier: MIT + * + * This is NOT the real FreeRTOS kernel. It provides just enough types and + * macros for the FreeRTOS+FAT sources to compile and run on a host so that + * algorithmic logic (such as partition-table parsing) can be unit tested + * without a running scheduler. + */ + +#ifndef UNIT_TEST_FREERTOS_H +#define UNIT_TEST_FREERTOS_H + +#include +#include +#include + +#include "FreeRTOSConfig.h" + +/* ProjDefs equivalents. */ +typedef long BaseType_t; +typedef unsigned long UBaseType_t; +typedef uint32_t TickType_t; +typedef uint32_t StackType_t; + +#ifndef pdTRUE + #define pdTRUE ( ( BaseType_t ) 1 ) +#endif +#ifndef pdFALSE + #define pdFALSE ( ( BaseType_t ) 0 ) +#endif +#ifndef pdPASS + #define pdPASS ( pdTRUE ) +#endif +#ifndef pdFAIL + #define pdFAIL ( pdFALSE ) +#endif + +/* Endian markers used by FreeRTOSFATConfigDefaults.h. These are also defined + * in FreeRTOS_errno_FAT.h; guard so both inclusion orders are safe. */ +#ifndef pdFREERTOS_LITTLE_ENDIAN + #define pdFREERTOS_LITTLE_ENDIAN 0 + #define pdFREERTOS_BIG_ENDIAN 1 +#endif + +#ifndef portMAX_DELAY + #define portMAX_DELAY ( ( TickType_t ) 0xffffffffUL ) +#endif + +#ifndef portINLINE + #define portINLINE __inline +#endif + +#ifndef pdMS_TO_TICKS + #define pdMS_TO_TICKS( xTimeInMs ) ( ( TickType_t ) ( xTimeInMs ) ) +#endif + +#ifndef configASSERT + #define configASSERT( x ) assert( x ) +#endif + +/* Critical-section macros become no-ops on the host. */ +#define taskENTER_CRITICAL() do {} while( 0 ) +#define taskEXIT_CRITICAL() do {} while( 0 ) +#define taskYIELD() do {} while( 0 ) +#define portYIELD() do {} while( 0 ) + +#endif /* UNIT_TEST_FREERTOS_H */ diff --git a/test/unit-test/include/FreeRTOSConfig.h b/test/unit-test/include/FreeRTOSConfig.h new file mode 100644 index 0000000..fd25468 --- /dev/null +++ b/test/unit-test/include/FreeRTOSConfig.h @@ -0,0 +1,14 @@ +/* + * Minimal FreeRTOSConfig.h for host-based FreeRTOS+FAT unit tests. + * + * SPDX-License-Identifier: MIT + */ + +#ifndef UNIT_TEST_FREERTOS_CONFIG_H +#define UNIT_TEST_FREERTOS_CONFIG_H + +#define configUSE_16_BIT_TICKS 0 +#define configSUPPORT_DYNAMIC_ALLOCATION 1 +#define configSUPPORT_STATIC_ALLOCATION 0 + +#endif /* UNIT_TEST_FREERTOS_CONFIG_H */ diff --git a/test/unit-test/include/event_groups.h b/test/unit-test/include/event_groups.h new file mode 100644 index 0000000..868727e --- /dev/null +++ b/test/unit-test/include/event_groups.h @@ -0,0 +1,28 @@ +/* + * Minimal FreeRTOS event_groups.h stub for host-based FreeRTOS+FAT unit tests. + * + * SPDX-License-Identifier: MIT + */ + +#ifndef UNIT_TEST_EVENT_GROUPS_H +#define UNIT_TEST_EVENT_GROUPS_H + +#include "FreeRTOS.h" + +typedef void * EventGroupHandle_t; +typedef TickType_t EventBits_t; + +EventGroupHandle_t xEventGroupCreate( void ); +void vEventGroupDelete( EventGroupHandle_t xEventGroup ); +EventBits_t xEventGroupSetBits( EventGroupHandle_t xEventGroup, + const EventBits_t uxBitsToSet ); +EventBits_t xEventGroupClearBits( EventGroupHandle_t xEventGroup, + const EventBits_t uxBitsToClear ); +EventBits_t xEventGroupWaitBits( EventGroupHandle_t xEventGroup, + const EventBits_t uxBitsToWaitFor, + const BaseType_t xClearOnExit, + const BaseType_t xWaitForAllBits, + TickType_t xTicksToWait ); +EventBits_t xEventGroupGetBits( EventGroupHandle_t xEventGroup ); + +#endif /* UNIT_TEST_EVENT_GROUPS_H */ diff --git a/test/unit-test/include/semphr.h b/test/unit-test/include/semphr.h new file mode 100644 index 0000000..cb05076 --- /dev/null +++ b/test/unit-test/include/semphr.h @@ -0,0 +1,20 @@ +/* + * Minimal FreeRTOS semphr.h stub for host-based FreeRTOS+FAT unit tests. + * + * SPDX-License-Identifier: MIT + */ + +#ifndef UNIT_TEST_SEMPHR_H +#define UNIT_TEST_SEMPHR_H + +#include "FreeRTOS.h" + +typedef void * SemaphoreHandle_t; + +SemaphoreHandle_t xSemaphoreCreateRecursiveMutex( void ); +void vSemaphoreDelete( SemaphoreHandle_t xSemaphore ); +BaseType_t xSemaphoreTakeRecursive( SemaphoreHandle_t xMutex, + TickType_t xBlockTime ); +BaseType_t xSemaphoreGiveRecursive( SemaphoreHandle_t xMutex ); + +#endif /* UNIT_TEST_SEMPHR_H */ diff --git a/test/unit-test/include/task.h b/test/unit-test/include/task.h new file mode 100644 index 0000000..2ae276a --- /dev/null +++ b/test/unit-test/include/task.h @@ -0,0 +1,17 @@ +/* + * Minimal FreeRTOS task.h stub for host-based FreeRTOS+FAT unit tests. + * + * SPDX-License-Identifier: MIT + */ + +#ifndef UNIT_TEST_TASK_H +#define UNIT_TEST_TASK_H + +#include "FreeRTOS.h" + +typedef void * TaskHandle_t; + +void vTaskDelay( TickType_t xTicksToDelay ); +TaskHandle_t xTaskGetCurrentTaskHandle( void ); + +#endif /* UNIT_TEST_TASK_H */ diff --git a/tools/cmock/coverage.cmake b/tools/cmock/coverage.cmake new file mode 100644 index 0000000..993c3e2 --- /dev/null +++ b/tools/cmock/coverage.cmake @@ -0,0 +1,59 @@ +# Runs the unit tests and collects lcov coverage for the FreeRTOS+FAT sources. +# Adapted from the FreeRTOS core library repositories. Coverage is scoped to the +# library sources under test (ff_*.c) rather than an upstream "source" tree. +cmake_minimum_required(VERSION 3.13) + +# Reset coverage counters and prepare the output directory. +execute_process( + COMMAND lcov --directory ${CMAKE_BINARY_DIR} + --base-directory ${CMAKE_BINARY_DIR} + --zerocounters + COMMAND ${CMAKE_COMMAND} -E make_directory ${CMAKE_BINARY_DIR}/coverage + ) + +# Baseline (zeroed) capture so unexecuted lines still appear in the report. +execute_process( + COMMAND lcov --directory ${CMAKE_BINARY_DIR} + --base-directory ${CMAKE_BINARY_DIR} + --initial + --capture + --rc branch_coverage=1 + --output-file ${CMAKE_BINARY_DIR}/base_coverage.info + --include "*ff_*.c" + ) + +file(GLOB files "${CMAKE_BINARY_DIR}/bin/tests/*") + +set(REPORT_FILE ${CMAKE_BINARY_DIR}/utest_report.txt) +file(WRITE ${REPORT_FILE} "") + +# Run every test executable, collecting their output for CI logs. +foreach(testname ${files}) + get_filename_component(test ${testname} NAME_WLE) + message("Running ${testname}") + execute_process(COMMAND ${testname} + OUTPUT_FILE ${CMAKE_BINARY_DIR}/${test}_out.txt) + file(READ ${CMAKE_BINARY_DIR}/${test}_out.txt CONTENTS) + file(APPEND ${REPORT_FILE} "${CONTENTS}") +endforeach() + +# Capture coverage after running the tests. +execute_process( + COMMAND lcov --capture + --rc branch_coverage=1 + --base-directory ${CMAKE_BINARY_DIR} + --directory ${CMAKE_BINARY_DIR} + --output-file ${CMAKE_BINARY_DIR}/second_coverage.info + --include "*ff_*.c" + ) + +# Combine the baseline with the post-run data. +execute_process( + COMMAND lcov --base-directory ${CMAKE_BINARY_DIR} + --directory ${CMAKE_BINARY_DIR} + --add-tracefile ${CMAKE_BINARY_DIR}/base_coverage.info + --add-tracefile ${CMAKE_BINARY_DIR}/second_coverage.info + --output-file ${CMAKE_BINARY_DIR}/coverage.info + --rc branch_coverage=1 + --include "*ff_*.c" + ) diff --git a/tools/cmock/create_test.cmake b/tools/cmock/create_test.cmake new file mode 100644 index 0000000..1494a09 --- /dev/null +++ b/tools/cmock/create_test.cmake @@ -0,0 +1,135 @@ +# CMake helpers for building CMock/Unity based unit tests. +# +# Adapted from the FreeRTOS core library repositories (originally from the +# amazon-freertos repository). The "real library" warning flags have been +# relaxed relative to the upstream copy: the FreeRTOS+FAT sources predate the +# strict -Werror/-Wconversion regime used by the newer core libraries, so the +# aggressive set is not applied here. Coverage instrumentation is retained. + +# Function to create the test executable. +function(create_test test_name + test_src + link_list + dep_list + include_list) + set(mocks_dir "${CMAKE_CURRENT_BINARY_DIR}/mocks") + include(CTest) + get_filename_component(test_src_absolute ${test_src} ABSOLUTE) + add_custom_command(OUTPUT ${test_name}_runner.c + COMMAND ruby + ${CMOCK_DIR}/vendor/unity/auto/generate_test_runner.rb + ${MODULE_ROOT_DIR}/tools/cmock/project.yml + ${test_src_absolute} + ${test_name}_runner.c + DEPENDS ${test_src} + ) + add_executable(${test_name} ${test_src} ${test_name}_runner.c) + set_target_properties(${test_name} PROPERTIES + COMPILE_FLAG "-O0 -ggdb" + RUNTIME_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/bin/tests" + INSTALL_RPATH_USE_LINK_PATH TRUE + ) + # The module under test is built with coverage instrumentation, so the test + # executable must link the coverage runtime. + target_link_options(${test_name} PRIVATE --coverage) + target_include_directories(${test_name} PUBLIC + ${mocks_dir} + ${include_list} + ) + + target_link_directories(${test_name} PUBLIC + ${CMAKE_CURRENT_BINARY_DIR} + ) + + # Link all libraries sent through parameters. + foreach(link IN LISTS link_list) + target_link_libraries(${test_name} ${link}) + endforeach() + + # Add a dependency to each entry of the dep_list parameter. + foreach(dependency IN LISTS dep_list) + add_dependencies(${test_name} ${dependency}) + target_link_libraries(${test_name} ${dependency}) + endforeach() + target_link_libraries(${test_name} unity) + target_link_directories(${test_name} PUBLIC + ${CMAKE_CURRENT_BINARY_DIR}/lib + ) + add_test(NAME ${test_name} + COMMAND ${CMAKE_BINARY_DIR}/bin/tests/${test_name} + WORKING_DIRECTORY ${CMAKE_BINARY_DIR} + ) +endfunction() + +# Generates a mock library based on a module's header file, placing the +# generated source in the build directory. +# mock_name : target name for the mock library +# mock_list : list of header files to mock +# cmock_config : CMock configuration (project.yml) +# mock_include_list : include directories required to compile the mocks +# mock_define_list : extra compile definitions for the mocks +function(create_mock_list mock_name + mock_list + cmock_config + mock_include_list + mock_define_list) + set(mocks_dir "${CMAKE_CURRENT_BINARY_DIR}/mocks") + add_library(${mock_name} SHARED) + foreach(mock_file IN LISTS mock_list) + get_filename_component(mock_file_abs ${mock_file} ABSOLUTE) + get_filename_component(mock_file_name ${mock_file} NAME_WLE) + get_filename_component(mock_file_dir ${mock_file} DIRECTORY) + add_custom_command( + OUTPUT ${mocks_dir}/mock_${mock_file_name}.c + COMMAND ruby + ${CMOCK_DIR}/lib/cmock.rb + -o${cmock_config} ${mock_file_abs} + WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR} + ) + target_sources(${mock_name} PUBLIC + ${mocks_dir}/mock_${mock_file_name}.c + ) + target_include_directories(${mock_name} PUBLIC ${mock_file_dir}) + endforeach() + target_include_directories(${mock_name} PUBLIC + ${mocks_dir} + ${mock_include_list} + ) + if(APPLE) + set_target_properties(${mock_name} PROPERTIES + LIBRARY_OUTPUT_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}/lib + POSITION_INDEPENDENT_CODE ON + LINK_FLAGS "-Wl,-undefined,dynamic_lookup" + ) + else() + set_target_properties(${mock_name} PROPERTIES + LIBRARY_OUTPUT_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}/lib + POSITION_INDEPENDENT_CODE ON + ) + endif() + target_compile_definitions(${mock_name} PUBLIC ${mock_define_list}) + target_link_libraries(${mock_name} cmock unity) +endfunction() + +# Builds the module under test as a static library, instrumented for coverage. +function(create_real_library target + src_file + real_include_list + mock_name) + add_library(${target} STATIC ${src_file}) + target_include_directories(${target} PUBLIC ${real_include_list}) + set_target_properties(${target} PROPERTIES + COMPILE_FLAGS "-Wall -Wextra \ + -Wno-unused-but-set-variable \ + -Wno-unused-parameter \ + -fprofile-arcs -ftest-coverage \ + -fno-inline \ + -fno-optimize-sibling-calls \ + -O0" + LINK_FLAGS "-fprofile-arcs -ftest-coverage" + ARCHIVE_OUTPUT_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}/lib + ) + if(NOT(mock_name STREQUAL "")) + add_dependencies(${target} ${mock_name}) + endif() +endfunction() diff --git a/tools/cmock/project.yml b/tools/cmock/project.yml new file mode 100644 index 0000000..788a637 --- /dev/null +++ b/tools/cmock/project.yml @@ -0,0 +1,31 @@ +# CMock configuration for the FreeRTOS+FAT unit tests. +# Based on the configuration used across the FreeRTOS core libraries. +:cmock: + :mock_prefix: mock_ + :when_no_prototypes: :warn + :enforce_strict_ordering: TRUE + :plugins: + - :ignore + - :ignore_arg + - :expect_any_args + - :array + - :callback + - :return_thru_ptr + :callback_include_count: true + :callback_after_arg_check: false + :treat_as: + uint8: HEX8 + uint16: HEX16 + uint32: UINT32 + int8: INT8 + bool: UINT8 + # The FreeRTOS+FAT headers are not self-contained: they expect to be reached + # through "ff_headers.h", which pulls in the kernel types, the library config + # and the FF_* type definitions. Force that umbrella header in ahead of the + # mocked header so each generated mock compiles. + :includes_h_pre_orig_header: + - "ff_headers.h" + :includes: + - + - + :treat_externs: :include