ThrowTheSwitch / Ceedling

Ruby-based unit testing and build system for C projects
http://throwtheswitch.org
Other
582 stars 244 forks source link

Ceedling test:all works on first build, but fails on subsequent builds #616

Open bigbrett opened 3 years ago

bigbrett commented 3 years ago

As stated in the title. If I rm -rf the test build directory, the first time I run ceedling test:all, it sucessfully executes my tests, as expected.

However when I run the same command again right after, some of my tests run to completion, but some fail with the compiler complaining about re-definitions:

Test 'test_logic_flash_logging.c'                                                                                                                                                                  
---------------------------------                                                                                                                                                                  
Preprocessing util_clock.h...                                                                                                                                                                      
Generating include list for util_clock.h...                                                                                                                                                        
Creating mock for util_clock...                                                                                                                                                                    
Generating dependencies for mock_util_clock.c...                                                                                                                                                   
Generating runner for test_logic_flash_logging.c...                                                                                                                                                
Compiling test_logic_flash_logging_runner.c...                                                                                                                                                     
Compiling test_logic_flash_logging.c...                                                                                                                                                            
In file included from ./logic/logic_flash_logging.h:14,                                                                                                                                            
                 from test/logic/test_logic_flash_logging.c:8:                                                                                                                                     
./build/test/cache/proto_flash.h:22:5: error: redeclaration of enumerator ‘PROTO_FLASH_ERROR_NOT_IN_PROGRESS’                                                                                      
   22 |     PROTO_FLASH_ERROR_NOT_IN_PROGRESS = ((PROTO_FLASH_BASE) + (0)),                                                                                                                        
      |     ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~                                                                                                                                                      
In file included from build/test/mocks/mock_proto_flash.h:6,                                                                                                                                       
                 from test/logic/test_logic_flash_logging.c:7:                                                                                                                                     
./build/test/cache/proto_flash.h:22:5: note: previous definition of ‘PROTO_FLASH_ERROR_NOT_IN_PROGRESS’ was here                                                                                   
   22 |     PROTO_FLASH_ERROR_NOT_IN_PROGRESS = ((PROTO_FLASH_BASE) + (0)),                                                                                                                        
      |     ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~                        

and


#<Thread:0x0000555ea98d5dd0 /home/brett/workspace/firmware/link-controller/apps/link_controller/vendor/ceedling/lib/ceedling/par_map.rb:7 run> terminated with exception (report_on_exception is true):
Traceback (most recent call last):
        11: from /home/brett/workspace/firmware/link-controller/apps/link_controller/vendor/ceedling/lib/ceedling/par_map.rb:10:in `block (2 levels) in par_map'
        10: from /home/brett/workspace/firmware/link-controller/apps/link_controller/vendor/ceedling/lib/ceedling/task_invoker.rb:97:in `block in invoke_test_objects'
         9: from /usr/share/rubygems-integration/all/gems/rake-13.0.1/lib/rake/task.rb:188:in `invoke'
         8: from /usr/share/rubygems-integration/all/gems/rake-13.0.1/lib/rake/task.rb:199:in `invoke_with_call_chain'
         7: from /usr/share/rubygems-integration/all/gems/rake-13.0.1/lib/rake/task.rb:199:in `synchronize'
         6: from /usr/share/rubygems-integration/all/gems/rake-13.0.1/lib/rake/task.rb:219:in `block in invoke_with_call_chain'
         5: from /usr/share/rubygems-integration/all/gems/rake-13.0.1/lib/rake/task.rb:281:in `execute'
         4: from /usr/share/rubygems-integration/all/gems/rake-13.0.1/lib/rake/task.rb:281:in `each'
         3: from /usr/share/rubygems-integration/all/gems/rake-13.0.1/lib/rake/task.rb:281:in `block in execute'
         2: from /home/brett/workspace/firmware/link-controller/apps/link_controller/vendor/ceedling/lib/ceedling/rules_tests.rake:17:in `block in <top (required)>'
         1: from /home/brett/workspace/firmware/link-controller/apps/link_controller/vendor/ceedling/lib/ceedling/generator.rb:99:in `generate_object_file'
/home/brett/workspace/firmware/link-controller/apps/link_controller/vendor/ceedling/lib/ceedling/tool_executor.rb:88:in `exec': ShellExecutionException (ShellExecutionException)
rake aborted!
ShellExecutionException: ShellExecutionException
/home/brett/workspace/firmware/link-controller/apps/link_controller/vendor/ceedling/lib/ceedling/tool_executor.rb:88:in `exec'
/home/brett/workspace/firmware/link-controller/apps/link_controller/vendor/ceedling/lib/ceedling/generator.rb:99:in `generate_object_file'
/home/brett/workspace/firmware/link-controller/apps/link_controller/vendor/ceedling/lib/ceedling/rules_tests.rake:17:in `block in <top (required)>'
/home/brett/workspace/firmware/link-controller/apps/link_controller/vendor/ceedling/lib/ceedling/task_invoker.rb:97:in `block in invoke_test_objects'
/home/brett/workspace/firmware/link-controller/apps/link_controller/vendor/ceedling/lib/ceedling/par_map.rb:10:in `block (2 levels) in par_map'
Tasks: TOP => build/test/out/c/test_logic_flash_logging.o
(See full trace by running task with --trace)
ERROR: Ceedling Failed

Looks like a caching issue?

Additionally, when I clobber, the error persists. The only way to prevent the error from occurring again is to rm -rf the build directory.

Project File

---

:project:
  :use_exceptions: FALSE
  :use_test_preprocessor: TRUE
  :use_auxiliary_dependencies: TRUE
  :use_deep_dependencies: TRUE
  :generate_deep_dependencies: TRUE
  :build_root: build
  :test_file_prefix: test_
  :which_ceedling: vendor/ceedling
  :use_assebly: FALSE
  :default_tasks:
    - test:all

:environment:

:extension:
  :executable: .out

:module_generator:
  :project_root: ./
  :source_root: ./
  :test_root: ./test/

:paths:
  :test:
    - +:test/**
    - -:test/support
  :source:
    - ./**

    - ../../common/robot/**
    - ../../common/som
    - ../../common/proto
    - ../../common/util

    - ../../lib/CMSIS/Include/**
    - ../../lib/xip/**
    - ../../lib/lwip/port/**
    - ../../lib/lwip/src/**
    - ../../lib/lwip/src/include/**
    - ../../lib/lwip/src/include/lwip/**
    - -:../../lib/lwip/src/include/lwip/prot/**
    - ../../lib/lwip/src/include/netif/**
    - ../../lib/sbl/**
    - ../../lib/rtcesl/CM7F_RTCESL_4.5_MCUX/** # added to eliminate file-not-found error for "gflib_FP.h"
    - ../../lib/MCUX/**                        # added to eliminate file-not-found error for "cr_section_macros.h"

  :support:
    - test/support

:defines:
  :commmon: &common_defines
    - __packed="__attribute__((packed))"
  :test:
    - *common_defines
    - TEST
    - UNITY_INCLUDE_PRINT_FORMATTED
  :test_preprocess:
    - *common_defines
    - TEST

:cmock:
  :mock_prefix: mock_
  :when_no_prototypes: :warn
  :enforce_strict_ordering: FALSE # true
  :plugins:
    - :ignore
    - :callback
    - :expect_any_args
    - :ignore_arg
    - :return_thru_ptr
  :treat_as:
    uint8:    HEX8
    uint16:   HEX16
    uint32:   UINT32
    int8:     INT8
    bool:     UINT8
  :defines:
   - CMOCK_MEM_DYNAMIC

:gcov:
    :html_report_type: basic

:flags:
  :test:
    :compile:
        :*:
            - -Wno-int-to-pointer-cast
            - -Wno-pointer-to-int-cast
            - -fshort-enums 
            - -Wno-implicit-function-declaration # included to suppress issuance of warnings regarding implictly-declared functions within "arm_math.h"

:libraries:
  :placement: :end
  :flag: "${1}"  # or "-L ${1}" for example
  :common: &common_libraries []
  :test:
    - *common_libraries
  :release:
    - *common_libraries

:plugins:
  :load_paths:
    - vendor/ceedling/plugins
  :enabled:
    - stdout_pretty_tests_report
    - module_generator
    - raw_output_report
...

Ceedling Info:

      Ceedling:: 0.31.1
      Unity:: 2.5.4
      CMock:: 2.5.4
      CException:: 1.3.3

Running on Ubuntu 20.04 LTS

fetiu commented 2 years ago

Seeing the error logs, looks like proto_flash.h has been included twice. Can you share your source files, test_logic_flash_logging.c, logic_flash_logging.h and proto_flash.h?

maybe putting #pragma once at the top of your header file would work too.

Diego-19xx commented 1 year ago

@bigbrett did you solved it??, because I'm facing the same problem

Letme commented 1 year ago

I also have a feeling this is about missing include guards in one of the header files of proto_flash.h

Diego-19xx commented 1 year ago

Well, actually after some testing i was able to replicate the problem:

This happens only when you have inline functions and includes in the header files to mock, like this

$ tree -L 3
.
|-- app
|   |-- drvs        
|   |   |-- myadc.c             --> #include "myadc.h"
|   |   |-- myadc.h             --> #include "mypwm.h"  here is where the inline functions are declared
|   |   |-- mypwm.c          
|   |   `-- mypwm.h          --> #include "myadc.h"
|   |-- dummy.c               --> #include "myadc.h"  
|   |-- dummy.h     
|   `-- main.c      
|-- makefile        
|-- project.yml     
`-- test
    `-- test_dummy.c      -- > #include "mock_myadc.h" 

the problem relays on myadc.h includes mypwm.h and mypwm.h is also inluding for some reason myadc.h again (I'm just replicating the issue), if we do:

$ ceedling test:all

Test 'test_dummy.c'
-------------------
Generating include list for myadc.h...
Creating mock for myadc...
Generating runner for test_dummy.c...
Compiling test_dummy_runner.c...
Compiling test_dummy.c...
Compiling mock_myadc.c...
Build/ceedling/test/mocks/mock_myadc.c:120:9: error: redefinition of 'adc_c_to_f'
  120 | uint8_t adc_c_to_f(uint8_t c)
      |         ^~~~~~~~~~
In file included from ./app/drvs/mypwm.h:4,
                 from Build/ceedling/test/mocks/myadc.h:1,
                 from Build/ceedling/test/mocks/mock_myadc.h:6,
                 from Build/ceedling/test/mocks/mock_myadc.c:6:
./app/drvs/myadc.h:9:55: note: previous definition of 'adc_c_to_f' with type 'uint8_t(uint8_t)' {aka 'unsigned char(unsigned char)'}
    9 | static inline __attribute__ ((always_inline)) uint8_t adc_c_to_f( uint8_t c )
      |   

ceedling build too complains about a redefinition, The ceedling/test/mocks folder contains the proper files as expected

$ ceedling test:dummy

Test 'test_dummy.c'
-------------------
Compiling mock_myadc.c...
Compiling unity.c...
Compiling dummy.c...
Compiling CException.c...
Compiling cmock.c...
Linking test_dummy.exe...
Running test_dummy.exe...

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

problem is solved

Letme commented 1 year ago

You can check a static inline module in CMock which you can use to mock also static inline functions. I spinout the implementation of static inline functions in separate header (lets say postfix with _inline_impl.h) which I only include when ifndef TEST. It helps in such cases - but I do not use Ceedling to build in those projects, so I can't give you example configuration.

Since you found a solution we can probably close this ticket?

Diego-19xx commented 1 year ago

Is not a solution because you have to manually modify what ceedling is generating, @mvandervoord nad i don't know if this break any other stuffs in ceedling

mvandervoord commented 1 year ago

Was your solution to add protection to the generated header, like so:

ifndef MYADCH

define MYADCH

// all other generated code

endif

Diego-19xx commented 1 year ago

exactly. I can provide with the example in case some body else want to tested, make some suggestion about how to run some more exhaustive tests

inlines.zip

mvandervoord commented 1 year ago

Honestly, I thought we had this feature already... but it appears we do not. Perhaps this was because we were afraid of getting name conflicts... but that seems solveable... we could always generate the protection as something ridiculous like GUARD_HEADER_FOR_filename_H