Ceedling icon indicating copy to clipboard operation
Ceedling copied to clipboard

Add support for full paths in `#include` directives of test files

Open parmi93 opened this issue 2 months ago • 3 comments

src/production_1/production_1.h:

#ifndef PRODUCTION_1_H
#define PRODUCTION_1_H

int prod_1(void);

#endif // PRODUCTION_1_H

src/production_1/production_1.c:

#include "production_1.h"
#include "src/production_2/production_2.h"

int prod_1(void)
{
    return 10 + prod_2();
}

src/production_2/production_2.h:

#ifndef PRODUCTION_2_H
#define PRODUCTION_2_H

int prod_2(void);

#endif // PRODUCTION_2_H

src/production_2/production_2.c:

#include "production_2.h"

int prod_2(void)
{
    return 20;
}

test_production_1.c:

#include "src/production_1/production_1.h" // I want to use this full path instead of `#include "production_1.h"`
#include "src/production_2/production_2.h" // I want to use this full path instead of `#include "production_2.h"`

#include "unity.h"

void setUp(void)
{
}

void tearDown(void)
{
}

void test_1(void)
{
    int ret = prod_1();

    TEST_ASSERT_EQUAL(30, ret);
}
project.yml
# =========================================================================
#   Ceedling - Test-Centered Build System for C
#   ThrowTheSwitch.org
#   Copyright (c) 2010-25 Mike Karlesky, Mark VanderVoord, & Greg Williams
#   SPDX-License-Identifier: MIT
# =========================================================================

---
:project:
  # how to use ceedling. If you're not sure, leave this as `gem` and `?`
  :which_ceedling: gem
  :ceedling_version: 1.0.2

  # optional features. If you don't need them, keep them turned off for performance
  :use_mocks: TRUE
  :use_test_preprocessor: :none  # options are :none, :mocks, :tests, or :all
  :use_deep_preprocessor: :none  # options are :none, :mocks, :tests, or :all
  :use_backtrace: :simple        # options are :none, :simple, or :gdb
  :use_decorators: :auto         # decorate Ceedling's output text. options are :auto, :all, or :none

  # tweak the way ceedling handles automatic tasks
  :build_root: build
  :test_file_prefix: test_
  :default_tasks:
    - test:all

  # performance options. If your tools start giving mysterious errors, consider 
  # dropping this to 1 to force single-tasking
  :test_threads: 8
  :compile_threads: 8

  # enable release build (more details in release_build section below)
  :release_build: FALSE

# Specify where to find mixins and any that should be enabled automatically
:mixins:
  :enabled: []
  :load_paths: []

# further details to configure the way Ceedling handles test code
:test_build:
  :use_assembly: FALSE

# further details to configure the way Ceedling handles release code
:release_build:
  :output: MyApp.out
  :use_assembly: FALSE
  :artifacts: []

# Plugins are optional Ceedling features which can be enabled. Ceedling supports
# a variety of plugins which may effect the way things are compiled, reported, 
# or may provide new command options. Refer to the readme in each plugin for 
# details on how to use it.
:plugins:
  :load_paths: []
  :enabled:
    #- beep                           # beeps when finished, so you don't waste time waiting for ceedling
    - module_generator                # handy for quickly creating source, header, and test templates
    #- gcov                           # test coverage using gcov. Requires gcc, gcov, and a coverage analyzer like gcovr
    #- bullseye                       # test coverage using bullseye. Requires bullseye for your platform
    #- command_hooks                  # write custom actions to be called at different points during the build process
    #- compile_commands_json_db       # generate a compile_commands.json file
    #- dependencies                   # automatically fetch 3rd party libraries, etc.
    #- subprojects                    # managing builds and test for static libraries
    #- fake_function_framework        # use FFF instead of CMock

    # Report options (You'll want to choose one stdout option, but may choose multiple stored options if desired)
    #- report_build_warnings_log
    #- report_tests_gtestlike_stdout
    #- report_tests_ide_stdout
    #- report_tests_log_factory
    - report_tests_pretty_stdout
    #- report_tests_raw_output_log
    #- report_tests_teamcity_stdout

# Specify which reports you'd like from the log factory
:report_tests_log_factory:
  :reports:
    - json 
    - junit 
    - cppunit 
    - html 

# override the default extensions for your system and toolchain
:extension:
  #:header: .h
  #:source: .c
  #:assembly: .s
  #:dependencies: .d
  #:object: .o
  :executable: .out
  #:testpass: .pass
  #:testfail: .fail
  #:subprojects: .a

# This is where Ceedling should look for your source and test files.
# see documentation for the many options for specifying this.
:paths:
  :test:
    - +:test/**
    - -:test/support
  :source:
    - ./**
  :include:
    - ./** # In simple projects, this entry often duplicates :source
  :support:
    - test/support
  :libraries: []

# You can even specify specific files to add or remove from your test
# and release collections. Usually it's better to use paths and let
# Ceedling do the work for you!
:files:
  :test: []
  :source: []

# Compilation symbols to be injected into builds
# See documentation for advanced options:
#  - Test name matchers for different symbols per test executable build
#  - Referencing symbols in multiple lists using advanced YAML
#  - Specifiying symbols used during test preprocessing
:defines:
  :test:
    - TEST # Simple list option to add symbol 'TEST' to compilation of all files in all test executables
  :release: []

  # Enable to inject name of a test as a unique compilation symbol into its respective executable build. 
  :use_test_definition: FALSE 

# Configure additional command line flags provided to tools used in each build step
# :flags:
#   :release:
#     :compile:         # Add '-Wall' and '--02' to compilation of all files in release target
#       - -Wall
#       - --O2
#   :test:
#     :compile:
#       '(_|-)special': # Add '-pedantic' to compilation of all files in all test executables with '_special' or '-special' in their names
#         - -pedantic
#       '*':            # Add '-foo' to compilation of all files in all test executables
#         - -foo

# Configuration Options specific to CMock. See CMock docs for details
:cmock:
  # Core configuration
  :plugins:                        # What plugins should be used by CMock?
    - :ignore
    - :callback
  :verbosity:  2                   # the options being 0 errors only, 1 warnings and errors, 2 normal info, 3 verbose
  :when_no_prototypes:  :warn      # the options being :ignore, :warn, or :erro

  # File configuration
  :skeleton_path:  ''              # Subdirectory to store stubs when generated (default: '')
  :mock_prefix:  'mock_'           # Prefix to append to filenames for mocks
  :mock_suffix:  ''                # Suffix to append to filenames for mocks

  # Parser configuration
  :strippables:  ['(?:__attribute__\s*\([ (]*.*?[ )]*\)+)']
  :attributes:
     - __ramfunc
     - __irq
     - __fiq
     - register
     - extern
  :c_calling_conventions:
     - __stdcall
     - __cdecl
     - __fastcall
  :treat_externs:  :exclude        # the options being :include or :exclude
  :treat_inlines:  :exclude        # the options being :include or :exclude

  # Type handling configuration
  #:unity_helper_path: ''          # specify a string of where to find a unity_helper.h file to discover custom type assertions
  :treat_as:                       # optionally add additional types to map custom types
    uint8:    HEX8
    uint16:   HEX16
    uint32:   UINT32
    int8:     INT8
    bool:     UINT8
  #:treat_as_array:  {}            # hint to cmock that these types are pointers to something
  #:treat_as_void:  []             # hint to cmock that these types are actually aliases of void
  :memcmp_if_unknown:  true        # allow cmock to use the memory comparison assertions for unknown types
  :when_ptr:  :compare_data        # hint to cmock how to handle pointers in general, the options being :compare_ptr, :compare_data, or :smart

  # Mock generation configuration
  :weak:  ''                       # Symbol to use to declare weak functions
  :enforce_strict_ordering: true   # Do we want cmock to enforce ordering of all function calls?
  :fail_on_unexpected_calls: true  # Do we want cmock to fail when it encounters a function call that wasn't expected?
  :callback_include_count: true    # Do we want cmock to include the number of calls to this callback, when using callbacks?
  :callback_after_arg_check: false # Do we want cmock to enforce an argument check first when using a callback?
  #:includes: []                   # You can add additional includes here, or specify the location with the options below
  #:includes_h_pre_orig_header: [] 
  #:includes_h_post_orig_header: []
  #:includes_c_pre_header:  []
  #:includes_c_post_header:  []
  #:array_size_type:  []            # Specify a type or types that should be used for array lengths
  #:array_size_name:  'size|len'    # Specify a name or names that CMock might automatically recognize as the length of an array
  :exclude_setjmp_h:  false        # Don't use setjmp when running CMock. Note that this might result in late reporting or out-of-order failures.

# Configuration options specific to Unity. 
:unity:
  :defines:
    - UNITY_EXCLUDE_FLOAT

# You can optionally have ceedling create environment variables for you before
# performing the rest of its tasks.
:environment: []
# :environment:
#   # List enforces order allowing later to reference earlier with inline Ruby substitution
#   - :var1: value
#   - :var2: another value
#   - :path:            # Special PATH handling with platform-specific path separators
#     - #{ENV['PATH']}  # Environment variables can use inline Ruby substitution
#     - /another/path/to/include

# LIBRARIES
# These libraries are automatically injected into the build process. Those specified as
# common will be used in all types of builds. Otherwise, libraries can be injected in just
# tests or releases. These options are MERGED with the options in supplemental yaml files.
:libraries:
  :placement: :end
  :flag: "-l${1}"
  :path_flag: "-L ${1}"
  :system: []    # for example, you might list 'm' to grab the math library
  :test: []
  :release: []

################################################################
# PLUGIN CONFIGURATION
################################################################

# Add -gcov to the plugins list to make sure of the gcov plugin
# You will need to have gcov and gcovr both installed to make it work.
# For more information on these options, see docs in plugins/gcov
:gcov:
  :summaries: TRUE                # Enable simple coverage summaries to console after tests
  :report_task: FALSE             # Disabled dedicated report generation task (this enables automatic report generation)
  :utilities:
    - gcovr           # Use gcovr to create the specified reports (default).
    #- ReportGenerator # Use ReportGenerator to create the specified reports.
  :reports: # Specify one or more reports to generate.
    # Make an HTML summary report.
    - HtmlBasic
    # - HtmlDetailed
    # - Text
    # - Cobertura
    # - SonarQube
    # - JSON
    # - HtmlInline
    # - HtmlInlineAzure
    # - HtmlInlineAzureDark
    # - HtmlChart
    # - MHtml
    # - Badges
    # - CsvSummary
    # - Latex
    # - LatexSummary
    # - PngChart
    # - TeamCitySummary
    # - lcov
    # - Xml
    # - XmlSummary
  :gcovr:
    # :html_artifact_filename: TestCoverageReport.html
    # :html_title: Test Coverage Report
    :html_medium_threshold: 75
    :html_high_threshold: 90
    # :html_absolute_paths: TRUE
    # :html_encoding: UTF-8

# :module_generator:
#   :naming: :snake #options: :bumpy, :camel, :caps, or :snake
#   :includes:
#     :tst: []
#     :src: []
#   :boilerplates:
#     :src: ""
#     :inc: ""
#     :tst: ""

# :dependencies:
#   :libraries:
#     - :name: WolfSSL
#       :source_path:   third_party/wolfssl/source
#       :build_path:    third_party/wolfssl/build
#       :artifact_path: third_party/wolfssl/install
#       :fetch:
#         :method: :zip
#         :source: \\shared_drive\third_party_libs\wolfssl\wolfssl-4.2.0.zip
#       :environment:
#         - CFLAGS+=-DWOLFSSL_DTLS_ALLOW_FUTURE
#       :build:
#         - "autoreconf -i"
#         - "./configure --enable-tls13 --enable-singlethreaded"
#         - make
#         - make install
#       :artifacts:
#         :static_libraries:
#           - lib/wolfssl.a
#         :dynamic_libraries:
#           - lib/wolfssl.so
#         :includes:
#           - include/**

# :subprojects:  
#   :paths:
#    - :name: libprojectA
#      :source:
#        - ./subprojectA/source
#      :include:
#        - ./subprojectA/include
#      :build_root: ./subprojectA/build
#      :defines: []

# :command_hooks:
#   :pre_mock_preprocess:
#   :post_mock_preprocess:
#   :pre_test_preprocess:
#   :post_test_preprocess:
#   :pre_mock_generate:
#   :post_mock_generate:
#   :pre_runner_generate:
#   :post_runner_generate:
#   :pre_compile_execute:
#   :post_compile_execute:
#   :pre_link_execute:
#   :post_link_execute:
#   :pre_test_fixture_execute:
#   :post_test_fixture_execute:
#   :pre_test:
#   :post_test:
#   :pre_release:
#   :post_release:
#   :pre_build:
#   :post_build:
#   :post_error:

################################################################
# TOOLCHAIN CONFIGURATION
################################################################

#:tools:
# Ceedling defaults to using gcc for compiling, linking, etc.
# As [:tools] is blank, gcc will be used (so long as it's in your system path)
# See documentation to configure a given toolchain for use
# :tools:
#   :test_compiler: 
#     :executable:
#     :arguments: []
#     :name: 
#     :optional: FALSE
#   :test_linker: 
#     :executable:
#     :arguments: []
#     :name: 
#     :optional: FALSE
#   :test_assembler: 
#     :executable:
#     :arguments: []
#     :name: 
#     :optional: FALSE
#   :test_fixture: 
#     :executable:
#     :arguments: []
#     :name: 
#     :optional: FALSE
#   :test_includes_preprocessor: 
#     :executable:
#     :arguments: []
#     :name: 
#     :optional: FALSE
#   :test_file_preprocessor: 
#     :executable:
#     :arguments: []
#     :name: 
#     :optional: FALSE
#   :test_file_preprocessor_directives: 
#     :executable:
#     :arguments: []
#     :name: 
#     :optional: FALSE
#   :release_compiler: 
#     :executable:
#     :arguments: []
#     :name: 
#     :optional: FALSE
#   :release_linker: 
#     :executable:
#     :arguments: []
#     :name: 
#     :optional: FALSE
#   :release_assembler: 
#     :executable:
#     :arguments: []
#     :name: 
#     :optional: FALSE
#   :release_dependencies_generator: 
#     :executable:
#     :arguments: []
#     :name: 
#     :optional: FALSE
...
Output error
Loaded project configuration from working directory.
 > Using: C:/Users/Parminder/Desktop/ceedling_full_path/project.yml
 > Working directory: C:/Users/Parminder/Desktop/ceedling_full_path

Ceedling set up completed in 148 milliseconds

Preparing Build Paths...

Collecting Test Context
-----------------------
Parsing test_production_1.c for build directive macros, #includes, and test case names...

Ingesting Test Configurations
-----------------------------
Collecting search paths, flags, and defines test_production_1.c...

Determining Files to be Generated...

Mocking
-------

Test Runners
------------
Generating runner for test_production_1.c...

Determining Artifacts to Be Built...

Building Objects
----------------
Compiling test_production_1.c...
Compiling test_production_1::unity.c...
Compiling test_production_1::test_production_1_runner.c...

Building Test Executables
-------------------------
Linking test_production_1.out...
EXCEPTION: 'Default Test Linker' (gcc.exe) terminated with exit code [1] and output >>
collect2.exe: error: ld returned 5 exit status
Executed Command: gcc.exe "build/test/out/test_production_1/test_production_1.o" "build/test/out/test_production_1/test_production_1_runner.o" "build/test/out/test_production_1/unity.o" -o "build/test/out/test_production_1/test_production_1.out"
Ceedling could not complete operations because of errors

The project I'm working on is configured to use the root folder as the base path, so all #include statements in our project start from the base folder. However, in test files we're forced to use #include in the form #include "production_1.h", otherwise Ceedling gives an error. The problem with using includes this way is that the IDE and IntelliSense can't find the production_1.h and production_2.h files, which results in fake errors being shown and the loss of autocompletion and IDE suggestions when writing test files.

So I would like to use this full paths (#include "src/production_1/production_1.h" and #include "src/production_2/production_2.h") instead of just the header file name to test (#include "production_1.h" and #include "production_2.h"). Is there a way to be able to use #include statements in this way?

Example project to reproduce the issue: ceedling_full_path.zip

parmi93 avatar Oct 16 '25 23:10 parmi93

Add proper paths to IDE configuration instead of trying to change ceedling directory structure? There is a lot more than just includes for ceedling to automatically find mocks and all. So I suggest you fix your ide, which is the problem.

Letme avatar Oct 17 '25 06:10 Letme

I would argue this is not an IDE issue but rather a Ceedling limitation, as it does not support #include directives with full paths that all compilers accept.

However, the bigger problem with this Ceedling limitation is: what happens when there are two header files with the same name in different paths?

src/folder_1/production.h:

#ifndef SRC_FOLDER_1_PRODUCTION_H
#define SRC_FOLDER_1_PRODUCTION_H

int prod_1(void);

#endif // SRC_FOLDER_1_PRODUCTION_H

src/folder_1/production.c:

#include "production.h"

int prod_1(void)
{
    return 10;
}

src/folder_2/production.h:

#ifndef SRC_FOLDER_2_PRODUCTION_H
#define SRC_FOLDER_2_PRODUCTION_H

int prod_2(void);

#endif // SRC_FOLDER_2_PRODUCTION_H

src/folder_2/production.c:

#include "production.h"

int prod_2(void)
{
    return 20;
}

test/folder_2/test_prod_2.c:


// I want to test `src/folder_2/production.h`, but Ceedling is testing `src/folder_1/production.h`
#include "production.h" // < Ceedling is wrongly including `src/folder_1/production.h`

#include "unity.h"

void setUp(void)
{
}

void tearDown(void)
{
}

void test_1(void)
{
    int ret = prod_2();

    TEST_ASSERT_EQUAL(20, ret);
}
Ceedling error
Loaded project configuration from working directory.
 > Using: C:/Users/Parminder/Desktop/ceedling_full_path/project.yml
 > Working directory: C:/Users/Parminder/Desktop/ceedling_full_path

Ceedling set up completed in 144 milliseconds

Preparing Build Paths...

Collecting Test Context
-----------------------
Parsing test_prod_2.c for build directive macros, #includes, and test case names...

Ingesting Test Configurations
-----------------------------
Collecting search paths, flags, and defines test_prod_2.c...

Determining Files to be Generated...

Mocking
-------

Test Runners
------------
Generating runner for test_prod_2.c...

Determining Artifacts to Be Built...

Building Objects
----------------
Compiling test_prod_2.c...
Compiling test_prod_2::production.c...
Compiling test_prod_2::unity.c...
Compiling test_prod_2::test_prod_2_runner.c...
EXCEPTION: 'Default Test Compiler' (gcc.exe) terminated with exit code [1] and output >>
test/folder_2/test_prod_2.c: In function 'test_1':
test/folder_2/test_prod_2.c:15:15: error: implicit declaration of function 'prod_2'; did you mean 'prod_1'? [-Wimplicit-function-declaration]
   15 |     int ret = prod_2();
      |               ^~~~~~
      |               prod_1
Executed Command: gcc.exe -I"build/test/mocks/test_prod_2" -I"test/support" -I"." -I"build" -I"build/artifacts" -I"build/test" -I"build/test/mocks" -I"build/test/out" -I"build/vendor" -I"build/vendor/cmock" -I"build/vendor/unity" -I"src" -I"test" -I"build/artifacts/test" -I"build/logs" -I"build/test/cache" -I"build/test/dependencies" -I"build/test/mocks/test_production_1" -I"build/test/out/test_production_1" -I"build/test/results" -I"build/test/runners" -I"build/vendor/cmock/src" -I"build/vendor/unity/src" -I"src/folder_1" -I"src/folder_2" -I"test/folder_2" -D"TEST" -D"UNITY_EXCLUDE_FLOAT" -D"UNITY_USE_COMMAND_LINE_ARGS" -DGNU_COMPILER -g -c "test/folder_2/test_prod_2.c" -o "build/test/out/test_prod_2/test_prod_2.o" -MMD -MF "build/test/dependencies/test_prod_2.d"
Ceedling could not complete operations because of errors

As you can see from the example, this limitation prevents me from testing the prod_2() function. This limitation could be overcome if Ceedling supported full paths (#include "src/folder_2/production.h").

Example project: ceedling_cant_test_prod_2.zip

parmi93 avatar Oct 17 '25 12:10 parmi93

Both of these topics are definitely issues. I believe they both exist in at least one open GitHub Issue each? In any case, it's a feature that was implemented poorly in the past and left people with more problems than it solved. So it's been paused and will be re-implemented "soon". (The word "soon" here meaning that it's not in the next batch of changes, but very likely to be in a group after that).

mvandervoord avatar Oct 17 '25 14:10 mvandervoord