Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Issue when combining use_test_preprocessor and CMock's treat_inlines #706

Open
i-adamov opened this issue Oct 13, 2022 · 28 comments
Open

Issue when combining use_test_preprocessor and CMock's treat_inlines #706

i-adamov opened this issue Oct 13, 2022 · 28 comments

Comments

@i-adamov
Copy link

i-adamov commented Oct 13, 2022

I am working on a big firmware project where Ceedling is used to run unittests and mock driver headers.
The issue I am having is that if I enable the test preprocessor feature and have CMock configured to treat header files with inline functions (of which we have some) the compilation of the unittest fails.

Additional info specific to our project is that the unittests (and project.yml file) are located in a separate directory so I am doing CEEDLING_MAIN_PROJECT_FILE=./unittests/project.yml before calling Ceedling.

I was able to recreate the issue using a simple example project that contains only a few files - https://github.com/i-adamov/ceedling-issue-example
I have a module which I need to test (./src/example_file.c and ./inc/example_file.h) which includes a driver header (./driverv/drv_bbb.h) and in turn the driver header includes a HAL header (./driver/hal/hal_aaa.h). This simulates how our project is structured.
There is another header (./inc/other_header.h) which also includes the driver header and it is also included by the example_file module. It simulates the header of another module that may interact with the module I am testing.

When running Ceedling without the preprocessing it works fine:
export CEEDLING_MAIN_PROJECT_FILE=./unittests/project.yml ; ceedling clobber test:all

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



Test 'test_unittest.c'
----------------------
Creating mock for drv_aaa...
Generating runner for test_unittest.c...
Compiling test_unittest_runner.c...
Compiling test_unittest.c...
Compiling mock_drv_aaa.c...
Compiling unity.c...
Compiling cmock.c...
Linking test_unittest.out...
Running test_unittest.out...


[==========] Running 2 tests from 1 test cases.
[----------] Global test environment set-up.   
[----------] 2 tests from test_unittest.c      
[ RUN      ] test_unittest.c.test_func1
[       OK ] test_unittest.c.test_func1 (0 ms)
[ RUN      ] test_unittest.c.test_static_func2
[       OK ] test_unittest.c.test_static_func2 (0 ms)
[----------] 2 tests from test_unittest.c (0 ms total)

[----------] Global test environment tear-down.
[==========] 2 tests from 0 test cases ran.
[  PASSED  ] 2 tests.
[  FAILED  ] 0 tests.

 0 FAILED TESTS

 0 FAILED TESTS

However if I set :use_test_preprocessor: TRUE the build fails:

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



Test 'test_unittest.c'
----------------------
Generating include list for drv_aaa.h...
Creating mock for drv_aaa...
In file included from unittests/build/test/mocks/mock_drv_aaa.h:6:0,
                 from unittests/test/test_unittest.c:3:
unittests/build/test/mocks/drv_aaa.h:1:10: fatal error: driver/hal/hal_bbb.h: No such file or directory
 #include "driver/hal/hal_bbb.h"
          ^~~~~~~~~~~~~~~~~~~~~~
compilation terminated.
ERROR: Shell command failed.
> Shell executed command:
'gcc -E -I"/var/lib/gems/2.5.0/gems/ceedling-0.31.1/vendor/unity/src" -I"/var/lib/gems/2.5.0/gems/ceedling-0.31.1/vendor/cmock/src" -I"unittests/build/test/mocks" -I"unittests/test" -I"unittests/test/support" -I"src" -I"inc" -I"driver/inc" -I"driver/hal" -D__STATIC_INLINE="static inline" -DTEST -D__STATIC_INLINE="static inline" -DTEST -DGNU_COMPILER "unittests/test/test_unittest.c" -o "unittests/build/test/preprocess/files/test_unittest.c"'
> And exited with status: [1].

rake aborted!
ShellExecutionException: ShellExecutionException
/var/lib/gems/2.5.0/gems/ceedling-0.31.1/lib/ceedling/tool_executor.rb:88:in `exec'
/var/lib/gems/2.5.0/gems/ceedling-0.31.1/lib/ceedling/preprocessinator_file_handler.rb:12:in `preprocess_file'
/var/lib/gems/2.5.0/gems/ceedling-0.31.1/lib/ceedling/preprocessinator.rb:48:in `preprocess_file'
/var/lib/gems/2.5.0/gems/ceedling-0.31.1/lib/ceedling/preprocessinator.rb:12:in `block in setup'
/var/lib/gems/2.5.0/gems/ceedling-0.31.1/lib/ceedling/preprocessinator_helper.rb:37:in `preprocess_test_file'
/var/lib/gems/2.5.0/gems/ceedling-0.31.1/lib/ceedling/preprocessinator.rb:33:in `preprocess_test_and_invoke_test_mocks'
/var/lib/gems/2.5.0/gems/ceedling-0.31.1/lib/ceedling/test_invoker.rb:84:in `block in setup_and_invoke'
/var/lib/gems/2.5.0/gems/ceedling-0.31.1/lib/ceedling/test_invoker.rb:51:in `setup_and_invoke'
/var/lib/gems/2.5.0/gems/ceedling-0.31.1/lib/ceedling/tasks_tests.rake:13:in `block (2 levels) in <top (required)>'
/var/lib/gems/2.5.0/gems/ceedling-0.31.1/bin/ceedling:345:in `block in <top (required)>'
/var/lib/gems/2.5.0/gems/ceedling-0.31.1/bin/ceedling:332:in `<top (required)>'
/usr/local/bin/ceedling:23:in `load'
/usr/local/bin/ceedling:23:in `<main>'
Tasks: TOP => test:all
(See full trace by running task with --trace)
ERROR: Ceedling Failed

What I see as a difference is that the driver header file (unittests/build/test/mocks/drv_aaa.h) which is processed by CMock to make the inline functions testable has changes to the include macros in the top of the file. It is also missing its include guard.
When running without preprocessor:

#ifndef DRV_AAA_H
#define DRV_AAA_H

#include "hal_bbb.h"

#define AAA 10

int get_aaa(void);

#endif

When running with preprocessor (empty lines truncated):

#include "driver/hal/hal_bbb.h"

int get_aaa(void);

The use of this driver/hal/hal_bbb.h filepath makes it impossible for the compiler to locate the file as the root directory is not used as an include path. However if I add it to the :paths: :include: section of the project.yml file, I get another issue with undefined macros:

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



Test 'test_unittest.c'
----------------------
Generating include list for drv_aaa.h...
Creating mock for drv_aaa...
Generating runner for test_unittest.c...
Compiling test_unittest_runner.c...
Compiling test_unittest.c...
In file included from unittests/test/test_unittest.c:4:0:
src/example_file.c: In function 'func1':
inc/other_header.h:6:20: error: 'AAA' undeclared (first use in this function)
 #define SOMETHING (AAA + 5)
                    ^
src/example_file.c:13:24: note: in expansion of macro 'SOMETHING'
     return a + b + x + SOMETHING;
                        ^~~~~~~~~
inc/other_header.h:6:20: note: each undeclared identifier is reported only once for each function it appears in
 #define SOMETHING (AAA + 5)
                    ^
src/example_file.c:13:24: note: in expansion of macro 'SOMETHING'
     return a + b + x + SOMETHING;
                        ^~~~~~~~~
unittests/test/test_unittest.c: In function 'test_func1':
inc/other_header.h:6:20: error: 'AAA' undeclared (first use in this function)
 #define SOMETHING (AAA + 5)
                    ^
unittests/test/test_unittest.c:25:36: note: in expansion of macro 'SOMETHING'
     int expected = a + x * x + x + SOMETHING;
                                    ^~~~~~~~~
ERROR: Shell command failed.
> Shell executed command:
'gcc -I"/var/lib/gems/2.5.0/gems/ceedling-0.31.1/vendor/unity/src" -I"/var/lib/gems/2.5.0/gems/ceedling-0.31.1/vendor/cmock/src" -I"unittests/build/test/mocks" -I"unittests/test" -I"unittests/test/support" -I"src" -I"." -I"inc" -I"driver/inc" -I"driver/hal" -D__STATIC_INLINE="static inline" -DTEST -DGNU_COMPILER -g -c "unittests/test/test_unittest.c" -o "unittests/build/test/out/c/test_unittest.o" -MMD -MF "unittests/build/test/dependencies/test_unittest.d"'
> And exited with status: [1].

#<Thread:0x0000556d90ed87c8@/var/lib/gems/2.5.0/gems/ceedling-0.31.1/lib/ceedling/par_map.rb:7 run> terminated with exception (report_on_exception is true):
/var/lib/gems/2.5.0/gems/ceedling-0.31.1/lib/ceedling/tool_executor.rb:88:in `exec': ShellExecutionException (ShellExecutionException)
        from /var/lib/gems/2.5.0/gems/ceedling-0.31.1/lib/ceedling/generator.rb:99:in `generate_object_file'
        from /var/lib/gems/2.5.0/gems/ceedling-0.31.1/lib/ceedling/rules_tests.rake:17:in `block in <top (required)>'
        from /usr/lib/ruby/vendor_ruby/rake/task.rb:271:in `block in execute'
        from /usr/lib/ruby/vendor_ruby/rake/task.rb:271:in `each'
        from /usr/lib/ruby/vendor_ruby/rake/task.rb:271:in `execute'
        from /usr/lib/ruby/vendor_ruby/rake/task.rb:213:in `block in invoke_with_call_chain'
        from /usr/lib/ruby/2.5.0/monitor.rb:226:in `mon_synchronize'
        from /usr/lib/ruby/vendor_ruby/rake/task.rb:193:in `invoke_with_call_chain'
        from /usr/lib/ruby/vendor_ruby/rake/task.rb:182:in `invoke'
        from /var/lib/gems/2.5.0/gems/ceedling-0.31.1/lib/ceedling/task_invoker.rb:97:in `block in invoke_test_objects'
        from /var/lib/gems/2.5.0/gems/ceedling-0.31.1/lib/ceedling/par_map.rb:10:in `block (2 levels) in par_map'
rake aborted!
ShellExecutionException: ShellExecutionException
/var/lib/gems/2.5.0/gems/ceedling-0.31.1/lib/ceedling/tool_executor.rb:88:in `exec'
/var/lib/gems/2.5.0/gems/ceedling-0.31.1/lib/ceedling/generator.rb:99:in `generate_object_file'
/var/lib/gems/2.5.0/gems/ceedling-0.31.1/lib/ceedling/rules_tests.rake:17:in `block in <top (required)>'
/var/lib/gems/2.5.0/gems/ceedling-0.31.1/lib/ceedling/task_invoker.rb:97:in `block in invoke_test_objects'
/var/lib/gems/2.5.0/gems/ceedling-0.31.1/lib/ceedling/par_map.rb:10:in `block (2 levels) in par_map'
Tasks: TOP => unittests/build/test/out/c/test_unittest.o
(See full trace by running task with --trace)
ERROR: Ceedling Failed

What am I doing wrong? Do I need to enable some of the other Ceedling setting?
I need to be able to preprocess macros and to mock static functions in header files.

@M-Bab
Copy link

M-Bab commented Jan 12, 2023

First of all thanks for writing this issue as it helped me to figure out my problem (which is exactly the same). I can confirm the issue and also provide info why this is the case and propose a solution.

Here is the problem step-by-step:

  1. If :treat_inlines: :include is enabled, a modified header file needs to be generated (obviously because static/inline is removed from the header code as well). The modified header file can be found in test/mocks and is prioritized over the original header. The generation of this modified header happens in this line: https://github.com/ThrowTheSwitch/CMock/pull/261/files#diff-04520bc1e2e09e1dd300c2060876f6341f8b76093d60f845c52b8021bf36c5f1R66 as part of CMock.
  2. If :use_test_preprocessor: TRUE is also enabled in Ceedling any header code provided to CMock is "filtered" through the actual GCC preprocessor. That preprocessor does a lot of stuff and among them is the removal of all macros - obviously because these macros are usually applied by the GCC preprocessor.
  3. As consequence of 1+2 you end up with a modified header file that is not only stripped of the keywords static/inline but also of all macros. When the GCC compiler finally tries to compile all the sources and any piece of code tries to use a macro of the original file it fails.

Here are two solutions that came to my mind:

  1. Let the preprocessor do its job but prevent the removal of macros. The gcc preprocessor has extensive options (https://gcc.gnu.org/onlinedocs/gcc/Preprocessor-Options.html) where it might support anything like this, but I did not further dive into this. Also the ceedling option use_preprocessor_directives sounds pretty much like that, but did not resolve the problem described above.
  2. Avoid feeding the preprocessor filtered file to the part of CMock which creates the modified header (without static/inline). Instead ensure this part gets the original header file where it might strip out all static/inline keywords. Then the modified but not preprocessed file is stored under test/mocks. This is completely legitimate because once it actually compiles the stuff with gcc the preprocessor will run over the header again and is able to apply all the macros which are still stored. I will create a pull request where @mvandervoord can check if this is a good/okay solution.

@laurensmiers as the creator of the original feature (ThrowTheSwitch/CMock#261) you might also be interested in this issue.

@Letme
Copy link

Letme commented Jan 12, 2023

The 2. solution is not what you want. The point of the use_test_preprocessor is to remove (well more like expand) the macros by preprocessor, so that CMock and other tools can correctly parse and generate only C valid (used) C code. In that part the 3. is actually what you would expect.

The top issue more points out that the path of the file included should be dependant on the preprocessed file and/or built file instead of original, or that inclusion of the file should actually be compiler argument include path dependant (not include directive dependant). It should also have include guard (which I agree is most probably the bug), but that will not solve the problem of removing the macro (which is what preprocessor does - not cmock, as you found out). So why do you want to use preprocessor if you do not want macros from header files to be removed?

@M-Bab
Copy link

M-Bab commented Jan 12, 2023

The 2. solution is not what you want. The point of the use_test_preprocessor is to remove (well more like expand) the macros by preprocessor, so that CMock and other tools can correctly parse and generate only C valid (used) C code. In that part the 3. is actually what you would expect.

I fear you didnt fully grasp my description. CMock will still get the correctly preprocessed header to operate normally. Only the very specific part of generating a copy of the original header without the static/inline keywords operates on the original file and not on the preprocessed file.

But I have to admit this issue is pretty much brainfuck.

@Letme
Copy link

Letme commented Jan 12, 2023

So you do not forward the preprocessed file to the CMock for stripping static inline functions, but the original file is passed to the function?

@M-Bab
Copy link

M-Bab commented Jan 12, 2023

My solution forwards both parts to CMock: The preprocessed header file and the original file. All usual operations run as always in CMock only for the specific part of creating the copy with the static/inline keywords stripped the original file is fed.

Here are the decision tree options:

  • :treat_inlines: :exclude (default) - No copy of the original header file is created. The original header file is used for compilation.
  • :treat_inlines: :include and :use_test_preprocessor: FALSE - A copy of the original header with the static/include keywords stripped is created. This file works fine and still contains all the macros. It is obviously also what you get when you call CMock directly (without Ceedling there is no preprocessor option).
  • :treat_inlines: :include and :use_test_preprocessor: TRUE - This is the error case. A copy of the original header is created where all the macros are removed. If the tests try to build and need any macros from the original header this build will fail.

@Letme
Copy link

Letme commented Jan 12, 2023

And (check if I am correct):

  • :treat_inlines: :exclude (default) and use_test_preprocessor: TRUE - Creates preprocessed copy of a header file where all macros are removed

@M-Bab
Copy link

M-Bab commented Jan 12, 2023

Yes and no. There is a copy created which is preprocessed but not what I consider as a copy in the description above. So not a copy which is placed in the include path under test/mocks.

Yes: There is a cache copy somewhere and the preprocessed data is passed to CMock.

No: This preprocessed header is not used when compiling the header later. When compiling the unittests the original header is used again and therefore naturally preprocessed by GCC and all macros are available. If this would be different the problem would be much bigger.

So to be more specific:

  • :treat_inlines: :exclude (default) - No copy of the original header file is created in the include path. The original header file is used for compilation of the unittests. This statement is valid independent of the use_test_preprocessor setting.

@informatimago
Copy link

You may add -I. so that #include "driver/hal/hal_bbb.h" be successful. You can do that in project.yml:

:paths:
  :source:
    - .
    - hal # etc

But it's strange and this shouldn't be needed.

@stemschmidt
Copy link

I am very surprised that treat_inlines creates a new header file even if the original header does not have any inline functions!?!

I understand the need to create a header file if the inline implementation has to be mocked. But why is this a global setting and not depending on the content of the header?

@M-Bab
Copy link

M-Bab commented Jan 31, 2024

Probably because its easier. Just do a batch processing of all files instead of actually looking into them and create a copy only if required.
But even the "smarter" behavior would still be fatal: Chances that important macros and inline functions are in the same header are pretty high.

@stemschmidt
Copy link

stemschmidt commented Jan 31, 2024

Adding to your solution 2 from January 12th 2023: Wouldn't it be good to have the strippables also be applied to the modified header (if your compiler does not understand some options)? As far as I reverse engineered it this is only applied to the mocked files?

@M-Bab
Copy link

M-Bab commented Jan 31, 2024

Phew ... the modified headers only exist for the sake of treat_inlines. I am not sure if my solution meddles with strippables in any way. I hope the behavior of strippables just stays exactly the same as without treat_inlines.

As also mentioned in my PRs: I am not very happy with the solution I created there. It was just the best shot I had in a few tries with very limited knowledge of ruby & rake.

@mkarlesky
Copy link
Member

@i-adamov I believe the latest prerelease of Ceedling 1.0.0 (formerly 0.32) fixes the problems documented in this issue. Ceedling's much improved preprocessing and CMock's :treat_inlines now work together as they should. I am going to leave this issue open to collect any followup. For anyone following this thread, please let us know if this problem has been corrected.

@M-Bab
Copy link

M-Bab commented Jul 18, 2024

This is great news and will make Ceedling 1.0 the framework we are absolutely looking forward to. I will try this out as soon as I can (but this might need till begin of August) and provide feedback, if this works well in our setup.

@M-Bab
Copy link

M-Bab commented Oct 8, 2024

Unfortunately the issue continues to exist according to the description with the latest ceedling version. Compare comment #868 (comment)

@mkarlesky
Copy link
Member

@i-adamov and @M-Bab So very sorry for the slow response. After staring at everything for a while and scratching my head, I think I finally properly grasp the various problems at play. And, yes, this is not fixed as I thought it was. The current handling in 1.0.0 does work correctly for some limited cases but not the sophisticated cases typically at play here.

@M-Bab Your suggestion of using CMock to strip inlines and then perform preprocessing does certainly handle your case and many more cases than the current implementation. Unfortunately, it's not a universal solution. It's entirely plausible, and even likely in these sophisticated header files scenarios, where crazy macros are generating the problematic inline function signatures we want to handle specially. On the one hand, simple string manipulating like CMock uses will not handle all cases. On the other hand, running the preprocessor to get down to plain C code that we can work on blows away all macros with no clear path to getting them back (or preserving them in the first place).

If you have ideas, please share them. We're discussing how to handle all this before finally publishing 1.0.0.

I think there may be the appropriate options in the GCC preprocessor we could use with a scan-modify-stitch approach that gets us to 100% handling of all cases. But, I do not know yet.

@mkarlesky mkarlesky pinned this issue Nov 1, 2024
@mkarlesky
Copy link
Member

Referencing #938 by @MichaelBMiner to collect related issues here

@i-adamov
Copy link
Author

i-adamov commented Nov 4, 2024

Sorry for ghosting this tread for almost two years but I see @M-Bab has taken the torch and has done a great job of replicating the issue and tracking its progress. I try to keep up with the updates here but I've been too busy to try newer versions to see if the issue is fixed.
In my case, we just decided not to use the test preprocessor functionality we write our tests with this limitation in mind. We've had a bunch of problems that all come from this limitation but so far adding some modified or custom headers to the support dir in the unittests has been sufficient workaround.

@mkarlesky
Copy link
Member

For all those playing along at home… @mvandervoord and I have looked at this problem long and hard now. We think we have a solution that will handle all cases. We are working on implementing it and testing it now. If nothing else it should support everything but oddball edge cases.

This seems to be one of the most important advanced needs of power users, so, we think it's best to tackle this as the very last need before releasing 1.0.0. The whole idea of mocking is especially valuable to anyone working with complex header files, and inline usage is especially common in complex header files.

Wish us luck…

@M-Bab
Copy link

M-Bab commented Nov 5, 2024

Dear @mkarlesky and @mvandervoord of course I wish you all the luck in the world for this. Again I can offer you my assistance, if I can be of any help. Also reading my comments/MRs can help to grasp the full scale of the problem.

Your suggestion of using CMock to strip inlines and then perform preprocessing does certainly handle your case and many more cases than the current implementation. Unfortunately, it's not a universal solution. It's entirely plausible, and even likely in these sophisticated header files scenarios, where crazy macros are generating the problematic inline function signatures we want to handle specially. On the one hand, simple string manipulating like CMock uses will not handle all cases. On the other hand, running the preprocessor to get down to plain C code that we can work on blows away all macros with no clear path to getting them back (or preserving them in the first place).

On the other hand this issue also easily leads to overthinking and I want to make sure you don't get any headaches about this: All you want is having a header copy where all the inlines are stripped without actually using the preprocessor before. Therefore I am still convinced that my concept of the 2 merge requests was very valid. I am not sure if you are able to construct a header where it actually fails. All you need is remove the inline and any macro which leads to an inline. And this list here was already rather excessive - handling the HAL headers of multiple microcontroller companies. Those headers are huge and complex but it worked without any hassle (and without any need for fake headers to get the job done). Here are some examples you don't have to worry about:

  1. Macros which are depending on conditions if they result as inline or not --> strip the macro. Additionally you still have the option here to use a compile flag to force the macro into the non-inline mode.
  2. inline functions are within macro blocks where it is hard to decide whether it will be compiled or not --> again just strip the inline. There is no harm in it. Before the header enters CMock the preprocessor will run and remove all the parts - does not matter if there is an inline keyword or not.
  3. ...?

Can you give me an example where you actually see difficulties incoming? You should rather think of it as an easy search & replace operation before your whole ceedling stuff is ongoing 😉 .

As said I am very convinced the concept of handling inlines was already quite fine in my MRs. On the other hand I was never too happy with the way I implemented it code quality wise. But I think I already emphasized this in the MRs itself. The way it was distributed among Ceedling and CMock was also not very neat.

@M-Bab
Copy link

M-Bab commented Nov 5, 2024

These were my MRs to cover the issue:

The trick there was to sneak the original header past the preprocessor to present it to the treat_inlines handling (and only to that feature).

@M-Bab
Copy link

M-Bab commented Nov 5, 2024

It's entirely plausible, and even likely in these sophisticated header files scenarios, where crazy macros are generating the problematic inline function signatures we want to handle specially.

Okay well yeah I see it now. If you use Macros to actually create your header lines you are in for a bad time. Not sure if this is a great style though. IDE parsers trying to help you with autocompletion might also crunch on that. So you are thinking of something like this:

#define GENERATE_INLINE_FUNCTION_DECL(x) inline void (x)(void)

GENERATE_INLINE_FUNCTION_DECL(myfunc);

Can't remember if I have ever seen this. Most probably headers are rather auto generated with some scripts instead of doing such evil things. Well my suggestion here would probably be ...

#if UNIT_TESTING
#define GENERATE_INLINE_FUNCTION_DECL(x) void (x)(void)
#else
#define GENERATE_INLINE_FUNCTION_DECL(x) inline void (x)(void)
#endif

GENERATE_INLINE_FUNCTION_DECL(myfunc);

But I am also curious about your generic solution which will handle it all. I will certainly test it with our Code base as a quite complex benchmark.

@mkarlesky
Copy link
Member

mkarlesky commented Nov 5, 2024

@M-Bab Yup. Exactly. You got it on your example generator macro. I'll grant you that that's ugly enough that it causes a variety of other problems. It's absolutely possible that we may have to fall back to a less universal solution such as what you've proposed. Our unfortunate reality is that (A) embedded development is the primary use case for Ceedling (B) the embedded code Ceedling is asked to work with is often a bit crazy and remarkably ugly (C) the more universal any solution can be the more easily we can support other core needs in favor of time consuming edge cases (even just keeping up with questions).

So, we're gonna go for it with the solution we hope might work. If we're right, it'll handle even nutso cases — which means it will easily handle common, reasonable cases like yours too. If it's not going to work we should know soon enough.

@mkarlesky
Copy link
Member

mkarlesky commented Nov 19, 2024

Update: The first working version of the (hopefully) universal solution to this problem is in a branch and successfully running. It produces preprocessed header files that CMock is able to mock with optional handling of inline declarations. Any and all #define and #pragma statements in the source header file are preserved such that they remain in the copy of the header file CMock generates for inline handling. As a bonus, this fuller preprocessing technique is also able to preserve TEST_SOURCE_FILE() and TEST_INCLUDE_PATH() macros in test files (these were previously incompatible new features with Ceedling's preprocessing). Some significant and long-needed memory optimizations are also in place.

This all needs some further cleanup, optimization, and documentation before it's ready to be fully exercised. I'm not sure when it will be available, but it looks like we're close to a proper solution.

@mkarlesky
Copy link
Member

mkarlesky commented Nov 29, 2024

@M-Bab The latest prerelease (1.0.0-13d0e3d) is available for testing. It preserves macro definitions and pragmas in a header file that CMock copies and modifies when :treat_inlines is set to :include. This has all been fairly thoroughly unit tested and exercised with a project, but the real test is code from a real world, sophisticated project like what you have. Please give it a try at your earliest convenience and report on back. 🤞

@M-Bab
Copy link

M-Bab commented Dec 6, 2024

Hi @mkarlesky ! Okay I did test it with 1.0.0-13d0e3d. Unfortunately not successful yet. I can provide you a little guide-through without using too much of our internal code.

  1. There is our rather simple osal.h header. Functions of it get used so it gets mocked in the test. osal-mocked.zip
  2. Once it gets mocked with all the preprocessor functions active it creates a new osal.h (together with mock_osal.h) in build/test/mocks/test_...
  3. The amount of includes is dramatically reduced. Unfortunately it does not include mdp_config.h anymore. In there is a lot of configuration and among them the OSAL_MUTEX and OSAL_SEMAPHORE enums.
  4. Building the test fails because the types are unknown because mdp_config.h is not included anymore.

I hope this can somehow help you to do another step. I am certain that this was already one of the faults I got with the previous version I tested.


Slightly off-topic. I build Ceedling with a cloned repo and gem. But before ceedling actually worked I had to

gem install thor
gem install erb

Is it possibled that these modules are somehow missing in your requirements specifications?

@mkarlesky
Copy link
Member

@M-Bab We did do some gem housekeeping recently. Thor is definitely in the Gemfile and gemspec. Not sure why it's failing. Erb is curious for other reasons. We will look into it.

@mkarlesky
Copy link
Member

@M-Bab Could you kindly change the generated osal.h to use the proper list of #include directives and then try to run your test or the (failing) compilation step within your build? I'd like to narrow in on what to fix. If the generated osal.h includes the needed symbols it was previously lacking, then I know we only need to focus on the #include statements.

I can think of two options:

  1. Temporarily alter your project :cmock configuration to inject all the missing headers and re-run one or more of your test files to completion.
  2. Re-run your test file with verbosity elevated. Copy the compilation command line for your test file. Hand edit the generated (incomplete) osal.h in the mock directory to reference the needed header files and manually compile your test at your terminal using the command line previously copied.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

6 participants