Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
87 changes: 87 additions & 0 deletions docs/CeedlingPacket.md
Original file line number Diff line number Diff line change
Expand Up @@ -1393,6 +1393,93 @@ _**Notes:**_
order of any Mixins. Paths specified with Mixins will be added to
path lists in your project configuration in the order of merging.

## Test-specific mock include directives

Ceedling supports build directive macros that can be placed in test files to provide additional build context. These macros do not affect the compiled test code directly, but Ceedling scans them and uses them while preparing the test executable.

In addition to `TEST_INCLUDE_PATH(...)` and `TEST_SOURCE_FILE(...)`, test files can request additional headers to be injected into generated mocks for that specific test executable.

### `TEST_MOCK_INCLUDE(...)`

```c
TEST_MOCK_INCLUDE("mock_driver.h", "test_driver_types.h")
```

This injects `test_driver_types.h` into the generated mock for `mock_driver.h` for the current test executable only.

This is useful when a mock needs additional test-specific types, macros, or configuration headers, but those headers should not be added globally to every generated mock.

Example:

```c
#include "unity.h"
#include "mock_driver.h"

TEST_MOCK_INCLUDE("mock_driver.h", "test_driver_types.h")

void test_driver_initializes(void)
{
driver_init_Expect();
system_init();
}
```

The directive above affects only the generated mock for `mock_driver.h` in this test executable.

### Location-specific variants

For finer control, the include location can be selected explicitly:

```c
TEST_MOCK_INCLUDE_H_PRE_ORIG_HEADER("mock_driver.h", "test_driver_types.h")
TEST_MOCK_INCLUDE_H_POST_ORIG_HEADER("mock_driver.h", "test_driver_types.h")
TEST_MOCK_INCLUDE_C_PRE_HEADER("mock_driver.h", "test_driver_config.h")
TEST_MOCK_INCLUDE_C_POST_HEADER("mock_driver.h", "test_driver_config.h")
```

The generic form:

```c
TEST_MOCK_INCLUDE("mock_driver.h", "test_driver_types.h")
```

is equivalent to:

```c
TEST_MOCK_INCLUDE_H_PRE_ORIG_HEADER("mock_driver.h", "test_driver_types.h")
```

### Difference from global CMock include options

CMock also supports global include configuration options such as:

```yaml
:cmock:
:includes_h_pre_orig_header:
- some_global_header.h
:includes_h_post_orig_header: []
:includes_c_pre_header: []
:includes_c_post_header: []
```

Those options apply globally to generated mocks.

`TEST_MOCK_INCLUDE(...)` is different: it is scoped to the test file and mock where it is declared. This prevents unrelated mocks from receiving unnecessary or conflicting includes.

### Notes

The mock argument should be written as a quoted mock header or mock name:

```c
TEST_MOCK_INCLUDE("mock_driver.h", "test_driver_types.h")
TEST_MOCK_INCLUDE("mock_driver", "test_driver_types.h")
```

Both forms refer to the same generated mock.

Header paths are interpreted the same way as normal include paths used by the test build.


## Search Paths for Release Builds

Unlike test builds, release builds are relatively straightforward. Each
Expand Down
5 changes: 5 additions & 0 deletions lib/ceedling/constants.rb
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,11 @@ class PATTERNS
TEST_STDOUT_STATISTICS = /\n-+\s*(\d+)\s+Tests\s+(\d+)\s+Failures\s+(\d+)\s+Ignored\s+(OK|FAIL)\s*/i
TEST_SOURCE_FILE = /TEST_SOURCE_FILE\s*\(\s*\"\s*([^"]+)\s*\"\s*\)/
TEST_INCLUDE_PATH = /TEST_INCLUDE_PATH\s*\(\s*\"\s*([^"]+)\s*\"\s*\)/
TEST_MOCK_INCLUDE = /TEST_MOCK_INCLUDE\s*\(\s*\"\s*([^"]+)\s*\"\s*,\s*\"\s*([^"]+)\s*\"\s*\)/
TEST_MOCK_INCLUDE_H_PRE_ORIG_HEADER = /TEST_MOCK_INCLUDE_H_PRE_ORIG_HEADER\s*\(\s*\"\s*([^"]+)\s*\"\s*,\s*\"\s*([^"]+)\s*\"\s*\)/
TEST_MOCK_INCLUDE_H_POST_ORIG_HEADER = /TEST_MOCK_INCLUDE_H_POST_ORIG_HEADER\s*\(\s*\"\s*([^"]+)\s*\"\s*,\s*\"\s*([^"]+)\s*\"\s*\)/
TEST_MOCK_INCLUDE_C_PRE_HEADER = /TEST_MOCK_INCLUDE_C_PRE_HEADER\s*\(\s*\"\s*([^"]+)\s*\"\s*,\s*\"\s*([^"]+)\s*\"\s*\)/
TEST_MOCK_INCLUDE_C_POST_HEADER = /TEST_MOCK_INCLUDE_C_POST_HEADER\s*\(\s*\"\s*([^"]+)\s*\"\s*,\s*\"\s*([^"]+)\s*\"\s*\)/
end

GIT_COMMIT_SHA_FILENAME = 'GIT_COMMIT_SHA'
Expand Down
38 changes: 37 additions & 1 deletion lib/ceedling/generator.rb
Original file line number Diff line number Diff line change
Expand Up @@ -53,8 +53,26 @@ def generate_mock(context:, mock:, test:, input_filepath:, output_path:)
# - Add option to CMock to generate mock to any destination path
# - Make CMock thread-safe

#TODO: mock_key = normalize_mock_include_target( mock )
# def normalize_mock_include_target(mock)
# mock
# .to_s
# .strip
# .sub(/^.*[\\\/]/, '')
# .sub(/#{Regexp.escape(@configurator.extension_header)}$/, '')
# end

mock_include_config = @test_context_extractor.lookup_mock_includes_for_mock( test, mock )

if !mock_include_config_empty?(mock_include_config)
@loginator.log(
"Applying test-specific mock includes for #{mock_key} in #{test}\n",
Verbosity::DEBUG
)
end

# Get default config created by Ceedling and customize it
config = @generator_mocks.build_configuration( output_path )
config = @generator_mocks.build_configuration( output_path, mock_include_config )

# Generate mock
msg = @reportinator.generate_module_progress(
Expand Down Expand Up @@ -361,4 +379,22 @@ def generate_test_results(tool:, context:, test_name:, test_filepath:, executabl
shell_result
end

private

def normalize_mock_include_target(mock)
mock
.to_s
.strip
.sub(/^.*[\\\/]/, '')
.sub(/#{Regexp.escape(@configurator.extension_header)}$/, '')
end

def mock_include_config_empty?(mock_include_config)
return true if mock_include_config.nil? || mock_include_config.empty?

mock_include_config.all? do |_key, headers|
headers.nil? || headers.empty?
end
end

end
23 changes: 22 additions & 1 deletion lib/ceedling/generator_mocks.rb
Original file line number Diff line number Diff line change
Expand Up @@ -15,10 +15,12 @@ def manufacture(config)
return CMock.new(config)
end

def build_configuration( output_path )
def build_configuration( output_path, mock_include_config=nil )
config = @configurator.get_cmock_config
config[:mock_path] = output_path

apply_mock_include_config( config, mock_include_config )

# Verbosity management for logging messages
verbosity = @configurator.project_verbosity

Expand All @@ -37,5 +39,24 @@ def build_configuration( output_path )

return config
end

private

def apply_mock_include_config(config, mock_include_config)
return if mock_include_config.nil? || mock_include_config.empty?

[
:includes_h_pre_orig_header,
:includes_h_post_orig_header,
:includes_c_pre_header,
:includes_c_post_header
].each do |key|
next if mock_include_config[key].nil? || mock_include_config[key].empty?

config[key] ||= []
config[key] += mock_include_config[key]
config[key].uniq!
end
end

end
Loading