CMock: static inline functions result in incorrectly generated mocks

When I try to mock driver functions from a file in the NRF5 SDK that contain static inline functions the generated mocks seem broken.

For example, lets have a module in my app called timekeeper.c that depends upon nrfx_rtc.h (which should be mocked).

The two errors I can see in the generated mocks are:

  1. The include path in the generated mock file is a full relative path, rather than being relative to the include directories specified in project.yml. Therefore it results in the file not being able to be discovered?
Test 'test_timekeeper.c'                                                                                                                                                                                          
------------------------                                                                                                                                                                                          
Generating include list for nrfx_rtc.h...                                                                                                                                                                         
Creating mock for nrfx_rtc...                                                                                                                                                                                     
In file included from build/test/mocks/mock_nrfx_rtc.h:6,                                                                                                                                                         
                 from test/test_timekeeper.c:4:                                                                                                                                                                   
build/test/mocks/nrfx_rtc.h:1:10: fatal error: sdk/nRF5_SDK_16.0.0_98a08e2/modules/nrfx/hal/nrf_rtc.h: No such file or directory                                                                                  
    1 | #include "sdk/nRF5_SDK_16.0.0_98a08e2/modules/nrfx/hal/nrf_rtc.h"
  1. When I try and mock one of the functions that happens to be static inline in nrfx_rtc.h by using :cmock: :treat_inlines: :include in project.yml, the output at build/test/mocks/nrf_rtc.h contains TWO different definitions for each original instance of static inline functions… one that is static inline and one that is not…e.g.
uint32_t nrfx_rtc_counter_get(nrfx_rtc_t const * const p_instance);
// ..... later on
static inline uint32_t nrfx_rtc_counter_get(nrfx_rtc_t const * const p_instance);

That is about as deep as I am able to trace things down. When I manually edit the generated files to have the correct include path, it chokes on the two different definitions. When I try and manually remove one of the definitions just to see what would happen, the test never executes

Test 'test_timekeeper.c'                                                                                                                                                                                          
------------------------                                                                                                                                                                                          
Compiling test_timekeeper_runner.c...                                                                                                                                                                             
Compiling test_timekeeper.c...                                                                                                                                                                                    
Compiling mock_nrfx_rtc.c...                                                                                                                                                                                      
Compiling unity.c...                                                                                                                                                                                              
Compiling timekeeper.c...                                                                                                                                                                                         
Compiling cmock.c...                                                                                                                                                                                              
Linking test_timekeeper.out...                                                                                                                                                                                    
Running test_timekeeper.out...                                                                                                                                                                                    
                                                                                                                                                                                                                  
ERROR: Test executable "test_timekeeper.out" failed.                                                                                                                                                              
> Produced no output to $stdout.                                                                                                                                                                                  
> And exited with status: [0] (count of failed tests).                                                                                                                                                            
> This is often a symptom of a bad memory access in source or test code.                                                                                                                                          
                                                                                                                                                                                                                  
rake aborted!                                                                                                                                                                                                     
                                                                                                                                                                                                                  
/home/brett/workspace/avatech/scope/vendor/ceedling/lib/ceedling/generator_helper.rb:36:in `test_results_error_handler'                                                                                           
/home/brett/workspace/avatech/scope/vendor/ceedling/lib/ceedling/generator.rb:170:in `generate_test_results'                                                                                                      
/home/brett/workspace/avatech/scope/vendor/ceedling/lib/ceedling/rules_tests.rake:55:in `block in <top (required)>'                                                                                               
/home/brett/workspace/avatech/scope/vendor/ceedling/lib/ceedling/task_invoker.rb:102:in `invoke_test_results'                                                                                                     
/home/brett/workspace/avatech/scope/vendor/ceedling/lib/ceedling/test_invoker.rb:144:in `block in setup_and_invoke'                                                                                               
/home/brett/workspace/avatech/scope/vendor/ceedling/lib/ceedling/test_invoker.rb:76:in `each'                                                                                                                     
/home/brett/workspace/avatech/scope/vendor/ceedling/lib/ceedling/test_invoker.rb:76:in `setup_and_invoke'                                                                                                         
/home/brett/workspace/avatech/scope/vendor/ceedling/lib/ceedling/rules_tests.rake:71:in `block (2 levels) in <top (required)>'                                                                                    
/home/brett/.gem/ruby/2.7.0/gems/ceedling-0.29.1/bin/ceedling:334:in `block in <top (required)>'                                                                                                                  
/home/brett/.gem/ruby/2.7.0/gems/ceedling-0.29.1/bin/ceedling:321:in `<top (required)>'                                                                                                                           
/home/brett/.gem/ruby/2.6.0/bin/ceedling:23:in `load'                                                                                                                                                             
/home/brett/.gem/ruby/2.6.0/bin/ceedling:23:in `<main>'                                                                                                                                                           
Tasks: TOP => build/test/results/test_timekeeper.pass                                                                                                                                                             
(See full trace by running task with --trace)                                                                                                                                                                     
                                                                                                                                                                                                                  
--------------------                                                                                                                                                                                              
OVERALL TEST SUMMARY                                                                                                                                                                                              
--------------------                                                                                                                                                                                              
                                                                                                                                                                                                                  
No tests executed

About this issue

  • Original URL
  • State: open
  • Created 3 years ago
  • Comments: 29 (8 by maintainers)

Most upvoted comments

@bigbrett Please go to vendor folder in your project and remove ceedling folder. Next clone my branch in this folder:

git clone --branch static-inline-headers --recursive https://github.com/CezaryGapinski/ceedling.git

Next try to run ceedling clobber test:all You should have this output:

Clobbering all generated files...
(For large projects, this task may take a long time to complete)



Test 'test_proto_flash.c'
-------------------------
Generating include list for fsl_flexspi.h...
Creating mock for fsl_flexspi...
Creating mock for fsl_flexspi...
Generating include list for proto_flash.c...
Generating runner for test_proto_flash.c...
Compiling test_proto_flash_runner.c...
Compiling test_proto_flash.c...
Compiling mock_fsl_flexspi.c...
Compiling unity.c...
Compiling proto_flash.c...
common/proto/proto_flash.c: In function ‘proto_flash_init’:
common/proto/proto_flash.c:5:20: warning: passing argument 1 of ‘FLEXSPI_SoftwareReset’ makes pointer from integer without a cast [-Wint-conversion]
    5 | #define FLASH_ADDR (0xFEEDBEEF)
      |                    ^~~~~~~~~~~~
      |                    |
      |                    unsigned int
common/proto/proto_flash.c:9:27: note: in expansion of macro ‘FLASH_ADDR’
    9 |     FLEXSPI_SoftwareReset(FLASH_ADDR);
      |                           ^~~~~~~~~~
In file included from <command-line>:
./build/test/mocks/fsl_flexspi.h:9:35: note: expected ‘void *’ but argument is of type ‘unsigned int’
    9 | void FLEXSPI_SoftwareReset(void * addr);
      |                            ~~~~~~~^~~~
Compiling cmock.c...
Linking test_proto_flash.out...
Running test_proto_flash.out...

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

Of course there is a warning but I think you should change #define FLASH_ADDR (0xFEEDBEEF) to #define FLASH_ADDR (void *)(0xFEEDBEEF)

If anybody interested I updated my branch. Now I’m trying to cheat compiler to use generated header with the highest priority by the -include file option from gcc which can help to “Process file as if #include “file” appeared as the first line of the primary source file.”.

Hi! I’m trying to fix it in ceedling when preprocessor is enabled. If anybody interested here is my branch for tests. I think ceedling shouldn’t take preprocessed header with expanded include paths to generate parsed header with non-inline functions. When :treat_inlines option is disabled then generated mock points to real unpreprocessed header, so when :treat_inlines is set to :include mock also should points to generated header which is not preprocessed and let compiler to preprocess and compile everything in the next steps. At this moment I have to generate mock twice, once for unpreprocessed header to generate header without inlines, and next again to generate mock based on preprocessed header.

Oh damn, sorry completely missed that part… Will read @Letme 's comment a bit more in detail cause the rabbit hole seems to be deeper than I thought.

Some quick observations: cmock seems to receive the header with already a full relative path iso #include "fsl_common.h" as source-to-be-parsed when it gets to mocking it (+transforming the inline functions), so it’s happening before cmock gets to touch the file. If I disable the test preprocessor (in project.yml: :use_test_preprocessor: FALSE iso TRUE) in your example code, I don’t see the issue.

I know the testprocessor setting runs the test files and headers through the gcc preprocessor and hands these outputs to ceedling/cmock to be parsed (so cmock doesn’t know about the original include here actually…), but I wouldn’t expect him to change paths like this (but again, this may be due to my lack of knowledge) Will try to understand this better but can’t promise a solution. To fix this (very crude solution), cmock would need to know the original file (or have access to its original non-preprocessed sources) and use that to generate the non-inline header. However, I guess some users want this behaviour, so you would need a way to flag a header as use_test_preprocessor or not, which quickly becomes a mess I fear.

EDIT: Using the test preprocessor together with the static-inline mocking is giving several issues ( I think #328 is also related to this). To be honest, I don’t know what the best solution is to be able to use these together. I’d have to look more into what ceedling does with the headers and how it calls cmock for generating the mocks.

As an easy answer: The core of unit testing is to swap paths, so if you have /bla/sub/folder/unit.h you want to have it in build/bla/sub/folder/unit.h. Now, this can become a problem with relative paths and hence we should only #include "unit.h" and let the linker do the path swapping.

I went a bit through your example and I see the issue where gcc includes_preprocessor_tool returns common/hal/fsl_common.h as include path. Not sure if this is new or something, but it seems wrong:

$ ./vendor/ceedling/bin/ceedling verbosity[4] test

...
Generating include list for fsl_flexspi.h...
> Shell executed command:
'gcc -E -MM -MG -I"/home/bla/cmock-bug-repro/vendor/ceedling/vendor/unity/src" -I"/home/bla/cmock-bug-repro/vendor/ceedling/vendor/cmock/src" -I"build/test/mocks" -I"test" -I"test/common" -I"test/common/proto" -I"common" -I"common/hal" -I"common/proto" -DCPU_MIMXRT1051DVL6B -DTEST -DUNITY_INCLUDE_PRINT_FORMATTED -DCPU_MIMXRT1051DVL6B -DTEST -DGNU_COMPILER "build/temp/_fsl_flexspi.h"'
> Produced output:
_fsl_flexspi.o: build/temp/_fsl_flexspi.h common/hal/fsl_common.h \
 @@@@fsl_common.h
...

I also moved the test file around different subfolders and that makes no effect, so its purely something to do with preprocessor and adding includes. I think somehow that relative path is added to the file, while it is skipped for non-inline parser. Same as you, my Ruby knowledge is basically non-existent, so all you can do is wait…