Skip to content

Commit a19d793

Browse files
author
roeglinj
committed
🌱 Add test-specific mock include directives
Global CMock include options apply to all generated mocks, which can introduce unwanted dependencies or conflicts in unrelated tests. Add test-file build directives for injecting headers into a specific generated mock for the current test executable only. This allows tests to provide mock-specific types, macros, or configuration headers without changing production headers or global CMock configuration. The generic TEST_MOCK_INCLUDE directive maps to the existing CMock pre-original-header include location. Location-specific variants are also supported for finer control. Add parser coverage for the new directives and document their usage.
1 parent 6b90429 commit a19d793

7 files changed

Lines changed: 324 additions & 3 deletions

File tree

‎docs/CeedlingPacket.md‎

Lines changed: 87 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1393,6 +1393,93 @@ _**Notes:**_
13931393
order of any Mixins. Paths specified with Mixins will be added to
13941394
path lists in your project configuration in the order of merging.
13951395
1396+
## Test-specific mock include directives
1397+
1398+
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.
1399+
1400+
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.
1401+
1402+
### `TEST_MOCK_INCLUDE(...)`
1403+
1404+
```c
1405+
TEST_MOCK_INCLUDE("mock_driver.h", "test_driver_types.h")
1406+
```
1407+
1408+
This injects `test_driver_types.h` into the generated mock for `mock_driver.h` for the current test executable only.
1409+
1410+
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.
1411+
1412+
Example:
1413+
1414+
```c
1415+
#include "unity.h"
1416+
#include "mock_driver.h"
1417+
1418+
TEST_MOCK_INCLUDE("mock_driver.h", "test_driver_types.h")
1419+
1420+
void test_driver_initializes(void)
1421+
{
1422+
driver_init_Expect();
1423+
system_init();
1424+
}
1425+
```
1426+
1427+
The directive above affects only the generated mock for `mock_driver.h` in this test executable.
1428+
1429+
### Location-specific variants
1430+
1431+
For finer control, the include location can be selected explicitly:
1432+
1433+
```c
1434+
TEST_MOCK_INCLUDE_H_PRE_ORIG_HEADER("mock_driver.h", "test_driver_types.h")
1435+
TEST_MOCK_INCLUDE_H_POST_ORIG_HEADER("mock_driver.h", "test_driver_types.h")
1436+
TEST_MOCK_INCLUDE_C_PRE_HEADER("mock_driver.h", "test_driver_config.h")
1437+
TEST_MOCK_INCLUDE_C_POST_HEADER("mock_driver.h", "test_driver_config.h")
1438+
```
1439+
1440+
The generic form:
1441+
1442+
```c
1443+
TEST_MOCK_INCLUDE("mock_driver.h", "test_driver_types.h")
1444+
```
1445+
1446+
is equivalent to:
1447+
1448+
```c
1449+
TEST_MOCK_INCLUDE_H_PRE_ORIG_HEADER("mock_driver.h", "test_driver_types.h")
1450+
```
1451+
1452+
### Difference from global CMock include options
1453+
1454+
CMock also supports global include configuration options such as:
1455+
1456+
```yaml
1457+
:cmock:
1458+
:includes_h_pre_orig_header:
1459+
- some_global_header.h
1460+
:includes_h_post_orig_header: []
1461+
:includes_c_pre_header: []
1462+
:includes_c_post_header: []
1463+
```
1464+
1465+
Those options apply globally to generated mocks.
1466+
1467+
`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.
1468+
1469+
### Notes
1470+
1471+
The mock argument should be written as a quoted mock header or mock name:
1472+
1473+
```c
1474+
TEST_MOCK_INCLUDE("mock_driver.h", "test_driver_types.h")
1475+
TEST_MOCK_INCLUDE("mock_driver", "test_driver_types.h")
1476+
```
1477+
1478+
Both forms refer to the same generated mock.
1479+
1480+
Header paths are interpreted the same way as normal include paths used by the test build.
1481+
1482+
13961483
## Search Paths for Release Builds
13971484

13981485
Unlike test builds, release builds are relatively straightforward. Each

‎lib/ceedling/constants.rb‎

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -75,6 +75,11 @@ class PATTERNS
7575
TEST_STDOUT_STATISTICS = /\n-+\s*(\d+)\s+Tests\s+(\d+)\s+Failures\s+(\d+)\s+Ignored\s+(OK|FAIL)\s*/i
7676
TEST_SOURCE_FILE = /TEST_SOURCE_FILE\s*\(\s*\"\s*([^"]+)\s*\"\s*\)/
7777
TEST_INCLUDE_PATH = /TEST_INCLUDE_PATH\s*\(\s*\"\s*([^"]+)\s*\"\s*\)/
78+
TEST_MOCK_INCLUDE = /TEST_MOCK_INCLUDE\s*\(\s*\"\s*([^"]+)\s*\"\s*,\s*\"\s*([^"]+)\s*\"\s*\)/
79+
TEST_MOCK_INCLUDE_H_PRE_ORIG_HEADER = /TEST_MOCK_INCLUDE_H_PRE_ORIG_HEADER\s*\(\s*\"\s*([^"]+)\s*\"\s*,\s*\"\s*([^"]+)\s*\"\s*\)/
80+
TEST_MOCK_INCLUDE_H_POST_ORIG_HEADER = /TEST_MOCK_INCLUDE_H_POST_ORIG_HEADER\s*\(\s*\"\s*([^"]+)\s*\"\s*,\s*\"\s*([^"]+)\s*\"\s*\)/
81+
TEST_MOCK_INCLUDE_C_PRE_HEADER = /TEST_MOCK_INCLUDE_C_PRE_HEADER\s*\(\s*\"\s*([^"]+)\s*\"\s*,\s*\"\s*([^"]+)\s*\"\s*\)/
82+
TEST_MOCK_INCLUDE_C_POST_HEADER = /TEST_MOCK_INCLUDE_C_POST_HEADER\s*\(\s*\"\s*([^"]+)\s*\"\s*,\s*\"\s*([^"]+)\s*\"\s*\)/
7883
end
7984

8085
GIT_COMMIT_SHA_FILENAME = 'GIT_COMMIT_SHA'

‎lib/ceedling/generator.rb‎

Lines changed: 37 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -53,8 +53,26 @@ def generate_mock(context:, mock:, test:, input_filepath:, output_path:)
5353
# - Add option to CMock to generate mock to any destination path
5454
# - Make CMock thread-safe
5555

56+
#TODO: mock_key = normalize_mock_include_target( mock )
57+
# def normalize_mock_include_target(mock)
58+
# mock
59+
# .to_s
60+
# .strip
61+
# .sub(/^.*[\\\/]/, '')
62+
# .sub(/#{Regexp.escape(@configurator.extension_header)}$/, '')
63+
# end
64+
65+
mock_include_config = @test_context_extractor.lookup_mock_includes_for_mock( test, mock )
66+
67+
if !mock_include_config_empty?(mock_include_config)
68+
@loginator.log(
69+
"Applying test-specific mock includes for #{mock_key} in #{test}\n",
70+
Verbosity::DEBUG
71+
)
72+
end
73+
5674
# Get default config created by Ceedling and customize it
57-
config = @generator_mocks.build_configuration( output_path )
75+
config = @generator_mocks.build_configuration( output_path, mock_include_config )
5876

5977
# Generate mock
6078
msg = @reportinator.generate_module_progress(
@@ -361,4 +379,22 @@ def generate_test_results(tool:, context:, test_name:, test_filepath:, executabl
361379
shell_result
362380
end
363381

382+
private
383+
384+
def normalize_mock_include_target(mock)
385+
mock
386+
.to_s
387+
.strip
388+
.sub(/^.*[\\\/]/, '')
389+
.sub(/#{Regexp.escape(@configurator.extension_header)}$/, '')
390+
end
391+
392+
def mock_include_config_empty?(mock_include_config)
393+
return true if mock_include_config.nil? || mock_include_config.empty?
394+
395+
mock_include_config.all? do |_key, headers|
396+
headers.nil? || headers.empty?
397+
end
398+
end
399+
364400
end

‎lib/ceedling/generator_mocks.rb‎

Lines changed: 22 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,10 +15,12 @@ def manufacture(config)
1515
return CMock.new(config)
1616
end
1717

18-
def build_configuration( output_path )
18+
def build_configuration( output_path, mock_include_config=nil )
1919
config = @configurator.get_cmock_config
2020
config[:mock_path] = output_path
2121

22+
apply_mock_include_config( config, mock_include_config )
23+
2224
# Verbosity management for logging messages
2325
verbosity = @configurator.project_verbosity
2426

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

3840
return config
3941
end
42+
43+
private
44+
45+
def apply_mock_include_config(config, mock_include_config)
46+
return if mock_include_config.nil? || mock_include_config.empty?
47+
48+
[
49+
:includes_h_pre_orig_header,
50+
:includes_h_post_orig_header,
51+
:includes_c_pre_header,
52+
:includes_c_post_header
53+
].each do |key|
54+
next if mock_include_config[key].nil? || mock_include_config[key].empty?
55+
56+
config[key] ||= []
57+
config[key] += mock_include_config[key]
58+
config[key].uniq!
59+
end
60+
end
4061

4162
end

0 commit comments

Comments
 (0)