Ceedling unit testing in a project with FreeRTOS dependency

1.5k views Asked by At

I'm trying to implement unit tests in Unity (Learn more about Unity) with a little help of CMock (Learn more about CMock). To do that I use the Ceedling tool which combines all Unity components into a one framework (Learn more about Ceedling).

Everything works as expect until I add FreeRTOS to my project/testing. When added, Ceedling struggles to generate mocks for FreeRTOS components, like queue.c or list.c. It gives following error:

Generating include list for queue.h...
build/temp/_queue.h:32:6: error: #error "include FreeRTOS.h" must appear in source files before "include queue.h"
   32 |     #error "include FreeRTOS.h" must appear in source files before "include queue.h"
      |      ^~~~~
In file included from build/temp/_queue.h:40:
../build/_deps/rtos.freertos-src/include/task.h:32:6: error: #error "include FreeRTOS.h must appear in source files before include task.h"
   32 |     #error "include FreeRTOS.h must appear in source files before include task.h"
      |      ^~~~~
In file included from ../build/_deps/rtos.freertos-src/include/task.h:35,
                 from build/temp/_queue.h:40:
../build/_deps/rtos.freertos-src/include/list.h:60:6: error: #error "FreeRTOS.h must be included before list.h"
   60 |     #error "FreeRTOS.h must be included before list.h"
      |      ^~~~~

For me, it looks like compiles queue.c file to find out list of functions which then should be mocked. That's ok but I don't know to force it to add FreeRTOS.h include while it cheks queue.c or list.c files.

I modified my project.yml file so paths to FreeRTOS are added and also FreeRTOS.h header is added to includes:

:paths:
  :test:
    - +:test/**
    - -:test/support
    - ../
    - ../build/_deps/rtos.freertos-src/portable/GCC/ARM_CM4F
    - ../build/_deps/rtos.freertos-src/include
    - ../debug/
  :source:
    - src/**
    - ../build/_deps/rtos.freertos-src/portable/GCC/ARM_CM4F/**
    - ../build/_deps/rtos.freertos-src/include/**
    - ../build/_deps/rtos.freertos-src/**
  :support:
    - test/support
  :libraries: []

... skipped lines ...
... skipped lines ...
... skipped lines ...

:cmock:
  :mock_prefix: mock_
  :when_no_prototypes: :warn
  :enforce_strict_ordering: TRUE
  :plugins:
    - :ignore
    - :callback
  :treat_as:
    uint8:    HEX8
    uint16:   HEX16
    uint32:   UINT32
    int8:     INT8
    bool:     UINT8
  :includes_h_pre_orig_header:
    - FreeRTOS.h
  :includes_c_pre_header:
    - FreeRTOS.h
  :includes:
    - ../debug/assert.h
    - FreeRTOS.h

Even with added includes_h_pre_orig_header, includes_c_pre_header and includes it still fails like FreeRTOS.h. is not added before queue.h nor list.h headers.

I've found similar problem, the resolution there was to add Init and Verify calls to SetUp and Teardown methods in the test script and also to add Expect for calls related to FreeRTOS. I tried that without luck.

My test script is simple:

#include "unity.h"
#include "cmock.h"

#include "FreeRTOS.h"
#include "FreeRTOSConfig.h"
#include "mock_queue.h"

#include "test.h"

void setUp(void) {
    mock_queue_Init();
    mock_list_Init();
    mock_task_Init();

}

void tearDown(void) {
    mock_queue_Verify();
    mock_list_Verify();
    mock_task_Verify();
}

void test_Rtos(void)
{
    xQueueCreate_Expect(8, 16);

    TestRtos();
}

The source file is also simple, just created to check if Ceedling will work with FreeRTOS:

#include "test.h"

#include "FreeRTOSConfig.h"
#include "FreeRTOS.h"
#include "queue.h"

void TestRtos(void)
{
    QueueHandle_t someQueue = xQueueCreate(8, 16);
    uxQueueMessagesWaiting(someQueue);
}

I also looked at some examples from Amazon but their project.yml doesn't even mention some paths or header files related to FreeRTOS. What I missed here?

2

There are 2 answers

0
GlebPlekhotko On

Though my reply will not help the author of the topic, I believe someone may find it using Google, and maybe it will help him to resolve this problem.

So, recently I've run into this problem too. And my little study showed, that roots are in the dependency generation steps the Ceedling performs before actually building and running a set of tests. You may see footprints of this fancy stuff in the console:

Test 'Test_SomethingToTest_create.c'
--------------------------------
Preprocessing queue.h...
Generating include list for queue.h...
Creating mock for queue...
Generating dependencies for Test_SomethingToTest_create.c...
Generating dependencies for Mock_queue.c...
Generating dependencies for unity.c...
Generating runner for Test_SomethingToTest_create.c...
Compiling Test_SomethingToTest_create_runner.c...
Compiling Test_SomethingToTest_create.c...
Compiling Mock_queue.c...
Compiling unity.c...
Compiling cmock.c...
Linking Test_SomethingToTest_create.out...
Running Test_SomethingToTest_create.out...

--------------------
OVERALL TEST SUMMARY
--------------------
TESTED:  1
PASSED:  1
FAILED:  0
IGNORED: 0

Those "Preprocessing...", "Generating include...", "Generating dependencies..." and so on engage GCC with -E option to generate some ancillary files to be later used during mocks and tests runners generation. And the Ceedling feeds "queue.h" (in my case, of course) to the GCC as is. It just doesn't know that there must be the "FreeRTOS.h" included before it. And so it fails with the "#error" message.

If we manually add the "#include <FreeRTOS.h>" directly to the "queue.h" it does resolve this issue. Because the GCC will be able to find this file in the list of the search directories. But of course it is not a good solution.

I decided to use the "-include" option to resolve this issue. Here what it does, quote: "Process file as if #include "file" appeared as the first line of the primary source file" (see GCC documentation, clause "3.13 Options Controlling the Preprocessor"). So we simply must add "-include FreeRTOS.h" to the command performing preprocessing, dependency analysis and so on.

To do that, we must define ":tools:" section in the "project.yml" file. Unfortunately, it is poorly covered in the documentation, but you may use "input.yml" in the "build\test\cache" directory as a template (at least one successful build must be performed, a fake or empty one is fine too). We're interested in the following subsections:

  • test_file_preprocessor;
  • test_file_preprocessor_directives;
  • test_includes_preprocessor;
  • test_dependencies_generator;

Here is how they look in my case:

:tools:
  :test_file_preprocessor:
    :executable: gcc.exe
    :name: default_test_file_preprocessor
    :stderr_redirect: :none
    :background_exec: :none
    :optional: false
    :arguments:
    - ''
    - ''
    - "-E"
    - -I"$": COLLECTION_PATHS_TEST_SUPPORT_SOURCE_INCLUDE_VENDOR
    - -I"$": COLLECTION_PATHS_TEST_TOOLCHAIN_INCLUDE
    - "-D$": COLLECTION_DEFINES_TEST_AND_VENDOR
    - "-D$": DEFINES_TEST_PREPROCESS
    - "-DGNU_COMPILER"
    - "-include FreeRTOS.h"
    - '"${1}"'
    - -o "${2}"
  :test_file_preprocessor_directives:
    :executable: gcc.exe
    :name: default_test_file_preprocessor_directives
    :stderr_redirect: :none
    :background_exec: :none
    :optional: false
    :arguments:
    - "-E"
    - -I"$": COLLECTION_PATHS_TEST_SUPPORT_SOURCE_INCLUDE_VENDOR
    - -I"$": COLLECTION_PATHS_TEST_TOOLCHAIN_INCLUDE
    - "-D$": COLLECTION_DEFINES_TEST_AND_VENDOR
    - "-D$": DEFINES_TEST_PREPROCESS
    - "-DGNU_COMPILER"
    - "-include FreeRTOS.h"
    - "-fdirectives-only"
    - '"${1}"'
    - -o "${2}"
  :test_includes_preprocessor:
    :executable: gcc.exe
    :name: default_test_includes_preprocessor
    :stderr_redirect: :none
    :background_exec: :none
    :optional: false
    :arguments:
    - ''
    - ''
    - "-E"
    - "-MM"
    - "-MG"
    - -I"$": COLLECTION_PATHS_TEST_SUPPORT_SOURCE_INCLUDE_VENDOR
    - -I"$": COLLECTION_PATHS_TEST_TOOLCHAIN_INCLUDE
    - "-D$": COLLECTION_DEFINES_TEST_AND_VENDOR
    - "-D$": DEFINES_TEST_PREPROCESS
    - "-DGNU_COMPILER"
    - "-include FreeRTOS.h"
    - '"${1}"'
  :test_dependencies_generator:
    :executable: gcc.exe
    :name: default_test_dependencies_generator
    :stderr_redirect: :none
    :background_exec: :none
    :optional: false
    :arguments:
    - ''
    - ''
    - "-E"
    - -I"$": COLLECTION_PATHS_TEST_SUPPORT_SOURCE_INCLUDE_VENDOR
    - -I"$": COLLECTION_PATHS_TEST_TOOLCHAIN_INCLUDE
    - "-D$": COLLECTION_DEFINES_TEST_AND_VENDOR
    - "-D$": DEFINES_TEST_PREPROCESS
    - "-DGNU_COMPILER"
    - "-include FreeRTOS.h"
    - -MT "${3}"
    - "-MM"
    - "-MD"
    - "-MG"
    - -MF "${2}"
    - -c "${1}"

With these settings we successfully pass all preparation steps but still fail building of the tests runner file. That is because the Ceedling won't add "#include <FreeRTOS.h>" to the generated mock file automatically. And so we must add one more option named "includes" to the "cmock" section of the "project.yml":

:cmock:
  :includes:
    - FreeRTOS.h

And that finally resolves this issue.

I hope that will help someone. Good luck!

0
Thomas Friesen On

I had the exact same problem. I solved it by including the file FreeRTOS.h in the file queue.h (for instance) at the very beginning. I think it is not the best solution but it works.

Thanks.